264 lines
9.1 KiB
JavaScript
264 lines
9.1 KiB
JavaScript
const UserError = require('vn-loopback/util/user-error');
|
|
|
|
module.exports = Self => {
|
|
Self.remoteMethodCtx('globalInvoicing', {
|
|
description: 'Make a global invoice',
|
|
accessType: 'WRITE',
|
|
accepts: [
|
|
{
|
|
arg: 'invoiceDate',
|
|
type: 'date',
|
|
description: 'The invoice date'
|
|
},
|
|
{
|
|
arg: 'maxShipped',
|
|
type: 'date',
|
|
description: 'The maximum shipped date'
|
|
},
|
|
{
|
|
arg: 'fromClientId',
|
|
type: 'number',
|
|
description: 'The minimum client id'
|
|
},
|
|
{
|
|
arg: 'toClientId',
|
|
type: 'number',
|
|
description: 'The maximum client id'
|
|
},
|
|
{
|
|
arg: 'companyFk',
|
|
type: 'number',
|
|
description: 'The company id to invoice'
|
|
}
|
|
],
|
|
returns: {
|
|
type: 'object',
|
|
root: true
|
|
},
|
|
http: {
|
|
path: '/globalInvoicing',
|
|
verb: 'POST'
|
|
}
|
|
});
|
|
|
|
Self.globalInvoicing = async(ctx, options) => {
|
|
const args = ctx.args;
|
|
|
|
let tx;
|
|
const myOptions = {};
|
|
|
|
if (typeof options == 'object')
|
|
Object.assign(myOptions, options);
|
|
|
|
if (!myOptions.transaction) {
|
|
tx = await Self.beginTransaction({});
|
|
myOptions.transaction = tx;
|
|
}
|
|
|
|
const invoicesIds = [];
|
|
const failedClients = [];
|
|
let query;
|
|
try {
|
|
query = `
|
|
SELECT MAX(issued) issued
|
|
FROM vn.invoiceOut io
|
|
JOIN vn.time t ON t.dated = io.issued
|
|
WHERE io.serial = 'A'
|
|
AND t.year = YEAR(?)
|
|
AND io.companyFk = ?`;
|
|
const [maxIssued] = await Self.rawSql(query, [
|
|
args.invoiceDate,
|
|
args.companyFk
|
|
], myOptions);
|
|
|
|
const maxSerialDate = maxIssued.issued || args.invoiceDate;
|
|
if (args.invoiceDate < maxSerialDate)
|
|
args.invoiceDate = maxSerialDate;
|
|
|
|
if (args.invoiceDate < args.maxShipped)
|
|
args.maxShipped = args.invoiceDate;
|
|
|
|
const minShipped = new Date();
|
|
minShipped.setFullYear(minShipped.getFullYear() - 1);
|
|
|
|
// Packaging liquidation
|
|
const vIsAllInvoiceable = false;
|
|
const clientsWithPackaging = await getClientsWithPackaging(ctx, myOptions);
|
|
for (let client of clientsWithPackaging) {
|
|
await Self.rawSql('CALL packageInvoicing(?, ?, ?, ?, @newTicket)', [
|
|
client.id,
|
|
args.invoiceDate,
|
|
args.companyFk,
|
|
vIsAllInvoiceable
|
|
], myOptions);
|
|
}
|
|
|
|
const invoiceableClients = await getInvoiceableClients(ctx, myOptions);
|
|
|
|
if (!invoiceableClients.length) return;
|
|
|
|
for (let client of invoiceableClients) {
|
|
try {
|
|
if (client.hasToInvoiceByAddress) {
|
|
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
|
minShipped,
|
|
args.maxShipped,
|
|
client.addressFk,
|
|
args.companyFk
|
|
], myOptions);
|
|
} else {
|
|
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
|
args.maxShipped,
|
|
client.id,
|
|
args.companyFk
|
|
], myOptions);
|
|
}
|
|
|
|
// Make invoice
|
|
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
|
|
|
// Validates ticket nagative base
|
|
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
|
if (hasAnyNegativeBase && isSpanishCompany)
|
|
continue;
|
|
|
|
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
|
const [invoiceSerial] = await Self.rawSql(query, [
|
|
client.id,
|
|
args.companyFk,
|
|
'G'
|
|
], myOptions);
|
|
const serialLetter = invoiceSerial.serial;
|
|
|
|
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
|
await Self.rawSql(query, [
|
|
serialLetter,
|
|
args.invoiceDate
|
|
], myOptions);
|
|
|
|
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
|
if (newInvoice.id) {
|
|
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
|
|
|
invoicesIds.push(newInvoice.id);
|
|
}
|
|
} catch (e) {
|
|
failedClients.push({
|
|
id: client.id,
|
|
stacktrace: e
|
|
});
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (failedClients.length > 0)
|
|
await notifyFailures(ctx, failedClients, myOptions);
|
|
|
|
if (tx) await tx.commit();
|
|
} catch (e) {
|
|
if (tx) await tx.rollback();
|
|
throw e;
|
|
}
|
|
|
|
// Print invoices PDF
|
|
for (let invoiceId of invoicesIds)
|
|
await Self.createPdf(ctx, invoiceId);
|
|
|
|
return invoicesIds;
|
|
};
|
|
|
|
async function getNegativeBase(options) {
|
|
const models = Self.app.models;
|
|
const query = 'SELECT hasAnyNegativeBase() AS base';
|
|
const [result] = await models.InvoiceOut.rawSql(query, null, options);
|
|
|
|
return result && result.base;
|
|
}
|
|
|
|
async function getIsSpanishCompany(companyId, options) {
|
|
const models = Self.app.models;
|
|
const query = `SELECT COUNT(*) AS total
|
|
FROM supplier s
|
|
JOIN country c ON c.id = s.countryFk
|
|
AND c.code = 'ES'
|
|
WHERE s.id = ?`;
|
|
const [supplierCompany] = await models.InvoiceOut.rawSql(query, [
|
|
companyId
|
|
], options);
|
|
|
|
return supplierCompany && supplierCompany.total;
|
|
}
|
|
|
|
async function getClientsWithPackaging(ctx, options) {
|
|
const models = Self.app.models;
|
|
const args = ctx.args;
|
|
const query = `SELECT DISTINCT clientFk AS id
|
|
FROM ticket t
|
|
JOIN ticketPackaging tp ON t.id = tp.ticketFk
|
|
WHERE t.shipped BETWEEN '2017-11-21' AND ?
|
|
AND t.clientFk BETWEEN ? AND ?`;
|
|
return models.InvoiceOut.rawSql(query, [
|
|
args.maxShipped,
|
|
args.fromClientId,
|
|
args.toClientId
|
|
], options);
|
|
}
|
|
|
|
async function getInvoiceableClients(ctx, options) {
|
|
const models = Self.app.models;
|
|
const args = ctx.args;
|
|
const minShipped = new Date();
|
|
minShipped.setFullYear(minShipped.getFullYear() - 1);
|
|
|
|
const query = `SELECT
|
|
c.id,
|
|
SUM(IFNULL(s.quantity * s.price * (100-s.discount)/100, 0) + IFNULL(ts.quantity * ts.price,0)) AS sumAmount,
|
|
c.hasToInvoiceByAddress,
|
|
c.email,
|
|
c.isToBeMailed,
|
|
a.id addressFk
|
|
FROM ticket t
|
|
LEFT JOIN sale s ON s.ticketFk = t.id
|
|
LEFT JOIN ticketService ts ON ts.ticketFk = t.id
|
|
JOIN address a ON a.id = t.addressFk
|
|
JOIN client c ON c.id = t.clientFk
|
|
WHERE ISNULL(t.refFk) AND c.id BETWEEN ? AND ?
|
|
AND t.shipped BETWEEN ? AND util.dayEnd(?)
|
|
AND t.companyFk = ? AND c.hasToInvoice
|
|
AND c.isTaxDataChecked
|
|
GROUP BY c.id, IF(c.hasToInvoiceByAddress,a.id,TRUE) HAVING sumAmount > 0`;
|
|
|
|
return models.InvoiceOut.rawSql(query, [
|
|
args.fromClientId,
|
|
args.toClientId,
|
|
minShipped,
|
|
args.maxShipped,
|
|
args.companyFk
|
|
], options);
|
|
}
|
|
|
|
async function notifyFailures(ctx, failedClients, options) {
|
|
const models = Self.app.models;
|
|
const userId = ctx.req.accessToken.userId;
|
|
const $t = ctx.req.__; // $translate
|
|
|
|
const worker = await models.EmailUser.findById(userId, null, options);
|
|
const subject = $t('Global invoicing failed');
|
|
let body = $t(`Wasn't able to invoice the following clients`) + ':<br/><br/>';
|
|
|
|
for (client of failedClients) {
|
|
body += `ID: <strong>${client.id}</strong>
|
|
<br/> <strong>${client.stacktrace}</strong><br/><br/>`;
|
|
}
|
|
|
|
await Self.rawSql(`
|
|
INSERT INTO vn.mail (sender, replyTo, sent, subject, body)
|
|
VALUES (?, ?, FALSE, ?, ?)`, [
|
|
worker.email,
|
|
worker.email,
|
|
subject,
|
|
body
|
|
], options);
|
|
}
|
|
};
|