From e19e50de148f9e46a12fa1e4c9807489d514b432 Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 10 Feb 2025 13:04:23 +0100 Subject: [PATCH 01/66] feat: refs #8227 Update roadmap triggers to manage eta adjustments and prevent recursive calls --- .../vn/triggers/roadmapStop_beforeUpdate.sql | 35 +++++++++++-------- .../vn/triggers/roadmap_afterUpdate.sql | 25 ++++++++----- .../vn/triggers/roadmap_beforeUpdate.sql | 20 +++++++++++ .../11436-bronzeMonstera/00-firstScript.sql | 7 ++++ 4 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 db/versions/11436-bronzeMonstera/00-firstScript.sql diff --git a/db/routines/vn/triggers/roadmapStop_beforeUpdate.sql b/db/routines/vn/triggers/roadmapStop_beforeUpdate.sql index c3142c8acc..93c44b042b 100644 --- a/db/routines/vn/triggers/roadmapStop_beforeUpdate.sql +++ b/db/routines/vn/triggers/roadmapStop_beforeUpdate.sql @@ -19,24 +19,31 @@ BEGIN CALL util.throw('Departure time can not be after arrival time'); END IF; - SELECT MAX(eta) INTO vMaxEta - FROM roadmapStop - WHERE roadmapFk = NEW.roadmapFk - AND id <> OLD.id; + IF @roadmapTriggerIsActive IS NULL THEN + SET @roadmapTriggerIsActive = TRUE; - IF vMaxEta < NEW.eta OR vMaxEta IS NULL THEN - SET vMaxEta = NEW.eta; - END IF; + SELECT MAX(eta) INTO vMaxEta + FROM roadmapStop + WHERE roadmapFk = NEW.roadmapFk + AND id <> OLD.id; - SELECT eta INTO vCurrentEta - FROM roadmap - WHERE id = NEW.roadmapFk; + IF vMaxEta < NEW.eta OR vMaxEta IS NULL THEN + SET vMaxEta = NEW.eta; + END IF; - IF (vMaxEta <> vCurrentEta OR vMaxEta IS NULL) OR vMaxEta IS NOT NULL THEN - UPDATE roadmap - SET eta = vMaxEta + SELECT eta INTO vCurrentEta + FROM roadmap WHERE id = NEW.roadmapFk; + + IF (vMaxEta <> vCurrentEta OR vMaxEta IS NULL) OR vMaxEta IS NOT NULL THEN + UPDATE roadmap + SET eta = vMaxEta + WHERE id = NEW.roadmapFk; + END IF; + + SET @roadmapTriggerIsActive = NULL; + END IF; END IF; END$$ -DELIMITER ; +DELIMITER ; \ No newline at end of file diff --git a/db/routines/vn/triggers/roadmap_afterUpdate.sql b/db/routines/vn/triggers/roadmap_afterUpdate.sql index 7fcc31d922..26f77dd0cc 100644 --- a/db/routines/vn/triggers/roadmap_afterUpdate.sql +++ b/db/routines/vn/triggers/roadmap_afterUpdate.sql @@ -1,17 +1,26 @@ DELIMITER $$ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_afterUpdate` - AFTER UPDATE ON `roadmap` - FOR EACH ROW +AFTER UPDATE ON `roadmap` +FOR EACH ROW BEGIN DECLARE vSeconds INT; - IF NOT (NEW.etd <=> OLD.etd) THEN - SET vSeconds = TIME_TO_SEC(TIMEDIFF(NEW.etd, OLD.etd)); - IF vSeconds IS NOT NULL AND vSeconds <> 0 THEN - UPDATE roadmapStop - SET eta = eta + INTERVAL vSeconds SECOND - WHERE roadmapFk = NEW.id; + IF @roadmapTriggerIsActive IS NULL THEN + SET @roadmapTriggerIsActive = TRUE; + + IF NOT (NEW.etd <=> OLD.etd) THEN + SET vSeconds = TIME_TO_SEC(TIMEDIFF(NEW.etd, OLD.etd)); + + IF vSeconds IS NOT NULL AND vSeconds <> 0 THEN + UPDATE vn.roadmapStop + SET eta = eta + INTERVAL vSeconds SECOND + WHERE roadmapFk = NEW.id; + + SET NEW.eta = NEW.eta + INTERVAL vSeconds SECOND; + END IF; END IF; + + SET @roadmapTriggerIsActive = NULL; END IF; END$$ DELIMITER ; \ No newline at end of file diff --git a/db/routines/vn/triggers/roadmap_beforeUpdate.sql b/db/routines/vn/triggers/roadmap_beforeUpdate.sql index 4f355915bc..01f1ba7061 100644 --- a/db/routines/vn/triggers/roadmap_beforeUpdate.sql +++ b/db/routines/vn/triggers/roadmap_beforeUpdate.sql @@ -3,6 +3,8 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate` BEFORE UPDATE ON `roadmap` FOR EACH ROW BEGIN + DECLARE vSeconds INT; + SET NEW.editorFk = account.myUser_getId(); IF NOT (NEW.name <=> OLD.name) THEN @@ -37,5 +39,23 @@ BEGIN FROM worker w WHERE w.id = NEW.driverChangeFk); END IF; + + IF @trigger_active IS NULL THEN + SET @trigger_active = TRUE; + + IF NOT (NEW.etd <=> OLD.etd) THEN + SET vSeconds = TIME_TO_SEC(TIMEDIFF(NEW.etd, OLD.etd)); + + IF vSeconds IS NOT NULL AND vSeconds <> 0 THEN + UPDATE vn.roadmapStop + SET eta = eta + INTERVAL vSeconds SECOND + WHERE roadmapFk = NEW.id; + + SET NEW.eta = NEW.eta + INTERVAL vSeconds SECOND; + END IF; + END IF; + + SET @trigger_active = NULL; + END IF; END$$ DELIMITER ; \ No newline at end of file diff --git a/db/versions/11436-bronzeMonstera/00-firstScript.sql b/db/versions/11436-bronzeMonstera/00-firstScript.sql new file mode 100644 index 0000000000..eaf0c48648 --- /dev/null +++ b/db/versions/11436-bronzeMonstera/00-firstScript.sql @@ -0,0 +1,7 @@ +ALTER TABLE vn.roadmap + MODIFY COLUMN dollyPlate varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL COMMENT + 'Vehículo sin motor diseñado para conectarse a una unidad tractora, un camión o un vehículo tractor con fuerte potencia de tracción'; + +GRANT UPDATE (palletM3) ON vn.volumeConfig TO deliveryBoss; + +ALTER TABLE vn.volumeConfig ADD COLUMN id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST; From 75b6867be8cfdc931396b1f9b00d79c431a7277b Mon Sep 17 00:00:00 2001 From: guillermo Date: Mon, 10 Feb 2025 13:06:05 +0100 Subject: [PATCH 02/66] feat: refs #8227 Minor change --- db/routines/vn/triggers/roadmap_beforeUpdate.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/routines/vn/triggers/roadmap_beforeUpdate.sql b/db/routines/vn/triggers/roadmap_beforeUpdate.sql index 01f1ba7061..e9b4b1b981 100644 --- a/db/routines/vn/triggers/roadmap_beforeUpdate.sql +++ b/db/routines/vn/triggers/roadmap_beforeUpdate.sql @@ -40,8 +40,8 @@ BEGIN WHERE w.id = NEW.driverChangeFk); END IF; - IF @trigger_active IS NULL THEN - SET @trigger_active = TRUE; + IF @roadmapTriggerIsActive IS NULL THEN + SET @roadmapTriggerIsActive = TRUE; IF NOT (NEW.etd <=> OLD.etd) THEN SET vSeconds = TIME_TO_SEC(TIMEDIFF(NEW.etd, OLD.etd)); @@ -55,7 +55,7 @@ BEGIN END IF; END IF; - SET @trigger_active = NULL; + SET @roadmapTriggerIsActive = NULL; END IF; END$$ DELIMITER ; \ No newline at end of file From 02f51a244d6b71b8210b25f31f5f73c3238b2fe7 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 11 Feb 2025 12:19:15 +0100 Subject: [PATCH 03/66] feat: refs #8555 added new filter field --- modules/travel/back/methods/travel/extraCommunityFilter.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/travel/back/methods/travel/extraCommunityFilter.js b/modules/travel/back/methods/travel/extraCommunityFilter.js index 2f3f998d6e..5c294a9651 100644 --- a/modules/travel/back/methods/travel/extraCommunityFilter.js +++ b/modules/travel/back/methods/travel/extraCommunityFilter.js @@ -67,6 +67,11 @@ module.exports = Self => { type: 'number', description: 'The freighter supplier id' }, + { + arg: 'entrySupplierFk', + type: 'number', + description: 'The supplier of the entry(not freighter) id' + }, ], returns: { type: ['Object'], @@ -94,6 +99,8 @@ module.exports = Self => { return {'t.landed': {lte: value}}; case 'continent': return {'cnt.code': value}; + case 'entrySupplierFk': + return {'e.supplierFk': value}; case 'id': case 'agencyModeFk': case 'warehouseOutFk': From 3fbd740a2b3ecd9419881c2ee0fa5f8cfbc35a37 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 11 Feb 2025 14:31:26 +0100 Subject: [PATCH 04/66] fix: address invoice daily --- db/routines/vn/procedures/ticket_close.sql | 24 +-- .../ticket/back/methods/ticket/closeAll.js | 154 ++++++------------ .../methods/ticket/specs/closeAll.spec.js | 2 +- 3 files changed, 55 insertions(+), 125 deletions(-) diff --git a/db/routines/vn/procedures/ticket_close.sql b/db/routines/vn/procedures/ticket_close.sql index e2dcef9a5a..2b6a33fbaa 100644 --- a/db/routines/vn/procedures/ticket_close.sql +++ b/db/routines/vn/procedures/ticket_close.sql @@ -18,6 +18,7 @@ BEGIN DECLARE vWithPackage BOOL; DECLARE vHasToInvoice BOOL; DECLARE vSerial VARCHAR(2); + DECLARE vStateCode VARCHAR(45); DECLARE cur CURSOR FOR SELECT ticketFk FROM tmp.ticket_close; @@ -80,27 +81,14 @@ BEGIN AND getSpecialPrice(e.freightItemFk, vClientFk) > 0 GROUP BY e.freightItemFk); - IF(vHasDailyInvoice) AND vHasToInvoice THEN - SELECT invoiceSerial(vClientFk, vCompanyFk, 'quick') INTO vSerial; - IF vSerial IS NULL THEN - CALL util.throw('Cannot booking without a serial'); - END IF; - CALL ticket_setState(vCurTicketFk, 'DELIVERED'); - - IF vIsTaxDataChecked THEN - CALL invoiceOut_newFromClient( - vClientFk, - vSerial, - vShipped, - vCompanyFk, - NULL, - NULL, - vNewInvoiceId); - END IF; + IF vHasDailyInvoice AND vHasToInvoice THEN + SET vStateCode = 'DELIVERED'; ELSE - CALL ticket_setState(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk))); + SELECT vn.getAlert3State(vCurTicketFk) INTO vStateCode; END IF; + CALL ticket_setState(vCurTicketFk, vStateCode); + END LOOP; CLOSE cur; diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index 71122808cc..d98f448cf5 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -1,5 +1,3 @@ -const closure = require('./closure'); - module.exports = Self => { Self.remoteMethodCtx('closeAll', { description: 'Makes the closure process from all warehouses', @@ -36,111 +34,32 @@ module.exports = Self => { toDate.setHours(0, 0, 0, 0); toDate.setDate(toDate.getDate() - 1); - const tickets = await Self.rawSql(` - SELECT t.id, - t.clientFk, - t.companyFk, - c.id clientFk, - c.name clientName, - c.email recipient, - c.salesPersonFk, - c.isToBeMailed, - c.hasToInvoice, - c.hasDailyInvoice, - eu.email salesPersonEmail, - t.addressFk - FROM ticket t - JOIN agencyMode am ON am.id = t.agencyModeFk - JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - JOIN client c ON c.id = t.clientFk - JOIN province p ON p.id = c.provinceFk - JOIN country co ON co.id = p.countryFk - LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk - JOIN ticketConfig tc ON TRUE - WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) - AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?) - AND t.refFk IS NULL - GROUP BY t.id + await Self.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close; + CREATE TEMPORARY TABLE tmp.ticket_close + ENGINE = MEMORY + SELECT + DISTINCT t.id ticketFk, + t.clientFk, + t.addressFk, + c.hasToInvoiceByAddress, + t.totalWithVat, + t.companyFk + FROM ticket t + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN alertLevel al ON al.id = ts.alertLevel + JOIN client c ON c.id = t.clientFk + JOIN province p ON p.id = c.provinceFk + JOIN country co ON co.id = p.countryFk + LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk + JOIN ticketConfig tc ON TRUE + WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) + AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?) + AND t.refFk IS NULL; + CALL ticket_close(); `, [toDate, toDate], myOptions); - const ticketIds = tickets.map(ticket => ticket.id); - await Self.rawSql(` - INSERT INTO util.debug (variable, value) - VALUES ('nightInvoicing', ?) - `, [ticketIds.join(',')], myOptions); - - await Self.rawSql(` - WITH ticketNotInvoiceable AS( - SELECT JSON_OBJECT( - 'tickets', - JSON_ARRAYAGG( - JSON_OBJECT( - 'ticketId', ticketFk, - 'reason', reason, - 'clientId', clientFk - ) - ) - )errors - FROM ( - SELECT ticketFk, - CONCAT_WS(', ', - IF(hasErrorToInvoice, 'Facturar', NULL), - IF(hasErrorTaxDataChecked, 'Datos comprobados', NULL), - IF(hasErrorDeleted, 'Eliminado', NULL), - IF(hasErrorItemTaxCountry, 'Impuesto no informado', NULL), - IF(hasErrorAddress, 'Sin dirección', NULL), - IF(hasErrorInfoTaxAreaWorld, 'Datos exportaciones', NULL)) reason, - clientFk - FROM ( - SELECT t.id ticketFk, - SUM(NOT c.hasToInvoice) hasErrorToInvoice, - SUM(NOT c.isTaxDataChecked) hasErrorTaxDataChecked, - SUM(t.isDeleted) hasErrorDeleted, - SUM(itc.id IS NULL) hasErrorItemTaxCountry, - SUM(a.id IS NULL) hasErrorAddress, - SUM(ios.code IS NOT NULL - AND(ad.customsAgentFk IS NULL - OR ad.incotermsFk IS NULL)) hasErrorInfoTaxAreaWorld, - t.clientFk clientFk - FROM ticket t - LEFT JOIN address ad ON ad.id = t.addressFk - JOIN sale s ON s.ticketFk = t.id - JOIN item i ON i.id = s.itemFk - JOIN supplier su ON su.id = t.companyFk - JOIN agencyMode am ON am.id = t.agencyModeFk - JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission - JOIN ticketState ts ON ts.ticketFk = t.id - JOIN alertLevel al ON al.id = ts.alertLevel - JOIN client c ON c.id = t.clientFk - JOIN province p ON p.id = c.provinceFk - JOIN ticketConfig tc ON TRUE - LEFT JOIN autonomy a ON a.id = p.autonomyFk - JOIN country co ON co.id = p.countryFk - LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk - LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id - AND itc.countryFk = su.countryFk - LEFT JOIN vn.invoiceOutSerial ios ON ios.taxAreaFk = 'WORLD' - AND ios.code = invoiceSerial(t.clientFk, t.companyFk, 'multiple') - WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) - AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?) - AND t.refFk IS NULL - AND c.hasDailyInvoice - GROUP BY ticketFk - HAVING hasErrorToInvoice - OR hasErrorTaxDataChecked - OR hasErrorDeleted - OR hasErrorItemTaxCountry - OR hasErrorAddress - OR hasErrorInfoTaxAreaWorld - )sub - )sub2 - ) SELECT IF(errors = '{"tickets": null}', - 'No errors', - util.notification_send('invoice-ticket-closure', errors, NULL)) - FROM ticketNotInvoiceable`, [toDate, toDate], myOptions); - - await closure(ctx, Self, tickets, myOptions); await Self.rawSql(` UPDATE ticket t @@ -157,9 +76,32 @@ module.exports = Self => { AND tob.id IS NULL AND t.routeFk`, [toDate, toDate], myOptions); + const [clients] = await Self.rawSql(` + SELECT clientFk clientId, + addressFk addressId, + companyFk, + SUM(totalWithVat) total, + 'quick' serialType + FROM tmp.ticket_close + GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk + HAVING total > 0; + DROP TEMPORARY TABLE tmp.ticket_close; + `, [], myOptions); + if (tx) await tx.commit(); + for (const client of clients) { + ctx.args = { + ...client, + invoiceDate: Date.vnNew(), + maxShipped: Date.vnNew() + }; + const id = await Self.app.models.InvoiceOut.invoiceClient(ctx); + // if (id) + // await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id); + } + return { message: 'Success' }; diff --git a/modules/ticket/back/methods/ticket/specs/closeAll.spec.js b/modules/ticket/back/methods/ticket/specs/closeAll.spec.js index f01541eec0..923f688bbb 100644 --- a/modules/ticket/back/methods/ticket/specs/closeAll.spec.js +++ b/modules/ticket/back/methods/ticket/specs/closeAll.spec.js @@ -1,7 +1,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); -describe('Ticket Closure - closeAll function', () => { +fdescribe('Ticket Closure - closeAll function', () => { let ctx = { req: { getLocale: () => 'es', From 92232b34cec774d3983960011ef029e79b6402e8 Mon Sep 17 00:00:00 2001 From: carlossa Date: Tue, 11 Feb 2025 15:55:08 +0100 Subject: [PATCH 05/66] fix: hotfix delivery-note --- print/templates/reports/delivery-note/delivery-note.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index 89bc07488e..4f5d71106e 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -70,7 +70,7 @@ {{sale.itemFk}} {{sale.quantity}} {{sale.concept}} - {{sale.subName}} + {{sale.subName}} {{sale.price | currency('EUR', $i18n.locale)}} {{(sale.discount / 100) | percentage}} {{sale.vatType}} From e7c027a8b1e9fff5e15ebad00a20c6e55aefcaa5 Mon Sep 17 00:00:00 2001 From: guillermo Date: Wed, 12 Feb 2025 10:06:48 +0100 Subject: [PATCH 06/66] refactor: refs #6944 Update ticket_setState to improve state change logic and user tracking --- db/routines/vn/procedures/ticket_setState.sql | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/db/routines/vn/procedures/ticket_setState.sql b/db/routines/vn/procedures/ticket_setState.sql index f4906fb115..a8dcc0da6e 100644 --- a/db/routines/vn/procedures/ticket_setState.sql +++ b/db/routines/vn/procedures/ticket_setState.sql @@ -3,21 +3,25 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setState`( vSelf INT, vStateCode VARCHAR(255) COLLATE utf8_general_ci ) -BEGIN +proc:BEGIN /** * Modifica el estado de un ticket si se cumplen las condiciones necesarias. * * @param vSelf el id del ticket * @param vStateCode estado a modificar del ticket */ - DECLARE vticketAlertLevel INT; + DECLARE vTicketAlertLevel INT; DECLARE vTicketStateCode VARCHAR(255); DECLARE vCanChangeState BOOL; DECLARE vPackedAlertLevel INT; DECLARE vZoneFk INT; + DECLARE vOldWorkerFk INT; + DECLARE vNewWorkerFk INT; - SELECT s.alertLevel, s.`code`, t.zoneFk - INTO vticketAlertLevel, vTicketStateCode, vZoneFk + SET vNewWorkerFk = account.myUser_getId(); + + SELECT s.alertLevel, s.`code`, t.zoneFk, tt.userFk + INTO vTicketAlertLevel, vTicketStateCode, vZoneFk, vOldWorkerFk FROM state s JOIN ticketTracking tt ON tt.stateFk = s.id JOIN ticket t ON t.id = tt.ticketFk @@ -33,24 +37,27 @@ BEGIN SET vCanChangeState = (( vStateCode <> 'ON_CHECKING' AND vStateCode <> 'CHECKED') OR - vticketAlertLevel < vPackedAlertLevel - )AND NOT ( + vTicketAlertLevel < vPackedAlertLevel + ) AND NOT ( vTicketStateCode IN ('CHECKED', 'CHECKING') AND vStateCode IN ('PREPARED', 'ON_PREPARATION') ); IF vCanChangeState THEN - INSERT INTO ticketTracking (stateFk, ticketFk, userFk) - SELECT id, vSelf, account.myUser_getId() - FROM state - WHERE `code` = vStateCode COLLATE utf8_unicode_ci; - IF vStateCode = 'PACKED' THEN CALL ticket_doCmr(vSelf); END IF; + + IF vStateCode = vTicketStateCode COLLATE utf8_general_ci AND vOldWorkerFk = vNewWorkerFk THEN + LEAVE proc; + END IF; + + INSERT INTO ticketTracking (stateFk, ticketFk, userFk) + SELECT id, vSelf, vNewWorkerFk + FROM state + WHERE `code` = vStateCode COLLATE utf8_unicode_ci; ELSE CALL util.throw('INCORRECT_TICKET_STATE'); END IF; - END$$ DELIMITER ; From 1c9417556b9429fa1393ba93caf696691bbc28d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Wed, 12 Feb 2025 12:13:23 +0100 Subject: [PATCH 07/66] feat: enhance ticket closure process with error handling and email notifications --- .../ticket/back/methods/ticket/closeAll.js | 85 ++++++++++++++++--- .../methods/ticket/specs/closeAll.spec.js | 2 +- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index d98f448cf5..307cc011dd 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -1,3 +1,6 @@ +const smtp = require('vn-print/core/smtp'); +const config = require('vn-print/core/config'); + module.exports = Self => { Self.remoteMethodCtx('closeAll', { description: 'Makes the closure process from all warehouses', @@ -23,13 +26,14 @@ module.exports = Self => { Self.closeAll = async(ctx, options) => { const userId = ctx.req.accessToken.userId; const myOptions = {userId}; - + let tx; if (typeof options == 'object') Object.assign(myOptions, options); - let tx; - // IMPORTANT: Due to its high cost in production, wrapping this process in a transaction may cause timeouts. - + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } const toDate = Date.vnNew(); toDate.setHours(0, 0, 0, 0); toDate.setDate(toDate.getDate() - 1); @@ -41,6 +45,9 @@ module.exports = Self => { SELECT DISTINCT t.id ticketFk, t.clientFk, + c.name clientName, + c.email recipient, + eu.email salesPersonEmail, t.addressFk, c.hasToInvoiceByAddress, t.totalWithVat, @@ -51,14 +58,12 @@ module.exports = Self => { JOIN ticketState ts ON ts.ticketFk = t.id JOIN alertLevel al ON al.id = ts.alertLevel JOIN client c ON c.id = t.clientFk - JOIN province p ON p.id = c.provinceFk - JOIN country co ON co.id = p.countryFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk JOIN ticketConfig tc ON TRUE WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?) AND t.refFk IS NULL; - CALL ticket_close(); + CALL ticket_close(); `, [toDate, toDate], myOptions); await Self.rawSql(` @@ -78,6 +83,9 @@ module.exports = Self => { const [clients] = await Self.rawSql(` SELECT clientFk clientId, + clientName, + recipient, + salesPersonEmail, addressFk addressId, companyFk, SUM(totalWithVat) total, @@ -91,19 +99,76 @@ module.exports = Self => { if (tx) await tx.commit(); + const failedClients = []; + // Only for testing + const nestedTransaction = options?.transaction ? myOptions : {}; for (const client of clients) { ctx.args = { ...client, invoiceDate: Date.vnNew(), maxShipped: Date.vnNew() }; - const id = await Self.app.models.InvoiceOut.invoiceClient(ctx); - // if (id) - // await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id); + try { + const id = await Self.app.models.InvoiceOut.invoiceClient(ctx, nestedTransaction); + if (id) + await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id, null, nestedTransaction); + } catch (error) { + await Self.rawSql(` + INSERT INTO util.debug (variable, value) + VALUES ('invoicingTicketError', ?) + `, [client.clientId + ' - ' + error]); + + if (error.responseCode == 450) { + await invalidEmail(client); + continue; + } + + failedClients.push({ + id: client.clientId, + address: client.addressId, + error + }); + } + } + + if (failedClients.length > 0) { + let body = 'This following tickets have failed:

'; + + for (const client of failedClients) { + body += `Client: ${client.id} + Address: ${client.address} +
${client.error}

`; + } + + smtp.send({ + to: config.app.reportEmail, + subject: '[API] Nightly ticket closure report', + html: body, + }).catch(err => console.error(err)); } return { message: 'Success' }; }; + + async function invalidEmail(client) { + await Self.rawSql( + `UPDATE client SET email = NULL WHERE id = ?`, + [client.clientId], + myOptions + ); + + const body = `No se ha podido facturar al cliente ${client.clientId} - ${client.clientName} + porque la dirección de email "${client.recipient}" no es correcta + o no está disponible.

+ Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente. + Actualiza la dirección de email con una correcta.`; + + smtp.send({ + to: client.salesPersonEmail, + subject: 'No se ha podido enviar el albarán', + html: body, + }).catch(err => console.error(err)); + } }; diff --git a/modules/ticket/back/methods/ticket/specs/closeAll.spec.js b/modules/ticket/back/methods/ticket/specs/closeAll.spec.js index 923f688bbb..f01541eec0 100644 --- a/modules/ticket/back/methods/ticket/specs/closeAll.spec.js +++ b/modules/ticket/back/methods/ticket/specs/closeAll.spec.js @@ -1,7 +1,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); -fdescribe('Ticket Closure - closeAll function', () => { +describe('Ticket Closure - closeAll function', () => { let ctx = { req: { getLocale: () => 'es', From b40981aa03c36aa943b0bd6c5845d6a1962268dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Wed, 12 Feb 2025 13:18:18 +0100 Subject: [PATCH 08/66] feat: enhance ticket closure process with error handling and email notifications --- modules/ticket/back/methods/ticket/closeAll.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index 307cc011dd..11c7b2cfc3 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -49,6 +49,7 @@ module.exports = Self => { c.email recipient, eu.email salesPersonEmail, t.addressFk, + c.hasDailyInvoice c.hasToInvoiceByAddress, t.totalWithVat, t.companyFk @@ -91,6 +92,7 @@ module.exports = Self => { SUM(totalWithVat) total, 'quick' serialType FROM tmp.ticket_close + WHERE hasDailyInvoice GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk HAVING total > 0; DROP TEMPORARY TABLE tmp.ticket_close; From 9785ab5a7dac9a1dc1158771a4810cb6e7d1730a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Wed, 12 Feb 2025 13:23:57 +0100 Subject: [PATCH 09/66] feat: enhance ticket closure process with error handling and email notifications --- modules/ticket/back/methods/ticket/closeAll.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index 11c7b2cfc3..539b8a64e2 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -49,7 +49,7 @@ module.exports = Self => { c.email recipient, eu.email salesPersonEmail, t.addressFk, - c.hasDailyInvoice + c.hasDailyInvoice, c.hasToInvoiceByAddress, t.totalWithVat, t.companyFk From b95db2eff134044ae5ee83bd91feefed86c7b75e Mon Sep 17 00:00:00 2001 From: Pako Date: Wed, 12 Feb 2025 13:45:07 +0100 Subject: [PATCH 10/66] feat(productionControl and collection_new): refs #8575 new itempackingtype a Refs: #8575 --- db/routines/vn/procedures/collection_new.sql | 4 +++- db/routines/vn/procedures/productionControl.sql | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/db/routines/vn/procedures/collection_new.sql b/db/routines/vn/procedures/collection_new.sql index 84133d36eb..42ab85dfdb 100644 --- a/db/routines/vn/procedures/collection_new.sql +++ b/db/routines/vn/procedures/collection_new.sql @@ -160,9 +160,11 @@ BEGIN OR (NOT s.isPreparable AND NOT s.isPrintable) OR pb.collectionH IS NOT NULL OR pb.collectionV IS NOT NULL + OR pb.collectionA IS NOT NULL OR pb.collectionN IS NOT NULL - OR (NOT pb.H AND pb.V > 0 AND vItemPackingTypeFk = 'H') + OR (NOT pb.H AND pb.V + pb.A > 0 AND vItemPackingTypeFk = 'H') OR (NOT pb.V AND vItemPackingTypeFk = 'V') + OR (NOT pb.A AND vItemPackingTypeFk = 'A') OR (pc.isPreviousPreparationRequired AND pb.previousWithoutParking) OR LENGTH(pb.problem) OR pb.lines > vLinesLimit diff --git a/db/routines/vn/procedures/productionControl.sql b/db/routines/vn/procedures/productionControl.sql index 605c06dba7..e23232b8b0 100644 --- a/db/routines/vn/procedures/productionControl.sql +++ b/db/routines/vn/procedures/productionControl.sql @@ -91,6 +91,7 @@ proc: BEGIN pk.code parking, 0 H, 0 V, + 0 A, 0 N, st.isOk, ag.isOwn, @@ -138,6 +139,7 @@ proc: BEGIN CHANGE COLUMN `problem` `problem` VARCHAR(255), ADD COLUMN `collectionH` INT, ADD COLUMN `collectionV` INT, + ADD COLUMN `collectionA` INT, ADD COLUMN `collectionN` INT; -- Clientes Nuevos o Recuperados @@ -178,12 +180,14 @@ proc: BEGIN ENGINE = MEMORY SELECT ticketFk, SUM(sub.H) H, - SUM(sub.V) V, + SUM(sub.V) V, + SUM(sub.A) A, SUM(sub.N) N FROM ( SELECT t.ticketFk, SUM(i.itemPackingTypeFk = 'H') H, SUM(i.itemPackingTypeFk = 'V') V, + SUM(i.itemPackingTypeFk = 'A') A, SUM(i.itemPackingTypeFk IS NULL) N FROM tmp.productionTicket t JOIN sale s ON s.ticketFk = t.ticketFk @@ -196,6 +200,7 @@ proc: BEGIN JOIN tItemPackingType ti ON ti.ticketFk = pb.ticketFk SET pb.H = ti.H, pb.V = ti.V, + pb.A = ti.A, pb.N = ti.N; -- Colecciones segun tipo de encajado @@ -203,6 +208,7 @@ proc: BEGIN JOIN ticketCollection tc ON pb.ticketFk = tc.ticketFk SET pb.collectionH = IF(pb.H, tc.collectionFk, NULL), pb.collectionV = IF(pb.V, tc.collectionFk, NULL), + pb.collectionA = IF(pb.A, tc.collectionFk, NULL), pb.collectionN = IF(pb.N, tc.collectionFk, NULL); -- Previa pendiente From ba3909a9847c3755ba486a883012f60fe4eb776a Mon Sep 17 00:00:00 2001 From: guillermo Date: Wed, 12 Feb 2025 14:13:39 +0100 Subject: [PATCH 11/66] refactor: refs #6944 Requested changes --- db/routines/vn/procedures/ticket_setState.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/routines/vn/procedures/ticket_setState.sql b/db/routines/vn/procedures/ticket_setState.sql index a8dcc0da6e..782783f992 100644 --- a/db/routines/vn/procedures/ticket_setState.sql +++ b/db/routines/vn/procedures/ticket_setState.sql @@ -11,7 +11,7 @@ proc:BEGIN * @param vStateCode estado a modificar del ticket */ DECLARE vTicketAlertLevel INT; - DECLARE vTicketStateCode VARCHAR(255); + DECLARE vTicketStateCode VARCHAR(255) COLLATE utf8_general_ci; DECLARE vCanChangeState BOOL; DECLARE vPackedAlertLevel INT; DECLARE vZoneFk INT; @@ -48,7 +48,7 @@ proc:BEGIN CALL ticket_doCmr(vSelf); END IF; - IF vStateCode = vTicketStateCode COLLATE utf8_general_ci AND vOldWorkerFk = vNewWorkerFk THEN + IF vStateCode = vTicketStateCode AND vOldWorkerFk = vNewWorkerFk THEN LEAVE proc; END IF; From 3754ede42d0ccafd1c08bdef2117c0faf864a03c Mon Sep 17 00:00:00 2001 From: jorgep Date: Wed, 12 Feb 2025 14:15:53 +0100 Subject: [PATCH 12/66] feat: refs #8571 enhance email formatting in sendToSupport function with structured HTML table --- back/methods/osticket/sendToSupport.js | 51 ++++++++++++++++++++------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/back/methods/osticket/sendToSupport.js b/back/methods/osticket/sendToSupport.js index dabd35f80e..55ac65635b 100644 --- a/back/methods/osticket/sendToSupport.js +++ b/back/methods/osticket/sendToSupport.js @@ -33,25 +33,52 @@ module.exports = Self => { const emailUser = await Self.app.models.EmailUser.findById(userId, {fields: ['email']}); - let html = `

Motivo: ${reason}

`; - html += `

Usuario: ${userId} ${emailUser.email}

`; - html += `

Additional Data:

`; - html += '