diff --git a/db/changes/10110-postCampaign/00-ticketClosure.sql b/db/changes/10110-postCampaign/00-ticketClosure.sql new file mode 100644 index 000000000..07c7ef812 --- /dev/null +++ b/db/changes/10110-postCampaign/00-ticketClosure.sql @@ -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 ; + diff --git a/print/config/print.json b/print/config/print.json index ee2ab6f79..e1a93e152 100755 --- a/print/config/print.json +++ b/print/config/print.json @@ -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": { diff --git a/print/core/components/email-footer/locale/fr.yml b/print/core/components/email-footer/locale/fr.yml index de4cf9095..c3eb5c3ff 100644 --- a/print/core/components/email-footer/locale/fr.yml +++ b/print/core/components/email-footer/locale/fr.yml @@ -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 diff --git a/print/core/database.js b/print/core/database.js index 02ae2a9b0..553fe3265 100644 --- a/print/core/database.js +++ b/print/core/database.js @@ -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() { diff --git a/print/core/router.js b/print/core/router.js index 173c31565..f015ac03b 100644 --- a/print/core/router.js +++ b/print/core/router.js @@ -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); }; diff --git a/print/core/smtp.js b/print/core/smtp.js index 6f8bb20f9..b274eafa3 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -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' + }); + }); } }; diff --git a/print/methods/closure.js b/print/methods/closure.js new file mode 100644 index 000000000..c12dd07b0 --- /dev/null +++ b/print/methods/closure.js @@ -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' + }); + }); +}; diff --git a/print/methods/email.js b/print/methods/email.js new file mode 100644 index 000000000..1a180935e --- /dev/null +++ b/print/methods/email.js @@ -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'}); + }); +}; diff --git a/print/methods/report.js b/print/methods/report.js new file mode 100644 index 000000000..bf7c47dcb --- /dev/null +++ b/print/methods/report.js @@ -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); + } + }); +}; diff --git a/print/templates/email/delivery-note-link/assets/css/import.js b/print/templates/email/delivery-note-link/assets/css/import.js new file mode 100644 index 000000000..c742fdf90 --- /dev/null +++ b/print/templates/email/delivery-note-link/assets/css/import.js @@ -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(); diff --git a/print/templates/email/delivery-note-link/assets/css/style.css b/print/templates/email/delivery-note-link/assets/css/style.css new file mode 100644 index 000000000..5db85befa --- /dev/null +++ b/print/templates/email/delivery-note-link/assets/css/style.css @@ -0,0 +1,5 @@ +.external-link { + border: 2px dashed #8dba25; + border-radius: 3px; + text-align: center +} \ No newline at end of file diff --git a/print/templates/email/delivery-note-link/delivery-note-link.html b/print/templates/email/delivery-note-link/delivery-note-link.html new file mode 100644 index 000000000..6a23ed4b5 --- /dev/null +++ b/print/templates/email/delivery-note-link/delivery-note-link.html @@ -0,0 +1,75 @@ + + +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('title') }}+{{$t('dear')}} + +
+
+
+
+
+ {{$t('copyLink')}} +
+ https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={{ticketId}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+