const server_constants = require('../tools/server_constants'); const shared_consts = require('../tools/shared_nodejs'); const tools = require('../tools/general'); const axios = require('axios'); const SERVER_A_URL = process.env.SERVER_A_URL || "http://IP_DI_SERVER_A:3000"; const API_KEY = process.env.API_KEY_MSSQL; // Funzione per ottenere i dati const getArticlesSales = async () => { try { const query = ` SELECT a.IdArticolo, a.Titolo, a.DataPubblicazione, a.Ean13 AS isbn, a.IdCollana, y.DescrizioneCollana, i2.DescrArgomento, a.ListaArgomenti, a.Pagine, a.IdTipoFormato, a.Misure, COALESCE(o.totVen, 0) as totVen, COALESCE(u.totFat, 0) as totFat, COALESCE(p.rank3M, 0) as rank3M, COALESCE(t.fatrank3M, 0) as fatrank3M, COALESCE(q.rank6M, 0) as rank6M, COALESCE(r.rank1Y, 0) as rank1Y, COALESCE(t.fat3mesi, 0) as fatLast3M, COALESCE(t2.fat6mesi, 0) as fatLast6M, COALESCE(p.venduti3mesi, 0) as vLast3M, COALESCE(q.venduti6mesi, 0) as vLast6M, COALESCE(r.venduti1anno, 0) as vLastY, s.ultimoOrdine as dataUltimoOrdine FROM T_WEB_Articoli a LEFT JOIN (SELECT CodArticoloGM, SUM(Qta) as totVen FROM T_WEB_Ordini GROUP BY CodArticoloGM) o ON a.IdArticolo = o.CodArticoloGM LEFT JOIN (SELECT CodArticolo, SUM(TRY_CAST(Qta AS INT)) as totFat FROM T_WEB_ArticoliFatturati WHERE ISNUMERIC(Qta) = 1 GROUP BY CodArticolo) u ON a.IdArticolo = u.CodArticolo WHERE a.IdStatoProdotto IS NOT NULL ORDER BY totVen DESC; `; const response = await axios.post(SERVER_A_URL + '/query', { query }, { headers: { 'x-api-key': API_KEY } }); return response.data || []; } catch (error) { console.error("Errore nel recupero degli articoli:", error); throw new Error("Errore nel recupero degli articoli venduti."); } }; // Endpoint per ottenere i dati in formato JSON exports.getArticlesSalesHandler = async (req, res) => { try { const data = await getArticlesSales(); if (!data.length) return res.status(404).json({ message: "Nessun articolo trovato." }); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }; // Endpoint per esportare i dati come file JSON exports.exportArticlesSalesByJSON = async (req, res) => { try { const data = await getArticlesSales(); if (!data.length) return res.status(404).json({ message: "Nessun articolo trovato." }); res.setHeader("Content-Type", "application/json"); res.setHeader("Content-Disposition", `attachment; filename="ranking_articles_${new Date().toISOString().split('T')[0]}.json"`); res.json(data); } catch (error) { res.status(500).json({ error: error.message }); } }; exports.getTableContent = async (options) => { try { // Chiama getTableContent, se ritorna errore hangup, allora attendi 2 secondi e poi richiamala. const tableContent = await getTableContentBase(options); return tableContent; } catch (error) { console.error('Error: ', error); if (error.message === 'socket hang up') { console.log('Error: hangup, waiting 2 seconds and retrying...'); await new Promise(resolve => setTimeout(resolve, 2000)); return await this.getTableContent(options); } else { throw error; } } }; const formatDate = (dateValue) => { const date = new Date(dateValue); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}/${month}/${year}`; }; const getTableContentBase = async (options) => { try { // Verifica se la tabella esiste const checkTableQuery = `SELECT COUNT(*) as tableExists FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '${options.nameTable}'`; const checkResponse = await axios.post(SERVER_A_URL + '/query', { query: checkTableQuery }, { headers: { 'x-api-key': API_KEY } }); if (!checkResponse?.data || checkResponse?.data.length === 0 || checkResponse?.data[0].tableExists === 0) { return `La tabella '${options.nameTable}' non esiste.`; } // Recupera le colonne della tabella principale dal catalogo const columnsQuery = `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${options.nameTable}'`; const columnsResponse = await axios.post(SERVER_A_URL + '/query', { query: columnsQuery }, { headers: { 'x-api-key': API_KEY } }); const tableColumns = columnsResponse.data.map(col => col.COLUMN_NAME); // Mappatura per unire i campi (ID e Descrizione) const mergedMapping = { "IdStatoProdotto": "DescrizioneStatoProdotto", "IdTipologia": "DescrizioneTipologia", "IdTipoFormato": "DescrizioneFormato", "IdCollana": "DescrizioneCollana", "ListaArgomenti": "DescrArgomento", "ListaAutori": "AutoriCompleti", "IdMarchioEditoriale": "CasaEditrice", }; // Costruisce la query per recuperare i record let dataQuery = ""; 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 ? ` ,f.DescrizioneStatoProdotto ,i.DescrizioneTipologia ,n.DescrizioneFormato ,y.DescrizioneCollana ,z.AutoriCompleti ,i2.DescrArgomento ,z3.CasaEditrice` : ``) + (options.showQtaDisponibile ? ` ,q.QtaDisponibile ` : ``) + ` 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( SELECT e.IdStatoProdotto, e.Descrizione as DescrizioneStatoProdotto FROM T_WEB_StatiProdotto e JOIN( SELECT IdStatoProdotto, MAX(DataOra) as data1 FROM T_WEB_StatiProdotto GROUP BY IdStatoProdotto ) c ON e.IdStatoProdotto = c.IdStatoProdotto AND e.DataOra = c.data1 ) f ON T.IdStatoProdotto = f.IdStatoProdotto LEFT JOIN( SELECT g.IdTipologia, g.Descrizione as DescrizioneTipologia FROM T_WEB_Tipologie g JOIN( SELECT IdTipologia, MAX(DataOra) as data1 FROM T_WEB_Tipologie GROUP BY IdTipologia ) h ON g.IdTipologia = h.IdTipologia AND g.DataOra = h.data1 ) i ON T.IdTipologia = i.IdTipologia LEFT JOIN( SELECT l.IdTipoFormato, l.Descrizione as DescrizioneFormato FROM T_WEB_TipiFormato l JOIN( SELECT IdTipoFormato, MAX(DataOra) as data1 FROM T_WEB_TipiFormato GROUP BY IdTipoFormato ) m ON l.IdTipoFormato = m.IdTipoFormato AND l.DataOra = m.data1 ) n ON T.IdTipoFormato = n.IdTipoFormato LEFT JOIN( SELECT v.IdCollana, v.Descrizione as DescrizioneCollana FROM T_WEB_Collane v INNER JOIN( SELECT IdCollana, MAX(ID) as MaxID FROM T_WEB_Collane GROUP BY IdCollana ) x ON v.IdCollana = x.IdCollana AND v.ID = x.MaxID ) y ON T.IdCollana = y.IdCollana LEFT JOIN( SELECT g2.IdArgomento, g2.Descrizione as DescrArgomento FROM T_WEB_Argomenti g2 INNER JOIN( SELECT IdArgomento, MAX(DataOra) as data12 FROM T_WEB_Argomenti GROUP BY IdArgomento ) h ON g2.IdArgomento = h.IdArgomento AND g2.DataOra = h.data12 ) i2 ON T.ListaArgomenti = i2.IdArgomento LEFT JOIN( SELECT T1.IdArticolo, STUFF(( SELECT ',' + ISNULL(A2.AutoreCompleto, '') FROM( SELECT CAST('' + REPLACE(T1.ListaAutori, ',', '') + '' AS XML) AS DataXML ) X CROSS APPLY X.DataXML.nodes('/root/x') AS A(x) CROSS APPLY( SELECT TRY_CAST(LTRIM(RTRIM(A.x.value('.', 'VARCHAR(100)'))) AS INT) AS AutoreID ) CA JOIN( SELECT a.IdAutore, CONCAT(a.Nome, ' ', a.Cognome) AS AutoreCompleto FROM T_WEB_Autori a JOIN( SELECT IdAutore, MAX(DataOra) AS maxData FROM T_WEB_Autori GROUP BY IdAutore ) aa ON a.IdAutore = aa.IdAutore AND a.DataOra = aa.maxData ) A2 ON CA.AutoreID = A2.IdAutore FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS AutoriCompleti FROM T_WEB_Articoli T1 GROUP BY T1.IdArticolo, T1.ListaAutori ) z ON T.IdArticolo = z.IdArticolo LEFT JOIN( SELECT a3.IdMarchioEditoriale, a3.Descrizione as CasaEditrice FROM T_WEB_MarchiEditoriali a3 JOIN( SELECT IdMarchioEditoriale, MAX(DataOra) as maxData FROM T_WEB_MarchiEditoriali GROUP BY IdMarchioEditoriale ) aa3 ON a3.IdMarchioEditoriale = aa3.IdMarchioEditoriale AND a3.DataOra = aa3.maxData ) z3 ON T.IdMarchioEditoriale = z3.IdMarchioEditoriale ` : ``) + (options.showQtaDisponibile ? ` LEFT JOIN( SELECT o.Codice, o.QtaDisponibile FROM T_WEB_Disponibile o JOIN( SELECT Codice, MAX(DataOra) as data1 FROM T_WEB_Disponibile GROUP BY Codice ) p ON o.Codice = p.Codice AND o.DataOra = p.data1 ) q ON T.IdArticolo = q.Codice` : ``) } else { dataQuery += ` SELECT TOP ${options.numrec} 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 `; } } else { dataQuery = `SELECT TOP ${options.numrec || 10000} * FROM ${options.nameTable} `; } 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 } }); const records = dataResponse?.data; if (!records || records.length === 0) { return `Nessun record trovato nella tabella '${options.nameTable}'.`; } // Determina quali colonne visualizzare. let displayColumns; if (options.nameTable.toLowerCase() === 't_web_articoli') { // Usa tutte le proprietà del record, escludendo le colonne dei campi uniti (quelle usate per il merge) displayColumns = Object.keys(records[0]).filter(col => !Object.values(mergedMapping).includes(col)); } else { displayColumns = tableColumns; } // Funzione per ottenere il valore da visualizzare, fondendo i campi se presente nella mappatura const getDisplayValue = (record, col) => { let value = record[col] ?? 'NULL'; // Format date solo se il nome della colonna indica una data/ora if ((col.toLowerCase().includes("data") || col.toLowerCase().includes("ora")) && value !== 'NULL') { if (value.includes(',')) { // Se ci sono più valori separati da virgola, formatta ciascuno se è una data valida value = value.split(',') .map(item => { const trimmed = item.trim(); const parsed = Date.parse(trimmed); return !isNaN(parsed) ? formatDate(trimmed) : trimmed; }) .join(', '); } else { const parsed = Date.parse(value); if (!isNaN(parsed)) { value = formatDate(value); } } } if (mergedMapping[col]) { return `${record[mergedMapping[col]] || ''} (${value})`; } return value; }; // Costruisce l'output HTML let output = ""; if (options.outhtml) { if (records.length === 1) { // Se c'è un solo record, visualizza una lista di chiavi e valori const record = records[0]; output += ` `; displayColumns.forEach(column => { output += ` `; }); output += `
Campo Valore
${column} ${getDisplayValue(record, column)}
`; } else { // Se ci sono più record, visualizza una tabella con intestazioni output += ""; displayColumns.forEach(column => { output += `< th style = "padding: 8px; background-color: #f2f2f2;" > ${column} `; }); output += ""; records.forEach(record => { output += ""; displayColumns.forEach(column => { output += `< td style = "padding: 8px;" > ${getDisplayValue(record, column)} `; }); output += ""; }); output += "
"; } } else { // solo dati output = {}; if (options.fieldGM) { if (records && records.length === 1) { output[options.fieldGM] = records[0][options.fieldGM]; } } else { output = []; records.forEach(record => { let myrec = {} if (options.recordraw) { myrec = record; } else { displayColumns.forEach(column => { const value = record[column]; if (value !== undefined && value !== null) { const type = typeof value; if (type === 'number' && !mergedMapping[column]) { myrec[column] = value; } else if (type === 'boolean') { myrec[column] = value; } else if (value instanceof Date) { myrec[column] = formatDate(value); } else { myrec[column] = `${getDisplayValue(record, column)}`.trim(); } } }); } output.push(myrec) }); } } return output; } catch (error) { output = error.message; console.error("Errore nel recupero della tabella: ", error.message); if (options.outhtml) { output = ` Errore nel Recupero della Tabella ${options.nameTable}
Errore nel Recupero della Tabella ${options.nameTable} con query: ${options.where}
${error.response.data.error || error.stack || error.message}
`; } return output; // throw new Error("Errore nel recupero della tabella."); } }; const setTableContent = async (options) => { try { // checkPermissions() const esegui = true if (esegui) { // Verifica se la tabella esiste const checkTableQuery = `SELECT COUNT(*) as tableExists FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '${options.nameTable}'`; const checkResponse = await axios.post(SERVER_A_URL + '/query', { query: checkTableQuery }, { headers: { 'x-api-key': API_KEY } }); if (!checkResponse.data || checkResponse.data.length === 0 || checkResponse.data[0].tableExists === 0) { return `La tabella '${options.nameTable}' non esiste.`; } // Costruisce la query per inserire o aggiornare i record let dataQuery = ""; if (options.insertMode) { // Modalità INSERT const columns = Object.keys(options.data); const values = columns.map(col => `'${options.data[col]}'`).join(", "); dataQuery = ` INSERT INTO ${options.nameTable} (${columns.join(", ")}) VALUES (${values}); `; } else { // Modalità UPDATE const updateFields = Object.keys(options.data) .map(col => `${col} = '${options.data[col]}'`) .join(", "); const whereClause = options.where ? `WHERE ${options.where}` : ""; dataQuery = ` UPDATE ${options.nameTable} SET ${updateFields} ${whereClause}; `; } console.log('dataQuery', dataQuery); // Esegue la query per inserire o aggiornare i dati const dataResponse = await axios.post(SERVER_A_URL + '/query', { query: dataQuery }, { headers: { 'x-api-key': API_KEY } }); if (dataResponse.data && dataResponse.data.affectedRows > 0) { return `Operazione completata con successo su '${options.nameTable}'.`; } else { return `Nessun record modificato nella tabella '${options.nameTable}'.`; } } } catch (error) { console.error("Errore nell'inserimento o aggiornamento della tabella: ", error.message); if (options.outhtml) { output = ` Errore nell'inserimento o aggiornamento della Tabella ${options.nameTable}
Errore nell'inserimento o aggiornamento della Tabella ${options.nameTable}
${error.response.data.error || error.stack || error.message}
`; return output; } return "Errore nell'inserimento o aggiornamento della tabella."; } }; const checkPermissions = async (options) => { try { const dataQuery = ` SELECT dp.name AS UserName, dp.type_desc AS UserType, o.name AS ObjectName, p.permission_name, p.state_desc AS PermissionState FROM sys.database_permissions p JOIN sys.objects o ON p.major_id = o.object_id JOIN sys.database_principals dp ON p.grantee_principal_id = dp.principal_id WHERE o.name = 'T_WEB_Articoli'; `; console.log('checkPermissions query:', dataQuery); // Esegue la query per inserire o aggiornare i dati const dataResponse = await axios.post(SERVER_A_URL + '/query', { query: dataQuery }, { headers: { 'x-api-key': API_KEY } }); console.log('checkPermissions result:', dataResponse.data); if (dataResponse.data && dataResponse.data.affectedRows > 0) { return `Operazione completata con successo.`; } else { return `Nessun permesso.`; } } catch (error) { console.error("Errore nel check dei Permessi: ", error.message); if (options.outhtml) { output = ` Errore nell'inserimento o aggiornamento della Tabella ${options.nameTable}
Errore nell'inserimento o aggiornamento della Tabella ${options.nameTable}
${error.response.data.error || error.stack || error.message}
`; return output; } return "Errore nell'inserimento o aggiornamento della tabella."; } }; // Endpoint per mostrare i dati della tabella exports.viewTable = async (req, res) => { try { const options = req.body.options; const tableContent = await this.getTableContent(options); let out = {}; if (options.outhtml) { out = `

Tabella: ${options.nameTable}

Query: ${options.where}
${tableContent}
` } else { out = tableContent; } if (tableContent && tableContent.length > 0) { if (options.updatelocaldb) { this.updateLocalDb(tableContent[0], options) } } return res.send({ code: server_constants.RIS_CODE_OK, data: out }); } catch (error) { console.error('Error: ', error); return res.send({ code: server_constants.RIS_CODE_ERR, error }); } }; exports.updateLocalDb = async (tableContent, options) => { try { const ProductInfo = require('../models/productInfo'); const CatProd = require('../models/catprod'); let recproductInfo = { code: tableContent.Ean13.trim(), }; let risrecUpdated = null; const recfound = await ProductInfo.findOne({ code: recproductInfo.code }).lean(); if (recfound) { ListaArgomenti = tableContent.ListaArgomenti; let arrayPulito = ListaArgomenti .trim() // Rimuove gli spazi all'inizio e alla fine .replace(/[\(\)]/g, '') // Rimuove le parentesi tonde .split(','); // Divide la stringa in un array usando la virgola come separatore if (arrayPulito && arrayPulito.length > 0) { let aggiornacat = false; const precCatProds = recfound.idCatProds; let reccatprods = []; for (let i = 0; i < arrayPulito.length; i++) { const idArgomento = parseInt(arrayPulito[i]); reccateg = await CatProd.findOne({ idArgomento }).lean(); if (reccateg) { // aggiungi solo se non esiste già if (!reccatprods.includes(reccateg._id)) { reccatprods.push(reccateg._id); } } } // ora controlla se l'array reccatprods e' diverso da precCatProds if (reccatprods.length !== precCatProds.length) { aggiornacat = true; } else { for (let i = 0; i < reccatprods.length; i++) { if (reccatprods[i].toString() !== precCatProds[i].toString()) { aggiornacat = true; break; } } } if (aggiornacat) { recproductInfo.idCatProds = reccatprods; aggiorna = true; } } if (tableContent.DataPubblicazione.trim()) { recproductInfo.date_pub = new Date(tools.convertiDataItaliana(tableContent.DataPubblicazione.trim()).date); // convert data to timestamp recproductInfo.date_pub_ts = recproductInfo.date_pub.getTime(); aggiorna = true; } if (aggiorna) { risrecUpdated = await ProductInfo.findOneAndUpdate({ code: recproductInfo.code }, { $set: recproductInfo }, { new: true, upsert: true }); } return risrecUpdated; } } catch (e) { console.error('Error: ', e); return null; } } // Endpoint per mostrare i dati della tabella exports.queryTable = async (req, res) => { try { const options = req.body.options; const tableContent = await this.getTableContent(options); let out = {}; if (options.outhtml) { out = `

Tabella: ${options.nameTable}

Query: ${options.where}
${tableContent} ` } else { out = tableContent; } return res.send({ code: server_constants.RIS_CODE_OK, data: out }); } catch (error) { console.error('Error: ', error); return res.send({ code: server_constants.RIS_CODE_ERR, error }); } }; // Endpoint per salvare i dati di una tabella exports.saveTable = async (req, res) => { try { const options = req.body.options; const tableContent = await setTableContent(options); let out = {}; if (options.outhtml) { out = `

Tabella: ${options.nameTable}

Query: ${options.where}
${tableContent}
` } else { out = tableContent; } return res.send({ code: server_constants.RIS_CODE_OK, data: out }); } catch (error) { console.error('Error: ', error); return res.send({ code: server_constants.RIS_CODE_ERR, error }); } }; exports.updateAllBook = async (req, res) => { 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 res.status(200).send({ data: result }); } catch (e) { console.error(e.message); return res.status(400).send(e); } }