Global invoicing improvements
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
parent
3a45c99594
commit
e65e90d00f
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue