refs #5772 Parallelism added to PDF generation
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
parent
d4dceac74c
commit
47a4a9950f
|
@ -17,7 +17,7 @@ rules:
|
|||
camelcase: 0
|
||||
default-case: 0
|
||||
no-eq-null: 0
|
||||
no-console: ["error"]
|
||||
no-console: ["warn"]
|
||||
no-warning-comments: 0
|
||||
no-empty: [error, allowEmptyCatch: true]
|
||||
complexity: 0
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
|
||||
VALUES
|
||||
('InvoiceOut','makePdfAndNotify','WRITE','ALLOW','ROLE','invoicing'),
|
||||
('InvoiceOutConfig','*','READ','ALLOW','ROLE','invoicing');
|
||||
|
||||
CREATE OR REPLACE TABLE vn.invoiceOutConfig (
|
||||
id INT UNSIGNED auto_increment NOT NULL,
|
||||
parallelism int UNSIGNED DEFAULT 1 NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb3
|
||||
COLLATE=utf8mb3_unicode_ci;
|
|
@ -603,6 +603,9 @@ UPDATE `vn`.`invoiceOut` SET ref = 'T3333333' WHERE id = 3;
|
|||
UPDATE `vn`.`invoiceOut` SET ref = 'T4444444' WHERE id = 4;
|
||||
UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5;
|
||||
|
||||
INSERT INTO vn.invoiceOutConfig
|
||||
SET parallelism = 8;
|
||||
|
||||
INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`)
|
||||
VALUES
|
||||
(1, 895.76, 89.58, 4722000010),
|
||||
|
|
|
@ -30,15 +30,10 @@ module.exports = Self => {
|
|||
type: 'number',
|
||||
description: 'The company id to invoice',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'printerFk',
|
||||
type: 'number',
|
||||
description: 'The printer to print',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
type: 'number',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -50,29 +45,23 @@ module.exports = Self => {
|
|||
Self.invoiceClient = async(ctx, options) => {
|
||||
const args = ctx.args;
|
||||
const models = Self.app.models;
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
const $t = ctx.req.__; // $translate
|
||||
const origin = ctx.req.headers.origin;
|
||||
|
||||
options = typeof options == 'object'
|
||||
? Object.assign({}, options) : {};
|
||||
options.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;
|
||||
}
|
||||
if (!options.transaction)
|
||||
tx = options.transaction = await Self.beginTransaction({});
|
||||
|
||||
const minShipped = Date.vnNew();
|
||||
minShipped.setFullYear(args.maxShipped.getFullYear() - 1);
|
||||
|
||||
let invoiceId;
|
||||
let invoiceOut;
|
||||
try {
|
||||
const client = await models.Client.findById(args.clientId, {
|
||||
fields: ['id', 'hasToInvoiceByAddress']
|
||||
}, myOptions);
|
||||
}, options);
|
||||
|
||||
if (client.hasToInvoiceByAddress) {
|
||||
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
||||
|
@ -80,53 +69,57 @@ module.exports = Self => {
|
|||
args.maxShipped,
|
||||
args.addressId,
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
], options);
|
||||
} else {
|
||||
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
||||
args.maxShipped,
|
||||
client.id,
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
], options);
|
||||
}
|
||||
|
||||
// Make invoice
|
||||
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
||||
// Check negative bases
|
||||
|
||||
// Validates ticket nagative base
|
||||
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
||||
let query =
|
||||
`SELECT COUNT(*) isSpanishCompany
|
||||
FROM supplier s
|
||||
JOIN country c ON c.id = s.countryFk
|
||||
AND c.code = 'ES'
|
||||
WHERE s.id = ?`;
|
||||
const [supplierCompany] = await Self.rawSql(query, [
|
||||
args.companyFk
|
||||
], options);
|
||||
|
||||
const isSpanishCompany = supplierCompany?.isSpanishCompany;
|
||||
|
||||
query = 'SELECT hasAnyNegativeBase() AS base';
|
||||
const [result] = await Self.rawSql(query, null, options);
|
||||
|
||||
const hasAnyNegativeBase = result?.base;
|
||||
if (hasAnyNegativeBase && isSpanishCompany)
|
||||
throw new UserError('Negative basis');
|
||||
|
||||
// Invoicing
|
||||
|
||||
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
||||
const [invoiceSerial] = await Self.rawSql(query, [
|
||||
client.id,
|
||||
args.companyFk,
|
||||
'G'
|
||||
], myOptions);
|
||||
], options);
|
||||
const serialLetter = invoiceSerial.serial;
|
||||
|
||||
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
||||
await Self.rawSql(query, [
|
||||
serialLetter,
|
||||
args.invoiceDate
|
||||
], myOptions);
|
||||
], options);
|
||||
|
||||
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
||||
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, options);
|
||||
if (!newInvoice)
|
||||
throw new UserError('No tickets to invoice', 'notInvoiced');
|
||||
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
||||
|
||||
invoiceOut = await models.InvoiceOut.findById(newInvoice.id, {
|
||||
fields: ['id', 'ref', 'clientFk'],
|
||||
include: {
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['email', 'isToBeMailed', 'salesPersonFk']
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], options);
|
||||
invoiceId = newInvoice.id;
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
@ -135,66 +128,6 @@ module.exports = Self => {
|
|||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
await Self.makePdf(invoiceId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new UserError('Error while generating PDF', 'pdfError');
|
||||
}
|
||||
|
||||
if (invoiceOut.client().isToBeMailed) {
|
||||
try {
|
||||
ctx.args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: args.clientId,
|
||||
recipient: invoiceOut.client().email
|
||||
};
|
||||
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
||||
} catch (err) {
|
||||
const message = $t('Mail not sent', {
|
||||
clientId: args.clientId,
|
||||
clientUrl: `${origin}/#!/claim/${args.id}/summary`
|
||||
});
|
||||
const salesPersonId = invoiceOut.client().salesPersonFk;
|
||||
|
||||
if (salesPersonId)
|
||||
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
||||
|
||||
throw new UserError('Error when sending mail to client', 'mailNotSent');
|
||||
}
|
||||
} else {
|
||||
const query = `
|
||||
CALL vn.report_print(
|
||||
'invoice',
|
||||
?,
|
||||
account.myUser_getId(),
|
||||
JSON_OBJECT('refFk', ?),
|
||||
'normal'
|
||||
);`;
|
||||
await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]);
|
||||
}
|
||||
|
||||
return invoiceId;
|
||||
};
|
||||
|
||||
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(*) isSpanishCompany
|
||||
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.isSpanishCompany;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('makePdfAndNotify', {
|
||||
description: 'Create invoice PDF and send it to client',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
description: 'The invoice id',
|
||||
required: true,
|
||||
http: {source: 'path'}
|
||||
}, {
|
||||
arg: 'printerFk',
|
||||
type: 'number',
|
||||
description: 'The printer to print',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: '/:id/makePdfAndNotify',
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.makePdfAndNotify = async function(ctx, id, printerFk) {
|
||||
const models = Self.app.models;
|
||||
|
||||
options = typeof options == 'object'
|
||||
? Object.assign({}, options) : {};
|
||||
options.userId = ctx.req.accessToken.userId;
|
||||
|
||||
try {
|
||||
await Self.makePdf(id, options);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new UserError('Error while generating PDF', 'pdfError');
|
||||
}
|
||||
|
||||
const invoiceOut = await Self.findById(id, {
|
||||
fields: ['ref', 'clientFk'],
|
||||
include: {
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk']
|
||||
}
|
||||
}
|
||||
}, options);
|
||||
|
||||
const ref = invoiceOut.ref;
|
||||
const client = invoiceOut.client();
|
||||
|
||||
if (client.isToBeMailed) {
|
||||
try {
|
||||
ctx.args = {
|
||||
reference: ref,
|
||||
recipientId: client.id,
|
||||
recipient: client.email
|
||||
};
|
||||
await Self.invoiceEmail(ctx, ref);
|
||||
} catch (err) {
|
||||
const origin = ctx.req.headers.origin;
|
||||
const message = ctx.req.__('Mail not sent', {
|
||||
clientId: client.id,
|
||||
clientUrl: `${origin}/#!/claim/${id}/summary`
|
||||
});
|
||||
const salesPersonId = client.salesPersonFk;
|
||||
|
||||
if (salesPersonId)
|
||||
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
||||
|
||||
throw new UserError('Error when sending mail to client', 'mailNotSent');
|
||||
}
|
||||
} else {
|
||||
const query = `
|
||||
CALL vn.report_print(
|
||||
'invoice',
|
||||
?,
|
||||
account.myUser_getId(),
|
||||
JSON_OBJECT('refFk', ?),
|
||||
'normal'
|
||||
);`;
|
||||
await Self.rawSql(query, [printerFk, ref], options);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -2,6 +2,9 @@
|
|||
"InvoiceOut": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"InvoiceOutConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"InvoiceOutSerial": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "InvoiceOutConfig",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "invoiceOutConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"parallelism": {
|
||||
"type": "number",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -12,6 +12,7 @@ module.exports = Self => {
|
|||
require('../methods/invoiceOut/createManualInvoice')(Self);
|
||||
require('../methods/invoiceOut/clientsToInvoice')(Self);
|
||||
require('../methods/invoiceOut/invoiceClient')(Self);
|
||||
require('../methods/invoiceOut/makePdfAndNotify')(Self);
|
||||
require('../methods/invoiceOut/refund')(Self);
|
||||
require('../methods/invoiceOut/invoiceEmail')(Self);
|
||||
require('../methods/invoiceOut/exportationPdf')(Self);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<vn-card
|
||||
ng-if="$ctrl.status"
|
||||
class="vn-w-lg vn-pa-md"
|
||||
style="height: 80px; display: flex; align-items: center; justify-content: center; gap: 20px;">
|
||||
class="status vn-w-lg vn-pa-md">
|
||||
<vn-spinner
|
||||
enable="$ctrl.status != 'done'">
|
||||
</vn-spinner>
|
||||
|
@ -20,8 +19,15 @@
|
|||
Ended process
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="$ctrl.nAddresses" class="text-caption text-secondary">
|
||||
{{$ctrl.percentage | percentage: 0}} ({{$ctrl.addressNumber}} {{'of' | translate}} {{$ctrl.nAddresses}})
|
||||
<div ng-if="$ctrl.nAddresses">
|
||||
<div class="text-caption text-secondary">
|
||||
{{$ctrl.percentage | percentage: 0}}
|
||||
({{$ctrl.addressNumber}} <span translate>of</span> {{$ctrl.nAddresses}})
|
||||
</div>
|
||||
<div class="text-caption text-secondary">
|
||||
{{$ctrl.nPdfs}} <span translate>of</span> {{$ctrl.totalPdfs}}
|
||||
<span translate>PDFs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</vn-card>
|
||||
|
@ -141,7 +147,7 @@
|
|||
<vn-submit
|
||||
ng-if="$ctrl.invoicing"
|
||||
label="Stop"
|
||||
ng-click="$ctrl.stopInvoicing()">
|
||||
ng-click="$ctrl.status = 'stopping'">
|
||||
</vn-submit>
|
||||
</vn-vertical>
|
||||
</form>
|
||||
|
|
|
@ -9,30 +9,21 @@ class Controller extends Section {
|
|||
Object.assign(this, {
|
||||
maxShipped: new Date(date.getFullYear(), date.getMonth(), 0),
|
||||
clientsToInvoice: 'all',
|
||||
companyFk: this.vnConfig.companyFk
|
||||
});
|
||||
|
||||
this.$http.get('UserConfigs/getUserConfig')
|
||||
.then(res => {
|
||||
this.companyFk = res.data.companyFk;
|
||||
this.getInvoiceDate(this.companyFk);
|
||||
});
|
||||
}
|
||||
|
||||
getInvoiceDate(companyFk) {
|
||||
const params = {companyFk: companyFk};
|
||||
this.fetchInvoiceDate(params);
|
||||
}
|
||||
|
||||
fetchInvoiceDate(params) {
|
||||
const params = {companyFk: this.companyFk};
|
||||
this.$http.get('InvoiceOuts/getInvoiceDate', {params})
|
||||
.then(res => {
|
||||
this.minInvoicingDate = res.data.issued ? new Date(res.data.issued) : null;
|
||||
this.invoiceDate = this.minInvoicingDate;
|
||||
});
|
||||
}
|
||||
|
||||
stopInvoicing() {
|
||||
this.status = 'stopping';
|
||||
const filter = {fields: ['parallelism']};
|
||||
this.$http.get('InvoiceOutConfigs/findOne', {filter})
|
||||
.then(res => {
|
||||
this.parallelism = res.data.parallelism || 1;
|
||||
});
|
||||
}
|
||||
|
||||
makeInvoice() {
|
||||
|
@ -70,8 +61,11 @@ class Controller extends Section {
|
|||
if (!this.addresses.length)
|
||||
throw new UserError(`There aren't tickets to invoice`);
|
||||
|
||||
this.nRequests = 0;
|
||||
this.nPdfs = 0;
|
||||
this.totalPdfs = 0;
|
||||
this.addressIndex = 0;
|
||||
return this.invoiceOut();
|
||||
this.invoiceClient();
|
||||
})
|
||||
.catch(err => this.handleError(err));
|
||||
} catch (err) {
|
||||
|
@ -85,8 +79,11 @@ class Controller extends Section {
|
|||
throw err;
|
||||
}
|
||||
|
||||
invoiceOut() {
|
||||
if (this.addressIndex == this.addresses.length || this.status == 'stopping') {
|
||||
invoiceClient() {
|
||||
if (this.nRequests == this.parallelism || this.isInvoicing) return;
|
||||
|
||||
if (this.addressIndex >= this.addresses.length || this.status == 'stopping') {
|
||||
if (this.nRequests) return;
|
||||
this.invoicing = false;
|
||||
this.status = 'done';
|
||||
return;
|
||||
|
@ -95,36 +92,27 @@ class Controller extends Section {
|
|||
this.status = 'invoicing';
|
||||
const address = this.addresses[this.addressIndex];
|
||||
this.currentAddress = address;
|
||||
this.isInvoicing = true;
|
||||
|
||||
const params = {
|
||||
clientId: address.clientId,
|
||||
addressId: address.id,
|
||||
invoiceDate: this.invoiceDate,
|
||||
maxShipped: this.maxShipped,
|
||||
companyFk: this.companyFk,
|
||||
printerFk: this.printerFk,
|
||||
companyFk: this.companyFk
|
||||
};
|
||||
|
||||
this.$http.post(`InvoiceOuts/invoiceClient`, params)
|
||||
.then(() => this.invoiceNext())
|
||||
.then(res => {
|
||||
this.isInvoicing = false;
|
||||
if (res.data)
|
||||
this.makePdfAndNotify(res.data, address);
|
||||
this.invoiceNext();
|
||||
})
|
||||
.catch(res => {
|
||||
this.isInvoicing = false;
|
||||
if (res.status >= 400 && res.status < 500) {
|
||||
const error = res.data?.error;
|
||||
let isWarning;
|
||||
const filter = {
|
||||
where: {
|
||||
id: address.clientId
|
||||
}
|
||||
};
|
||||
switch (error?.code) {
|
||||
case 'pdfError':
|
||||
case 'mailNotSent':
|
||||
isWarning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const message = error?.message || res.message;
|
||||
this.errors.unshift({address, message, isWarning});
|
||||
this.invoiceError(address, res);
|
||||
this.invoiceNext();
|
||||
} else {
|
||||
this.invoicing = false;
|
||||
|
@ -136,7 +124,27 @@ class Controller extends Section {
|
|||
|
||||
invoiceNext() {
|
||||
this.addressIndex++;
|
||||
this.invoiceOut();
|
||||
this.invoiceClient();
|
||||
}
|
||||
|
||||
makePdfAndNotify(invoiceId, address) {
|
||||
this.nRequests++;
|
||||
this.totalPdfs++;
|
||||
const params = {printerFk: this.printerFk};
|
||||
this.$http.post(`InvoiceOuts/${invoiceId}/makePdfAndNotify`, params)
|
||||
.catch(res => {
|
||||
this.invoiceError(address, res, true);
|
||||
})
|
||||
.finally(() => {
|
||||
this.nPdfs++;
|
||||
this.nRequests--;
|
||||
this.invoiceClient();
|
||||
});
|
||||
}
|
||||
|
||||
invoiceError(address, res, isWarning) {
|
||||
const message = res.data?.error?.message || res.message;
|
||||
this.errors.unshift({address, message, isWarning});
|
||||
}
|
||||
|
||||
get nAddresses() {
|
||||
|
|
|
@ -10,6 +10,7 @@ Build packaging tickets: Generando tickets de embalajes
|
|||
Address id: Id dirección
|
||||
Printer: Impresora
|
||||
of: de
|
||||
PDFs: PDFs
|
||||
Client: Cliente
|
||||
Current client id: Id cliente actual
|
||||
Invoicing client: Facturando cliente
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
@import "variables";
|
||||
|
||||
vn-invoice-out-global-invoicing{
|
||||
|
||||
h5{
|
||||
vn-invoice-out-global-invoicing {
|
||||
h5 {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.status {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
#error {
|
||||
line-break: normal;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue