restructure router methods, added closure method
gitea/salix/1391-ticket_closure This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-11-18 15:04:51 +01:00
parent 4b51d607f7
commit 8643030bf0
20 changed files with 407 additions and 70 deletions

View File

@ -0,0 +1,116 @@
USE `vn`;
DROP procedure IF EXISTS `ticketClosure`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketClosure`()
BEGIN
/**
* Realiza el cierre de todos los
* tickets de la table ticketClosure.
*/
DECLARE vDone BOOL;
DECLARE vClientFk INT;
DECLARE vTicketFk INT;
DECLARE vIsTaxDataChecked BOOL;
DECLARE vCompanyFk INT;
DECLARE vShipped DATE;
DECLARE vPriority INT DEFAULT 1;
DECLARE vReportDeliveryNote INT DEFAULT 1;
DECLARE vNewInvoiceId INT;
DECLARE vHasDailyInvoice BOOL;
DECLARE vWithPackage BOOL;
DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticketClosure;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
RESIGNAL;
END;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure2;
CREATE TEMPORARY TABLE tmp.ticketClosure2
SELECT ticketFk FROM tmp.ticketClosure;
INSERT INTO tmp.ticketClosure
SELECT id FROM stowaway s
JOIN tmp.ticketClosure2 tc ON s.shipFk = tc.ticketFk;
OPEN cur;
proc: LOOP
SET vDone = FALSE;
FETCH cur INTO vTicketFk;
IF vDone THEN
LEAVE proc;
END IF;
-- ticketClosure start
SELECT
c.id,
c.isTaxDataChecked,
t.companyFk,
t.shipped,
co.hasDailyInvoice,
w.isManaged
INTO vClientFk,
vIsTaxDataChecked,
vCompanyFk,
vShipped,
vHasDailyInvoice,
vWithPackage
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
JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vTicketFk;
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
(SELECT vTicketFk, p.id, COUNT(*)
FROM expedition e
JOIN packaging p ON p.itemFk = e.itemFk
WHERE e.ticketFk = vTicketFk AND p.isPackageReturnable
AND vWithPackage
GROUP BY p.itemFk);
-- No retornables o no catalogados
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
(SELECT e.itemFk, vTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
FROM expedition e
JOIN item i ON i.id = e.itemFk
LEFT JOIN packaging p ON p.itemFk = i.id
WHERE e.ticketFk = vTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
AND getSpecialPrice(e.itemFk, vClientFk) > 0
GROUP BY e.itemFk);
IF(vHasDailyInvoice) THEN
-- Facturacion rapida
CALL ticketTrackingAdd(vTicketFk, 'DELIVERED', NULL);
-- Facturar si está contabilizado
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
vShipped,
vCompanyFk,
NULL,
vNewInvoiceId);
END IF;
ELSE
-- Albaran_print
CALL ticketTrackingAdd(vTicketFk, (SELECT vn.getAlert3State(vTicketFk)), NULL);
END IF;
-- ticketClosure end
END LOOP;
CLOSE cur;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure2;
END$$
DELIMITER ;

View File

@ -2,7 +2,8 @@
"app": {
"host": "http://localhost:5000",
"port": 3000,
"senderMail": "nocontestar@verdnatura.es",
"senderEmail": "nocontestar@verdnatura.es",
"reportEmail": "cau@verdnatura.es",
"senderName": "Verdnatura"
},
"i18n": {

View File

@ -4,13 +4,13 @@ buttons:
privacy:
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
· www.verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVIS - Ce message est privé et confidentiel et doit être utilisé.exclusivamente
por la persona destinataria del mismo. Si has recibido este mensajepor error,
te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier documentoadjunto
que pudiera contener. Verdnatura Levante SL no renuncia a la confidencialidad
ni aningún privilegio por causa de transmisión errónea o mal funcionamiento. Igualmente
no se haceresponsable de los cambios, alteraciones, errores u omisiones que pudieran
hacerse al mensaje una vez enviado.'
disclaimer: "- AVIS - Ce message est privé et confidentiel et doit être utilisé
exclusivement par le destinataire. Si vous avez reçu ce message par erreur,
veuillez en informer l'expéditeur et supprimer ce message ainsi que tous les
documents joints qu'il pourrait contenir. Verdnatura Levante SL ne renonce pas à la
confidentialité ni aux privilèges résultant d'une transmission erronée ou d'un dysfonctionnement.
De même, il n'est pas responsable des modifications, altérations, erreurs ou
omissions qui pourraient être apportées au message une fois envoyé."
law: En cumplimiento de lo dispuesto en la Ley Orgánica 15/1999, de Protección de
Datos de Carácter Personal, te comunicamos que los datos personales que facilites
se incluirán en ficheros automatizados de VERDNATURA LEVANTE S.L.,pudiendo en

View File

@ -10,13 +10,20 @@ module.exports = {
});
}
},
find(query, params) {
/**
* Makes a query from a raw sql
* @param {String} query - The raw SQL query
* @param {Object} params - Parameterized values
*
* @return {Object} - Result
*/
rawSql(query, params) {
return this.pool.query(query, params).then(([rows]) => {
return rows;
});
},
findOne(query, params) {
return this.find(query, params).then(([rows]) => rows);
return this.rawSql(query, params).then(([rows]) => rows);
},
findFromDef() {

View File

@ -1,51 +1,6 @@
const Report = require('./report');
const Email = require('./email');
module.exports = app => {
app.get(`/api/report/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs)
res.json({message: 'Required params recipient, clientId'});
try {
const report = new Report(req.params.name, args);
const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf');
stream.pipe(res);
} catch (e) {
next(e);
}
});
app.get(`/api/email/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['recipient', 'clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs)
res.json({message: 'Required params recipient, clientId'});
try {
const email = new Email(req.params.name, args);
if (args.isPreview === 'true') {
const rendered = await email.render();
res.send(rendered);
} else {
await email.send();
res.status(200).json({message: 'Sent'});
}
} catch (e) {
next(e);
}
});
// Import methods
require('../methods/closure')(app);
require('../methods/report')(app);
require('../methods/email')(app);
};

View File

@ -1,5 +1,6 @@
const nodemailer = require('nodemailer');
const config = require('./config');
const db = require('./database');
module.exports = {
init() {
@ -8,15 +9,30 @@ module.exports = {
},
send(options) {
options.from = `${config.app.senderName} <${config.app.senderMail}>`;
options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
if (process.env.NODE_ENV !== 'production') {
if (!config.smtp.auth.user)
return Promise.resolve(true);
options.to = config.app.senderMail;
options.to = config.app.senderEmail;
}
return this.transporter.sendMail(options);
let error;
return this.transporter.sendMail(options).catch(err => {
error = err;
throw err;
}).finally(async() => {
await db.rawSql(`
INSERT INTO vn.mail (sender, replyTo, sent, subject, body, status)
VALUES (:recipient, :sender, 1, :subject, :body, :status)`, {
sender: config.app.senderEmail,
recipient: options.to,
subject: options.subject,
body: options.text || options.html,
status: error && error.message || 'Sent'
});
});
}
};

59
print/methods/closure.js Normal file
View File

@ -0,0 +1,59 @@
const db = require('../core/database');
const Email = require('../core/email');
const smtp = require('../core/smtp');
const config = require('../core/config');
module.exports = app => {
app.get('/api/closure', async function(req, res) {
const failedtickets = [];
const tickets = await db.rawSql(`
SELECT
t.id,
t.clientFk,
c.email recipient
FROM expedition e
JOIN ticket t ON t.id = e.ticketFk
JOIN client c ON c.id = t.clientFk
JOIN warehouse w ON w.id = t.warehouseFk AND hasComission
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
WHERE
ts.alertLevel = 2
AND DATE(t.shipped) BETWEEN DATE_ADD(CURDATE(), INTERVAL -2 DAY) AND CURDATE()
AND t.refFk IS NULL
GROUP BY e.ticketFk`);
for (const ticket of tickets) {
try {
await db.rawSql(`CALL vn.ticketClosureTicket(:ticketId)`, {
ticketId: ticket.id
});
const args = {
ticketId: ticket.id,
clientId: ticket.clientFk,
recipient: ticket.recipient
};
const email = new Email('delivery-note-link', args);
await email.send();
} catch (error) {
// Save tickets on a list of failed ids
failedtickets.push(ticket.id);
}
}
// Send email with failed tickets
if (failedtickets.length > 0) {
const ticketList = failedtickets.join(', ');
smtp.send({
to: config.app.reportEmail,
subject: 'Nightly ticket closure has failed',
text: `This following tickets has failed: ${ticketList}`
});
}
res.status(200).json({
statusCode: 200,
message: 'Closure executed successfully'
});
});
};

33
print/methods/email.js Normal file
View File

@ -0,0 +1,33 @@
const Email = require('../core/email');
module.exports = app => {
app.get(`/api/email/:name`, async(req, res) => {
const args = req.query;
const requiredArgs = ['recipient', 'clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs) {
return res.status(400).json({
message: 'Required params recipient, clientId'
});
}
try {
const email = new Email(req.params.name, args);
if (args.isPreview === 'true') {
const rendered = await email.render();
res.send(rendered);
} else await email.send();
} catch (e) {
console.error(e);
return res.status(500).json({message: 'Email not sent'});
}
res.status(200).json({message: 'Sent'});
});
};

24
print/methods/report.js Normal file
View File

@ -0,0 +1,24 @@
const Report = require('../core/report');
module.exports = app => {
app.get(`/api/report/:name`, async(req, res, next) => {
const args = req.query;
const requiredArgs = ['clientId'];
const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
});
if (!hasRequiredArgs)
res.json({message: 'Required params recipient, clientId'});
try {
const report = new Report(req.params.name, args);
const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf');
stream.pipe(res);
} catch (e) {
next(e);
}
});
};

View File

@ -0,0 +1,9 @@
const Stylesheet = require(`${appPath}/core/stylesheet`);
module.exports = new Stylesheet([
`${appPath}/common/css/spacing.css`,
`${appPath}/common/css/misc.css`,
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,5 @@
.external-link {
border: 2px dashed #8dba25;
border-radius: 3px;
text-align: center
}

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html v-bind:lang="locale">
<head>
<meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no">
<title>{{ $t('subject') }}</title>
<style type="text/css">
a {
color: #8dba25
}
</style>
</head>
<body>
<table class="grid">
<tbody>
<tr>
<td>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
<!-- Header block -->
<div class="grid-row">
<div class="grid-block">
<email-header
v-bind:is-preview="isPreview"
v-bind:locale="locale">
</email-header>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block white vn-pa-lg">
<h1>{{ $t('title') }}</h1>
<p>{{$t('dear')}}</p>
<p v-html="$t('description', [ticketId])"></p>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block white vn-px-lg">
<p>{{$t('copyLink')}}</p>
<div class="external-link vn-pa-sm vn-m-md">
https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={{ticketId}}
</div>
</div>
</div>
<!-- Block -->
<div class="grid-row">
<div class="grid-block white vn-pa-lg">
<p v-html="$t('poll')"></p>
<p v-html="$t('help')"></p>
<p v-html="$t('conclusion')"></p>
</div>
</div>
<!-- Footer block -->
<div class="grid-row">
<div class="grid-block">
<email-footer
v-bind:is-preview="isPreview"
v-bind:locale="locale">
</email-footer>
</div>
</div>
<!-- Empty block -->
<div class="grid-row">
<div class="grid-block empty"></div>
</div>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@ -0,0 +1,16 @@
const Component = require(`${appPath}/core/component`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
module.exports = {
name: 'delivery-note-link',
components: {
'email-header': emailHeader.build(),
'email-footer': emailFooter.build()
},
props: {
ticketId: {
required: true
}
}
};

View File

@ -0,0 +1,11 @@
subject: Aquí tienes tu albarán
title: "Aquí tienes tu albarán"
dear: Estimado cliente
description: Ya está disponible el albarán correspondiente al pedido <strong>{0}</strong>. <br/>
Puedes verlo haciendo clic <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">en este enlace</a>.
copyLink: 'Como alternativa, puedes copiar el siguiente enlace en tu navegador:'
poll: Si lo deseas, puedes responder a nuestra encuesta de satisfacción para
ayudarnos a prestar un mejor servicio. ¡Tu opinión es muy importante para nosotros!
help: Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para
atenderte!</strong>
conclusion: ¡Gracias por tu atención!

View File

@ -0,0 +1,10 @@
subject: Voici votre bon de livraison
title: "Voici votre bon de livraison"
dear: Cher client,
description: Le bon de livraison correspondant à la commande <strong>{0}</strong> est maintenant disponible.<br/>
Vous pouvez le voir en cliquant <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}" target="_blank">sur ce lien</a>.
copyLink: 'Vous pouvez également copier le lien suivant dans votre navigateur:'
poll: Si vous le souhaitez, vous pouvez répondre à notre questionaire de satisfaction
pour nous aider à améliorer notre service. Votre avis est très important pour nous!
help: N'hésitez pas nous envoyer toute doute ou question, <strong>nous sommes là pour vous aider!</strong>
conclusion: Merci pour votre attention!

View File

@ -43,7 +43,7 @@ module.exports = {
WHERE cl.id = ?`, [claimId]);
},
fetchSales(claimId) {
return db.find(
return db.rawSql(
`SELECT
s.id,
s.quantity,

View File

@ -76,7 +76,7 @@ module.exports = {
},
fetchSales(ticketId) {
return db.find(
return db.rawSql(
`SELECT
s.id,
s.itemFk,
@ -128,12 +128,12 @@ module.exports = {
ORDER BY (it.isPackaging), s.concept, s.itemFk`, [ticketId]);
},
fetchTaxes(ticketId) {
return db.find(`CALL vn.ticketGetTaxAdd(?)`, [ticketId]).then(rows => {
return db.rawSql(`CALL vn.ticketGetTaxAdd(?)`, [ticketId]).then(rows => {
return rows[0];
});
},
fetchPackagings(ticketId) {
return db.find(
return db.rawSql(
`SELECT
tp.quantity,
i.name,
@ -145,7 +145,7 @@ module.exports = {
ORDER BY itemFk`, [ticketId]);
},
fetchServices(ticketId) {
return db.find(
return db.rawSql(
`SELECT
tc.description taxDescription,
ts.description,

View File

@ -41,7 +41,7 @@ module.exports = {
},
// Redmine #1855 Replace function Averiguar_ComercialCliente_Id()
fetchTickets(routeId) {
return db.find(
return db.rawSql(
`SELECT
t.nickname addressName,
t.packages,

View File

@ -44,7 +44,7 @@ module.exports = {
WHERE i.id = ? AND clb.warehouse_id = ?`, [id, warehouseId]);
},
fetchItemTags(itemId) {
return db.find(
return db.rawSql(
`SELECT t.code, t.name, it.value
FROM vn.itemTag it
JOIN vn.tag t ON t.id = it.tagFk

View File

@ -40,7 +40,7 @@ module.exports = {
WHERE c.id = ?`, [clientId]);
},
fetchSales(clientId, companyId) {
return db.find(
return db.rawSql(
`CALL vn.clientGetDebtDiary(:clientId, :companyId)`, {
clientId: clientId,
companyId: companyId,