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, }, delete: { type: Boolean, }, 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, { 'productInfo.catprods.name': { $in: words.map(escapeRegex) }, }, ], }; 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, }, }, { $lookup: { from: 'catprods', localField: 'productInfo.idCatProds', foreignField: '_id', as: 'productInfo.catprods', }, }, { $unwind: { path: '$productInfo.catprods', 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', catprods: '$productInfo.catprods', }, 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, $or: [{ delete: { $exists: false } }, { delete: false }], }; } 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); } };