refactor(print): print service refactor with better router handler
gitea/salix/pipeline/head There was a failure building this commit Details

Refs: 3245, 3477, 2800
This commit is contained in:
Joan Sanchez 2022-01-05 14:48:21 +01:00
parent 30da0b7ed1
commit 63ea0902d1
27 changed files with 4531 additions and 23618 deletions

27579
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,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

@ -17,6 +17,8 @@ module.exports = async app => {
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
// app.use('/api', require('./methods/router'));
// Init database instance
require('./core/database').init();
// Init SMTP Instance

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

@ -17,6 +17,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);
@ -49,6 +53,39 @@ class Report extends Component {
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,9 +1,80 @@
const path = require('path');
const fs = require('fs');
const db = require('./database');
module.exports = app => {
const methodsPath = path.resolve(__dirname, '../methods');
const routes = [
{
url: '/api/report',
cb: require('../methods/report')
},
{
url: '/api/email',
cb: require('../methods/email')
},
{
url: '/api/closure',
cb: require('../methods/closure')
},
];
const paths = routes.map(route => route.url);
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 = ?`;
const auth = await db.findOne(query, [token]);
if (!auth || isTokenExpired(auth.created, auth.ttl))
throw new Error('Invalid authorization token');
const args = Object.assign({}, request.query);
const props = Object.assign(args, request.body);
props.authorization = auth.id;
response.locals = props;
response.locals.auth = {
userId: auth.userId,
token: auth.id,
email: auth.email,
locale: auth.lang
};
next();
} catch (error) {
next(error);
}
});
// Register routes
for (let route of routes)
app.use(route.url, route.cb);
function getToken(request) {
const headers = request.headers;
const queryParams = request.query;
return headers.authorization || queryParams.authorization;
}
function isTokenExpired(created, ttl) {
const date = new Date(created);
const currentDate = new Date();
date.setSeconds(date.getSeconds() + ttl);
if (currentDate > date)
return true;
return false;
}
// app.use('/api/email', require('../methods/email'));
/* const methodsPath = path.resolve(__dirname, '../methods');
const methodsDir = fs.readdirSync(methodsPath);
const methods = [];
@ -20,7 +91,7 @@ module.exports = app => {
app.use(paths, async function(req, res, next) {
const token = getToken(req);
const query = `SELECT at.id, at.userId, eu.email, u.lang, at.ttl, at.created
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
@ -71,5 +142,5 @@ module.exports = app => {
// Mount methods
for (let method of methods)
require(`../methods/${method}`)(app);
require(`../methods/${method}`)(app); */
};

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

@ -0,0 +1,62 @@
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' OR (am.code = 'refund' AND al.code != 'delivered'))
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);
// 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);
}
};

View File

@ -0,0 +1,63 @@
const Report = require('vn-print/core/report'); // Put inside block to avoid circular dependency
module.exports = {
async start(tickets) {
if (tickets.length == 0) return;
const failedtickets = [];
for (const ticket of tickets) {
try {
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: []
};
// Store invoice
if (invoiceOut) {
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`;
storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
mailOptions.attachments.push({
filename: fileName,
content: stream
});
}
} 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
});
}
}
};

View File

@ -3,6 +3,7 @@ const Email = require('../core/email');
const Report = require('../core/report');
const smtp = require('../core/smtp');
const config = require('../core/config');
const storage = require('../core/storage');
module.exports = app => {
app.get('/api/closure/all', async function(req, res, next) {
@ -18,16 +19,16 @@ module.exports = app => {
const tickets = await db.rawSql(`
SELECT
t.id
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
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
WHERE al.code = 'PACKED'
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered'))
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]);
GROUP BY t.id`, [reqArgs.to, reqArgs.to]);
const ticketIds = tickets.map(ticket => ticket.id);
await closeAll(ticketIds, req.args);
@ -39,7 +40,7 @@ module.exports = app => {
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)
WHERE DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?)
AND al.code NOT IN('DELIVERED','PACKED')
AND t.routeFk
@ -168,6 +169,7 @@ module.exports = app => {
});
async function closeAll(ticketIds, reqArgs) {
if (ticketIds.length == 0) return;
const failedtickets = [];
const tickets = await db.rawSql(`
SELECT
@ -191,6 +193,49 @@ module.exports = app => {
try {
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 args = Object.assign({
invoiceId: invoiceOut.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
if (invoiceOut) {
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`;
storage.write(stream, {
type: 'invoice',
path: `${year}/${month}/${day}`,
fileName: fileName
});
mailOptions.attachments.push({
filename: fileName,
content: stream
});
}
if (!ticket.salesPersonFk || !ticket.isToBeMailed) continue;
if (!ticket.recipient) {
@ -209,33 +254,23 @@ module.exports = app => {
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,
/* const args = Object.assign({
invoiceId: invoiceOut.id,
recipientId: ticket.clientFk,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail
}, reqArgs);
let mailOptions = {};
if (invoice.serial == 'E' && invoice.companyCode == 'VNL') {
*/
if (invoiceOut.serial == 'E' && invoiceOut.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 fileName = `cites-${invoiceOut.ref}.pdf`;
mailOptions.attachments.push({
filename: fileName,
content: stream
});
}
const email = new Email('invoice', args);

View File

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

View File

@ -1,6 +1,21 @@
const Email = require('../core/email');
const Email = require('vn-print/core/email');
module.exports = app => {
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);
}
};
/* module.exports = app => {
app.get(`/api/email/:name`, async(req, res, next) => {
try {
const reportName = req.params.name;
@ -31,3 +46,4 @@ module.exports = app => {
}
});
};
*/

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);
}
};

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

View File

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

View File

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