Merge pull request 'hotFix_daily_addressInvoice_2' (!3441) from hotFix_daily_addressInvoice_2 into test
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #3441 Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
commit
fad95f2cf9
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
Self.remoteMethodCtx('closeAll', {
|
||||
|
@ -25,122 +26,45 @@ 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);
|
||||
|
||||
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,
|
||||
c.name clientName,
|
||||
c.email recipient,
|
||||
eu.email salesPersonEmail,
|
||||
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
|
||||
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,11 +81,94 @@ module.exports = Self => {
|
|||
AND tob.id IS NULL
|
||||
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
|
||||
GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk
|
||||
HAVING total > 0;
|
||||
DROP TEMPORARY TABLE tmp.ticket_close;
|
||||
`, [], myOptions);
|
||||
|
||||
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()
|
||||
};
|
||||
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 {
|
||||
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));
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue