mongoose = require('mongoose').set('debug', false) const Schema = mongoose.Schema; const tools = require('../tools/general'); const Producer = require('../models/producer'); const Storehouse = require('../models/storehouse'); const Provider = require('../models/provider'); const CatProd = require('../models/catprod'); const SubCatProd = require('../models/subcatprod'); const Gasordine = require('../models/gasordine'); const Scontistica = require('../models/scontistica'); const shared_consts = require('../tools/shared_nodejs'); const { ObjectId } = require('mongodb'); mongoose.Promise = global.Promise; mongoose.level = "F"; // A1P // Resolving error Unknown modifier: $pushAll mongoose.plugin(schema => { schema.options.usePushEach = true }); const productSchema = new Schema({ idapp: { type: String, }, active: { type: Boolean, default: true, }, isbn: { type: String, }, idProductInfo: { type: Schema.Types.ObjectId, ref: 'ProductInfo', index: true }, idProducer: { type: Schema.Types.ObjectId, ref: 'Producer', index: true }, idStorehouses: [ { type: Schema.Types.ObjectId, ref: 'Storehouse', index: true } ], idGasordine: { type: Schema.Types.ObjectId, ref: 'Gasordine', index: true }, idScontisticas: [ { type: Schema.Types.ObjectId, ref: 'Scontistica', index: true } ], idProvider: { type: Schema.Types.ObjectId, ref: 'Provider', index: true }, prezzo_ivato: { // Con IVA type: Number }, perc_iva: { // 4, 10, 22 & type: Number }, price: { type: Number, required: true, }, arrvariazioni: [ { active: { type: Boolean, }, versione: { type: Number, }, status: { //publish type: String, }, price: { type: Number, }, sale_price: { type: Number, }, quantita: { // in magazzino type: Number, }, pagine: { type: Number, }, misure: { type: String, }, edizione: { type: String, }, ristampa: { type: String, }, formato: { type: String, }, tipologia: { type: String, }, idTipologia: { type: Number, }, idTipoFormato: { type: Number, }, edizione: { type: String, }, preOrderDate: { type: Date }, addtocart_link: { type: String }, eta: { type: String }, } ], price_acquistato: { type: Number, required: true, }, after_price: { type: String }, minBuyQty: { // quantità minima acquistabile type: Number, default: 1, required: true, }, minStepQty: { // step quantità acquistabile type: Number, default: 1, required: true, }, maxBookableSinglePersQty: { // quantità massima Pre-ordinabile (singolarmente) type: Number, }, stockQty: { // in magazzino type: Number, default: 0, index: true, }, stockBloccatiQty: { // Prenotati Bloccati type: Number, }, bookedQtyOrdered: { // Quantità Prenotate ordinate (in Lavorazione) type: Number, }, bookedQtyConfirmed: { // Quantità Prenotate Confermate Totali type: Number, }, // GAS: qtyToReachForGas: { // Quantità minima da raggiungere per fare l'ordine GAS type: Number, }, maxbookableGASQty: { // Quantità massima (ancora disponibile) Ordine GAS prenotabile (Complessivamente tra tutti gli ordini) type: Number, }, bookedGASQtyOrdered: { // Quantità Ordine GAS Prenotate Totali type: Number, }, bookedGASQtyConfirmed: { // Quantità Ordine GAS Confermate Totali type: Number, }, bookableGASBloccatiQty: { // Quantità Prenotate Bloccate GAS type: Number, }, quantityLow: { //Soglia disponibilità bassa type: Number, }, visibilityProductOutOfStock: { // Visibilità prodotto "esaurito" type: Boolean, }, canBeShipped: { // è spedibile type: Boolean, }, canBeBuyOnline: { // è acquistabile online type: Boolean, }, stars: { type: Number, }, dateAvailableFrom: { type: Date }, note: { type: String, }, producer_name: { type: String, }, provider_name: { type: String, }, magazzino_name: { type: String, }, cat_name: { type: String, }, subcat_name: { type: String, }, sconto1: { type: String, }, sconto2: { type: String, }, gas_name: { type: String, }, date_created: { type: Date, default: Date.now }, date_updated: { type: Date, }, validaprod: { esito: { type: Number, }, data: { type: Date, }, username: { type: String, }, note: { type: String, }, }, }); var Product = module.exports = mongoose.model('Product', productSchema); productSchema.index({ idapp: 1 }); module.exports.getFieldsForSearch = function () { return [ { field: 'name', type: tools.FieldType.string }, { field: 'description', type: tools.FieldType.string }, ] }; module.exports.executeQueryTable = function (idapp, params) { params.fieldsearch = this.getFieldsForSearch(); return tools.executeQueryTable(this, idapp, params); }; module.exports.executeQueryPickup = async function (idapp, params) { let strfindInput = tools.removeAccents(params.search.trim().toLowerCase()); // Rimuove le parole "il" e "la" e gli spazi, le @ e i tabulazioni // per non farli influire sulla ricerca strfindInput = strfindInput.replace(/\b(il|la|gli|le|lo|un|una)\b/g, '').replace(/[-@\t]/g, '').trim(); if (strfindInput === '' && !params.filter) { return []; } let filterfindexact = {}; if (strfindInput) { filterfindexact = { comune: strfindInput }; } let limit = 20; let risexact = []; const cleanInput = tools.removeAccents(strfindInput.trim()); const words = cleanInput.split(/\s+/); const escapeRegex = w => w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // 🔹 Pattern per productInfo.name: tutte le parole devono essere presenti const patternAllWords = words.map(w => `(?=.*\\b${escapeRegex(w)})`).join('') + '.*'; let authorConditions = []; if (words.length > 0) { authorConditions = words.map((word) => { const regex = new RegExp(escapeRegex(word), 'i'); return { 'productInfo.authors': { $elemMatch: { $or: [ { name: regex }, { surname: regex } ] } } }; }); authorConditions = [{ $and: authorConditions }]; } // 🔹 Filtro finale const filterfind = { idapp, $or: [ { 'productInfo.name': { $regex: patternAllWords, $options: 'i' } }, { 'productInfo.code': { $regex: `\\b${escapeRegex(cleanInput)}`, $options: 'i' } }, { 'productInfo.sku': cleanInput }, ...authorConditions ] }; if (params.filter) { filterfind = { ...params.filter, ...filterfind }; limit = 200; } let aggr2 = [ { $lookup: { from: 'productinfos', localField: 'idProductInfo', foreignField: '_id', as: 'productInfo' } }, { $unwind: { path: '$productInfo', preserveNullAndEmptyArrays: true, }, }, { $lookup: { from: 'authors', localField: 'productInfo.idAuthors', foreignField: '_id', as: 'productInfo.authors' } }, { $unwind: { path: '$authors', preserveNullAndEmptyArrays: true, }, }, { $match: filterfind, }, { $limit: limit }, { $project: { name: '$productInfo.name', // Nome del prodotto authors: '$productInfo.authors', productInfo: { name: '$productInfo.name', // Nome dell'autore authors: '$productInfo.authors', idStatoProdotto: "$productInfo.idStatoProdotto", date_pub: "$productInfo.date_pub", }, arrvariazioni: "$arrvariazioni", } }, { $sort: { 'productInfo.date_pub': -1, 'productInfo.name': 1 // Ordina per name in ordine crescente } } ]; let ris = await this.aggregate(aggr2).limit(limit); return [...risexact, ...ris]; }; module.exports.getProductByCode = function (idapp, code) { return Product.findAllIdApp(idapp, code); } module.exports.getProductById = async function (id) { const arrris = await Product.findAllIdApp('', '', id); return arrris && arrris.length > 0 ? arrris[0] : null } module.exports.getInStockById = async function (id) { const myprod = await Product.findOne({ _id: id }); if (myprod) { let instock = 0; if (myprod.idGasordine) { instock = myprod.maxbookableGASQty; } else { instock = myprod.stockQty; } return instock } return null; } module.exports.isLowQuantityInStockById = async function (id) { const instock = await Product.getInStockById(id); const myprod = await Product.findOne({ _id: id }); if (instock) { return (instock <= (myprod.quantityLow + 1)); } return false; } module.exports.findAllIdApp = async function (idapp, code, id, all) { let myfind = {}; let myqueryadd = {}; let query = []; try { if (id === 'undefined') { return []; } if (idapp) { myfind = { idapp }; } if (!all) { myfind = { ...myfind, active: true } } if (code) { myfind = { ...myfind, code } } if (id) { myqueryadd = { $addFields: { myId1: { $toObjectId: id, }, }, } myfind = { $expr: { $eq: ["$_id", "$myId1"], }, } query.push(myqueryadd); } // DA TOGLIEREE // myfind = { ...myfind, code: '4012824406094' }; // return await Product.find(myfind); query.push( // PRIMO FILTRO: riduce subito il numero di documenti { $match: myfind }, // UNICO LOOKUP ORDERS CON FACET PER RIDURRE I DOPPIONI { $lookup: { from: "orders", let: { productId: "$_id" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: ["$idProduct", "$$productId"] }, { $or: [ { $eq: ["$status", shared_consts.OrderStatus.CHECKOUT_SENT] }, { $and: [ { $lt: ["$status", shared_consts.OrderStatus.CHECKOUT_SENT] }, { $gt: [ "$modify_at", { $subtract: [new Date(), 60 * 60 * 1000] } ] } ] } ] } ] } } }, { $group: { _id: null, totalQty: { $sum: "$quantity" }, totalQtyPreordered: { $sum: "$quantitypreordered" } } } ], as: "orderSummary" } }, // ESTRAGGO LE QUANTITÀ IN CAMPI AGGIUNTIVI { $addFields: { QuantitaOrdinateInAttesa: { $ifNull: [{ $arrayElemAt: ["$orderSummary.totalQty", 0] }, 0] }, QuantitaPrenotateInAttesa: { $ifNull: [{ $arrayElemAt: ["$orderSummary.totalQtyPreordered", 0] }, 0] } } }, // CALCOLO DELLE DISPONIBILITÀ { $addFields: { quantityAvailable: { $subtract: ["$stockQty", "$QuantitaOrdinateInAttesa"] }, bookableAvailableQty: { $subtract: ["$maxbookableGASQty", "$QuantitaPrenotateInAttesa"] } } }, // ELIMINO IL RISULTATO TEMPORANEO { $unset: "orderSummary" }, // LOOKUP MULTIPLI MA ORGANIZZATI { $lookup: { from: "producers", localField: "idProducer", foreignField: "_id", as: "producer" } }, { $unwind: { path: "$producer", preserveNullAndEmptyArrays: true } }, { $lookup: { from: "productinfos", localField: "idProductInfo", foreignField: "_id", as: "productInfo" } }, { $unwind: { path: "$productInfo", preserveNullAndEmptyArrays: true } }, { $lookup: { from: "gasordines", localField: "idGasordine", foreignField: "_id", as: "gasordine" } }, { $unwind: { path: "$gasordine", preserveNullAndEmptyArrays: true } }, // FILTRO DOPO LOOKUP SU GASORDINE { $match: { $or: [ { "gasordine.active": true }, { gasordine: { $exists: false } } ] } }, // LOOKUP SU AUTHORS { $lookup: { from: "authors", localField: "productInfo.idAuthors", foreignField: "_id", as: "productInfo.authors" } }, // LOOKUP PUBBLICATORI, COLLANE, CATEGORIE, ECC. { $lookup: { from: "publishers", localField: "productInfo.idPublisher", foreignField: "_id", as: "productInfo.publisher" } }, { $unwind: { path: "$productInfo.publisher", preserveNullAndEmptyArrays: true } }, { $lookup: { from: "collanas", localField: "productInfo.idCollana", foreignField: "_id", as: "productInfo.collana" } }, { $unwind: { path: "$productInfo.collana", preserveNullAndEmptyArrays: true } }, { $lookup: { from: "catprods", localField: "productInfo.idCatProds", foreignField: "_id", as: "productInfo.catprods" } }, { $lookup: { from: "subcatprods", localField: "productInfo.idSubCatProds", foreignField: "_id", as: "productInfo.subcatprods" } }, { $lookup: { from: "scontisticas", localField: "idScontisticas", foreignField: "_id", as: "scontisticas" } }, { $lookup: { from: "storehouses", localField: "idStorehouses", foreignField: "_id", as: "storehouses" } }, { $lookup: { from: "providers", localField: "idProvider", foreignField: "_id", as: "provider" } }, { $unwind: { path: "$provider", preserveNullAndEmptyArrays: true } }, // ORDINAMENTO FINALE { $sort: { "productInfo.name": 1 } } ); // console.log('query=', query); let ris = await Product.aggregate(query) // console.table('ris', ris); return ris; } catch (e) { console.error('E', e); return []; } }; module.exports.getAllProducts = function (query, sort, callback) { Product.find(query, null, sort, callback) } module.exports.getProductByDepartment = function (query, sort, callback) { Product.find(query, null, sort, callback) } module.exports.getProductByCatProd = function (query, sort, callback) { Product.find(query, null, sort, callback) } module.exports.getProductByTitle = function (query, sort, callback) { Product.find(query, null, sort, callback) } module.exports.filterProductByDepartment = function (department, callback) { let regexp = new RegExp(`^${department}$`, 'i') var query = { department: { $regex: regexp } }; Product.find(query, callback) } module.exports.filterProductByCatProd = function (catprod, callback) { let regexp = new RegExp(`^${catprod}$`, 'i') var query = { catprod: { $regex: regexp } }; Product.find(query, callback); } module.exports.filterProductByTitle = function (title, callback) { let regexp = new RegExp(`^${title}$`, 'i') var query = { title: { $regex: regexp } }; Product.find(query, callback); } module.exports.getProductByID = function (id, callback) { Product.findById(id, callback); } module.exports.updateProductInOrder = async function (order) { if (order.product) order.product = await Product.getProductById(order.product._id); return order; } module.exports.createIndexes() .then(() => { }) .catch((err) => { throw err; }); module.exports.convertAfterImportALLPROD = async function (idapp, dataObjects) { const arrprod = await Product.find({ idapp }).lean(); for (const prod of arrprod) { await this.singlerecconvert_AfterImport_AndSave(prod); } }; module.exports.getArrCatProds = async function (idapp, cosa) { try { let addquery = []; let arr = []; if (cosa === shared_consts.PROD.GAS) { addquery = [ { $match: { idapp, idGasordine: { $exists: true, $ne: null, $type: 'objectId' } } } ]; addquery.push( { $lookup: { from: 'gasordines', localField: 'idGasordine', foreignField: '_id', as: 'gasordine' } } ); addquery.push( { $match: { "gasordine.active": true } } ); } else if (cosa === shared_consts.PROD.BOTTEGA) { addquery = [{ $match: { idapp, $or: [ { idGasordine: { $exists: false } }, { idGasordine: { $exists: true, $eq: null } } ] } }] } else { addquery = [{ $match: { idapp } }]; } let myquery = [...addquery, { $lookup: { from: "productinfos", localField: "idProductInfo", foreignField: "_id", as: "productInfo", }, }, { $lookup: { from: "catprods", localField: "productInfo.idCatProds", foreignField: "_id", as: "category" } }, { $unwind: "$category" }, { $group: { _id: "$category._id", name: { $first: "$category.name" }, idapp: { $first: "$category.idapp" }, idArgomento: { $first: "$category.idArgomento" } } }, { $match: { $or: [ { idapp: { $ne: tools.MACRO } }, { idapp: tools.MACRO, idArgomento: { $exists: true, $gt: 0 } } ] } }, { $sort: { name: 1 } } ]; try { let arr = []; arr = await Product.aggregate(myquery); // arr = result.map(category => category.name); return arr; } catch (e) { console.error('err', e); } // Ora uniqueCategories contiene l'array delle categorie univoche utilizzate in tutti i prodotti con active = true return arr; } catch (e) { console.error('err', e); return []; } } module.exports.singlerecconvert_AfterImport_AndSave = async function (idapp, prod, isnuovo) { let setta = false; try { let objtoset = {} let rec = null // Impostazioni Base: if (isnuovo) { objtoset = { idapp, // minBuyQty: 1, // minStepQty: 1, maxBookableSinglePersQty: 0, bookedGASQtyOrdered: 0, bookableGASBloccatiQty: 0, bookedGASQtyConfirmed: 0, // qtyToReachForGas: 0, // maxbookableGASQty: 0, } } if (prod.producer_name) { // Cerca il produttore let recproducer = await Producer.findOne({ idapp, name: prod.producer_name }).lean(); if (!recproducer) { // Non esiste questo produttore, quindi lo creo ! recproducer = new Producer({ idapp, name: prod.producer_name }); ris = await recproducer.save(); recproducer = await Producer.findOne({ idapp, name: prod.producer_name }).lean(); } if (recproducer) { objtoset = { ...objtoset, idProducer: recproducer._id, } setta = true; } } if (prod.magazzino_name) { // Cerca il produttore let recstorehouse = await Storehouse.findOne({ idapp, name: prod.magazzino_name }).lean(); if (!recstorehouse) { // Non esiste questo produttore, quindi lo creo ! recstorehouse = new Storehouse({ idapp, name: prod.magazzino_name }); ris = await recstorehouse.save(); recstorehouse = await Storehouse.findOne({ idapp, name: prod.magazzino_name }).lean(); } if (recstorehouse) { objtoset = { ...objtoset, idStorehouses: [recstorehouse._id], } setta = true; } } if (prod.provider_name) { // Cerca il produttore let recprovider = await Provider.findOne({ idapp, name: prod.provider_name }).lean(); if (!recprovider) { recprovider = new Provider({ idapp, name: prod.provider_name }); // Non esiste questo produttore, quindi lo creo ! ris = await recprovider.save(); recprovider = await Provider.findOne({ idapp, name: prod.provider_name }).lean(); } if (recprovider) { objtoset = { ...objtoset, idProvider: recprovider._id, } setta = true; } } if (prod.gas_name) { // Cerca il GAS rec = await Gasordine.findOne({ idapp, name: prod.gas_name }).lean(); if (!rec) { rec = new Gasordine({ idapp, name: prod.gas_name, active: true }); // Non esiste questo GAS, quindi lo creo ! ris = await rec.save(); rec = await Gasordine.findOne({ idapp, name: prod.gas_name }).lean(); } if (rec) { objtoset = { ...objtoset, idGasordine: rec._id, } setta = true; } } let arrsconti = [] if (prod.sconto1) { // Cerca la scontistica let recscontistica = await Scontistica.findOne({ idapp, code: prod.sconto1 }).lean(); if (!recscontistica) { recscontistica = new Scontistica({ idapp, code: prod.sconto1 }); // Non esiste questa scontistica, quindi lo creo ! ris = await recscontistica.save(); recscontistica = await Scontistica.findOne({ idapp, code: prod.sconto1 }).lean(); } if (recscontistica) { arrsconti.push(recscontistica); } } if (prod.sconto2) { // Cerca la scontistica let recscontistica = await Scontistica.findOne({ idapp, code: prod.sconto2 }).lean(); if (!recscontistica) { recscontistica = new Scontistica({ idapp, code: prod.sconto2 }); // Non esiste questa scontistica, quindi lo creo ! ris = await recscontistica.save(); recscontistica = await Scontistica.findOne({ idapp, code: prod.sconto2 }).lean(); } if (recscontistica) { arrsconti.push(recscontistica); } } if (arrsconti.length > 0) { objtoset = { ...objtoset, idScontisticas: arrsconti, } setta = true; } // Aggiorna il prezzo ? const aggiornaprezzo = false; if (aggiornaprezzo) { // cerca il prodotto const myprodinput = dataObjects.find((rec) => rec._id === prod._id) if (myprodinput) { objtoset = { ...objtoset, price: myprodinput.price, } } } if (!tools.isObjectEmpty(objtoset)) { ris = await Product.findOneAndUpdate({ _id: new ObjectId(prod._id) }, { $set: objtoset }) const objDelete = { cat_name: 1, subcat_name: 1, producer_name: 1, provider_name: 1, magazzino_name: 1, sconto1: 1, sconto2: 1, gas_name: 1, }; ris = await Product.updateOne({ _id: new ObjectId(prod._id) }, { $unset: objDelete }) if (ris && ris.modifiedCount > 0) { console.log('Modificato: ', objtoset.name); } // const campodarimuovere = 'producer_name'; // await Product.findOneAndUpdate({ _id: prod._id }, { $unset: { [campodarimuovere]: 1 } }) } } catch (e) { console.error('Err', e); } }