Merge branch 'dev' into 3521-Monitors_ticket_summary
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2022-01-20 11:03:33 +00:00
commit a9e8c5b374
93 changed files with 924 additions and 724 deletions

View File

@ -104,17 +104,17 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
(3, 'GBP', 'Libra', 1),
(4, 'JPY', 'Yen Japones', 1);
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`)
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
VALUES
(1, 'España', 1, 'ES', 1, 24, 4),
(2, 'Italia', 1, 'IT', 1, 27, 4),
(3, 'Alemania', 1, 'DE', 1, 22, 4),
(4, 'Rumania', 1, 'RO', 1, 24, 4),
(5, 'Holanda', 1, 'NL', 1, 18, 4),
(8, 'Portugal', 1, 'PT', 1, 27, 4),
(13,'Ecuador', 0, 'EC', 1, 24, 2),
(19,'Francia', 1, 'FR', 1, 27, 4),
(30,'Canarias', 1, 'IC', 1, 24, 4);
(1, 'España', 1, 'ES', 1, 24, 4, 0, 1),
(2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1),
(3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1),
(4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1),
(5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1),
(8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1),
(13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2),
(19,'Francia', 1, 'FR', 1, 27, 4, 0, 1),
(30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2);
INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`)
VALUES
@ -243,7 +243,7 @@ INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `autonomyFk`, `warehouseF
VALUES
(1, 'Province one', 1, 1, NULL),
(2, 'Province two', 1, 1, NULL),
(3, 'Province three', 1, 2, NULL),
(3, 'Province three', 30, 2, NULL),
(4, 'Province four', 2, 3, NULL),
(5, 'Province five', 13, 4, NULL);
@ -486,7 +486,9 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
('A', 'Global nacional', 1, 'NATIONAL', 0),
('T', 'Española rapida', 1, 'NATIONAL', 0),
('V', 'Intracomunitaria global', 0, 'CEE', 1),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0);
('M', 'Múltiple nacional', 1, 'NATIONAL', 0),
('E', 'Exportación rápida', 0, 'WORLD', 0);
;
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES

View File

@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const got = require('got');
const path = require('path');
const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('createPdf', {
@ -57,13 +57,13 @@ module.exports = Self => {
hasPdf: true
}, myOptions);
const response = got.stream(`${origin}/api/report/invoice`, {
searchParams: {
return axios.get(`${origin}/api/report/invoice`, {
responseType: 'stream',
params: {
authorization: auth.id,
invoiceId: id
}
});
}).then(async response => {
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
@ -79,17 +79,13 @@ module.exports = Self => {
if (tx) await tx.commit();
const writeStream = fs.createWriteStream(fileSrc);
writeStream.on('open', () => response.pipe(writeStream));
writeStream.on('finish', () => writeStream.end());
return new Promise(resolve => {
writeStream.on('close', () => resolve(invoiceOut));
response.data.pipe(fs.createWriteStream(fileSrc));
}).catch(async() => {
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
});
} catch (e) {
if (tx) await tx.rollback();
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
throw e;
}
};

View File

@ -19,6 +19,10 @@ class Controller extends Section {
this.id = value.id;
}
get hasInvoicing() {
return this.aclService.hasAny(['invoicing']);
}
loadData() {
const filter = {
include: [

View File

@ -12,6 +12,7 @@
"node": ">=14"
},
"dependencies": {
"axios": "^0.25.0",
"bmp-js": "^0.1.0",
"compression": "^1.7.3",
"fs-extra": "^5.0.0",
@ -42,6 +43,7 @@
"strong-error-handler": "^2.3.2",
"uuid": "^3.3.3",
"vn-loopback": "file:./loopback",
"vn-print": "file:./print",
"xml2js": "^0.4.23"
},
"devDependencies": {

View File

@ -1,11 +1,9 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const puppeteer = require('puppeteer');
const templatesPath = path.resolve(__dirname, './templates');
const componentsPath = path.resolve(__dirname, './core/components');
const config = require('./core/config');
module.exports = async app => {
global.appPath = __dirname;
@ -53,21 +51,4 @@ module.exports = async app => {
app.use(`/api/${templateName}/assets`, express.static(assetsDir));
});
});
// Instantiate Puppeteer browser
async function launchBrowser() {
config.browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--single-process',
'--no-zygote'
]
});
config.browser.on('disconnected', launchBrowser);
}
launchBrowser();
};

View File

@ -6,6 +6,8 @@
.grid {
font-family: Arial, Helvetica, sans-serif;
font-size: 16px !important;
max-width: 1200px;
margin: 0 auto;
width: 100%
}

View File

@ -48,6 +48,12 @@
"pool": true
},
"storage": {
"root": "./storage/dms"
"root": "./storage/dms",
"invoice": {
"root": "./storage/pdfs/invoice"
},
"signature": {
"root": "./storage/signatures"
}
}
}

View File

@ -27,29 +27,50 @@ class Component {
get locale() {
if (!this._locale)
this.getLocale();
this._locale = this.getLocales();
return this._locale;
}
getLocale() {
const mergedLocale = {messages: {}};
getLocales() {
const mergedLocales = {messages: {}};
const localePath = path.resolve(__dirname, `${this.path}/locale`);
if (!fs.existsSync(localePath))
return mergedLocale;
return mergedLocales;
const localeDir = fs.readdirSync(localePath);
localeDir.forEach(locale => {
for (const locale of localeDir) {
const fullPath = path.join(localePath, '/', locale);
const yamlLocale = fs.readFileSync(fullPath, 'utf8');
const jsonLocale = yaml.safeLoad(yamlLocale);
const localeName = locale.replace('.yml', '');
mergedLocale.messages[localeName] = jsonLocale;
});
mergedLocales.messages[localeName] = jsonLocale;
}
this._locale = mergedLocale;
return mergedLocales;
}
async getUserLocale() {
let locale = this.args.auth.locale;
// Fetches user locale from mixing method getLocale()
if (this.args.recipientId) {
const component = await this.component();
locale = await component.getLocale(this.args.recipientId);
}
const messages = this.locale.messages;
const userTranslations = messages[locale];
if (!userTranslations) {
const fallbackLocale = config.i18n.fallbackLocale;
return messages[fallbackLocale];
}
return userTranslations;
}
get stylesheet() {
@ -75,7 +96,7 @@ class Component {
build() {
const fullPath = path.resolve(__dirname, this.path);
if (!fs.existsSync(fullPath))
throw new Error(`Sample "${this.name}" not found`);
throw new Error(`Template "${this.name}" not found`);
const component = require(`${this.path}/${this.name}`);
component.i18n = this.locale;

View File

@ -19,22 +19,7 @@ class Email extends Component {
}
async getSubject() {
const component = await this.component();
let locale = this.args.auth.locale;
if (this.args.recipientId)
locale = await component.getLocale(this.args.recipientId);
const messages = this.locale.messages;
const userTranslations = messages[locale];
if (!userTranslations) {
const fallbackLocale = config.i18n.fallbackLocale;
return messages[fallbackLocale].subject;
}
return userTranslations.subject;
return (await this.getUserLocale())['subject'];
}
/**
@ -63,6 +48,7 @@ class Email extends Component {
const reportName = fileName.replace('.pdf', '');
const report = new Report(reportName, this.args);
fileCopy.content = await report.toPdfStream();
fileCopy.filename = await report.getFileName();
}
attachments.push(fileCopy);

View File

@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const config = require('./config');
const Component = require('./component');
const puppeteer = require('puppeteer');
if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/';
@ -17,6 +18,10 @@ class Report extends Component {
return `../templates/reports/${this.name}`;
}
async getName() {
return (await this.getUserLocale())['reportName'];
}
async toPdfStream() {
const template = await this.render();
const defaultOptions = Object.assign({}, config.pdf);
@ -27,7 +32,17 @@ class Report extends Component {
if (fs.existsSync(fullPath))
options = require(optionsPath);
const page = (await config.browser.pages())[0];
const browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--single-process',
'--no-zygote'
]
});
const page = (await browser.pages())[0];
await page.emulateMedia('screen');
await page.setContent(template);
@ -47,8 +62,43 @@ class Report extends Component {
const buffer = await page.pdf(options);
await browser.close();
return buffer;
}
/**
* Returns all the params that ends with id
*
* @return {array} List of identifiers
*/
getIdentifiers() {
const identifiers = [];
const args = this.args;
const keys = Object.keys(args);
for (let arg of keys) {
if (arg.endsWith('Id'))
identifiers.push(arg);
}
return identifiers;
}
async getFileName() {
const args = this.args;
const identifiers = this.getIdentifiers(args);
const name = await this.getName();
const params = [];
params.push(name);
for (let id of identifiers)
params.push(args[id]);
const fileName = params.join('_');
return `${fileName}.pdf`;
}
}
module.exports = Report;

View File

@ -1,43 +1,30 @@
const path = require('path');
const fs = require('fs');
const db = require('./database');
module.exports = app => {
const methodsPath = path.resolve(__dirname, '../methods');
const methodsDir = fs.readdirSync(methodsPath);
const methods = [];
const routes = require('../methods/routes');
// Get all methods
for (let method of methodsDir) {
if (method.includes('.js'))
methods.push(method.replace('.js', ''));
}
const paths = routes.map(route => route.url);
// Auth middleware
const paths = [];
for (let method of methods)
paths.push(`/api/${method}/*`);
app.use(paths, async function(req, res, next) {
const token = getToken(req);
app.use(paths, async function(request, response, next) {
try {
const token = getToken(request);
const query = `SELECT at.id, at.userId, eu.email, u.lang, at.ttl, at.created
FROM salix.AccessToken at
JOIN account.user u ON u.id = at.userid
JOIN account.emailUser eu ON eu.userFk = u.id
WHERE at.id = ?`;
try {
const auth = await db.findOne(query, [token]);
if (!auth || isTokenExpired(auth.created, auth.ttl))
throw new Error('Invalid authorization token');
const args = Object.assign({}, req.query);
const props = Object.assign(args, req.body);
const args = Object.assign({}, request.query);
const props = Object.assign(args, request.body);
props.authorization = auth.id;
req.args = props;
req.args.auth = {
response.locals = props;
response.locals.auth = {
userId: auth.userId,
token: auth.id,
email: auth.email,
@ -50,6 +37,10 @@ module.exports = app => {
}
});
// Register routes
for (let route of routes)
app.use(route.url, route.cb);
function getToken(request) {
const headers = request.headers;
const queryParams = request.query;
@ -68,8 +59,4 @@ module.exports = app => {
return false;
}
// Mount methods
for (let method of methods)
require(`../methods/${method}`)(app);
};

View File

@ -25,6 +25,7 @@ module.exports = {
throw err;
}).finally(async() => {
const attachments = [];
if (options.attachments) {
for (let attachment of options.attachments) {
const fileName = attachment.filename;
const filePath = attachment.path;
@ -33,6 +34,8 @@ module.exports = {
if (fileName || filePath)
attachments.push(filePath ? filePath : fileName);
}
}
const fileNames = attachments.join(',\n');
await db.rawSql(`
INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status)

28
print/core/storage.js Normal file
View File

@ -0,0 +1,28 @@
const config = require('./config.js');
const path = require('path');
const fs = require('fs-extra');
module.exports = {
async write(stream, options) {
const storage = config.storage[options.type];
if (!storage) return;
const src = path.join(storage.root, options.path);
const fileSrc = path.join(src, options.fileName);
await fs.mkdir(src, {recursive: true});
const writeStream = fs.createWriteStream(fileSrc);
writeStream.on('open', () => writeStream.write(stream));
writeStream.on('finish', () => writeStream.end());
return new Promise(resolve => {
writeStream.on('close', () => resolve());
});
},
load(type, data) {
}
};

View File

@ -7,9 +7,8 @@ class Stylesheet {
}
mergeStyles() {
this.files.forEach(file => {
for (const file of this.files)
this.css.push(fs.readFileSync(file));
});
return this.css.join('\n');
}

View File

@ -1,311 +0,0 @@
const db = require('../core/database');
const Email = require('../core/email');
const Report = require('../core/report');
const smtp = require('../core/smtp');
const config = require('../core/config');
module.exports = app => {
app.get('/api/closure/all', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.to)
throw new Error('The argument to is required');
res.status(200).json({
message: 'Task executed successfully'
});
const tickets = await db.rawSql(`
SELECT
t.id
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.to, reqArgs.to]);
const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, req.args);
await db.rawSql(`
UPDATE ticket t
JOIN ticketState ts ON t.id = ts.ticketFk
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN zone z ON z.id = t.zoneFk
SET t.routeFk = NULL
WHERE DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND al.code NOT IN('DELIVERED','PACKED')
AND t.routeFk
AND z.name LIKE '%MADRID%'`, [reqArgs.to, reqArgs.to]);
} catch (error) {
next(error);
}
});
app.get('/api/closure/by-ticket', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
res.status(200).json({
message: 'Task executed successfully'
});
const tickets = await db.rawSql(`
SELECT
t.id
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.id = ?
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.ticketId]);
const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, reqArgs);
} catch (error) {
next(error);
}
});
app.get('/api/closure/by-agency', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.agencyModeId)
throw new Error('The argument agencyModeId is required');
if (!reqArgs.warehouseId)
throw new Error('The argument warehouseId is required');
if (!reqArgs.to)
throw new Error('The argument to is required');
res.status(200).json({
message: 'Task executed successfully'
});
const agenciesId = reqArgs.agencyModeId.split(',');
const tickets = await db.rawSql(`
SELECT
t.id
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.agencyModeFk IN(?)
AND t.warehouseFk = ?
AND DATE(t.shipped) BETWEEN DATE_ADD(:to, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [
agenciesId,
reqArgs.warehouseId,
reqArgs.to,
reqArgs.to
]);
const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, reqArgs);
} catch (error) {
next(error);
}
});
app.get('/api/closure/by-route', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.routeId)
throw new Error('The argument routeId is required');
res.status(200).json({
message: 'Task executed successfully'
});
const tickets = await db.rawSql(`
SELECT
t.id
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
WHERE al.code = 'PACKED'
AND t.routeFk = ?
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.routeId]);
const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, reqArgs);
// Send route report to the agency
const agencyMail = await db.findValue(`
SELECT am.reportMail
FROM route r
JOIN agencyMode am ON am.id = r.agencyModeFk
WHERE r.id = ?`, [reqArgs.routeId]);
if (agencyMail) {
const args = Object.assign({
routeId: reqArgs.routeId,
recipient: agencyMail
}, reqArgs);
const email = new Email('driver-route', args);
await email.send();
}
} catch (error) {
next(error);
}
});
async function closeAll(ticketIds, reqArgs) {
const failedtickets = [];
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM ticket t
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE t.id IN(?)`, [ticketIds]);
for (const ticket of tickets) {
try {
await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]);
if (!ticket.salesPersonFk || !ticket.isToBeMailed) continue;
if (!ticket.recipient) {
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk}</strong> porque no tiene un email especificado.<br/><br/>
Para dejar de recibir esta notificación, asígnale un email o desactiva
la notificación por email para este cliente.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
continue;
}
const hasToInvoice = ticket.hasToInvoice && ticket.hasDailyInvoice;
if (hasToInvoice) {
const invoice = await db.findOne(`
SELECT io.id, io.ref, io.serial, cny.code companyCode
FROM ticket t
JOIN invoiceOut io ON io.ref = t.refFk
JOIN company cny ON cny.id = io.companyFk
WHERE t.id = ?
`, [ticket.id]);
const args = Object.assign({
invoiceId: invoice.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
let mailOptions = {};
if (invoice.serial == 'E' && invoice.companyCode == 'VNL') {
const exportation = new Report('exportation', args);
const stream = await exportation.toPdfStream();
const fileName = `exportation-${invoice.ref}.pdf`;
mailOptions = {
overrideAttachments: false,
attachments: [{
filename: fileName,
content: stream
}]
};
}
const email = new Email('invoice', args);
await email.send(mailOptions);
} else {
const args = Object.assign({
ticketId: ticket.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const email = new Email('delivery-note-link', args);
await email.send();
}
} catch (error) {
// Domain not found
if (error.responseCode == 450)
return invalidEmail(ticket);
// Save tickets on a list of failed ids
failedtickets.push({
id: ticket.id,
stacktrace: error
});
}
}
// Send email with failed tickets
if (failedtickets.length > 0) {
let body = 'This following tickets have failed:<br/><br/>';
for (ticket of failedtickets) {
body += `Ticket: <strong>${ticket.id}</strong>
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
}
smtp.send({
to: config.app.reportEmail,
subject: '[API] Nightly ticket closure report',
html: body
});
}
}
async function invalidEmail(ticket) {
await db.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
ticket.clientFk
]);
const oldInstance = `{"email": "${ticket.recipient}"}`;
const newInstance = `{"email": ""}`;
await db.rawSql(`
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
ticket.clientFk,
oldInstance,
newInstance
]);
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
}
};

View File

@ -0,0 +1,58 @@
const db = require('vn-print/core/database');
const closure = require('./closure');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.to)
throw new Error('The argument to is required');
response.status(200).json({
message: 'Success'
});
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM ticket t
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND t.refFk IS NULL
GROUP BY t.id`, [reqArgs.to, reqArgs.to]);
await closure.start(tickets, response.locals);
await db.rawSql(`
UPDATE ticket t
JOIN ticketState ts ON t.id = ts.ticketFk
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN zone z ON z.id = t.zoneFk
SET t.routeFk = NULL
WHERE DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND al.code NOT IN('DELIVERED','PACKED')
AND t.routeFk
AND z.name LIKE '%MADRID%'`, [reqArgs.to, reqArgs.to]);
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,58 @@
const db = require('vn-print/core/database');
const closure = require('./closure');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.agencyModeId)
throw new Error('The argument agencyModeId is required');
if (!reqArgs.warehouseId)
throw new Error('The argument warehouseId is required');
if (!reqArgs.to)
throw new Error('The argument to is required');
response.status(200).json({
message: 'Success'
});
const agencyIds = reqArgs.agencyModeId.split(',');
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND t.agencyModeFk IN(?)
AND t.warehouseFk = ?
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [
agencyIds,
reqArgs.warehouseId,
reqArgs.to,
reqArgs.to
]);
await closure.start(tickets, response.locals);
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,61 @@
const db = require('vn-print/core/database');
const Email = require('vn-print/core/email');
const closure = require('./closure');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.routeId)
throw new Error('The argument routeId is required');
response.status(200).json({
message: 'Success'
});
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND t.routeFk = ?
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.routeId]);
await closure.start(tickets, response.locals);
// Send route report to the agency
const agencyMail = await db.findValue(`
SELECT am.reportMail
FROM route r
JOIN agencyMode am ON am.id = r.agencyModeFk
WHERE r.id = ?`, [reqArgs.routeId]);
if (agencyMail) {
const args = Object.assign({
routeId: Number.parseInt(reqArgs.routeId),
recipient: agencyMail
}, response.locals);
const email = new Email('driver-route', args);
await email.send();
}
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,43 @@
const db = require('vn-print/core/database');
const closure = require('./closure');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
response.status(200).json({
message: 'Success'
});
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.name clientName,
c.email recipient,
c.salesPersonFk,
c.isToBeMailed,
c.hasToInvoice,
co.hasDailyInvoice,
eu.email salesPersonEmail
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED'
AND t.id = ?
AND t.refFk IS NULL
GROUP BY e.ticketFk`, [reqArgs.ticketId]);
await closure.start(tickets, response.locals);
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,153 @@
const db = require('vn-print/core/database');
const Report = require('vn-print/core/report');
const Email = require('vn-print/core/email');
const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config');
const storage = require('vn-print/core/storage');
module.exports = {
async start(tickets, reqArgs) {
if (tickets.length == 0) return;
const failedtickets = [];
for (const ticket of tickets) {
try {
await db.rawSql('START TRANSACTION');
await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]);
const invoiceOut = await db.findOne(`
SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
FROM ticket t
JOIN invoiceOut io ON io.ref = t.refFk
JOIN company cny ON cny.id = io.companyFk
WHERE t.id = ?
`, [ticket.id]);
const mailOptions = {
overrideAttachments: true,
attachments: []
};
const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
if (invoiceOut) {
const args = Object.assign({
invoiceId: invoiceOut.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const invoiceReport = new Report('invoice', args);
const stream = await invoiceReport.toPdfStream();
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice
storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
await db.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id]);
if (isToBeMailed) {
const invoiceAttachment = {
filename: fileName,
content: stream
};
if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
const exportation = new Report('exportation', args);
const stream = await exportation.toPdfStream();
const fileName = `CITES-${invoiceOut.ref}.pdf`;
mailOptions.attachments.push({
filename: fileName,
content: stream
});
}
mailOptions.attachments.push(invoiceAttachment);
const email = new Email('invoice', args);
await email.send(mailOptions);
}
} else if (isToBeMailed) {
const args = Object.assign({
ticketId: ticket.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const email = new Email('delivery-note-link', args);
await email.send();
}
await db.rawSql('COMMIT');
} catch (error) {
await db.rawSql('ROLLBACK');
// Domain not found
if (error.responseCode == 450)
return invalidEmail(ticket);
// Save tickets on a list of failed ids
failedtickets.push({
id: ticket.id,
stacktrace: error
});
}
}
// Send email with failed tickets
if (failedtickets.length > 0) {
let body = 'This following tickets have failed:<br/><br/>';
for (const ticket of failedtickets) {
body += `Ticket: <strong>${ticket.id}</strong>
<br/> <strong>${ticket.stacktrace}</strong><br/><br/>`;
}
smtp.send({
to: config.app.reportEmail,
subject: '[API] Nightly ticket closure report',
html: body
});
}
},
async invalidEmail(ticket) {
await db.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
ticket.clientFk
]);
const oldInstance = `{"email": "${ticket.recipient}"}`;
const newInstance = `{"email": ""}`;
await db.rawSql(`
INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
ticket.clientFk,
oldInstance,
newInstance
]);
const body = `No se ha podido enviar el albarán <strong>${ticket.id}</strong>
al cliente <strong>${ticket.clientFk} - ${ticket.clientName}</strong>
porque la dirección de email <strong>"${ticket.recipient}"</strong> no es correcta o no está disponible.<br/><br/>
Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
Actualiza la dirección de email con una correcta.`;
smtp.send({
to: ticket.salesPersonEmail,
subject: 'No se ha podido enviar el albarán',
html: body
});
}
};

View File

@ -0,0 +1,9 @@
const express = require('express');
const router = new express.Router();
router.get('/all', require('./closeAll'));
router.get('/by-ticket', require('./closeByTicket'));
router.get('/by-agency', require('./closeByAgency'));
router.get('/by-route', require('./closeByRoute'));
module.exports = router;

View File

@ -1,31 +0,0 @@
module.exports = app => {
app.use('/api/csv/delivery-note', require('./csv/delivery-note')(app));
app.use('/api/csv/invoice', require('./csv/invoice')(app));
app.toCSV = function toCSV(rows) {
const [columns] = rows;
let content = Object.keys(columns).join('\t');
for (let row of rows) {
const values = Object.values(row);
const finalValues = values.map(value => {
if (value instanceof Date) return formatDate(value);
if (value === null) return '';
return value;
});
content += '\n';
content += finalValues.join('\t');
}
return content;
};
function formatDate(date) {
return new Intl.DateTimeFormat('es', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(date);
}
};

31
print/methods/csv/csv.js Normal file
View File

@ -0,0 +1,31 @@
function toCSV(rows) {
const [columns] = rows;
let content = Object.keys(columns).join('\t');
for (let row of rows) {
const values = Object.values(row);
const finalValues = values.map(value => {
if (value instanceof Date) return formatDate(value);
if (value === null) return '';
return value;
});
content += '\n';
content += finalValues.join('\t');
}
return content;
}
function formatDate(date) {
return new Intl.DateTimeFormat('es', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(date);
}
module.exports = {
toCSV,
formatDate
};

View File

@ -0,0 +1,24 @@
const path = require('path');
const db = require('vn-print/core/database');
const {toCSV} = require('../csv');
const sqlPath = path.join(__dirname, 'sql');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const content = toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
response.setHeader('Content-type', 'text/csv');
response.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
response.end(content);
} catch (error) {
next(error);
}
};

View File

@ -1,82 +0,0 @@
const express = require('express');
const router = new express.Router();
const path = require('path');
const db = require('../../../core/database');
const sqlPath = path.join(__dirname, 'sql');
module.exports = app => {
router.get('/preview', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
res.setHeader('Content-type', 'application/json; charset=utf-8');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
router.get('/download', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
res.setHeader('Content-type', 'text/csv');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
const Email = require('../../../core/email');
router.get('/send', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const ticket = await db.findOneFromDef(`${sqlPath}/ticket`, [ticketId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const args = Object.assign({
ticketId: (String(ticket.id)),
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
const content = app.toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
const email = new Email('delivery-note', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
res.status(200).json({message: 'ok'});
} catch (error) {
next(error);
}
});
return router;
};

View File

@ -0,0 +1,40 @@
const path = require('path');
const db = require('vn-print/core/database');
const Email = require('vn-print/core/email');
const {toCSV} = require('../csv');
const sqlPath = path.join(__dirname, 'sql');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.ticketId)
throw new Error('The argument ticketId is required');
const ticketId = reqArgs.ticketId;
const ticket = await db.findOneFromDef(`${sqlPath}/ticket`, [ticketId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [ticketId]);
const args = Object.assign({
ticketId: (String(ticket.id)),
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, response.locals);
const content = toCSV(sales);
const fileName = `ticket_${ticketId}.csv`;
const email = new Email('delivery-note', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
response.status(200).json({message: 'Success'});
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,9 @@
const express = require('express');
const router = new express.Router();
router.get('/delivery-note/download', require('./delivery-note/download'));
router.get('/delivery-note/send', require('./delivery-note/send'));
router.get('/invoice/download', require('./invoice/download'));
router.get('/invoice/send', require('./invoice/send'));
module.exports = router;

View File

@ -0,0 +1,24 @@
const path = require('path');
const db = require('vn-print/core/database');
const {toCSV} = require('../csv');
const sqlPath = path.join(__dirname, 'sql');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const content = toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
response.setHeader('Content-type', 'text/csv');
response.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
response.end(content);
} catch (error) {
next(error);
}
};

View File

@ -1,82 +0,0 @@
const express = require('express');
const router = new express.Router();
const path = require('path');
const db = require('../../../core/database');
const sqlPath = path.join(__dirname, 'sql');
module.exports = app => {
router.get('/preview', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
res.setHeader('Content-type', 'application/json; charset=utf-8');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
router.get('/download', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
res.setHeader('Content-type', 'text/csv');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(content);
} catch (error) {
next(error);
}
});
const Email = require('../../../core/email');
router.get('/send', async function(req, res, next) {
try {
const reqArgs = req.args;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const args = Object.assign({
invoiceId: (String(invoice.id)),
recipientId: invoice.clientFk,
recipient: invoice.recipient,
replyTo: invoice.salesPersonEmail
}, reqArgs);
const content = app.toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
const email = new Email('invoice', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
res.status(200).json({message: 'ok'});
} catch (error) {
next(error);
}
});
return router;
};

View File

@ -0,0 +1,40 @@
const path = require('path');
const db = require('vn-print/core/database');
const Email = require('vn-print/core/email');
const {toCSV} = require('../csv');
const sqlPath = path.join(__dirname, 'sql');
module.exports = async function(request, response, next) {
try {
const reqArgs = request.query;
if (!reqArgs.invoiceId)
throw new Error('The argument invoiceId is required');
const invoiceId = reqArgs.invoiceId;
const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]);
const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]);
const args = Object.assign({
invoiceId: (String(invoice.id)),
recipientId: invoice.clientFk,
recipient: invoice.recipient,
replyTo: invoice.salesPersonEmail
}, response.locals);
const content = toCSV(sales);
const fileName = `invoice_${invoiceId}.csv`;
const email = new Email('invoice', args);
await email.send({
overrideAttachments: true,
attachments: [{
filename: fileName,
content: content
}]
});
response.status(200).json({message: 'Success'});
} catch (error) {
next(error);
}
};

View File

@ -1,33 +0,0 @@
const Email = require('../core/email');
module.exports = app => {
app.get(`/api/email/:name`, async(req, res, next) => {
try {
const reportName = req.params.name;
const email = new Email(reportName, req.args);
await email.send();
res.status(200).json({
message: 'Sent'
});
} catch (e) {
next(e);
}
});
app.get(`/api/email/:name/preview`, async(req, res, next) => {
try {
const reportName = req.params.name;
const args = req.args;
args.isPreview = true;
const email = new Email(reportName, args);
const rendered = await email.render();
res.send(rendered);
} catch (e) {
next(e);
}
});
};

View File

@ -0,0 +1,16 @@
const Email = require('vn-print/core/email');
module.exports = async function(request, response, next) {
try {
const templateName = request.params.name;
const args = response.locals;
const email = new Email(templateName, args);
await email.send();
response.status(200).json({
message: 'Sent'
});
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,7 @@
const express = require('express');
const router = new express.Router();
router.get('/:name', require('./email'));
router.get('/:name/preview', require('./preview'));
module.exports = router;

View File

@ -0,0 +1,14 @@
const Email = require('vn-print/core/email');
module.exports = async function(request, response, next) {
try {
const templateName = request.params.name;
const args = Object.assign({isPreview: true}, response.locals);
const email = new Email(templateName, args);
const template = await email.render();
response.send(template);
} catch (error) {
next(error);
}
};

View File

@ -1,54 +0,0 @@
const Report = require('../core/report');
module.exports = app => {
app.get(`/api/report/:name`, async(req, res, next) => {
try {
const reportName = req.params.name;
const fileName = getFileName(reportName, req.args);
const report = new Report(reportName, req.args);
if (req.args.preview) {
const template = await report.render();
res.send(template);
} else {
const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf');
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
res.end(stream);
}
} catch (error) {
next(error);
}
});
/**
* Returns all the params that ends with id
* @param {object} args - Params object
*
* @return {array} List of identifiers
*/
function getIdentifiers(args) {
const identifiers = [];
const keys = Object.keys(args);
for (let arg of keys) {
if (arg.endsWith('Id'))
identifiers.push(arg);
}
return identifiers;
}
function getFileName(name, args) {
const identifiers = getIdentifiers(args);
const params = [];
params.push(name);
for (let id of identifiers)
params.push(args[id]);
const fileName = params.join('_');
return `${fileName}.pdf`;
}
};

