185 lines
6.3 KiB
JavaScript
185 lines
6.3 KiB
JavaScript
const UserError = require('vn-loopback/util/user-error');
|
|
|
|
module.exports = Self => {
|
|
Self.remoteMethodCtx('createManualInvoice', {
|
|
description: 'Make a manual invoice',
|
|
accessType: 'WRITE',
|
|
accepts: [
|
|
{
|
|
arg: 'clientFk',
|
|
type: 'any',
|
|
description: 'The invoiceable client id'
|
|
},
|
|
{
|
|
arg: 'ticketFk',
|
|
type: 'any',
|
|
description: 'The invoiceable ticket id'
|
|
},
|
|
{
|
|
arg: 'maxShipped',
|
|
type: 'date',
|
|
description: 'The maximum shipped date'
|
|
},
|
|
{
|
|
arg: 'serial',
|
|
type: 'string',
|
|
description: 'The invoice serial'
|
|
},
|
|
{
|
|
arg: 'taxArea',
|
|
type: 'string',
|
|
description: 'The invoice tax area'
|
|
},
|
|
{
|
|
arg: 'reference',
|
|
type: 'string',
|
|
description: 'The invoice reference'
|
|
}
|
|
],
|
|
returns: {
|
|
type: 'object',
|
|
root: true
|
|
},
|
|
http: {
|
|
path: '/createManualInvoice',
|
|
verb: 'POST'
|
|
}
|
|
});
|
|
|
|
Self.createManualInvoice = async(ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options) => {
|
|
if (!clientFk && !ticketFk) throw new UserError(`Select ticket or client`);
|
|
const models = Self.app.models;
|
|
const myOptions = {userId: ctx.req.accessToken.userId};
|
|
let tx;
|
|
|
|
if (typeof options == 'object')
|
|
Object.assign(myOptions, options);
|
|
|
|
if (!myOptions.transaction) {
|
|
tx = await Self.beginTransaction({});
|
|
myOptions.transaction = tx;
|
|
}
|
|
|
|
let companyId;
|
|
let newInvoice;
|
|
let query;
|
|
try {
|
|
if (ticketFk) {
|
|
const ticket = await models.Ticket.findById(ticketFk, null, myOptions);
|
|
const company = await models.Company.findById(ticket.companyFk, null, myOptions);
|
|
|
|
clientFk = ticket.clientFk;
|
|
maxShipped = ticket.shipped;
|
|
companyId = ticket.companyFk;
|
|
|
|
// Validates invoiced ticket
|
|
if (ticket.refFk)
|
|
throw new UserError('This ticket is already invoiced');
|
|
|
|
// Validates ticket amount
|
|
if (ticket.totalWithVat == 0)
|
|
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
|
|
|
// Validates ticket nagative base
|
|
const hasNegativeBase = await getNegativeBase(maxShipped, clientFk, companyId, myOptions);
|
|
if (hasNegativeBase && company.code == 'VNL')
|
|
throw new UserError(`A ticket with a negative base can't be invoiced`);
|
|
} else {
|
|
if (!maxShipped)
|
|
throw new UserError(`Max shipped required`);
|
|
|
|
const company = await models.Ticket.findOne({
|
|
fields: ['companyFk'],
|
|
where: {
|
|
clientFk: clientFk,
|
|
shipped: {lte: maxShipped}
|
|
}
|
|
}, myOptions);
|
|
companyId = company.companyFk;
|
|
}
|
|
|
|
// Validate invoiceable client
|
|
const isClientInvoiceable = await isInvoiceable(clientFk, myOptions);
|
|
if (!isClientInvoiceable)
|
|
throw new UserError(`This client is not invoiceable`);
|
|
|
|
// Can't invoice tickets into future
|
|
const tomorrow = Date.vnNew();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
if (maxShipped >= tomorrow)
|
|
throw new UserError(`Can't invoice to future`);
|
|
|
|
const maxInvoiceDate = await getMaxIssued(serial, companyId, myOptions);
|
|
if (Date.vnNew() < maxInvoiceDate)
|
|
throw new UserError(`Can't invoice to past`);
|
|
|
|
if (ticketFk) {
|
|
query = `CALL invoiceOut_newFromTicket(?, ?, ?, ?, @newInvoiceId)`;
|
|
await Self.rawSql(query, [
|
|
ticketFk,
|
|
serial,
|
|
taxArea,
|
|
reference
|
|
], myOptions);
|
|
} else {
|
|
query = `CALL invoiceOut_newFromClient(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
|
|
await Self.rawSql(query, [
|
|
clientFk,
|
|
serial,
|
|
maxShipped,
|
|
companyId,
|
|
taxArea,
|
|
reference
|
|
], myOptions);
|
|
}
|
|
|
|
[newInvoice] = await Self.rawSql(`SELECT @newInvoiceId id`, null, myOptions);
|
|
|
|
if (tx) await tx.commit();
|
|
} catch (e) {
|
|
if (tx) await tx.rollback();
|
|
throw e;
|
|
}
|
|
|
|
if (!newInvoice.id) throw new UserError('It was not able to create the invoice');
|
|
|
|
await Self.createPdf(ctx, newInvoice.id);
|
|
|
|
return newInvoice;
|
|
};
|
|
|
|
async function isInvoiceable(clientFk, options) {
|
|
const models = Self.app.models;
|
|
const query = `SELECT (hasToInvoice AND isTaxDataChecked) AS invoiceable
|
|
FROM client
|
|
WHERE id = ?`;
|
|
const [result] = await models.InvoiceOut.rawSql(query, [clientFk], options);
|
|
|
|
return result.invoiceable;
|
|
}
|
|
|
|
async function getNegativeBase(maxShipped, clientFk, companyId, options) {
|
|
const models = Self.app.models;
|
|
await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)',
|
|
[maxShipped, clientFk, companyId], options
|
|
);
|
|
const query = 'SELECT vn.hasAnyNegativeBase() AS base';
|
|
const [result] = await models.InvoiceOut.rawSql(query, [], options);
|
|
|
|
return result.base;
|
|
}
|
|
|
|
async function getMaxIssued(serial, companyId, options) {
|
|
const models = Self.app.models;
|
|
const query = `SELECT MAX(issued) AS issued
|
|
FROM invoiceOut
|
|
WHERE serial = ? AND companyFk = ?`;
|
|
const [maxIssued] = await models.InvoiceOut.rawSql(query,
|
|
[serial, companyId], options);
|
|
const maxInvoiceDate = maxIssued && maxIssued.issued || Date.vnNew();
|
|
|
|
return maxInvoiceDate;
|
|
}
|
|
};
|