diff --git a/src/server/controllers/articleController.js b/src/server/controllers/articleController.js index 3bf81c1..020e6e2 100644 --- a/src/server/controllers/articleController.js +++ b/src/server/controllers/articleController.js @@ -6,9 +6,15 @@ const tools = require('../tools/general'); const axios = require('axios'); +const T_Web_Articoli = require('../models/t_web_articoli'); +const T_Web_StatiProdotto = require('../models/t_web_statiprodotto'); +const T_Web_TipiFormato = require('../models/t_web_tipiformato'); + const SERVER_A_URL = process.env.SERVER_A_URL || "http://IP_DI_SERVER_A:3000"; const API_KEY = process.env.API_KEY_MSSQL; +const mongoose = require('mongoose').set('debug', false); + // Funzione per ottenere i dati const getArticlesSales = async () => { try { @@ -74,7 +80,7 @@ exports.exportArticlesSalesByJSON = async (req, res) => { exports.getTableContent = async (options) => { try { // Chiama getTableContent, se ritorna errore hangup, allora attendi 2 secondi e poi richiamala. - const tableContent = await getTableContentBase(options); + const tableContent = await this.getTableContentBase(options); return tableContent; } catch (error) { console.error('Error: ', error); @@ -89,7 +95,7 @@ exports.getTableContent = async (options) => { }; -const formatDate = (dateValue) => { +exports.formatDate = (dateValue) => { const date = new Date(dateValue); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); @@ -97,7 +103,20 @@ const formatDate = (dateValue) => { return `${day}/${month}/${year}`; }; -const getTableContentBase = async (options) => { +exports.getModelByNameTable = (nameTable) => { + switch (nameTable) { + case 'T_Web_Articoli': + return T_Web_Articoli; + case 'T_Web_StatiProdotto': + return T_Web_StatiProdotto; + case 'T_Web_TipiFormato': + return T_Web_TipiFormato; + default: + return null; + } +} + +exports.getTableContentBase = async (options) => { try { // Verifica se la tabella esiste const checkTableQuery = `SELECT COUNT(*) as tableExists FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '${options.nameTable}'`; @@ -129,14 +148,58 @@ const getTableContentBase = async (options) => { // Costruisce la query per recuperare i record let dataQuery = ""; - let columnsToShow = 'T.*'; - if (options.fieldGM) { - columnsToShow = 'T.' + options.fieldGM; - } + let records = []; - if (options.nameTable.toLowerCase() === 't_web_articoli') { - if (true) { - dataQuery = ` + if (options?.usaDBGMLocale) { + + // Cerca il modello corrispondente alla tabella se esiste + let mymodel = this.getModelByNameTable(options.nameTable); + if (!mymodel) { + // fai una query sul db locale mongodb dela tabella chiamata "options.nameTable" + mymodel = mongoose.model(options.nameTable, new mongoose.Schema({}, { strict: false })); + if (!mymodel) + return `Il modello per la tabella '${options.nameTable}' non esiste.`; + } + + if (options.aggregation) { + records = await mymodel.aggregate(options.aggregation); + } else { + const pipeline = []; + + // Filtro base se specificato + if (options.where) { + const whereConditions = options.where; + pipeline.push({ $match: whereConditions }); + } + + if (options.sort) { + pipeline.push({ $sort: options.sort }); + } + + if (options.limit) { + pipeline.push({ $limit: options.limit }); + } + + // Selezione dei campi + if (options.fieldGM) { + pipeline.push({ $project: { [options.fieldGM]: 1 } }); + } + + records = await mymodel.aggregate(pipeline); + } + + console.log('results', records[0]); + + } else { + + let columnsToShow = 'T.*'; + if (options.fieldGM) { + columnsToShow = 'T.' + options.fieldGM; + } + + if (options.nameTable.toLowerCase() === 't_web_articoli') { + if (true) { + dataQuery = ` SELECT TOP ${options.numrec || 10000} ${columnsToShow} ` + (options.campispeciali ? ` @@ -147,14 +210,14 @@ const getTableContentBase = async (options) => { ,z.AutoriCompleti ,i2.DescrArgomento ,z3.CasaEditrice` : ``) + (options.showQtaDisponibile ? ` ,q.QtaDisponibile ` : ``) + - ` FROM T_WEB_Articoli T + ` FROM T_WEB_Articoli T JOIN( SELECT IdArticolo, MAX(DataOra) AS data FROM T_WEB_Articoli GROUP BY IdArticolo ) b ON T.IdArticolo = b.IdArticolo AND T.DataOra = b.data ` - + (options.campispeciali ? - ` LEFT JOIN( + + (options.campispeciali ? + ` LEFT JOIN( SELECT e.IdStatoProdotto, e.Descrizione as DescrizioneStatoProdotto FROM T_WEB_StatiProdotto e JOIN( @@ -234,9 +297,9 @@ const getTableContentBase = async (options) => { GROUP BY IdMarchioEditoriale ) aa3 ON a3.IdMarchioEditoriale = aa3.IdMarchioEditoriale AND a3.DataOra = aa3.maxData ) z3 ON T.IdMarchioEditoriale = z3.IdMarchioEditoriale ` - : ``) - + (options.showQtaDisponibile ? - ` LEFT JOIN( + : ``) + + (options.showQtaDisponibile ? + ` LEFT JOIN( SELECT o.Codice, o.QtaDisponibile FROM T_WEB_Disponibile o JOIN( @@ -245,8 +308,8 @@ const getTableContentBase = async (options) => { GROUP BY Codice ) p ON o.Codice = p.Codice AND o.DataOra = p.data1 ) q ON T.IdArticolo = q.Codice` : ``) - } else { - dataQuery += ` + } else { + dataQuery += ` SELECT TOP ${options.numrec} T.* FROM T_WEB_Articoli T @@ -256,24 +319,27 @@ const getTableContentBase = async (options) => { GROUP BY IdArticolo ) b ON T.IdArticolo = b.IdArticolo AND T.DataOra = b.data `; + } + } else { + dataQuery = `SELECT TOP ${options.numrec || 10000} * FROM ${options.nameTable} `; } - } else { - dataQuery = `SELECT TOP ${options.numrec || 10000} * FROM ${options.nameTable} `; - } - if (options.where && options.where.trim() !== "") { - dataQuery += ` WHERE ${options.where} `; + if (options.where && options.where.trim() !== "") { + dataQuery += ` WHERE ${options.where} `; + } + + console.log('dataQuery', dataQuery); + + // Esegue la query per recuperare i dati + // console.log('dataQuery', dataQuery); + const dataResponse = await axios.post(SERVER_A_URL + '/query', { query: dataQuery }, { + headers: { 'x-api-key': API_KEY } + }); + + records = dataResponse?.data; } - console.log('dataQuery', dataQuery); - - // Esegue la query per recuperare i dati - // console.log('dataQuery', dataQuery); - const dataResponse = await axios.post(SERVER_A_URL + '/query', { query: dataQuery }, { - headers: { 'x-api-key': API_KEY } - }); - const records = dataResponse?.data; if (!records || records.length === 0) { - return `Nessun record trovato nella tabella '${options.nameTable}'.`; + return []; } // Determina quali colonne visualizzare. @@ -296,13 +362,13 @@ const getTableContentBase = async (options) => { .map(item => { const trimmed = item.trim(); const parsed = Date.parse(trimmed); - return !isNaN(parsed) ? formatDate(trimmed) : trimmed; + return !isNaN(parsed) ? this.formatDate(trimmed) : trimmed; }) .join(', '); } else { const parsed = Date.parse(value); if (!isNaN(parsed)) { - value = formatDate(value); + value = this.formatDate(value); } } } @@ -382,7 +448,7 @@ const getTableContentBase = async (options) => { } else if (type === 'boolean') { myrec[column] = value; } else if (value instanceof Date) { - myrec[column] = formatDate(value); + myrec[column] = this.formatDate(value); } else { myrec[column] = `${getDisplayValue(record, column)}`.trim(); } @@ -808,20 +874,59 @@ exports.saveTable = async (req, res) => { } }; -exports.updateAllBook = async (req, res) => { +exports.mssqlmigrateTables = async (req) => { + const MssqlMigrator = require('../modules/MssqlMigrator'); // Importa la classe Macro + + try { + const options = req.body.options; + let listaTabelle = []; + + if (options?.tutte) { + // const listaTabelle = ['T_WEB_StatiProdotto']; + listaTabelle = ['T_WEB_TitoliOriginali', 'T_WEB_TestateOrdini', 'T_WEB_Ordini', 'T_WEB_Disponibile', 'T_WOO_TestateOrdini', 'T_WOO_Ordini', 'T_WEB_Articoli', + 'T_WEB_Argomenti', 'T_WEB_Autori', 'T_WEB_Collane', 'T_WEB_MarchiEditoriali', 'T_WEB_StatiProdotto', 'T_WEB_TipiFormato', 'T_WEB_Tipologie', 'T_WEB_ArticoliFatturati', 'T_WEB_IdInternetFatturati', + 'T_WEB_Edizioni', 'T_WEB_Contratti']; + } else { + listaTabelle = ['T_WEB_Articoli', 'T_WEB_ArticoliFatturati']; + } + + const migrator = new MssqlMigrator(); + return await migrator.migrateTables(listaTabelle); + + } catch (e) { + console.error(e.message); + return 'Errore: ' + e.message + } + +}; +exports.updateAllBook = async (idapp, options) => { const Macro = require('../modules/Macro'); // Importa la classe Macro try { - const idapp = req.body.idapp; - const options = req.body.options; const macro = new Macro(idapp); // Crea un'istanza della classe Macro - const result = await macro.updateLocalDbFromGM_T_Web_Articoli(options); + return await macro.updateLocalDbFromGM_T_Web_Articoli(options); - return res.status(200).send({ data: result }); } catch (e) { console.error(e.message); - return res.status(400).send(e); + throw e; + } +}; + +exports.updateAllBookRoute = async (req, res) => { + + try { + const idapp = req.body.idapp; + const options = req.body.options; + const result = this.updateAllBook(idapp, options); + + return res.status(200).send({ data: result }); + + } catch (e) { + console.error(e.message); + if (res) { + return res.status(400).send(e); + } } } diff --git a/src/server/models/productInfo.js b/src/server/models/productInfo.js index c84c96f..860a769 100755 --- a/src/server/models/productInfo.js +++ b/src/server/models/productInfo.js @@ -152,6 +152,7 @@ const productInfoSchema = new Schema({ vLast3M: Number, fatLast3M: Number, fatLast6M: Number, + fatLast1Y: Number, vLast6M: Number, vLastY: Number, vLast2Y: Number, @@ -445,6 +446,52 @@ module.exports.correggiProductTypes = async function () { } } +module.exports.updateProductInfoByStats = async function (idapp) { + try { + const ProductInfo = this; + + const T_WEB_ArticoliFatturati = require('./t_web_articolifatturati') + + // Ottieni le statistiche dalla query + const statistics = await T_WEB_ArticoliFatturati.getStatistics(); + + let log = "Inizio Aggiornamento Statistiche... \n"; + console.log(mylog); + + // Itera sui risultati e aggiorna productInfo + let countUpdate = 0; + for (const stat of statistics) { + const result = await ProductInfo.updateOne( + { + sku: stat.sku, + idapp + }, // Cerca il documento con lo stesso sku + { + $set: { + fatLast3M: stat.fatLast3M, + fatLast6M: stat.fatLast6M, + fatLast1Y: stat.fatLast1Y + } + }, + { upsert: false } // Non crea il documento se non esiste + ); + if (result.modifiedCount > 0) { + countUpdate++; + } + } + + mylog = `Aggiornati ${countUpdate} record di productInfo`; + + console.log(mylog); + + } catch (error) { + mylog = "Errore durante l'aggiornamento di productInfo:" + error; + console.error(mylog); + } + + return mylog; +} + module.exports.createIndexes() .then(() => { }) .catch((err) => { throw err; }); diff --git a/src/server/models/t_web_articoli.js b/src/server/models/t_web_articoli.js new file mode 100755 index 0000000..9dd39a4 --- /dev/null +++ b/src/server/models/t_web_articoli.js @@ -0,0 +1,107 @@ +mongoose = require('mongoose').set('debug', false) +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + + +// Resolving error Unknown modifier: $pushAll +mongoose.plugin(schema => { + schema.options.usePushEach = true +}); + + +/** + * @typedef {Object} Article + * @property {bigint} Id + * @property {number} IdArticolo + * @property {string} Ean13 + * @property {string} Titolo + * @property {string} ListaAutori + * @property {string} ListaArgomenti + * @property {number} IdStatoProdotto + * @property {number} PrezzoIvato + * @property {number} IdMarchioEditoriale + * @property {number} IdCollana + * @property {Date} DataPubblicazione + * @property {number} IdTipologia + * @property {number} IdTipoFormato + * @property {string} Misure + * @property {string} Pagine + * @property {string} Sottotitolo + * @property {string} Durata + * @property {string} Numero + * @property {string} Edizione + * @property {string} Ristampa + * @property {Date} DataInizioCampagna + * @property {Date} DataFineCampagna + * @property {number} ScontoCampagna + * @property {number} PrezzoIvatoScontatoCampagna + * @property {Date} DataOra + * @property {boolean} Enabled + * @property {number} IDTagGruppo + * @property {string} Utente + * @property {number} PercIva + * @property {number} IdTitoloOriginale + * @property {boolean} EnabledAlFresco + * @property {number} CodEdizione + * @property {string} FasciaEta + * @property {string} DescrizioneStatoProdotto + * @property {string} DescrizioneTipologia + * @property {string} DescrizioneFormato + * @property {string} DescrizioneCollana + * @property {string} DescrArgomento + * @property {string} AutoriCompleti + * @property {string} CasaEditrice + */ + +const T_WEB_ArticoliSchema = new Schema({ + IdArticolo: { type: Number, index: true }, + Ean13: { type: String, index: true }, + Titolo: { type: String, index: true }, + ListaAutori: String, + ListaArgomenti: String, + IdStatoProdotto: Number, + PrezzoIvato: Number, + IdMarchioEditoriale: Number, + IdCollana: Number, + DataPubblicazione: Date, + IdTipologia: Number, + IdTipoFormato: Number, + Misure: String, + Pagine: String, + Sottotitolo: String, + Durata: String, + Numero: String, + Edizione: String, + Ristampa: String, + DataInizioCampagna: Date, + DataFineCampagna: Date, + ScontoCampagna: Number, + PrezzoIvatoScontatoCampagna: Number, + DataOra: Date, + Enabled: Boolean, + IDTagGruppo: Number, + Utente: String, + PercIva: Number, + IdTitoloOriginale: Number, + EnabledAlFresco: Boolean, + CodEdizione: Number, + FasciaEta: String, + DescrizioneStatoProdotto: String, + DescrizioneTipologia: String, + DescrizioneFormato: String, + DescrizioneCollana: String, + DescrArgomento: String, + AutoriCompleti: String, + CasaEditrice: String, +}, { collection: 't_web_articolis' }); + +module.exports = mongoose.model('T_WEB_Articoli', T_WEB_ArticoliSchema); + +module.exports.createIndexes() + .then(() => { }) + .catch((err) => { throw err; }); + diff --git a/src/server/models/t_web_articolifatturati.js b/src/server/models/t_web_articolifatturati.js new file mode 100755 index 0000000..96389a7 --- /dev/null +++ b/src/server/models/t_web_articolifatturati.js @@ -0,0 +1,133 @@ +const mongoose = require('mongoose'); + +// Definizione dello schema +const articoliFatturatiSchema = new mongoose.Schema({ + Codice: { + type: Number, // Decimal in MongoDB è rappresentato come Number + required: true + }, + AnnoDoc: { + type: String, + maxlength: 4, + required: true + }, + NumeroDoc: { + type: Number, // Decimal in MongoDB è rappresentato come Number + required: true + }, + TipoDoc: { + type: String, + maxlength: 20, + required: true + }, + CodArticolo: { + type: String, + maxlength: 50, + required: true + }, + Qta: { + type: String, // Mantenuto come stringa per flessibilità (può essere convertito in Number se necessario) + maxlength: 50, + required: true + }, + DataOra: { + type: Date, // Datetime in MongoDB è rappresentato come Date + required: true + }, + CodTrackingCorriere: { + type: String, + maxlength: 50 + }, + Stato: { + type: Number, // Int in MongoDB è rappresentato come Number + required: true + }, + Note: { + type: String, + maxlength: 250 + }, + IdInternet: { + type: String, + maxlength: 50 + } +}, { + timestamps: true, // Aggiunge automaticamente i campi createdAt e updatedAt + collection: 't_web_articolifatturatis', +}); + +// Creazione del modello +var articoliFatturati = module.exports = mongoose.model('T_WEB_ArticoliFatturati', articoliFatturatiSchema); + +// Funzione per calcolare le statistiche +module.exports.getStatistics = async function () { + const currentDate = new Date(); + + // Calcola le date limite per i periodi di 3 mesi, 6 mesi e 1 anno + const threeMonthsAgo = new Date(currentDate); + threeMonthsAgo.setMonth(currentDate.getMonth() - 3); + + const sixMonthsAgo = new Date(currentDate); + sixMonthsAgo.setMonth(currentDate.getMonth() - 6); + + const oneYearAgo = new Date(currentDate); + oneYearAgo.setFullYear(currentDate.getFullYear() - 1); + + try { + // Query di aggregazione per calcolare le statistiche + const myquery = [ + { + $match: { + DataOra: { $gte: oneYearAgo } // Filtra solo i record degli ultimi 12 mesi + } + }, + { + $group: { + _id: "$CodArticolo", // Raggruppa per CodArticolo + fatLast3M: { + $sum: { + $cond: [ + { $gte: ["$DataOra", threeMonthsAgo] }, // Condizione: DataOra >= 3 mesi fa + { $toInt: "$Qta" }, // Se vero, somma la quantità + 0 // Altrimenti, somma 0 + ] + } + }, + fatLast6M: { + $sum: { + $cond: [ + { $gte: ["$DataOra", sixMonthsAgo] }, // Condizione: DataOra >= 6 mesi fa + { $toInt: "$Qta" }, // Se vero, somma la quantità + 0 // Altrimenti, somma 0 + ] + } + }, + fatLast1Y: { + $sum: { + $cond: [ + { $gte: ["$DataOra", oneYearAgo] }, // Condizione: DataOra >= 1 anno fa + { $toInt: "$Qta" }, // Se vero, somma la quantità + 0 // Altrimenti, somma 0 + ] + } + } + } + }, + { + $project: { + _id: 0, // Rimuove il campo _id dal risultato + sku: "$_id", // Rinomina _id in sku (equivalente a IdArticolo) + fatLast3M: 1, + fatLast6M: 1, + fatLast1Y: 1 + } + } + ]; + + const statistics = await articoliFatturati.aggregate(myquery); + + return statistics; + } catch (error) { + console.error("Errore durante il calcolo delle statistiche:", error); + throw error; + } +} diff --git a/src/server/models/t_web_statiprodotto.js b/src/server/models/t_web_statiprodotto.js new file mode 100755 index 0000000..5142b1a --- /dev/null +++ b/src/server/models/t_web_statiprodotto.js @@ -0,0 +1,58 @@ +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + + +/** + * @typedef {Object} StatoProdotto + * @property {bigint} Id + * @property {number} IdStatoProdotto + * @property {string} Descrizione + * @property {Date} DataOra + * @property {boolean} Enabled + * @property {boolean} EnabledAlFresco + */ + +const StatoProdottoSchema = new Schema({ + IdStatoProdotto: Number, + Descrizione: { type: String, maxlength: 100 }, + DataOra: Date, + Enabled: Boolean, + EnabledAlFresco: Boolean +}, { collection: 't_web_statiprodottos' }); + +const T_WEB_StatiProdotto = module.exports = mongoose.model('T_WEB_StatiProdotto', StatoProdottoSchema); + + +module.exports.findAllIdApp = async function () { + const myfind = {}; + + const myquery = [ + { + $group: { + _id: "$IdStatoProdotto", + record: { $max: "$DataOra" } + } + }, + { + $lookup: { + from: 't_web_statiprodottos', + localField: '_id', + foreignField: 'IdStatoProdotto', + as: 'record' + } + }, + { + $replaceRoot: { newRoot: { $arrayElemAt: ["$record", 0] } } + }, + { + $sort: { IdStatoProdotto: 1 } + } + ]; + + return await T_WEB_StatiProdotto.aggregate(myquery); +}; + + diff --git a/src/server/models/t_web_tipiformato.js b/src/server/models/t_web_tipiformato.js new file mode 100755 index 0000000..e65d11d --- /dev/null +++ b/src/server/models/t_web_tipiformato.js @@ -0,0 +1,23 @@ +// t_web_tipiformato.js +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +/** + * @typedef {Object} TipoFormato + * @property {bigint} Id + * @property {number} IdTipoFormato + * @property {string} Descrizione + * @property {Date} DataOra + * @property {boolean} Enabled + * @property {boolean} EnabledAlFresco + */ + +const TipoFormatoSchema = new Schema({ + IdTipoFormato: Number, + Descrizione: { type: String, maxlength: 100 }, + DataOra: Date, + Enabled: Boolean, + EnabledAlFresco: Boolean +}, { collection: 't_web_tipiformatos' }); + +module.exports = mongoose.model('T_WEB_TipiFormato', TipoFormatoSchema); diff --git a/src/server/modules/Macro.js b/src/server/modules/Macro.js index 0371959..6d1a9de 100644 --- a/src/server/modules/Macro.js +++ b/src/server/modules/Macro.js @@ -13,48 +13,306 @@ const shared_consts = require('../tools/shared_nodejs'); // Assicurati di avere const { getTableContent } = require('../controllers/articleController'); class Macro { - constructor(idapp) { + constructor(idapp, options) { this.idapp = idapp; + this.localoptions = options; } async updateLocalDbFromGM_T_Web_Articoli(params) { - const options = { - idapp: params.idapp, - nameTable: 'T_Web_Articoli', - campispeciali: true, - recordraw: true, - query: '', - showQtaDisponibile: true, - outhtml: false, - cmd: shared_consts.CmdQueryMs.GET, - inputdaGM: true, - } + let mylog = '' + let numrec = 0; + try { - if (params.caricatutti) { - options.where = ''; - } else { - // Singolo - options.where = 'T.IdArticolo =' + params.sku; - } - - let updated = 0, - imported = 0, - errors = 0; - - const recproducts = await getTableContent(options); - - if (Array.isArray(recproducts)) { - for (const recproduct of recproducts) { - await this.elaboraProdotto(recproduct, { updated, imported, errors, inputdaGM: options.inputdaGM, idapp: options.idapp }); + const options = { + idapp: params.idapp, + nameTable: 'T_Web_Articoli', + campispeciali: true, + recordraw: true, + query: '', + showQtaDisponibile: true, + outhtml: false, + cmd: shared_consts.CmdQueryMs.GET, + inputdaGM: true, + ...params, } + + let opt = { + updated: 0, + imported: 0, + errors: 0, + inputdaGM: options.inputdaGM, + idapp: options.idapp, + } + + let miomatch = {}; + let miolimit = 0; + + if (options.caricatutti) { + mylog = '*** CaricaTutti ***\n'; + + if (options.usaDBGMLocale) { + mylog += '*** usaDBGMLocale ***\n'; + miomatch = { IdStatoProdotto: { $in: [1, 4, 34, 45, 46] } }; + // options.where = { IdStatoProdotto: { $in: [1, 4, 34, 45, 46] } }; + } else { + options.where = ` + (DescrizioneStatoProdotto = 'In commercio' OR + DescrizioneStatoProdotto = '2023 in commercio' OR + DescrizioneStatoProdotto = 'Vendita sito' OR + DescrizioneStatoProdotto = 'In prevendita' OR + DescrizioneStatoProdotto = 'Prossima uscita') AND + (DescrizioneTipologia = 'Libri') + `; + } + + } else { + miolimit = 1; + miomatch = { + IdArticolo: Number(options.sku), + Ean13: options.isbn, + }; + } + + if (options.usaDBGMLocale) { + mylog += '*** usaDBGMLocale ***\n'; + options.aggregation = [ + { + $match: miomatch + }, + { + $sort: { + DataOra: -1, + }, + }, + { + $group: { + _id: "$IdArticolo", + lastRecord: { $first: "$$ROOT" } // prendi il record più recente + } + }, + { + $replaceRoot: { newRoot: "$lastRecord" } + }, + ...(miolimit > 0 ? [{ $limit: miolimit }] : []), + { + $lookup: { + from: 't_web_statiprodottos', + localField: 'IdStatoProdotto', + foreignField: 'IdStatoProdotto', + as: 'StatoProdotto', + } + }, + { + $addFields: { + DescrizioneStatoProdotto: { $arrayElemAt: ['$StatoProdotto.Descrizione', 0] }, + } + }, + { + $lookup: { + from: 't_web_tipologies', + localField: 'idTipologia', + foreignField: 'idTipologia', + as: 'DescrizioneTipologia', + } + }, + { + $addFields: { + DescrizioneTipologia: { $arrayElemAt: ['$DescrizioneTipologia.Descrizione', 0] }, + } + }, + { + $lookup: { + from: 't_web_tipiformatos', + localField: 'idFormato', + foreignField: 'idFormato', + as: 'DescrizioneFormato', + } + }, + { + $addFields: { + DescrizioneFormato: { $arrayElemAt: ['$DescrizioneFormato.Descrizione', 0] }, + } + }, + { + $lookup: { + from: 't_web_collanes', + localField: 'idCollana', + foreignField: 'idCollana', + as: 'DescrizioneCollana', + } + }, + { + $addFields: { + DescrizioneCollana: { $arrayElemAt: ['$DescrizioneCollana.Descrizione', 0] }, + } + }, + { + $lookup: { + from: 't_web_edizionis', + localField: 'IdEdizione', + foreignField: 'CodEdizione', + as: 'editore', + } + }, + { + $addFields: { + editore: { $arrayElemAt: ['$editore.Descrizione', 0] }, + } + }, + { + $addFields: { + ListaAutoriArray: { + $map: { + input: { $split: ['$ListaAutori', ','] }, + as: 'id', + in: { + $convert: { + input: { $trim: { input: '$$id' } }, + to: "int", + onError: null, + onNull: null + } + } + } + } + } + }, + { + $lookup: { + from: 't_web_autoris', // assicurati che il nome della collezione sia corretto + localField: 'ListaAutoriArray', + foreignField: 'IdAutore', + as: 'AutoriDettagliati' + } + }, + { + $addFields: { + AutoriCompleti: { + $reduce: { + input: '$AutoriDettagliati', + initialValue: '', + in: { + $cond: [ + { $eq: ['$$value', ''] }, + { $concat: ['$$this.Nome', ' ', '$$this.Cognome'] }, + { $concat: ['$$value', ', ', '$$this.Nome', ' ', '$$this.Cognome'] } + ] + } + } + } + } + }, + { + $addFields: { + ListaArgomentiArray: { + $map: { + input: { $split: ['$ListaArgomenti', ','] }, + as: 'id', + in: { $toInt: { $trim: { input: '$$id' } } } + } + } + } + }, + // Step: Lookup verso collezione degli argomenti, ordinando per DataOra + { + $lookup: { + from: 't_web_argomentis', + let: { argomentiArray: '$ListaArgomentiArray' }, + pipeline: [ + { + $match: { + $expr: { + $in: ['$IdArgomento', + { $ifNull: ["$$argomentiArray", []] } + ] + } + } + }, + { $sort: { DataOra: -1 } }, + { $limit: 1 } + ], + as: 'ArgomentiDettagliati' + } + }, + // Step: Genera campo DescrArgomento concatenando tutte le descrizioni + { + $addFields: { + DescrArgomento: { + $reduce: { + input: '$ArgomentiDettagliati', + initialValue: '', + in: { + $cond: [ + { $eq: ['$$value', ''] }, + '$$this.Descrizione', + { $concat: ['$$value', ', ', '$$this.Descrizione'] } + ] + } + } + } + } + }, + // Step: Pulisci i campi temporanei + { + $project: { + ListaArgomentiArray: 0, + // ArgomentiDettagliati: 0 + } + }, + { + $lookup: { + from: 't_web_marchieditorialis', + localField: 'IdMarchioEditoriale', + foreignField: 'IdMarchioEditoriale', + as: 'CasaEditrice', + } + }, + { + $addFields: { + CasaEditrice: { $arrayElemAt: ['$CasaEditrice.Descrizione', 0] }, + } + }, + + ]; + } else { + if (!options.caricatutti) { + // Singolo + options.where = 'T.IdArticolo =' + options.sku + ' AND T.Ean13 = ' + options.isbn; + } + } + + const recproducts = await getTableContent(options); + + + if (!tools.isArray(recproducts)) { + console.error('Error: ', recproducts); + mylog += recproducts + '\n'; + } else { + numrec = recproducts?.length || 0; + + console.log('numrec', numrec); + } + + + if (Array.isArray(recproducts)) { + for (const recproduct of recproducts) { + if (!options.caricatutti) { + await this.elaboraProdotto(recproduct, opt); + } + } + } + + mylog += ' *** IMPORTATI: ' + opt.imported + ' AGGIORNATI = ' + opt.updated + ' (su ' + numrec + ' RECORD)'; + + console.log(mylog); + return { updated: opt.updated, imported: opt.imported, errors: opt.errors, mylog }; + + } catch (e) { + mylog += 'ERRORE ! *** IMPORTATI: ' + opt.imported + ' AGGIORNATI = ' + opt.updated + ' (su ' + numrec + ' RECORD)'; + console.error(e.message); + return { updated: opt.updated, imported: opt.imported, errors: opt.errors, mylog }; } - console.log( - '*** IMPORTATI: ', - imported, - 'AGGIORNATI = ' + updated + ' (su ' + dataObjects.length + ' RECORD)' - ); - return { updated, imported, errors }; } @@ -151,7 +409,7 @@ class Macro { /** * Elabora un singolo prodotto. */ - async elaboraProdotto(productInput, { updated, imported, errors, inputdaGM, idapp }) { + async elaboraProdotto(productInput, options) { let isnuovo = false, setta = false, importa = true; @@ -159,17 +417,19 @@ class Macro { let product = { ...productInput }; - if (inputdaGM) - product = this.convertiDaCampiGMACampoFDV_ProductInfo(idapp, product); + if (options.inputdaGM) + product = this.convertiDaCampiGMACampoFDV_ProductInfo(options.idapp, product); if (!product.title || !product.sku) importa = false; if (importa) { const productInfo = this.preparaProductInfo(product); - const recrankingisbn = await ImportaIsbn.findOne({ sku: product.sku }).lean(); - if (recrankingisbn) { - this.aggiornaCampiDaIsbn(productInfo, recrankingisbn); + if (this.localoptions?.importadaFDV) { + const recrankingisbn = await ImportaIsbn.findOne({ sku: product.sku }).lean(); + if (recrankingisbn) { + this.aggiornaCampiDaIsbn(product, recrankingisbn); + } } if (!product.hasOwnProperty('active')) { @@ -189,10 +449,10 @@ class Macro { if (risrecInfo) { await this.aggiornaImmagineSeNecessario(risrecInfo); await this.gestisciGasOrdine(product, risrecInfo); - await this.gestisciVariazioni(product, risrecInfo, { updated, imported, errors }); + await this.gestisciVariazioni(product, risrecInfo, options); } else { console.error('Errore ProductInfo:', product.code); - errors++; + options.errors++; } } } @@ -223,6 +483,8 @@ class Macro { if (!recProductExist) { product.idGasordine = recGas ? recGas._id : null; + + await Product.findOneAndUpdate({ _id: product._id }, { $set: { idGasordine: product.idGasordine } }); } } @@ -248,10 +510,10 @@ class Macro { checkout_link: product.checkout_link || undefined, idStatoProdotto: product.idStatoProdotto || undefined, date_pub: product.date_pub || undefined, - sottotitolo: product.Sottotitolo || undefined, + sottotitolo: product.sottotitolo || undefined, }; } catch (e) { - console.error('Errore preparaProductInfo:', e); + console.error('Errore preparaProductInfo :', e); return {}; } } @@ -328,8 +590,8 @@ class Macro { short_descr: '', editore: productGM.CasaEditrice, collezione: productGM.DescrizioneCollana, + editore: productGM.CasaEditrice, Autore: productGM.AutoriCompleti, - publisher: productGM.CasaEditrice, DescrArgomento: productGM.DescrArgomento, idStatoProdotto: productGM.IdStatoProdotto, Stato: this.getStatusByIdStatoProdotto(productGM.IdStatoProdotto), @@ -491,8 +753,8 @@ class Macro { * Gestisce l'editore del prodotto. */ async gestisciEditore(productInfo, product) { - if (product.publisher) { - const publisher = product.publisher.trim(); + if (productInfo.publisher) { + const publisher = productInfo.publisher.trim(); const recpublisher = await Publisher.findOne({ idapp: this.idapp, name: publisher }).lean(); if (!recpublisher) { @@ -551,7 +813,7 @@ class Macro { /** * Gestisce le variazioni del prodotto. */ - async gestisciVariazioni(product, risrecInfo, { updated, imported, errors }) { + async gestisciVariazioni(product, risrecInfo, options) { const queryprod = { idProductInfo: risrecInfo._id }; const recold = await Product.findOne(queryprod).lean(); const variazione = this.preparaVariazione(product); @@ -561,13 +823,14 @@ class Macro { if (recold) { const arrvariazioni = this.aggiornaVariazioni(recold.arrvariazioni, variazione); risultupdate = await Product.findOneAndUpdate(queryprod, { $set: { arrvariazioni } }); + options.updated++; } else { const myproduct = { ...queryprod, arrvariazioni: [variazione] }; risultupdate = await Product.findOneAndUpdate(queryprod, { $set: myproduct }, { new: true, upsert: true }); - imported++; + options.imported++; } - - console.log('risultupdate', risultupdate); + + // console.log('risultupdate', risultupdate); } } diff --git a/src/server/modules/MssqlMigrator.js b/src/server/modules/MssqlMigrator.js new file mode 100644 index 0000000..b819c27 --- /dev/null +++ b/src/server/modules/MssqlMigrator.js @@ -0,0 +1,122 @@ +const axios = require('axios'); + +const mongoose = require('mongoose').set('debug', false) +const Schema = mongoose.Schema; + +const tools = require('../tools/general'); + +mongoose.Promise = global.Promise; +mongoose.level = "F"; + + +class MssqlMigrator { + /** + * Costruttore della classe MssqlMigrator + * @param {string} serverUrl - URL del server che esegue le query MSSQL + * @param {string} apiKey - API Key per l'autenticazione + */ + constructor() { + this.serverUrl = process.env.SERVER_A_URL; + this.apiKey = process.env.API_KEY_MSSQL; + } + + /** + * Migra una lista di tabelle MSSQL in MongoDB usando Mongoose + * @param {string[]} tableNames - Lista dei nomi delle tabelle MSSQL + * @returns {string} logs - Lista dei log di esecuzione + */ + async migrateTables(tableNames) { + + try { + const logs = []; + for (const tableName of tableNames) { + try { + + logs.push(`\n>> Recupero dati da MSSQL per la tabella: ${tableName}`); + console.log(logs[logs.length - 1]); + const dataQuery = `SELECT * FROM [${tableName}]`; + + let dataResponse = null; + + try { + dataResponse = await axios.post( + `${this.serverUrl}/query`, + { query: dataQuery }, + { headers: { 'x-api-key': this.apiKey } }, + null, + { timeout: 300000 }); + } catch (error) { + console.error('Error: ', error); + if (error.message === 'socket hang up') { + console.log('Error: hangup, waiting 5 seconds and retrying...'); + await new Promise(resolve => setTimeout(resolve, 5000)); + + dataResponse = await axios.post( + `${this.serverUrl}/query`, + { query: dataQuery }, + { headers: { 'x-api-key': this.apiKey } }, + null, + { timeout: 300000 }); + + } else { + throw error; + } + } + + const records = dataResponse?.data; + + if (!records || records.length === 0) { + logs.push(`⚠️ Nessun record trovato per la tabella: ${tableName}`); + console.log(logs[logs.length - 1]); + continue; + } + + const sample = records[0]; + const schemaDef = {}; + + for (const key of Object.keys(sample)) { + const value = sample[key]; + if (typeof value === 'string' && key.startsWith('Data') && !isNaN(Date.parse(value))) { + schemaDef[key] = Date; + } else if (typeof value === 'number') { + schemaDef[key] = Number; + } else if (typeof value === 'boolean') { + schemaDef[key] = Boolean; + } else if (value instanceof Date) { + schemaDef[key] = Date; + } else { + schemaDef[key] = mongoose.Schema.Types.Mixed; + } + } + + const dynamicSchema = new mongoose.Schema(schemaDef, { strict: false }); + const modelName = tableName.charAt(0).toUpperCase() + tableName.slice(1); + + // Se il modello esiste già, lo riutilizziamo + const DynamicModel = mongoose.models[modelName] || mongoose.model(modelName, dynamicSchema); + + // ❗ Elimina tutti i documenti esistenti prima dell'inserimento + await DynamicModel.deleteMany({}); + + logs.push(`✅ Inserimento di ${records.length} record nella collezione MongoDB: ${modelName}`); + console.log(logs[logs.length - 1]); + await DynamicModel.insertMany(records); + } catch (error) { + logs.push(`❌ Errore con la tabella ${tableName}:`, error.message); + console.log(logs[logs.length - 1]); + } + + } + + logs.push('\n🎉 Tutte le tabelle sono state migrate.'); + console.log(logs[logs.length - 1]); + return logs.join('\n'); + } catch (error) { + logs.push(`❌ Errore migrateTables:`, error.message); + console.log(logs[logs.length - 1]); + } + } +} + + +module.exports = MssqlMigrator; \ No newline at end of file diff --git a/src/server/router/admin_router.js b/src/server/router/admin_router.js index 554fada..b92fd1c 100755 --- a/src/server/router/admin_router.js +++ b/src/server/router/admin_router.js @@ -993,7 +993,7 @@ router.post('/import', authenticate, async (req, res) => { } else if (cmd === shared_consts.Cmd.MACRO_CATALOGO_JSON) { try { - const macro = new Macro(idapp); // Crea un'istanza della classe Macro + const macro = new Macro(idapp, {importadaFDV: true}); // Crea un'istanza della classe Macro const result = await macro.importaCatalogo(data); // Chiama il metodo importaCatalogo return res.status(200).send(result); } catch (e) { diff --git a/src/server/router/articleRoutes.js b/src/server/router/articleRoutes.js index c1d2969..a020a7e 100644 --- a/src/server/router/articleRoutes.js +++ b/src/server/router/articleRoutes.js @@ -1,5 +1,5 @@ const express = require("express"); -const { getArticlesSalesHandler, exportArticlesSalesByJSON, viewTable, saveTable, queryTable, updateAllBook } = require("../controllers/articleController"); +const { getArticlesSalesHandler, exportArticlesSalesByJSON, viewTable, saveTable, queryTable, updateAllBookRoute, mssqlmigrateTables } = require("../controllers/articleController"); const { authenticate } = require("../middleware/authenticate"); const router = express.Router(); @@ -11,9 +11,9 @@ router.post("/export-articles-sales-json", authenticate, exportArticlesSalesByJS router.post("/view-table", authenticate, viewTable); router.post("/save-table", authenticate, saveTable); router.post("/query", authenticate, queryTable); -router.post("/updateAllBook", authenticate, updateAllBook); - +router.post("/updateAllBook", authenticate, updateAllBookRoute); +router.post("/mssqlmigrateTables", authenticate, mssqlmigrateTables); module.exports = router; diff --git a/src/server/router/index_router.js b/src/server/router/index_router.js index 9a9d8e2..d397f80 100755 --- a/src/server/router/index_router.js +++ b/src/server/router/index_router.js @@ -84,6 +84,8 @@ const Department = require('../models/department'); const { Category } = require('../models/category'); const Group = require('../models/group'); +const T_WEB_StatiProdotto = require('../models/t_web_statiprodotto'); + const tools = require('../tools/general'); const server_constants = require('../tools/server_constants'); @@ -1941,6 +1943,7 @@ async function load(req, res, version = '0') { const socioresidente = req.user && req.user.profile ? req.user.profile.socioresidente : false; + // Costruzione dell'oggetto delle promesse const promises = { bookedevent: userId !== '0' @@ -2016,6 +2019,7 @@ async function load(req, res, version = '0') { collane: version >= 91 ? Collana.findAllIdApp(idapp) : Promise.resolve([]), catalogs: version >= 91 ? Catalog.findAllIdApp(idapp) : Promise.resolve([]), catprtotali: version >= 91 ? CatProd.getCatProdWithTitleCount(idapp) : Promise.resolve([]), + stati_prodotto: version >= 91 ? T_WEB_StatiProdotto.findAllIdApp() : Promise.resolve([]), myuserextra: req.user ? User.addExtraInfo(idapp, req.user, version) : Promise.resolve(null) @@ -2128,7 +2132,8 @@ async function load(req, res, version = '0') { myschedas: data.myschedas, collane: data.collane, catalogs: data.catalogs, - catprtotali: data.catprtotali + catprtotali: data.catprtotali, + stati_prodotto: data.stati_prodotto, }; } diff --git a/src/server/router/users_router.js b/src/server/router/users_router.js index 8d35c5e..c018075 100755 --- a/src/server/router/users_router.js +++ b/src/server/router/users_router.js @@ -1074,6 +1074,14 @@ async function eseguiDbOp(idapp, mydata, locale, req, res) { await CatProd.deleteMany({ idapp }); await SubCatProd.deleteMany({ idapp }); + } else if (mydata.dbop === 'updateAllBook') { + // chiama updateAllBook + const { updateAllBook } = require("../controllers/articleController"); + + mystr = await updateAllBook(req, null, mydata.options); + + ris = { mystr }; + } else if (mydata.dbop === 'creaUtentiTest') { let num = 0; @@ -1165,6 +1173,18 @@ async function eseguiDbOp(idapp, mydata, locale, req, res) { } ris = { mystr }; + } else if (mydata.dbop === 'UpdateStatFatturato') { + + mystr = await ProductInfo.updateProductInfoByStats(req.body.idapp); + ris = { mystr }; + + } else if (mydata.dbop === 'MigrateMSSQLToMongoDb') { + const { mssqlmigrateTables } = require("../controllers/articleController"); + + mystr = await mssqlmigrateTables(req); + + ris = { mystr }; + } else if (mydata.dbop === 'copyFrom1To14') { const idapporig = 1; const idappdest = 14; @@ -1672,22 +1692,18 @@ router.post('/dbop', authenticate, async (req, res) => { locale = req.body.locale; if (!User.isAdmin(req.user.perm)) { - // If without permissions, exit - return res.status(404). - send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' }); + return res.status(404).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED }); } try { - let risOp = await eseguiDbOp(idapp, mydata, locale, req, res); + + const risOp = await eseguiDbOp(idapp, mydata, locale, req, res); - // ris = await User.updateMyData(ris, idapp, req.user.username); - - res.send({ code: server_constants.RIS_CODE_OK, data: risOp }); + return res.send({ code: server_constants.RIS_CODE_OK, data: risOp }); } catch (e) { - res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e }); - console.log(e.message); + return res.status(400).send({ code: server_constants.RIS_CODE_ERR, msg: e.message }); } }); diff --git a/src/server/tools/globalTables.js b/src/server/tools/globalTables.js index 7e71fe1..c4893e2 100755 --- a/src/server/tools/globalTables.js +++ b/src/server/tools/globalTables.js @@ -76,6 +76,7 @@ const Collana = require('../models/collana'); const CatAI = require('../models/catai'); const { QueryAI } = require('../models/queryai'); const SubCatProd = require('../models/subcatprod'); +const T_Web_StatiProdotto = require('../models/t_web_statiprodotto'); const { Category } = require('../models/category'); const ShareWithUs = require('../models/sharewithus'); const Site = require('../models/site'); @@ -143,6 +144,8 @@ module.exports = { mytable = QueryAI; else if (tablename === 'subcatprods') mytable = SubCatProd; + else if (tablename === 't_web_statiprodottos') + mytable = T_Web_StatiProdotto; else if (tablename === 'sharewithus') mytable = ShareWithUs; else if (tablename === 'sites')