Global invoicing improvements
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Joan Sanchez 2021-08-10 11:48:29 +02:00
parent 3a45c99594
commit e65e90d00f
12 changed files with 145 additions and 70 deletions

View File

@ -3,5 +3,7 @@ DELETE FROM `salix`.`ACL` WHERE id = 188;
UPDATE `salix`.`ACL` tdms SET tdms.accessType = '*' UPDATE `salix`.`ACL` tdms SET tdms.accessType = '*'
WHERE tdms.id = 165; WHERE tdms.id = 165;
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES ('InvoiceOut', 'createManualInvoice', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'); VALUES
('InvoiceOut', 'createManualInvoice', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'),
('InvoiceOut', 'globalInvoicing', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');

View File

@ -110,5 +110,6 @@
"nickname": "nickname", "nickname": "nickname",
"State": "State", "State": "State",
"regular": "regular", "regular": "regular",
"reserved": "reserved" "reserved": "reserved",
"Global invoicing failed": "[Global invoicing] Wasn't able to invoice some of the clients"
} }

View File

@ -203,6 +203,6 @@
"This ticket is already invoiced": "Este ticket ya está facturado", "This ticket is already invoiced": "Este ticket ya está facturado",
"A ticket with an amount of zero can't be invoiced": "No se puede facturar un ticket con importe cero", "A ticket with an amount of zero can't be invoiced": "No se puede facturar un ticket con importe cero",
"A ticket with a negative base can't be invoiced": "No se puede facturar un ticket con una base negativa", "A ticket with a negative base can't be invoiced": "No se puede facturar un ticket con una base negativa",
"Not invoiceable": "Not invoiceable", "Global invoicing failed": "[Facturación global] No se han podido facturar algunos clientes",
"Not invoiceable 1101": "Not invoiceable 1101" "Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes"
} }

View File

@ -42,9 +42,9 @@ module.exports = Self => {
}); });
Self.globalInvoicing = async(ctx, options) => { Self.globalInvoicing = async(ctx, options) => {
const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const invoicesIds = []; const invoicesIds = [];
const failedClients = [];
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -81,7 +81,7 @@ module.exports = Self => {
const minShipped = new Date(); const minShipped = new Date();
minShipped.setFullYear(minShipped.getFullYear() - 1); minShipped.setFullYear(minShipped.getFullYear() - 1);
// Liquidacion de cubos y carros // Packaging liquidation
const vIsAllInvoiceable = false; const vIsAllInvoiceable = false;
const clientsWithPackaging = await getClientsWithPackaging(ctx, myOptions); const clientsWithPackaging = await getClientsWithPackaging(ctx, myOptions);
for (let client of clientsWithPackaging) { for (let client of clientsWithPackaging) {
@ -93,77 +93,70 @@ module.exports = Self => {
], myOptions); ], myOptions);
} }
const company = await models.Company.findById(args.companyFk, null, myOptions);
const invoiceableClients = await getInvoiceableClients(ctx, myOptions); const invoiceableClients = await getInvoiceableClients(ctx, myOptions);
if (!invoiceableClients.length) return; if (!invoiceableClients.length) return;
for (let client of invoiceableClients) { for (let client of invoiceableClients) {
// esto es para los que no tienen rol de invoicing?? try {
/* const [clientTax] = await Self.rawSql('SELECT vn.clientTaxArea(?, ?) AS taxArea', [ if (client.hasToInvoiceByAddress) {
client.id, await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
args.companyFk minShipped,
], myOptions); args.maxShipped,
const clientTaxArea = clientTax.taxArea; client.addressFk,
if (clientTaxArea != 'WORLD' && company.code === 'VNL' && hasRole('invoicing')) { args.companyFk
// Exit process?? ], myOptions);
console.log(clientTaxArea); } else {
throw new UserError('Not invoiceable ' + client.id); await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
} args.maxShipped,
*/ client.id,
if (client.hasToInvoiceByAddress) { args.companyFk
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [ ], myOptions);
minShipped, }
args.maxShipped,
client.addressFk, // Make invoice
args.companyFk const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
], myOptions);
} else { // Validates ticket nagative base
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [ const hasAnyNegativeBase = await getNegativeBase(myOptions);
args.maxShipped, if (hasAnyNegativeBase && isSpanishCompany)
continue;
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
const [invoiceSerial] = await Self.rawSql(query, [
client.id, client.id,
args.companyFk args.companyFk,
'G'
], myOptions); ], myOptions);
} const serialLetter = invoiceSerial.serial;
// Make invoice query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
await Self.rawSql(query, [
serialLetter,
args.invoiceDate
], myOptions);
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions); const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
if (newInvoice.id) {
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
// Validates ticket nagative base invoicesIds.push(newInvoice.id);
const hasAnyNegativeBase = await getNegativeBase(myOptions); }
if (hasAnyNegativeBase && isSpanishCompany) } catch (e) {
failedClients.push({
id: client.id,
stacktrace: e
});
continue; 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);
} }
// IMPRIMIR PDF ID 3?
} }
// Print invoice to network printer if (failedClients.length > 0)
await notifyFailures(ctx, failedClients, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
// Print invoices // Print invoices PDF
for (let invoiceId of invoicesIds) for (let invoiceId of invoicesIds)
await Self.createPdf(ctx, invoiceId); await Self.createPdf(ctx, invoiceId);
@ -243,4 +236,28 @@ module.exports = Self => {
args.companyFk args.companyFk
], options); ], 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);
}
}; };

View File

@ -14,6 +14,14 @@
data="companies" data="companies"
order="code"> order="code">
</vn-crud-model> </vn-crud-model>
<div
class="progress vn-my-md"
ng-if="$ctrl.isInvoicing">
<vn-horizontal>
<vn-icon vn-none icon="warning"></vn-icon>
<span vn-none translate>Invoicing in progress...</span>
</vn-horizontal>
</div>
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker
vn-one vn-one

View File

@ -6,6 +6,7 @@ class Controller extends Dialog {
constructor($element, $, $transclude) { constructor($element, $, $transclude) {
super($element, $, $transclude); super($element, $, $transclude);
this.isInvoicing = false;
this.invoice = { this.invoice = {
maxShipped: new Date() maxShipped: new Date()
}; };
@ -50,17 +51,20 @@ class Controller extends Dialog {
if (response !== 'accept') if (response !== 'accept')
return super.responseHandler(response); return super.responseHandler(response);
/* if (this.invoice.clientFk && !this.invoice.maxShipped) if (!this.invoice.invoiceDate || !this.invoice.maxShipped)
throw new Error('Client and the max shipped should be filled'); throw new Error('Invoice date and the max date should be filled');
if (!this.invoice.serial || !this.invoice.taxArea) if (!this.invoice.fromClientId || !this.invoice.toClientId)
throw new Error('Some fields are required'); */ throw new Error('Choose a valid clients range');
this.isInvoicing = true;
return this.$http.post(`InvoiceOuts/globalInvoicing`, this.invoice) return this.$http.post(`InvoiceOuts/globalInvoicing`, this.invoice)
.then(() => super.responseHandler(response)) .then(() => super.responseHandler(response))
.then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); .then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.finally(() => this.isInvoicing = false);
} catch (e) { } catch (e) {
this.vnApp.showError(this.$t(e.message)); this.vnApp.showError(this.$t(e.message));
this.isInvoicing = false;
return false; return false;
} }
} }

View File

@ -1,3 +1,9 @@
Create global invoice: Crear factura global Create global invoice: Crear factura global
Some fields are required: Algunos campos son obligatorios Some fields are required: Algunos campos son obligatorios
Max date: Fecha límite Max date: Fecha límite
Invoicing in progress...: Facturación en progreso...
Invoice date: Fecha de factura
From client: Desde el cliente
To client: Hasta el cliente
Invoice date and the max date should be filled: La fecha de factura y la fecha límite deben rellenarse
Choose a valid clients range: Selecciona un rango válido de clientes

View File

@ -1,5 +1,17 @@
@import "variables";
.vn-invoice-out-global-invoicing { .vn-invoice-out-global-invoicing {
tpl-body { tpl-body {
width: 500px width: 500px;
.progress {
font-weight: bold;
text-align: center;
font-size: 1.5rem;
color: $color-primary;
vn-horizontal {
justify-content: center
}
}
} }
} }

View File

@ -14,6 +14,14 @@
data="taxAreas" data="taxAreas"
order="code"> order="code">
</vn-crud-model> </vn-crud-model>
<div
class="progress vn-my-md"
ng-if="$ctrl.isInvoicing">
<vn-horizontal>
<vn-icon vn-none icon="warning"></vn-icon>
<span vn-none translate>Invoicing in progress...</span>
</vn-horizontal>
</div>
<vn-horizontal class="manifold-panel"> <vn-horizontal class="manifold-panel">
<vn-autocomplete <vn-autocomplete
url="Tickets" url="Tickets"

View File

@ -6,6 +6,7 @@ class Controller extends Dialog {
constructor($element, $, $transclude) { constructor($element, $, $transclude) {
super($element, $, $transclude); super($element, $, $transclude);
this.isInvoicing = false;
this.invoice = { this.invoice = {
maxShipped: new Date() maxShipped: new Date()
}; };
@ -22,14 +23,17 @@ class Controller extends Dialog {
if (!this.invoice.serial || !this.invoice.taxArea) if (!this.invoice.serial || !this.invoice.taxArea)
throw new Error('Some fields are required'); throw new Error('Some fields are required');
this.isInvoicing = true;
return this.$http.post(`InvoiceOuts/createManualInvoice`, this.invoice) return this.$http.post(`InvoiceOuts/createManualInvoice`, this.invoice)
.then(res => { .then(res => {
this.$state.go('invoiceOut.card.summary', {id: res.data.id}); this.$state.go('invoiceOut.card.summary', {id: res.data.id});
super.responseHandler(response); super.responseHandler(response);
}) })
.then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); .then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.finally(() => this.isInvoicing = false);
} catch (e) { } catch (e) {
this.vnApp.showError(this.$t(e.message)); this.vnApp.showError(this.$t(e.message));
this.isInvoicing = false;
return false; return false;
} }
} }

View File

@ -2,4 +2,5 @@ Create manual invoice: Crear factura manual
Some fields are required: Algunos campos son obligatorios Some fields are required: Algunos campos son obligatorios
Client and max shipped fields should be filled: Los campos de cliente y fecha límite deben rellenarse Client and max shipped fields should be filled: Los campos de cliente y fecha límite deben rellenarse
Max date: Fecha límite Max date: Fecha límite
Serial: Serie Serial: Serie
Invoicing in progress...: Facturación en progreso...

View File

@ -1,5 +1,17 @@
@import "variables";
.vn-invoice-out-manual { .vn-invoice-out-manual {
tpl-body { tpl-body {
width: 500px width: 500px;
.progress {
font-weight: bold;
text-align: center;
font-size: 1.5rem;
color: $color-primary;
vn-horizontal {
justify-content: center
}
}
} }
} }