diff --git a/back/methods/mrw-config/cancelShipment.ejs b/back/methods/mrw-config/cancelShipment.ejs new file mode 100644 index 000000000..9ef401bc8 --- /dev/null +++ b/back/methods/mrw-config/cancelShipment.ejs @@ -0,0 +1,20 @@ + + + + <%= mrw.franchiseCode %> + <%= mrw.subscriberCode %> + + <%= mrw.user %> + <%= mrw.password %> + + + + + + + <%= externalId %> + + + + + \ No newline at end of file diff --git a/back/methods/mrw-config/cancelShipment.js b/back/methods/mrw-config/cancelShipment.js new file mode 100644 index 000000000..218b6a96b --- /dev/null +++ b/back/methods/mrw-config/cancelShipment.js @@ -0,0 +1,46 @@ +const axios = require('axios'); +const fs = require('fs'); +const ejs = require('ejs'); +const {DOMParser} = require('xmldom'); + +module.exports = Self => { + Self.remoteMethod('cancelShipment', { + description: 'Cancel a shipment by providing the expedition ID, interacting with MRW WebService', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionFk', + type: 'number', + required: true + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/cancelShipment`, + verb: 'POST' + } + }); + + Self.cancelShipment = async expeditionFk => { + const models = Self.app.models; + + const mrw = await models.MrwConfig.findOne(); + const {externalId} = await models.Expedition.findById(expeditionFk); + + const template = fs.readFileSync(__dirname + '/cancelShipment.ejs', 'utf-8'); + const renderedXml = ejs.render(template, {mrw, externalId}); + const response = await axios.post(mrw.url, renderedXml, { + headers: { + 'Content-Type': 'application/soap+xml; charset=utf-8' + } + }); + + const xmlString = response.data; + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlString, 'text/xml'); + const [resultElement] = xmlDoc.getElementsByTagName('Mensaje'); + + return resultElement.textContent; + }; +}; diff --git a/back/methods/mrw-config/createShipment.ejs b/back/methods/mrw-config/createShipment.ejs new file mode 100644 index 000000000..bf8a07dab --- /dev/null +++ b/back/methods/mrw-config/createShipment.ejs @@ -0,0 +1,43 @@ + + + + + <%= mrw.franchiseCode %> + <%= mrw.subscriberCode %> + + <%= mrw.user %> + <%= mrw.password %> + + + + + + + + + <%= expeditionData.street %> + + + <%= expeditionData.postalCode %> + <%= expeditionData.city %> + + + + <%= expeditionData.fi %> + <%= expeditionData.clientName %> + <%= expeditionData.phone %> + + + <%= expeditionData.created %> + <%= expeditionData.expeditionDataId %> + <%= expeditionData.serviceType %> + 1 + <%= expeditionData.weekDays %> + <%= expeditionData.kg %> + + + + + + + \ No newline at end of file diff --git a/back/methods/mrw-config/createShipment.js b/back/methods/mrw-config/createShipment.js new file mode 100644 index 000000000..f0f7d66a2 --- /dev/null +++ b/back/methods/mrw-config/createShipment.js @@ -0,0 +1,109 @@ +const axios = require('axios'); +const {DOMParser} = require('xmldom'); +const fs = require('fs'); +const ejs = require('ejs'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('createShipment', { + description: 'Create an expedition and return a base64Binary label from de MRW WebService', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionFk', + type: 'number', + required: true + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/createShipment`, + verb: 'POST' + } + }); + + Self.createShipment = async(expeditionFk, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) + myOptions.transaction = await Self.beginTransaction({}); + + const models = Self.app.models; + const mrw = await models.MrwConfig.findOne(null, myOptions); + + if (!mrw) + throw new UserError(`Some mrwConfig parameters are not set`); + + const query = + `SELECT CASE co.code + WHEN 'ES' THEN a.postalCode + WHEN 'PT' THEN LEFT(a.postalCode, 4) + WHEN 'AD' THEN REPLACE(a.postalCode, 'AD', '00') + END postalCode, + a.city, + a.street, + co.code countryCode, + c.fi, + c.name clientName, + c.phone, + DATE_FORMAT(t.shipped, '%d/%m/%Y') created, + t.shipped, + e.id expeditionId, + LPAD(IF(mw.params IS NULL, ms.serviceType, mw.serviceType), 4 ,'0') serviceType, + IF(mw.weekdays, 'S', 'N') weekDays + FROM expedition e + JOIN ticket t ON e.ticketFk = t.id + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN mrwService ms ON ms.agencyModeCodeFk = am.code + LEFT JOIN mrwServiceWeekday mw ON mw.weekdays = DATE_FORMAT(t.shipped, '%a') + JOIN client c ON t.clientFk = c.id + JOIN address a ON t.addressFk = a.id + JOIN province p ON a.provinceFk = p.id + JOIN country co ON co.id = p.countryFk + WHERE e.id = ? + LIMIT 1`; + + const [expeditionData] = await Self.rawSql(query, [expeditionFk], myOptions); + + if (!expeditionData) + throw new UserError(`This expedition is not a MRW shipment`); + + const today = Date.vnNew(); + today.setHours(0, 0, 0, 0); + if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today) + throw new UserError(`This ticket has a shipped date earlier than today`); + + const shipmentResponse = await sendXmlDoc('createShipment', {mrw, expeditionData}, 'application/soap+xml'); + const shipmentId = getTextByTag(shipmentResponse, 'NumeroEnvio'); + + if (!shipmentId) + throw new UserError(getTextByTag(shipmentResponse, 'Mensaje')); + + const getLabelResponse = await sendXmlDoc('getLabel', {mrw, shipmentId}, 'text/xml'); + const file = getTextByTag(getLabelResponse, 'EtiquetaFile'); + + await models.Expedition.updateAll({id: expeditionFk}, {externalId: shipmentId}, myOptions); + return file; + }; + + function getTextByTag(xmlDoc, tag) { + return xmlDoc?.getElementsByTagName(tag)[0]?.textContent; + } + + async function sendXmlDoc(xmlDock, params, contentType) { + const parser = new DOMParser(); + + const xmlTemplate = fs.readFileSync(__dirname + `/${xmlDock}.ejs`, 'utf-8'); + const renderedTemplate = ejs.render(xmlTemplate, params); + const data = await axios.post(params.mrw.url, renderedTemplate, { + headers: { + 'Content-Type': `${contentType}; charset=utf-8` + } + }); + return parser.parseFromString(data.data, 'text/xml'); + } +}; diff --git a/back/methods/mrw-config/getLabel.ejs b/back/methods/mrw-config/getLabel.ejs new file mode 100644 index 000000000..09bdb3f6c --- /dev/null +++ b/back/methods/mrw-config/getLabel.ejs @@ -0,0 +1,25 @@ + + + + <%= mrw.franchiseCode %> + <%= mrw.subscriberCode %> + + <%= mrw.user %> + <%= mrw.password %> + + + + + + <%= shipmentId %> + 1 + + + + 0 + 0 + 0 + + + + \ No newline at end of file diff --git a/back/methods/mrw-config/specs/createShipment.spec.js b/back/methods/mrw-config/specs/createShipment.spec.js new file mode 100644 index 000000000..e7bba524a --- /dev/null +++ b/back/methods/mrw-config/specs/createShipment.spec.js @@ -0,0 +1,120 @@ +const models = require('vn-loopback/server/server').models; +const axios = require('axios'); +const fs = require('fs'); + +const mockBase64Binary = 'base64BinaryString'; +const ticket1 = { + 'id': '44', + 'clientFk': 1101, + 'shipped': Date.vnNew(), + 'nickname': 'MRW', + 'addressFk': 1, + 'agencyModeFk': 999 +}; + +const expedition1 = { + 'id': 17, + 'agencyModeFk': 999, + 'ticketFk': 44, + 'freightItemFk': 71, + 'created': '2001-01-01', + 'counter': 1, + 'workerFk': 18, + 'packagingFk': '94', + 'hostFk': '', + 'stateTypeFk': 3, + 'hasNewRoute': 0, + 'isBox': 71, + 'editorFk': 100 +}; + +let tx; +let options; + +describe('MRWConfig createShipment()', () => { + beforeEach(async() => { + options = tx = undefined; + tx = await models.MrwConfig.beginTransaction({}); + options = {transaction: tx}; + + await models.Agency.create( + {'id': 999, 'name': 'mrw'}, + options + ); + + await models.AgencyMode.create( + {'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'}, + options + ); + + await models.MrwConfig.create( + { + 'url': 'https://url.com', + 'user': 'user', + 'password': 'password', + 'franchiseCode': 'franchiseCode', + 'subscriberCode': 'subscriberCode' + }, options + ); + + await models.Application.rawSql( + `INSERT INTO vn.mrwService + SET agencyModeCodeFk = 'mrw', + clientType = 1, + serviceType = 1, + kg = 1`, null, options + ); + await models.Ticket.create(ticket1, options); + await models.Expedition.create(expedition1, options); + }); + + afterEach(async() => { + await tx.rollback(); + }); + + it('should create a shipment and return a base64Binary label', async() => { + const mockPostResponses = [ + {data: fs.readFileSync(__dirname + '/mockGetLabel.xml', 'utf-8')}, + {data: fs.readFileSync(__dirname + '/mockCreateShipment.xml', 'utf-8')} + ]; + + spyOn(axios, 'post').and.callFake(() => Promise.resolve(mockPostResponses.pop())); + + const base64Binary = await models.MrwConfig.createShipment(expedition1.id, options); + + expect(base64Binary).toEqual(mockBase64Binary); + }); + + it('should fail if mrwConfig has no data', async() => { + let error; + await models.MrwConfig.createShipment(expedition1.id).catch(e => { + error = e; + }).finally(async() => { + expect(error.message).toEqual(`Some mrwConfig parameters are not set`); + }); + + expect(error).toBeDefined(); + }); + + it('should fail if expeditionFk is not a MrwExpedition', async() => { + let error; + await models.MrwConfig.createShipment(undefined, options).catch(e => { + error = e; + }).finally(async() => { + expect(error.message).toEqual(`This expedition is not a MRW shipment`); + }); + }); + + it(' should fail if the creation date of this ticket is before the current date it', async() => { + let error; + const yesterday = Date.vnNew(); + + yesterday.setDate(yesterday.getDate() - 1); + await models.Ticket.updateAll({id: ticket1.id}, {shipped: yesterday}, options); + await models.MrwConfig.createShipment(expedition1.id, options).catch(e => { + error = e; + }).finally(async() => { + expect(error.message).toEqual(`This ticket has a shipped date earlier than today`); + }); + }); +}); diff --git a/back/methods/mrw-config/specs/mockCreateShipment.xml b/back/methods/mrw-config/specs/mockCreateShipment.xml new file mode 100644 index 000000000..dacc5d3f3 --- /dev/null +++ b/back/methods/mrw-config/specs/mockCreateShipment.xml @@ -0,0 +1,16 @@ + + + + + + 1 + + 1 + 1 + http://url.com + + + + \ No newline at end of file diff --git a/back/methods/mrw-config/specs/mockGetLabel.xml b/back/methods/mrw-config/specs/mockGetLabel.xml new file mode 100644 index 000000000..0401ce2c7 --- /dev/null +++ b/back/methods/mrw-config/specs/mockGetLabel.xml @@ -0,0 +1,14 @@ + + + + + + 1 + + base64BinaryString + + + + \ No newline at end of file diff --git a/back/model-config.json b/back/model-config.json index 27a94498c..c4eefd932 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -159,6 +159,9 @@ }, "VnRole": { "dataSource": "vn" + }, + "MrwConfig": { + "dataSource": "vn" } } diff --git a/back/models/mrw-config.js b/back/models/mrw-config.js new file mode 100644 index 000000000..f764b91cc --- /dev/null +++ b/back/models/mrw-config.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/mrw-config/createShipment')(Self); + require('../methods/mrw-config/cancelShipment')(Self); +}; diff --git a/back/models/mrw-config.json b/back/models/mrw-config.json new file mode 100644 index 000000000..f0cf799b1 --- /dev/null +++ b/back/models/mrw-config.json @@ -0,0 +1,31 @@ +{ + "name": "MrwConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "mrwConfig" + } + }, + "properties": { + "id": { + "type": "number", + "required": true + }, + "url": { + "type": "string", + "required": true + }, + "user": { + "type": "string" + }, + "password": { + "type": "string" + }, + "franchiseCode": { + "type": "string" + }, + "subscriberCode": { + "type": "string" + } + } +} diff --git a/db/versions/10889-redMedeola/00-firstScript.sql b/db/versions/10889-redMedeola/00-firstScript.sql new file mode 100644 index 000000000..ecae0234f --- /dev/null +++ b/db/versions/10889-redMedeola/00-firstScript.sql @@ -0,0 +1,24 @@ +-- Place your SQL code here +CREATE TABLE IF NOT EXISTS `vn`.`mrwConfig` ( + `id` INT auto_increment NULL, + `url` varchar(100) NULL, + `user` varchar(100) NULL, + `password` varchar(100) NULL, + `franchiseCode` varchar(100) NULL, + `subscriberCode` varchar(100) NULL, + CONSTRAINT mrwConfig_pk PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb3 +COLLATE=utf8mb3_unicode_ci; + +ALTER TABLE `vn`.`packingSite` ADD `hasNewLabelMrwMethod` BOOL NULL; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES('MrwConfig', 'cancelShipment', 'WRITE', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`) + VALUES ('MrwConfig','createShipment','WRITE','ALLOW','ROLE','employee'); + + + diff --git a/loopback/locale/es.json b/loopback/locale/es.json index aea0c311c..b14358e85 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -345,4 +345,4 @@ "Cmr file does not exist": "El archivo del cmr no existe", "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", "No tickets to invoice": "No hay tickets para facturar" -} +} \ No newline at end of file