View File

@ -0,0 +1,17 @@
const Report = require('vn-print/core/report');
module.exports = async function(request, response, next) {
try {
const reportName = request.params.name;
const args = response.locals;
const report = new Report(reportName, args);
const stream = await report.toPdfStream();
const fileName = await report.getFileName();
response.setHeader('Content-type', 'application/pdf');
response.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
response.end(stream);
} catch (error) {
next(error);
}
};

View File

@ -0,0 +1,7 @@
const express = require('express');
const router = new express.Router();
router.get('/:name', require('./document'));
router.get('/:name/preview', require('./preview'));
module.exports = router;

View File

@ -0,0 +1,13 @@
const Report = require('vn-print/core/report');
module.exports = async function(request, response, next) {
try {
const reportName = request.params.name;
const report = new Report(reportName, request.query);
const template = await report.render();
response.send(template);
} catch (error) {
next(error);
}
};

18
print/methods/routes.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = [
{
url: '/api/report',
cb: require('./report')
},
{
url: '/api/email',
cb: require('./email')
},
{
url: '/api/csv',
cb: require('./csv')
},
{
url: '/api/closure',
cb: require('./closure')
},
];

View File

@ -21,6 +21,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -10,6 +10,7 @@ module.exports = {
},
props: {
claimId: {
type: [Number, String],
required: true
}
}

View File

@ -16,6 +16,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -18,6 +18,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
}
}

View File

@ -10,6 +10,7 @@ module.exports = {
},
props: {
ticketId: {
type: [Number, String],
required: true
}
}

View File

@ -10,7 +10,7 @@ module.exports = {
},
props: {
ticketId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -10,7 +10,7 @@ module.exports = {
},
props: {
routeId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -18,7 +18,7 @@ module.exports = {
},
props: {
invoiceId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -30,9 +30,11 @@ module.exports = {
required: true
},
recipientId: {
type: [Number, String],
required: true
},
companyId: {
type: [Number, String],
required: true
}
}

View File

@ -1,5 +1,4 @@
const Component = require(`${appPath}/core/component`);
const db = require(`${appPath}/core/database`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
const attachment = new Component('attachment');
@ -28,9 +27,11 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
companyId: {
type: [Number, String],
required: true
},
}

View File

@ -26,6 +26,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
}
}

View File

@ -24,6 +24,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
}
}

View File

@ -16,9 +16,11 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
companyId: {
type: [Number, String],
required: true
}
}

View File

@ -21,6 +21,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -25,6 +25,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -1,3 +1,4 @@
reportName: consumo-cliente
title: Consumo
Client: Cliente
clientData: Datos del cliente

View File

