Merge branch 'test' into 8227-roadmapChanges
gitea/salix/pipeline/pr-test This commit looks good Details

This commit is contained in:
Guillermo Bonet 2025-02-13 08:34:20 +00:00
commit bd53f7367c
5 changed files with 152 additions and 143 deletions

View File

@ -18,6 +18,7 @@ BEGIN
DECLARE vWithPackage BOOL; DECLARE vWithPackage BOOL;
DECLARE vHasToInvoice BOOL; DECLARE vHasToInvoice BOOL;
DECLARE vSerial VARCHAR(2); DECLARE vSerial VARCHAR(2);
DECLARE vStateCode VARCHAR(45);
DECLARE cur CURSOR FOR DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticket_close; SELECT ticketFk FROM tmp.ticket_close;
@ -80,27 +81,14 @@ BEGIN
AND getSpecialPrice(e.freightItemFk, vClientFk) > 0 AND getSpecialPrice(e.freightItemFk, vClientFk) > 0
GROUP BY e.freightItemFk); 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 vHasDailyInvoice AND vHasToInvoice THEN
SET vStateCode = 'DELIVERED';
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
vSerial,
vShipped,
vCompanyFk,
NULL,
NULL,
vNewInvoiceId);
END IF;
ELSE ELSE
CALL ticket_setState(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk))); SELECT vn.getAlert3State(vCurTicketFk) INTO vStateCode;
END IF; END IF;
CALL ticket_setState(vCurTicketFk, vStateCode);
END LOOP; END LOOP;
CLOSE cur; CLOSE cur;

View File

@ -1,5 +1,7 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_doCmr`(vSelf INT) CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_doCmr`(
vSelf INT
)
BEGIN BEGIN
/** /**
* Crea u actualiza la información del CMR asociado con * Crea u actualiza la información del CMR asociado con
@ -19,6 +21,7 @@ BEGIN
a.id addressFk, a.id addressFk,
c2.defaultAddressFk, c2.defaultAddressFk,
IFNULL(sat.supplierFk, su.id) supplierFk, IFNULL(sat.supplierFk, su.id) supplierFk,
t.packages,
t.landed t.landed
FROM ticket t FROM ticket t
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
@ -52,6 +55,7 @@ BEGIN
c.addressToFk = t.addressFk, c.addressToFk = t.addressFk,
c.addressFromFk = t.defaultAddressFk, c.addressFromFk = t.defaultAddressFk,
c.supplierFk = t.supplierFk, c.supplierFk = t.supplierFk,
c.packagesList = t.packages,
c.ead = t.landed c.ead = t.landed
WHERE id = vCmrFk; WHERE id = vCmrFk;
ELSE ELSE
@ -62,6 +66,7 @@ BEGIN
addressToFk, addressToFk,
addressFromFk, addressFromFk,
supplierFk, supplierFk,
packagesList,
ead ead
) )
SELECT * FROM tTicket; SELECT * FROM tTicket;

View File

@ -3,21 +3,25 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setState`(
vSelf INT, vSelf INT,
vStateCode VARCHAR(255) COLLATE utf8_general_ci vStateCode VARCHAR(255) COLLATE utf8_general_ci
) )
BEGIN proc:BEGIN
/** /**
* Modifica el estado de un ticket si se cumplen las condiciones necesarias. * Modifica el estado de un ticket si se cumplen las condiciones necesarias.
* *
* @param vSelf el id del ticket * @param vSelf el id del ticket
* @param vStateCode estado a modificar del ticket * @param vStateCode estado a modificar del ticket
*/ */
DECLARE vticketAlertLevel INT; DECLARE vTicketAlertLevel INT;
DECLARE vTicketStateCode VARCHAR(255); DECLARE vTicketStateCode VARCHAR(255) COLLATE utf8_general_ci;
DECLARE vCanChangeState BOOL; DECLARE vCanChangeState BOOL;
DECLARE vPackedAlertLevel INT; DECLARE vPackedAlertLevel INT;
DECLARE vZoneFk INT; DECLARE vZoneFk INT;
DECLARE vOldWorkerFk INT;
DECLARE vNewWorkerFk INT;
SELECT s.alertLevel, s.`code`, t.zoneFk SET vNewWorkerFk = account.myUser_getId();
INTO vticketAlertLevel, vTicketStateCode, vZoneFk
SELECT s.alertLevel, s.`code`, t.zoneFk, tt.userFk
INTO vTicketAlertLevel, vTicketStateCode, vZoneFk, vOldWorkerFk
FROM state s FROM state s
JOIN ticketTracking tt ON tt.stateFk = s.id JOIN ticketTracking tt ON tt.stateFk = s.id
JOIN ticket t ON t.id = tt.ticketFk JOIN ticket t ON t.id = tt.ticketFk
@ -33,24 +37,27 @@ BEGIN
SET vCanChangeState = (( SET vCanChangeState = ((
vStateCode <> 'ON_CHECKING' AND vStateCode <> 'CHECKED') OR vStateCode <> 'ON_CHECKING' AND vStateCode <> 'CHECKED') OR
vticketAlertLevel < vPackedAlertLevel vTicketAlertLevel < vPackedAlertLevel
) AND NOT ( ) AND NOT (
vTicketStateCode IN ('CHECKED', 'CHECKING') vTicketStateCode IN ('CHECKED', 'CHECKING')
AND vStateCode IN ('PREPARED', 'ON_PREPARATION') AND vStateCode IN ('PREPARED', 'ON_PREPARATION')
); );
IF vCanChangeState THEN 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 IF vStateCode = 'PACKED' THEN
CALL ticket_doCmr(vSelf); CALL ticket_doCmr(vSelf);
END IF; END IF;
IF vStateCode = vTicketStateCode 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 ELSE
CALL util.throw('INCORRECT_TICKET_STATE'); CALL util.throw('INCORRECT_TICKET_STATE');
END IF; END IF;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,4 +1,5 @@
const closure = require('./closure'); const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('closeAll', { Self.remoteMethodCtx('closeAll', {
@ -25,122 +26,46 @@ module.exports = Self => {
Self.closeAll = async(ctx, options) => { Self.closeAll = async(ctx, options) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const myOptions = {userId}; const myOptions = {userId};
let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
let tx; if (!myOptions.transaction) {
// IMPORTANT: Due to its high cost in production, wrapping this process in a transaction may cause timeouts. tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const toDate = Date.vnNew(); const toDate = Date.vnNew();
toDate.setHours(0, 0, 0, 0); toDate.setHours(0, 0, 0, 0);
toDate.setDate(toDate.getDate() - 1); toDate.setDate(toDate.getDate() - 1);
const tickets = await Self.rawSql(` await Self.rawSql(`
SELECT t.id, DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close;
CREATE TEMPORARY TABLE tmp.ticket_close
ENGINE = MEMORY
SELECT
DISTINCT t.id ticketFk,
t.clientFk, t.clientFk,
t.companyFk,
c.id clientFk,
c.name clientName, c.name clientName,
c.email recipient, c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
c.hasDailyInvoice,
eu.email salesPersonEmail, eu.email salesPersonEmail,
t.addressFk t.addressFk,
c.hasDailyInvoice,
c.hasToInvoiceByAddress,
t.totalWithVat,
t.companyFk
FROM ticket t FROM ticket t
JOIN agencyMode am ON am.id = t.agencyModeFk JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
JOIN ticketState ts ON ts.ticketFk = t.id JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk 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 LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
JOIN ticketConfig tc ON TRUE JOIN ticketConfig tc ON TRUE
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) 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.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL;
GROUP BY t.id CALL ticket_close();
`, [toDate, toDate], myOptions); `, [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(` await Self.rawSql(`
UPDATE ticket t UPDATE ticket t
@ -157,11 +82,95 @@ module.exports = Self => {
AND tob.id IS NULL AND tob.id IS NULL
AND t.routeFk`, [toDate, toDate], myOptions); AND t.routeFk`, [toDate, toDate], myOptions);
const [clients] = await Self.rawSql(`
SELECT clientFk clientId,
clientName,
recipient,
salesPersonEmail,
addressFk addressId,
companyFk,
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;
`, [], myOptions);
if (tx) if (tx)
await tx.commit(); 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: toDate
};
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:<br/><br/>';
for (const client of failedClients) {
body += `Client: <strong>${client.id}</strong>
Address: <strong>${client.address}</strong>
<br/> <strong>${client.error}</strong><br/><br/>`;
}
smtp.send({
to: config.app.reportEmail,
subject: '[API] Nightly ticket closure report',
html: body,
}).catch(err => console.error(err));
}
return { return {
message: 'Success' 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 <strong>${client.clientId} - ${client.clientName}</strong>
porque la dirección de email <strong>"${client.recipient}"</strong> no es correcta
o no está disponible.<br/><br/>
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));
}
}; };

View File

@ -70,7 +70,7 @@
<td width="5%">{{sale.itemFk}}</td> <td width="5%">{{sale.itemFk}}</td>
<td class="number">{{sale.quantity}}</td> <td class="number">{{sale.quantity}}</td>
<td width="50%">{{sale.concept}}</td> <td width="50%">{{sale.concept}}</td>
<td width="5%" class="font light-gray" v-if="sale.subName">{{sale.subName}}</td> <td width="5%" class="font light-gray">{{sale.subName}}</td>
<td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td> <td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td>
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td> <td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td>
<td class="centered" v-if="showPrices">{{sale.vatType}}</td> <td class="centered" v-if="showPrices">{{sale.vatType}}</td>