@ -32,6 +32,7 @@ module.exports = {
},
props: {
claimId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: orden-de-recogida
title: Ord. recogida
claimId: Reclamación
clientId: Cliente

View File

@ -69,6 +69,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -1,3 +1,4 @@
reportName: extracto-cliente
title: Extracto
clientId: Cliente
clientData: Datos del cliente

View File

@ -1,3 +1,4 @@
reportName: releve-de-compte
title: Relevé de compte
clientId: Client
clientData: Données client

View File

@ -10,7 +10,6 @@ module.exports = {
throw new Error('Something went wrong');
this.client = await this.findOneFromDef('client', [this.ticket.clientFk]);
},
computed: {
issued: function() {
@ -23,6 +22,7 @@ module.exports = {
},
props: {
ticketId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: autorizacion-cmr
description: '<em>{socialName}</em> una sociedad debidamente constituida con responsabilidad <em>limitada</em>
y registrada conforme al derecho de sociedades de {country} y aquí representada por
<span>___________________</span>. {socialName}, con domicilio en {address},

View File

@ -1,3 +1,4 @@
reportName: solicitud-de-credito
fields:
title: Solicitud de crédito
date: Fecha

View File

@ -117,7 +117,7 @@ module.exports = {
},
props: {
ticketId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: delivery-note
title: Delivery note
ticketId: Delivery note
clientId: Client

View File

@ -1,3 +1,4 @@
reportName: albaran
title: Albarán
ticketId: Albarán
clientId: Cliente

View File

@ -1,3 +1,4 @@
reportName: bon-de-livraison
title: Bon de livraison
ticketId: BL
clientId: Client

View File

@ -1,3 +1,4 @@
reportName: nota-de-entrega
title: Nota de Entrega
ticketId: Nota de Entrega
clientId: Cliente

View File

@ -39,6 +39,7 @@ module.exports = {
},
props: {
routeId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: hoja-de-ruta
title: Hoja de ruta
information: Información
date: Fecha

View File

@ -40,7 +40,7 @@ module.exports = {
},
props: {
entryId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: pedido-de-entrada
title: Pedido
supplierName: Proveedor
supplierStreet: Dirección

View File

@ -28,6 +28,7 @@ module.exports = {
},
props: {
invoiceId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: carta-CITES
title: 'Carta CITES'
toAttention: 'A la atención del Sr. Administrador de la Aduana de la Farga de Moles.'
declaration: 'Por la presente DECLARO, bajo mi responsabilidad, que las mercancías detalladas en la factura

View File

@ -1,3 +1,4 @@
reportName: orden-de-carga
title: Orden de carga
reference: Referencia
information: Información

View File

@ -32,7 +32,7 @@ module.exports = {
},
props: {
invoiceId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: factura
title: Factura
invoice: Factura
clientId: Cliente

View File

@ -115,7 +115,7 @@ module.exports = {
},
props: {
invoiceId: {
type: String,
type: [Number, String],
required: true
}
}

View File

@ -0,0 +1,36 @@
reportName: invoice
title: Invoice
invoice: Invoice
clientId: Client
invoiceData: Invoice data
fiscalId: FI / NIF
invoiceRef: Invoice {0}
deliveryNote: Delivery note
shipped: Shipped
date: Date
reference: Ref.
quantity: Qty.
concept: Concept
price: PSP/u
discount: Disc.
vat: VAT
amount: Amount
type: Type
taxBase: Tax base
tax: Tax
fee: Fee
total: Total
subtotal: Subtotal
taxBreakdown: Tax breakdown
notes: Notes
intrastat: Intrastat
code: Code
description: Description
stems: Stems
netKg: Net kg
rectifiedInvoices: Rectified invoices
issued: Issued
plantPassport: Plant passport
observations: Observations
wireTransfer: "Pay method: Transferencia"
accountNumber: "Account number: {0}"

View File

@ -1,3 +1,4 @@
reportName: factura
title: Factura
invoice: Factura
clientId: Cliente

View File

@ -63,9 +63,11 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
companyId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: extracto-de-cuenta
title: Extracto
claimId: Reclamación
clientId: Cliente

View File

@ -1,3 +1,4 @@
reportName: releve-de-compte
title: Relevé de compte
claimId: Réclamation
clientId: Client

View File

@ -1,3 +1,4 @@
reportName: receipt
title: 'Recibo'
date: 'Fecha'
payed: 'En {0}, a {1} de {2} de {3}'

View File

@ -25,6 +25,7 @@ module.exports = {
},
props: {
receiptId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: orden-de-domiciliacion
title: Orden de domiciliación de adeudo SEPA CORE
description: Mediante la firma de esta orden de domiciliación, el deudor autoriza
(A) al acreedor a enviar instrucciones a la entidad del deudor para adeudar su cuenta

View File

@ -1,3 +1,4 @@
reportName: direct-debit
title: Direct Debit
description: En signant ce formulaire de mandat, vous autorisez VERDNATURA LEVANTE SL
à envoyer des instructions à votre banque pour débiter votre compte, et (B) votre banque

View File

@ -1,3 +1,4 @@
reportName: autorizacao-de-debito
title: Autorização de débito directo SEPA CORE
description: Ao subscrever esta autorização, está a autorizar a (A) Verdnatura Levante
S.L. a enviar instruções ao seu banco para debitar a sua conta e (B) seu banco a

View File

@ -40,9 +40,11 @@ const rptSepaCore = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
companyId: {
type: [Number, String],
required: true
}
}

View File

@ -1,3 +1,4 @@
reportName: consumo-proveedor
title: Consumo
Supplier: Proveedor
supplierData: Datos del proveedor

View File

@ -49,6 +49,7 @@ module.exports = {
},
props: {
recipientId: {
type: [Number, String],
required: true
},
from: {

View File

@ -13,6 +13,7 @@ module.exports = {
},
props: {
routeId: {
type: [Number, String],
required: true
}
}