diff --git a/Jenkinsfile b/Jenkinsfile index 03ac84de3f..4a1f9ba546 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,13 +62,13 @@ pipeline { } } } - // stage('Backend') { - // steps { - // nodejs('node-v14') { - // sh 'gulp launchBackTest --ci' - // } - // } - // } + stage('Backend') { + steps { + nodejs('node-v14') { + sh 'npm run test:back:ci' + } + } + } } } stage('Build') { diff --git a/README.md b/README.md index 1e3ad5e9e7..f73a8551be 100644 --- a/README.md +++ b/README.md @@ -54,17 +54,17 @@ $ gulp docker For client-side unit tests run from project's root. ``` -$ jest +$ npm run test:front ``` For server-side unit tests run from project's root. ``` -$ gulp backTest +$ npm run test:back ``` For end-to-end tests run from project's root. ``` -$ gulp e2e +$ npm run test:e2e ``` ## Visual Studio Code extensions diff --git a/back/methods/account/specs/change-password.spec.js b/back/methods/account/specs/change-password.spec.js index 9f1130df52..17fadb3c69 100644 --- a/back/methods/account/specs/change-password.spec.js +++ b/back/methods/account/specs/change-password.spec.js @@ -1,9 +1,12 @@ -const app = require('vn-loopback/server/server'); +const {models} = require('vn-loopback/server/server'); describe('account changePassword()', () => { it('should throw an error when old password is wrong', async() => { - let req = app.models.Account.changePassword(null, 1, 'wrongOldPass', 'newPass'); + let err; + await models.Account.changePassword(1, 'wrongPassword', 'nightmare.9999') + .catch(error => err = error.sqlMessage); - await expectAsync(req).toBeRejected(); + expect(err).toBeDefined(); + expect(err).toEqual('Invalid password'); }); }); diff --git a/back/methods/collection/spec/newCollection.spec.js b/back/methods/collection/spec/newCollection.spec.js index 88d105b4b8..6abe73f8ea 100644 --- a/back/methods/collection/spec/newCollection.spec.js +++ b/back/methods/collection/spec/newCollection.spec.js @@ -1,8 +1,8 @@ const app = require('vn-loopback/server/server'); -// #3400 analizar que hacer con rutas de back colletion -xdescribe('newCollection()', () => { - it('return a new collection', async() => { +describe('newCollection()', () => { + it('should return a new collection', async() => { + pending('#3400 analizar que hacer con rutas de back collection'); let ctx = {req: {accessToken: {userId: 1106}}}; let response = await app.models.Collection.newCollection(ctx, 1, 1, 1); diff --git a/back/methods/dms/deleteTrashFiles.js b/back/methods/dms/deleteTrashFiles.js new file mode 100644 index 0000000000..9d16e9d81e --- /dev/null +++ b/back/methods/dms/deleteTrashFiles.js @@ -0,0 +1,57 @@ +const fs = require('fs-extra'); +const path = require('path'); + +module.exports = Self => { + Self.remoteMethod('deleteTrashFiles', { + description: 'Deletes files that have trash type', + accessType: 'WRITE', + returns: { + type: 'object', + root: true + }, + http: { + path: `/deleteTrashFiles`, + verb: 'POST' + } + }); + + Self.deleteTrashFiles = async(options) => { + const tx = await Self.beginTransaction({}); + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) + myOptions.transaction = tx; + + try { + const models = Self.app.models; + const DmsContainer = models.DmsContainer; + + const trashDmsType = await models.DmsType.findOne({ + where: {code: 'trash'} + }, myOptions); + + const dmsToDelete = await models.Dms.find({ + where: { + dmsTypeFk: trashDmsType.id + } + }, myOptions); + + for (let dms of dmsToDelete) { + const pathHash = DmsContainer.getHash(dms.id); + const dmsContainer = await DmsContainer.container(pathHash); + const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file); + await fs.unlink(dstFile); + await dms.destroy(myOptions); + } + if (tx) await tx.commit(); + + } catch (e) { + if (tx) await tx.rollback(); + + throw e; + } + }; +}; diff --git a/back/models/dms.js b/back/models/dms.js index 91291a0c2a..24c072f565 100644 --- a/back/models/dms.js +++ b/back/models/dms.js @@ -5,6 +5,7 @@ module.exports = Self => { require('../methods/dms/uploadFile')(Self); require('../methods/dms/removeFile')(Self); require('../methods/dms/updateFile')(Self); + require('../methods/dms/deleteTrashFiles')(Self); Self.checkRole = async function(ctx, id) { const models = Self.app.models; diff --git a/back/nodemonConfig.json b/back/nodemonConfig.json new file mode 100644 index 0000000000..a1c9ca84fc --- /dev/null +++ b/back/nodemonConfig.json @@ -0,0 +1,24 @@ +{ + "verbose": true, + "watch": [ + "back/**/*.js", + "modules/**/*.js" + ], + "ignore": [ + "modules/account/front/**/*", + "modules/claim/front/**/*", + "modules/client/front/**/*", + "modules/entry/front/**/*", + "modules/invoiceIn/front/**/*", + "modules/invoiceOut/front/**/*", + "modules/item/front/**/*", + "modules/monitor/front/**/*", + "modules/order/front/**/*", + "modules/route/front/**/*", + "modules/supplier/front/**/*", + "modules/ticket/front/**/*", + "modules/travel/front/**/*", + "modules/worker/front/**/*", + "modules/zone/front/**/*" + ] +} \ No newline at end of file diff --git a/back/tests.js b/back/tests.js index c715c96bf3..8099061771 100644 --- a/back/tests.js +++ b/back/tests.js @@ -1,4 +1,5 @@ -require('require-yaml'); +const Docker = require('../db/docker.js'); +let dataSources = require('../loopback/server/datasources.json'); process.on('warning', warning => { console.log(warning.name); @@ -6,34 +7,64 @@ process.on('warning', warning => { console.log(warning.stack); }); -let verbose = false; +async function test() { + let isCI = false; -if (process.argv[2] === '--v') - verbose = true; + if (process.argv[2] === 'ci') + isCI = true; -let Jasmine = require('jasmine'); -let jasmine = new Jasmine(); -let SpecReporter = require('jasmine-spec-reporter').SpecReporter; + const container = new Docker(); -let serviceSpecs = [ - `${__dirname}/**/*[sS]pec.js`, - `${__dirname}/../loopback/**/*[sS]pec.js`, - `${__dirname}/../modules/*/back/**/*.[sS]pec.js` -]; + await container.run(isCI); + dataSources = JSON.parse(JSON.stringify(dataSources)); -jasmine.loadConfig({ - spec_dir: '.', - spec_files: serviceSpecs, - helpers: [] -}); + Object.assign(dataSources.vn, { + host: container.dbConf.host, + port: container.dbConf.port + }); -jasmine.addReporter(new SpecReporter({ - spec: { - // displayStacktrace: 'summary', - displaySuccessful: verbose, - displayFailedSpec: true, - displaySpecDuration: true + const bootOptions = {dataSources}; + const app = require('vn-loopback/server/server'); + app.boot(bootOptions); + + const Jasmine = require('jasmine'); + const jasmine = new Jasmine(); + + const SpecReporter = require('jasmine-spec-reporter').SpecReporter; + jasmine.addReporter(new SpecReporter({ + spec: { + displaySuccessful: isCI, + displayPending: isCI + }, + summary: { + displayPending: false, + } + })); + + if (isCI) { + const JunitReporter = require('jasmine-reporters'); + jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); + + jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; } -})); -jasmine.execute(); + const backSpecs = [ + './back/**/*[sS]pec.js', + './loopback/**/*[sS]pec.js', + './modules/*/back/**/*.[sS]pec.js' + ]; + + jasmine.loadConfig({ + spec_dir: '.', + spec_files: backSpecs, + helpers: [], + }); + + jasmine.exitOnCompletion = false; + await jasmine.execute(); + if (app) await app.disconnect(); + if (container) await container.rm(); + console.log('app disconnected & container removed'); +} + +test(); diff --git a/db/changes/10451-april/00-aclExpeditionState.sql b/db/changes/10451-april/00-aclExpeditionState.sql new file mode 100644 index 0000000000..d26117bbff --- /dev/null +++ b/db/changes/10451-april/00-aclExpeditionState.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) +VALUES('ExpeditionState', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10451-april/00-aclExpense.sql b/db/changes/10451-april/00-aclExpense.sql new file mode 100644 index 0000000000..55ca8c3895 --- /dev/null +++ b/db/changes/10451-april/00-aclExpense.sql @@ -0,0 +1,5 @@ +INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES('Expense', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES('Expense', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/changes/10451-april/00-aclSupplierActivity.sql b/db/changes/10451-april/00-aclSupplierActivity.sql new file mode 100644 index 0000000000..bf73a15068 --- /dev/null +++ b/db/changes/10451-april/00-aclSupplierActivity.sql @@ -0,0 +1,5 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) +VALUES('SupplierActivity', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) +VALUES('SupplierActivity', '*', 'WRITE', 'ALLOW', 'ROLE', 'administrative'); diff --git a/db/changes/10460-mother/00-dmsForeignKey.sql b/db/changes/10460-mother/00-dmsForeignKey.sql new file mode 100644 index 0000000000..f44391a17c --- /dev/null +++ b/db/changes/10460-mother/00-dmsForeignKey.sql @@ -0,0 +1,8 @@ +ALTER TABLE vn.propertyDms DROP FOREIGN KEY propertyDms_FK; +ALTER TABLE vn.propertyDms ADD CONSTRAINT propertyDms_FK FOREIGN KEY (dmsFk) REFERENCES vn.dms(id) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE vn.clientDms DROP FOREIGN KEY clientDms_ibfk_2; +ALTER TABLE vn.clientDms ADD CONSTRAINT clientDms_ibfk_2 FOREIGN KEY (dmsFk) REFERENCES vn.dms(id) ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE vn.workerDocument DROP FOREIGN KEY workerDocument_ibfk_2; +ALTER TABLE vn.workerDocument ADD CONSTRAINT workerDocument_ibfk_2 FOREIGN KEY (document) REFERENCES vn.dms(id) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/changes/10460-mother/01-dmsType.sql b/db/changes/10460-mother/01-dmsType.sql new file mode 100644 index 0000000000..e82c086cfe --- /dev/null +++ b/db/changes/10460-mother/01-dmsType.sql @@ -0,0 +1,5 @@ +ALTER TABLE vn.dmsType ADD monthToDelete INT UNSIGNED DEFAULT NULL NULL; +ALTER TABLE vn.dmsType MODIFY COLUMN monthToDelete int(10) unsigned DEFAULT NULL NULL COMMENT 'Meses en el pasado para ir borrando registros, dejar a null para no borrarlos nunca'; +UPDATE vn.dmsType + SET monthToDelete=6 + WHERE id=20; diff --git a/db/changes/10460-mother/02-dmsTrigger.sql b/db/changes/10460-mother/02-dmsTrigger.sql new file mode 100644 index 0000000000..a92efcfab0 --- /dev/null +++ b/db/changes/10460-mother/02-dmsTrigger.sql @@ -0,0 +1,18 @@ + +DELIMITER $$ +$$ +CREATE TRIGGER dms_beforeDelete +BEFORE DELETE +ON dms FOR EACH ROW +BEGIN + DECLARE vCanNotBeDeleted INT; + SELECT COUNT(*) INTO vCanNotBeDeleted + FROM dmsType dt + WHERE NOT (code <=> 'trash') + AND dt.id = OLD.dmsTypeFk; + + IF vCanNotBeDeleted THEN + CALL util.throw('A dms can not be deleted'); + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10460-mother/03-clean.sql b/db/changes/10460-mother/03-clean.sql new file mode 100644 index 0000000000..13951394fd --- /dev/null +++ b/db/changes/10460-mother/03-clean.sql @@ -0,0 +1,175 @@ +DROP PROCEDURE IF EXISTS vn.clean; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`clean`() +BEGIN + DECLARE vDateShort DATETIME; + DECLARE vOneYearAgo DATE; + DECLARE vFourYearsAgo DATE; + DECLARE v18Month DATE; + DECLARE v26Month DATE; + DECLARE v3Month DATE; + DECLARE vTrashId varchar(15); + + SET vDateShort = TIMESTAMPADD(MONTH, -2, CURDATE()); + SET vOneYearAgo = TIMESTAMPADD(YEAR,-1,CURDATE()); + SET vFourYearsAgo = TIMESTAMPADD(YEAR,-4,CURDATE()); + SET v18Month = TIMESTAMPADD(MONTH, -18,CURDATE()); + SET v26Month = TIMESTAMPADD(MONTH, -26,CURDATE()); + SET v3Month = TIMESTAMPADD(MONTH, -3, CURDATE()); + + DELETE FROM ticketParking WHERE created < vDateShort; + DELETE FROM routesMonitor WHERE dated < vDateShort; + DELETE FROM workerTimeControlLog WHERE created < vDateShort; + DELETE FROM `message` WHERE sendDate < vDateShort; + DELETE FROM messageInbox WHERE sendDate < vDateShort; + DELETE FROM messageInbox WHERE sendDate < vDateShort; + DELETE FROM workerTimeControl WHERE timed < vFourYearsAgo; + DELETE FROM itemShelving WHERE created < CURDATE() AND visible = 0; + DELETE FROM ticketDown WHERE created < TIMESTAMPADD(DAY,-1,CURDATE()); + DELETE FROM entryLog WHERE creationDate < vDateShort; + DELETE IGNORE FROM expedition WHERE created < v26Month; + DELETE FROM sms WHERE created < v18Month; + DELETE FROM saleTracking WHERE created < vOneYearAgo; + DELETE tobs FROM ticketObservation tobs + JOIN ticket t ON tobs.ticketFk = t.id WHERE t.shipped < TIMESTAMPADD(YEAR,-2,CURDATE()); + DELETE sc.* FROM saleCloned sc JOIN sale s ON s.id = sc.saleClonedFk JOIN ticket t ON t.id = s.ticketFk WHERE t.shipped < vOneYearAgo; + DELETE FROM sharingCart where ended < vDateShort; + DELETE FROM sharingClient where ended < vDateShort; + DELETE tw.* FROM ticketWeekly tw + LEFT JOIN sale s ON s.ticketFk = tw.ticketFk WHERE s.itemFk IS NULL; + DELETE FROM claim WHERE ticketCreated < vFourYearsAgo; + DELETE FROM message WHERE sendDate < vDateShort; + -- Robert ubicacion anterior de trevelLog comentario para debug + DELETE sc FROM saleChecked sc + JOIN sale s ON sc.saleFk = s.id WHERE s.created < vDateShort; + DELETE FROM zoneEvent WHERE `type` = 'day' AND dated < v3Month; + DELETE bm + FROM buyMark bm + JOIN buy b ON b.id = bm.id + JOIN entry e ON e.id = b.entryFk + JOIN travel t ON t.id = e.travelFk + WHERE t.landed <= vDateShort; + DELETE FROM stowaway WHERE created < v3Month; + DELETE FROM vn.buy WHERE created < vDateShort AND entryFk = 9200; + DELETE FROM vn.itemShelvingLog WHERE created < vDateShort; + DELETE FROM vn.stockBuyed WHERE creationDate < vDateShort; + + + -- Equipos duplicados + DELETE w.* + FROM workerTeam w + JOIN (SELECT id, team, workerFk, COUNT(*) - 1 as duplicated + FROM workerTeam + GROUP BY team,workerFk + HAVING duplicated + ) d ON d.team = w.team AND d.workerFk = w.workerFk AND d.id != w.id; + + DELETE sc + FROM saleComponent sc + JOIN sale s ON s.id= sc.saleFk + JOIN ticket t ON t.id= s.ticketFk + WHERE t.shipped < v18Month; + + DELETE c + FROM vn.claim c + JOIN vn.claimState cs ON cs.id = c.claimStateFk + WHERE cs.description = "Anulado" AND + c.created < vDateShort; + DELETE + FROM vn.expeditionTruck + WHERE ETD < v3Month; + + -- borrar travels sin entradas + DROP TEMPORARY TABLE IF EXISTS tmp.thermographToDelete; + CREATE TEMPORARY TABLE tmp.thermographToDelete + SELECT th.id,th.dmsFk + FROM vn.travel t + LEFT JOIN vn.entry e ON e.travelFk = t.id + JOIN vn.travelThermograph th ON th.travelFk = t.id + WHERE t.shipped < TIMESTAMPADD(MONTH, -3, CURDATE()) AND e.travelFk IS NULL; + + SELECT dt.id into vTrashId + FROM vn.dmsType dt + WHERE dt.code = 'trash'; + + UPDATE tmp.thermographToDelete th + JOIN vn.dms d ON d.id = th.dmsFk + SET d.dmsTypeFk = vTrashId; + + DELETE th + FROM tmp.thermographToDelete tmp + JOIN vn.travelThermograph th ON th.id = tmp.id; + + DELETE t + FROM vn.travel t + LEFT JOIN vn.entry e ON e.travelFk = t.id + WHERE t.shipped < TIMESTAMPADD(MONTH, -3, CURDATE()) AND e.travelFk IS NULL; + + UPDATE dms d + JOIN dmsType dt ON dt.id = d.dmsTypeFk + SET d.dmsTypeFk = vTrashId + WHERE created < TIMESTAMPADD(MONTH, -dt.monthToDelete, CURDATE()); + + -- borrar entradas sin compras + DROP TEMPORARY TABLE IF EXISTS tmp.entryToDelete; + CREATE TEMPORARY TABLE tmp.entryToDelete + SELECT e.* + FROM vn.entry e + LEFT JOIN vn.buy b ON b.entryFk = e.id + JOIN vn.entryConfig ec ON e.id != ec.defaultEntry + WHERE e.dated < TIMESTAMPADD(MONTH, -3, CURDATE()) AND b.entryFK IS NULL; + + DELETE e + FROM vn.entry e + JOIN tmp.entryToDelete tmp ON tmp.id = e.id; + + -- borrar de route registros menores a 4 años + DROP TEMPORARY TABLE IF EXISTS tmp.routeToDelete; + CREATE TEMPORARY TABLE tmp.routeToDelete + SELECT * + FROM vn.route r + WHERE created < TIMESTAMPADD(YEAR,-4,CURDATE()); + + UPDATE tmp.routeToDelete tmp + JOIN vn.dms d ON d.id = tmp.gestdocFk + SET d.dmsTypeFk = vTrashId; + + DELETE r + FROM tmp.routeToDelete tmp + JOIN vn.route r ON r.id = tmp.id; + + -- borrar registros de dua y awb menores a 2 años + DROP TEMPORARY TABLE IF EXISTS tmp.duaToDelete; + CREATE TEMPORARY TABLE tmp.duaToDelete + SELECT * + FROM vn.dua + WHERE operated < TIMESTAMPADD(YEAR,-2,CURDATE()); + + UPDATE tmp.duaToDelete tm + JOIN vn.dms d ON d.id = tm.gestdocFk + SET d.dmsTypeFk = vTrashId; + + DELETE d + FROM tmp.duaToDelete tmp + JOIN vn.dua d ON d.id = tmp.id; + + DELETE FROM vn.awb WHERE created < TIMESTAMPADD(YEAR,-2,CURDATE()); + + -- Borra los ficheros gestDoc + INSERT INTO vn.printServerQueue(priorityFk, labelReportFk)VALUES(1,11); + + -- Borra los registros de collection y ticketcollection + DELETE FROM vn.collection WHERE created < vDateShort; + + DROP TEMPORARY TABLE IF EXISTS tmp.thermographToDelete; + DROP TEMPORARY TABLE IF EXISTS tmp.entryToDelete; + DROP TEMPORARY TABLE IF EXISTS tmp.duaToDelete; + + DELETE FROM travelLog WHERE creationDate < v3Month; + + CALL shelving_clean; + +END$$ +DELIMITER ; diff --git a/db/changes/10460-mother/04-acl.sql b/db/changes/10460-mother/04-acl.sql new file mode 100644 index 0000000000..c576bf1b0a --- /dev/null +++ b/db/changes/10460-mother/04-acl.sql @@ -0,0 +1,2 @@ +INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId) + VALUES ('Dms','deleteTrashFiles','WRITE','ALLOW','ROLE','employee') \ No newline at end of file diff --git a/db/changes/10460-mothersDay/delete.keep b/db/changes/10460-mothersDay/delete.keep deleted file mode 100644 index 0e7498f402..0000000000 --- a/db/changes/10460-mothersDay/delete.keep +++ /dev/null @@ -1 +0,0 @@ -Delete file \ No newline at end of file diff --git a/db/docker.js b/db/docker.js index ea9fe8ed16..34026f85fe 100644 --- a/db/docker.js +++ b/db/docker.js @@ -24,7 +24,10 @@ module.exports = class Docker { let d = new Date(); let pad = v => v < 10 ? '0' + v : v; let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; + + log('Building container image...'); await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`); + log('Image built.'); let dockerArgs; @@ -39,6 +42,7 @@ module.exports = class Docker { let runChown = process.platform != 'linux'; + log('Starting container...'); const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`); this.id = container.stdout.trim(); @@ -158,6 +162,7 @@ module.exports = class Docker { return reject(new Error('Docker exited, please see the docker logs for more info')); let conn = mysql.createConnection(myConf); + conn.on('error', () => {}); conn.connect(err => { conn.destroy(); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 20de8844b5..c329e4c6e7 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -854,18 +854,35 @@ INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPack ('cc', 1640038.00, 56.00, 220.00, 128.00, 1, CURDATE(), 15, 90.00), ('pallet 100', 2745600.00, 100.00, 220.00, 120.00, 1, CURDATE(), 16, 0.00); -INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`, `externalId`, `packagingFk`) +INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`) VALUES - (1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 15, 1, 1, 18, 'UR9000006041', 94), - (2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 16, 2, 1, 18, 'UR9000006041', 94), - (3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 3, 1, 18, 'UR9000006041', 94), - (4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 4, 1, 18, 'UR9000006041', 94), - (5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94), - (6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL, 1, 1, 18, NULL, 94), - (7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), NULL, 1, 1, 18, NULL, 94), - (8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), NULL, 1, 1, 18, NULL, 94), - (9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94), - (10, 7, 7, 71, NOW(), NULL, 1, 1, 18, NULL, 94); + (1, 'En reparto', 'ON DELIVERY'), + (2, 'Entregada', 'DELIVERED'), + (3, 'Perdida', 'LOST'); + + +INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `checked`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`) + VALUES + (1, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 15, 1, 1, 18, 'UR9000006041', 94, 1), + (2, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 16, 2, 1, 18, 'UR9000006041', 94, 1), + (3, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 3, 1, 18, 'UR9000006041', 94, 2), + (4, 1, 1, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 4, 1, 18, 'UR9000006041', 94, 2), + (5, 1, 2, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94, 3), + (6, 7, 3, 71, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), NULL, 1, 1, 18, NULL, 94, 3), + (7, 2, 4, 71, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), NULL, 1, 1, 18, NULL, 94, NULL), + (8, 3, 5, 71, DATE_ADD(CURDATE(), INTERVAL -4 MONTH), NULL, 1, 1, 18, NULL, 94, 1), + (9, 3, 6, 71, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), NULL, 1, 1, 18, NULL, 94, 2), + (10, 7, 7, 71, NOW(), NULL, 1, 1, 18, NULL, 94, 3); + + +INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`) + VALUES + (1, CURDATE(), 1, 1, 1), + (2, CURDATE(), 2, 1, 1), + (3, CURDATE(), 3, 1, 1), + (4, CURDATE(), 3, 2, 1106), + (5, CURDATE(), 5, 1, 1106), + (6, CURDATE(), 5, 3, 1106); INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`, `created`, `pvp`) VALUES @@ -1296,11 +1313,11 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr (5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'), (6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'); -INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`) +INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`, `healthRegister`) VALUES - (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1), - (2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1), - (442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants', 1); + (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1, '400664487V'), + (2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1, '400664487V'), + (442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'complements', 1, '400664487V'); INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 2ecb739609..29176489ce 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -982,8 +982,8 @@ export default { save: 'vn-invoice-in-basic-data button[type=submit]' }, invoiceInTax: { - addTaxButton: 'vn-invoice-in-tax vn-icon-button[icon="add_circle"]', - thirdExpence: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.expenseFk"]', + addTaxButton: 'vn-invoice-in-tax vn-icon-button[vn-tooltip="Add tax"]', + thirdExpense: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.expenseFk"]', thirdTaxableBase: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-input-number[ng-model="invoiceInTax.taxableBase"]', thirdTaxType: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.taxTypeSageFk"]', thirdTransactionType: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.transactionTypeSageFk"]', diff --git a/e2e/helpers/tests.js b/e2e/helpers/tests.js new file mode 100644 index 0000000000..aac9963dd5 --- /dev/null +++ b/e2e/helpers/tests.js @@ -0,0 +1,87 @@ +require('@babel/register')({presets: ['@babel/env']}); +require('core-js/stable'); +require('regenerator-runtime/runtime'); + +const axios = require('axios'); +const Docker = require('../../db/docker.js'); +const e2eConfig = require('./config.js'); +const log = require('fancy-log'); + +process.on('warning', warning => { + console.log(warning.name); + console.log(warning.message); + console.log(warning.stack); +}); + +async function test() { + if (process.argv[2] === 'show') + process.env.E2E_SHOW = true; + + const container = new Docker('salix-db'); + + await container.run(); + + const Jasmine = require('jasmine'); + const jasmine = new Jasmine(); + + const specFiles = [ + `./e2e/paths/01*/*[sS]pec.js`, + `./e2e/paths/02*/*[sS]pec.js`, + `./e2e/paths/03*/*[sS]pec.js`, + `./e2e/paths/04*/*[sS]pec.js`, + `./e2e/paths/05*/*[sS]pec.js`, + `./e2e/paths/06*/*[sS]pec.js`, + `./e2e/paths/07*/*[sS]pec.js`, + `./e2e/paths/08*/*[sS]pec.js`, + `./e2e/paths/09*/*[sS]pec.js`, + `./e2e/paths/10*/*[sS]pec.js`, + `./e2e/paths/11*/*[sS]pec.js`, + `./e2e/paths/12*/*[sS]pec.js`, + `./e2e/paths/13*/*[sS]pec.js`, + `./e2e/paths/**/*[sS]pec.js` + ]; + + jasmine.loadConfig({ + spec_dir: '.', + spec_files: specFiles, + helpers: [], + random: false, + }); + + await backendStatus(); + + jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; + await jasmine.execute(); +} + +async function backendStatus() { + log('Awaiting backend connection...'); + + const milliseconds = 1000; + const maxAttempts = 10; + + return new Promise(resolve => { + let timer; + let attempts = 1; + timer = setInterval(async() => { + try { + attempts++; + const url = `${e2eConfig.url}/api/Applications/status`; + const {data} = await axios.get(url); + + if (data == true) { + clearInterval(timer); + log('Backend connection stablished!'); + resolve(attempts); + } + } catch (error) { + if (error && attempts >= maxAttempts) { + log('Could not connect to backend'); + process.exit(); + } + } + }, milliseconds); + }); +} + +test(); diff --git a/e2e/paths/09-invoice-in/04_tax.spec.js b/e2e/paths/09-invoice-in/04_tax.spec.js index 364a25d7e4..b1dbe20088 100644 --- a/e2e/paths/09-invoice-in/04_tax.spec.js +++ b/e2e/paths/09-invoice-in/04_tax.spec.js @@ -19,7 +19,7 @@ describe('InvoiceIn tax path', () => { it('should add a new tax', async() => { await page.waitToClick(selectors.invoiceInTax.addTaxButton); - await page.autocompleteSearch(selectors.invoiceInTax.thirdExpence, '6210000567'); + await page.autocompleteSearch(selectors.invoiceInTax.thirdExpense, '6210000567'); await page.write(selectors.invoiceInTax.thirdTaxableBase, '100'); await page.autocompleteSearch(selectors.invoiceInTax.thirdTaxType, '6'); await page.autocompleteSearch(selectors.invoiceInTax.thirdTransactionType, 'Operaciones exentas'); @@ -37,9 +37,9 @@ describe('InvoiceIn tax path', () => { expect(result).toEqual('Taxable base €1,323.16'); }); - it('should navigate back to the tax section and check the reciently added line contains the expected expense', async() => { + it('should navigate back to tax section, check the reciently added line contains the expected expense', async() => { await page.accessToSection('invoiceIn.card.tax'); - const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdExpence, 'value'); + const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdExpense, 'value'); expect(result).toEqual('6210000567: Alquiler VNH'); }); diff --git a/gulpfile.js b/gulpfile.js index 102a8a0bf3..d7e7d8e869 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,8 +3,6 @@ const gulp = require('gulp'); const PluginError = require('plugin-error'); const argv = require('minimist')(process.argv.slice(2)); const log = require('fancy-log'); -const got = require('got'); -const e2eConfig = require('./e2e/helpers/config.js'); const Docker = require('./db/docker.js'); // Configuration @@ -67,187 +65,6 @@ back.description = `Starts backend and database service`; const defaultTask = gulp.parallel(front, back); defaultTask.description = `Starts all application services`; - -// Backend tests - Private method - -async function launchBackTest(done) { - let err; - let dataSources = require('./loopback/server/datasources.json'); - - const container = new Docker(); - await container.run(argv.ci); - - dataSources = JSON.parse(JSON.stringify(dataSources)); - - Object.assign(dataSources.vn, { - host: container.dbConf.host, - port: container.dbConf.port - }); - - let bootOptions = {dataSources}; - - let app = require(`./loopback/server/server`); - - try { - app.boot(bootOptions); - - await new Promise((resolve, reject) => { - const jasmine = require('gulp-jasmine'); - - const options = { - verbose: false, - includeStackTrace: false, - errorOnFail: false, - timeout: 5000, - config: {} - }; - - if (argv.ci) { - const reporters = require('jasmine-reporters'); - options.reporter = new reporters.JUnitXmlReporter(); - } - - let backSpecFiles = [ - 'back/**/*.spec.js', - 'loopback/**/*.spec.js', - 'modules/*/back/**/*.spec.js' - ]; - - gulp.src(backSpecFiles) - .pipe(jasmine(options)) - .on('end', resolve) - .on('error', reject) - .resume(); - }); - } catch (e) { - err = e; - } - await app.disconnect(); - await container.rm(); - done(); - if (err) - throw err; -} -launchBackTest.description = ` - Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`; - -// Backend tests - -function backTest(done) { - const nodemon = require('gulp-nodemon'); - - nodemon({ - exec: ['node --tls-min-v1.0 ./node_modules/gulp/bin/gulp.js'], - args: ['launchBackTest'], - watch: backSources, - done: done - }); -} -backTest.description = `Watches for changes in modules to execute backTest task`; - -// End to end tests -function e2eSingleRun() { - require('@babel/register')({presets: ['@babel/env']}); - require('core-js/stable'); - require('regenerator-runtime/runtime'); - - const jasmine = require('gulp-jasmine'); - const SpecReporter = require('jasmine-spec-reporter').SpecReporter; - - if (argv.show || argv.s) - process.env.E2E_SHOW = true; - - const specFiles = [ - `${__dirname}/e2e/paths/01*/*[sS]pec.js`, - `${__dirname}/e2e/paths/02*/*[sS]pec.js`, - `${__dirname}/e2e/paths/03*/*[sS]pec.js`, - `${__dirname}/e2e/paths/04*/*[sS]pec.js`, - `${__dirname}/e2e/paths/05*/*[sS]pec.js`, - `${__dirname}/e2e/paths/06*/*[sS]pec.js`, - `${__dirname}/e2e/paths/07*/*[sS]pec.js`, - `${__dirname}/e2e/paths/08*/*[sS]pec.js`, - `${__dirname}/e2e/paths/09*/*[sS]pec.js`, - `${__dirname}/e2e/paths/10*/*[sS]pec.js`, - `${__dirname}/e2e/paths/11*/*[sS]pec.js`, - `${__dirname}/e2e/paths/12*/*[sS]pec.js`, - `${__dirname}/e2e/paths/13*/*[sS]pec.js`, - `${__dirname}/e2e/paths/**/*[sS]pec.js` - ]; - - return gulp.src(specFiles).pipe(jasmine({ - errorOnFail: false, - timeout: 30000, - config: { - random: false, - // TODO: Waiting for this option to be implemented - // https://github.com/jasmine/jasmine/issues/1533 - stopSpecOnExpectationFailure: false - }, - reporter: [ - new SpecReporter({ - spec: { - displayStacktrace: 'none', - displaySuccessful: true, - displayFailedSpec: true, - displaySpecDuration: true, - }, - summary: { - displayStacktrace: 'raw', - displayPending: false - }, - colors: { - enabled: true, - successful: 'brightGreen', - failed: 'brightRed' - }, - // stacktrace: { - // filter: stacktrace => { - // const lines = stacktrace.split('\n'); - // const filtered = []; - // for (let i = 1; i < lines.length; i++) { - // if (/e2e\/paths/.test(lines[i])) - // filtered.push(lines[i]); - // } - // return filtered.join('\n'); - // } - // } - }) - ] - })); -} - -e2e = gulp.series(docker, async function isBackendReady() { - const attempts = await backendStatus(); - log(`Backend ready after ${attempts} attempt(s)`); - - return attempts; -}, e2eSingleRun); -e2e.description = `Restarts database and runs the e2e tests`; - -async function backendStatus() { - const milliseconds = 250; - return new Promise(resolve => { - let timer; - let attempts = 1; - timer = setInterval(async() => { - try { - const url = `${e2eConfig.url}/api/Applications/status`; - const {body} = await got.get(url); - - if (body == 'true') { - clearInterval(timer); - resolve(attempts); - } else - attempts++; - } catch (error) { - if (error || attempts > 100) // 250ms * 100 => 25s timeout - throw new Error('Could not connect to backend'); - } - }, milliseconds); - }); -} -backendStatus.description = `Performs a simple requests to check the backend status`; - function install() { const install = require('gulp-install'); const print = require('gulp-print'); @@ -431,9 +248,6 @@ module.exports = { back, backOnly, backWatch, - backTest, - launchBackTest, - e2e, i, install, build, @@ -444,6 +258,5 @@ module.exports = { locales, localesRoutes, watch, - docker, - backendStatus, + docker }; diff --git a/jest.front.config.js b/jest.front.config.js index dbea13950e..a03c61d114 100644 --- a/jest.front.config.js +++ b/jest.front.config.js @@ -47,5 +47,6 @@ module.exports = { '^.+\\.js?$': 'babel-jest', '^.+\\.html$': 'html-loader-jest' }, + reporters: ['default', 'jest-junit'] }; diff --git a/modules/client/back/methods/client/specs/updatePortfolio.spec.js b/modules/client/back/methods/client/specs/updatePortfolio.spec.js index 4830156fc4..f56555c08c 100644 --- a/modules/client/back/methods/client/specs/updatePortfolio.spec.js +++ b/modules/client/back/methods/client/specs/updatePortfolio.spec.js @@ -12,11 +12,13 @@ describe('Client updatePortfolio', () => { const expectedResult = 841.63; - await models.Client.rawSql(`UPDATE vn.client SET salesPersonFk = ${salesPersonId} WHERE id = ${clientId}; `); + const clientQuery = `UPDATE vn.client SET salesPersonFk = ${salesPersonId} WHERE id = ${clientId}; `; + await models.Client.rawSql(clientQuery); await models.Client.updatePortfolio(); - let [salesPerson] = await models.Client.rawSql(`SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `, null, options); + const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `; + const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options); expect(salesPerson.portfolioWeight).toEqual(expectedResult); @@ -26,8 +28,9 @@ describe('Client updatePortfolio', () => { throw e; } }); - // task 3817 - xit('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => { + + it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => { + pending('task 3817'); const salesPersonId = 19; const tx = await models.Client.beginTransaction({}); @@ -40,7 +43,8 @@ describe('Client updatePortfolio', () => { await models.Client.updatePortfolio(); - let [salesPerson] = await models.Client.rawSql(`SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `, null, options); + const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `; + const [salesPerson] = await models.Client.rawSql(portfolioQuery, null, options); expect(salesPerson.portfolioWeight).toEqual(expectedResult); diff --git a/modules/client/back/models/specs/address.spec.js b/modules/client/back/models/specs/address.spec.js index 81af6ee283..f0b421d351 100644 --- a/modules/client/back/models/specs/address.spec.js +++ b/modules/client/back/models/specs/address.spec.js @@ -14,7 +14,7 @@ describe('loopback model address', () => { } }; - beforeEach(() => { + beforeAll(() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); diff --git a/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js index edfdac9884..41971a64c0 100644 --- a/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js +++ b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js @@ -1,5 +1,6 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); +const activeCtx = {accessToken: {userId: 9}}; describe('entry importBuysPreview()', () => { const entryId = 1; diff --git a/modules/invoiceIn/front/dueDay/index.html b/modules/invoiceIn/front/dueDay/index.html index 579ef36098..1a1935e728 100644 --- a/modules/invoiceIn/front/dueDay/index.html +++ b/modules/invoiceIn/front/dueDay/index.html @@ -25,6 +25,9 @@ ng-model="invoiceInDueDay.bankFk" url="Banks" show-field="bank" + select-fields="['id','bank']" + order="id" + search-function="$ctrl.bankSearchFunc($search)" rule> {{id}}: {{bank}} diff --git a/modules/invoiceIn/front/dueDay/index.js b/modules/invoiceIn/front/dueDay/index.js index 22b697f7e2..3cc1c81e8a 100644 --- a/modules/invoiceIn/front/dueDay/index.js +++ b/modules/invoiceIn/front/dueDay/index.js @@ -17,6 +17,12 @@ class Controller extends Section { this.card.reload(); }); } + + bankSearchFunc($search) { + return /^\d+$/.test($search) + ? {id: $search} + : {bank: {like: '%' + $search + '%'}}; + } } ngModule.vnComponent('vnInvoiceInDueDay', { diff --git a/modules/invoiceIn/front/tax/index.html b/modules/invoiceIn/front/tax/index.html index c495d44d2f..acc9cf4924 100644 --- a/modules/invoiceIn/front/tax/index.html +++ b/modules/invoiceIn/front/tax/index.html @@ -33,6 +33,13 @@ show-field="id" rule> {{id}}: {{name}} + + + + - \ No newline at end of file + + + + + +
+
{{$ctrl.$t('New expense')}}
+ + + + + + + + + + +
+
+ + + + +
\ No newline at end of file diff --git a/modules/invoiceIn/front/tax/index.js b/modules/invoiceIn/front/tax/index.js index 53cfc55980..d05a77f29f 100644 --- a/modules/invoiceIn/front/tax/index.js +++ b/modules/invoiceIn/front/tax/index.js @@ -1,7 +1,12 @@ import ngModule from '../module'; import Section from 'salix/components/section'; +import UserError from 'core/lib/user-error'; class Controller extends Section { + constructor($element, $, vnWeekDays) { + super($element, $); + this.expense = {}; + } taxRate(invoiceInTax, taxRateSelection) { const taxTypeSage = taxRateSelection && taxRateSelection.rate; const taxableBase = invoiceInTax && invoiceInTax.taxableBase; @@ -26,6 +31,27 @@ class Controller extends Section { this.card.reload(); }); } + + onResponse() { + try { + if (!this.expense.code) + throw new Error(`The code can't be empty`); + if (!this.expense.description) + throw new UserError(`The description can't be empty`); + + const data = [{ + id: this.expense.code, + isWithheld: this.expense.isWithheld, + name: this.expense.description + }]; + + this.$http.post(`Expenses`, data) .then(() => { + this.vnApp.showSuccess(this.$t('Expense saved!')); + }); + } catch (e) { + this.vnApp.showError(this.$t(e.message)); + } + } } ngModule.vnComponent('vnInvoiceInTax', { diff --git a/modules/invoiceIn/front/tax/index.spec.js b/modules/invoiceIn/front/tax/index.spec.js index 20d5d40d86..c62ada9ca8 100644 --- a/modules/invoiceIn/front/tax/index.spec.js +++ b/modules/invoiceIn/front/tax/index.spec.js @@ -1,16 +1,19 @@ import './index.js'; import watcher from 'core/mocks/watcher'; import crudModel from 'core/mocks/crud-model'; +const UserError = require('vn-loopback/util/user-error'); describe('InvoiceIn', () => { describe('Component tax', () => { let controller; let $scope; let vnApp; + let $httpBackend; beforeEach(ngModule('invoiceIn')); - beforeEach(inject(($componentController, $rootScope, _vnApp_) => { + beforeEach(inject(($componentController, $rootScope, _vnApp_, _$httpBackend_) => { + $httpBackend = _$httpBackend_; vnApp = _vnApp_; jest.spyOn(vnApp, 'showError'); $scope = $rootScope.$new(); @@ -19,6 +22,7 @@ describe('InvoiceIn', () => { const $element = angular.element(''); controller = $componentController('vnInvoiceInTax', {$element, $scope}); + controller.$.model = crudModel; controller.invoiceIn = {id: 1}; })); @@ -55,5 +59,56 @@ describe('InvoiceIn', () => { expect(controller.card.reload).toHaveBeenCalledWith(); }); }); + + describe('onResponse()', () => { + it('should return success message', () => { + controller.expense = { + code: 7050000005, + isWithheld: 0, + description: 'Test' + }; + + const data = [{ + id: controller.expense.code, + isWithheld: controller.expense.isWithheld, + name: controller.expense.description + }]; + + jest.spyOn(controller.vnApp, 'showSuccess'); + $httpBackend.expect('POST', `Expenses`, data).respond(); + + controller.onResponse(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Expense saved!'); + }); + + it('should return an error if code is empty', () => { + controller.expense = { + code: null, + isWithheld: 0, + description: 'Test' + }; + + jest.spyOn(controller.vnApp, 'showError'); + controller.onResponse(); + + expect(controller.vnApp.showError).toHaveBeenCalledWith(`The code can't be empty`); + }); + + it('should return an error if description is empty', () => { + controller.expense = { + code: 7050000005, + isWithheld: 0, + description: null + }; + + jest.spyOn(controller.vnApp, 'showError'); + controller.onResponse(); + + expect(controller.vnApp.showError).toHaveBeenCalledWith(`The description can't be empty`); + }); + }); }); }); + diff --git a/modules/invoiceIn/front/tax/locale/es.yml b/modules/invoiceIn/front/tax/locale/es.yml new file mode 100644 index 0000000000..3ff68ea402 --- /dev/null +++ b/modules/invoiceIn/front/tax/locale/es.yml @@ -0,0 +1,7 @@ +Create expense: Crear gasto +New expense: Nuevo gasto +It's a withholding: Es una retención +The fields can't be empty: Los campos no pueden estar vacíos +The code can't be empty: El código no puede estar vacío +The description can't be empty: La descripción no puede estar vacía +Expense saved!: Gasto guardado! \ No newline at end of file diff --git a/modules/item/back/models/expense.json b/modules/item/back/models/expense.json index b349e9dcf4..368876fbed 100644 --- a/modules/item/back/models/expense.json +++ b/modules/item/back/models/expense.json @@ -17,9 +17,6 @@ }, "isWithheld": { "type": "number" - }, - "taxTypeFk": { - "type": "number" } }, "relations": { @@ -28,13 +25,5 @@ "model": "TaxType", "foreignKey": "taxTypeFk" } - }, - "acls": [ - { - "accessType": "READ", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - } - ] + } } \ No newline at end of file diff --git a/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js b/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js index 81686144d1..ad4613caf6 100644 --- a/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js +++ b/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js @@ -1,7 +1,6 @@ const models = require('vn-loopback/server/server').models; -// Include after #3638 export database -xdescribe('AgencyTerm createInvoiceIn()', () => { +describe('AgencyTerm createInvoiceIn()', () => { const rows = [ { routeFk: 2, @@ -17,6 +16,7 @@ xdescribe('AgencyTerm createInvoiceIn()', () => { ]; it('should make an invoiceIn', async() => { + pending('Include after #3638 export database'); const tx = await models.AgencyTerm.beginTransaction({}); const options = {transaction: tx}; @@ -32,8 +32,12 @@ xdescribe('AgencyTerm createInvoiceIn()', () => { await models.AgencyTerm.createInvoiceIn(rows, dms, options); const [newInvoiceIn] = await models.InvoiceIn.rawSql('SELECT MAX(id) id FROM invoiceIn', null, options); - const [newInvoiceInDueDay] = await models.InvoiceInDueDay.rawSql('SELECT MAX(id) id FROM invoiceInDueDay', null, options); - const [newInvoiceInTax] = await models.InvoiceInTax.rawSql('SELECT MAX(id) id FROM invoiceInTax', null, options); + + const dueDayQuery = 'SELECT MAX(id) id FROM invoiceInDueDay'; + const [newInvoiceInDueDay] = await models.InvoiceInDueDay.rawSql(dueDayQuery, null, options); + + const taxQuery = 'SELECT MAX(id) id FROM invoiceInTax'; + const [newInvoiceInTax] = await models.InvoiceInTax.rawSql(taxQuery, null, options); expect(newInvoiceIn.id).toBeGreaterThan(oldInvoiceIn.id); expect(newInvoiceInDueDay.id).toBeGreaterThan(oldInvoiceInDueDay.id); diff --git a/modules/route/back/methods/route/specs/guessPriority.spec.js b/modules/route/back/methods/route/specs/guessPriority.spec.js index 892324acfb..902647ba1f 100644 --- a/modules/route/back/methods/route/specs/guessPriority.spec.js +++ b/modules/route/back/methods/route/specs/guessPriority.spec.js @@ -1,9 +1,21 @@ const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); describe('route guessPriority()', () => { const targetRouteId = 7; let routeTicketsToRestore; + const activeCtx = { + accessToken: {userId: 9}, + __: () => {} + }; + + beforeAll(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + afterAll(async() => { let restoreFixtures = []; routeTicketsToRestore.forEach(ticket => { @@ -12,12 +24,9 @@ describe('route guessPriority()', () => { await Promise.all(restoreFixtures); }); - it('should call guessPriority() and then check the tickets in the target route now have their priorities defined', async() => { + it('should call guessPriority() then check all tickets in that route have their priorities defined', async() => { const ctx = { - req: { - accessToken: {userId: 9}, - __: () => {} - }, + req: activeCtx }; routeTicketsToRestore = await app.models.Ticket.find({where: {routeFk: targetRouteId}}); diff --git a/modules/supplier/back/methods/supplier/getSummary.js b/modules/supplier/back/methods/supplier/getSummary.js index c29a2a058e..bf3fa56f58 100644 --- a/modules/supplier/back/methods/supplier/getSummary.js +++ b/modules/supplier/back/methods/supplier/getSummary.js @@ -41,7 +41,9 @@ module.exports = Self => { 'sageTaxTypeFk', 'sageTransactionTypeFk', 'sageWithholdingFk', - 'workerFk' + 'workerFk', + 'supplierActivityFk', + 'healthRegister' ], include: [ { @@ -98,6 +100,12 @@ module.exports = Self => { } } }, + { + relation: 'supplierActivity', + scope: { + fields: ['code', 'name'] + } + } ] }; diff --git a/modules/supplier/back/methods/supplier/specs/updateFiscalData.spec.js b/modules/supplier/back/methods/supplier/specs/updateFiscalData.spec.js index 0eec549263..a47e547d19 100644 --- a/modules/supplier/back/methods/supplier/specs/updateFiscalData.spec.js +++ b/modules/supplier/back/methods/supplier/specs/updateFiscalData.spec.js @@ -66,6 +66,8 @@ describe('Supplier updateFiscalData', () => { city: 'VALENCIA', provinceFk: 2, countryFk: 1, + supplierActivityFk: 'animals', + healthRegister: '400664487H' }; const result = await app.models.Supplier.updateFiscalData(ctx, supplierId); @@ -80,6 +82,8 @@ describe('Supplier updateFiscalData', () => { expect(result.city).toEqual('VALENCIA'); expect(result.provinceFk).toEqual(2); expect(result.countryFk).toEqual(1); + expect(result.supplierActivityFk).toEqual('animals'); + expect(result.healthRegister).toEqual('400664487H'); // Restores ctx.args = defaultData; diff --git a/modules/supplier/back/methods/supplier/updateFiscalData.js b/modules/supplier/back/methods/supplier/updateFiscalData.js index daa602af05..4604b3f910 100644 --- a/modules/supplier/back/methods/supplier/updateFiscalData.js +++ b/modules/supplier/back/methods/supplier/updateFiscalData.js @@ -56,6 +56,14 @@ module.exports = Self => { { arg: 'countryFk', type: 'any' + }, + { + arg: 'supplierActivityFk', + type: 'string' + }, + { + arg: 'healthRegister', + type: 'string' }], returns: { arg: 'res', diff --git a/modules/supplier/back/model-config.json b/modules/supplier/back/model-config.json index 7c1bba1ec6..dbc387ed20 100644 --- a/modules/supplier/back/model-config.json +++ b/modules/supplier/back/model-config.json @@ -11,6 +11,9 @@ "SupplierAccount": { "dataSource": "vn" }, + "SupplierActivity": { + "dataSource": "vn" + }, "SupplierAgencyTerm": { "dataSource": "vn" }, diff --git a/modules/supplier/back/models/supplier-activity.json b/modules/supplier/back/models/supplier-activity.json new file mode 100644 index 0000000000..7a0b9a5379 --- /dev/null +++ b/modules/supplier/back/models/supplier-activity.json @@ -0,0 +1,18 @@ +{ + "name": "SupplierActivity", + "base": "VnModel", + "options": { + "mysql": { + "table": "supplierActivity" + } + }, + "properties": { + "code": { + "type": "string", + "id": true + }, + "name": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index 8b4b8f26f0..b27073ca5c 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -101,6 +101,12 @@ }, "isPayMethodChecked": { "type": "boolean" + }, + "supplierActivityFk": { + "type": "string" + }, + "healthRegister": { + "type": "string" } }, "relations": { @@ -159,6 +165,11 @@ "type": "hasMany", "model": "SupplierAddress", "foreignKey": "supplierFk" + }, + "supplierActivity": { + "type": "belongsTo", + "model": "SupplierActivity", + "foreignKey": "supplierActivityFk" } } } \ No newline at end of file diff --git a/modules/supplier/front/fiscal-data/index.html b/modules/supplier/front/fiscal-data/index.html index 4f34528f2d..77a5cce4ea 100644 --- a/modules/supplier/front/fiscal-data/index.html +++ b/modules/supplier/front/fiscal-data/index.html @@ -30,6 +30,11 @@ data="sageWithholdings" order="withholding"> + +
@@ -87,6 +92,22 @@ {{id}}: {{transaction}} + + + + + + + + + diff --git a/modules/supplier/front/summary/locale/es.yml b/modules/supplier/front/summary/locale/es.yml index 512b75f9d4..35291e579d 100644 --- a/modules/supplier/front/summary/locale/es.yml +++ b/modules/supplier/front/summary/locale/es.yml @@ -7,4 +7,6 @@ Sage tax type: Tipo de impuesto Sage Sage transaction type: Tipo de transacción Sage Sage withholding: Retencion Sage Go to the supplier: Ir al proveedor -Responsible: Responsable \ No newline at end of file +Responsible: Responsable +Supplier activity: Actividad proveedor +Healt register: Pasaporte sanitario \ No newline at end of file diff --git a/modules/ticket/back/methods/expedition-state/filter.js b/modules/ticket/back/methods/expedition-state/filter.js new file mode 100644 index 0000000000..1483780f70 --- /dev/null +++ b/modules/ticket/back/methods/expedition-state/filter.js @@ -0,0 +1,41 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethod('filter', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + http: {source: 'query'}, + }, + ], + returns: { + type: ['object'], + root: true, + }, + http: { + path: `/filter`, + verb: 'GET', + }, + }); + + Self.filter = async(filter, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const stmt = new ParameterizedSQL( + `SELECT es.created, u.name, u.id workerFk, est.description state + FROM vn.expeditionState es + JOIN vn.expeditionStateType est ON est.id = es.typeFk + JOIN account.user u ON u.id = es.userFk + `); + stmt.merge(Self.buildSuffix(filter, 'es')); + + return Self.rawStmt(stmt, myOptions); + }; +}; diff --git a/modules/ticket/back/methods/expedition-state/specs/filter.spec.js b/modules/ticket/back/methods/expedition-state/specs/filter.spec.js new file mode 100644 index 0000000000..f144606ebd --- /dev/null +++ b/modules/ticket/back/methods/expedition-state/specs/filter.spec.js @@ -0,0 +1,21 @@ +const models = require('vn-loopback/server/server').models; + +describe('expeditionState filter()', () => { + it('should return the expedition states matching the filter', async() => { + const tx = await models.ExpeditionState.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const filter = {where: {expeditionFk: 5}}; + const response = await models.ExpeditionState.filter(filter, options); + + expect(response.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/expedition/filter.js b/modules/ticket/back/methods/expedition/filter.js index 5eb7510b73..538e199388 100644 --- a/modules/ticket/back/methods/expedition/filter.js +++ b/modules/ticket/back/methods/expedition/filter.js @@ -47,9 +47,10 @@ module.exports = Self => { e.packagingFk, es.workerFk expeditionScanWorkerFk, su.name scannerUserName, - es.scanned - FROM - vn.expedition e + es.scanned, + est.description state + FROM vn.expedition e + LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk LEFT JOIN vn.item i2 ON i2.id = e.itemFk INNER JOIN vn.item i1 ON i1.id = e.isBox LEFT JOIN vn.packaging p ON p.id = e.packagingFk diff --git a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js index 04e1c140a6..9b629e6346 100644 --- a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js +++ b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js @@ -28,8 +28,9 @@ describe('ticket setDeleted()', () => { expect(error.message).toEqual('You must delete the claim id %d first'); }); - // test excluded by task #3693 - xit('should delete the ticket, remove the stowaway link and change the stowaway ticket state to "FIXING" and get rid of the itemshelving', async() => { + + it('should delete ticket, remove stowaway and itemshelving then change stowaway state to "FIXING" ', async() => { + pending('test excluded by task #3693'); const tx = await models.Ticket.beginTransaction({}); try { diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 5d5f086946..41885ee33f 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -14,6 +14,9 @@ "Expedition": { "dataSource": "vn" }, + "ExpeditionState": { + "dataSource": "vn" + }, "Packaging": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/expedition-state.js b/modules/ticket/back/models/expedition-state.js new file mode 100644 index 0000000000..af76af718a --- /dev/null +++ b/modules/ticket/back/models/expedition-state.js @@ -0,0 +1,3 @@ +module.exports = function(Self) { + require('../methods/expedition-state/filter')(Self); +}; diff --git a/modules/ticket/back/models/expedition-state.json b/modules/ticket/back/models/expedition-state.json new file mode 100644 index 0000000000..262eb2e385 --- /dev/null +++ b/modules/ticket/back/models/expedition-state.json @@ -0,0 +1,28 @@ +{ + "name": "ExpeditionState", + "base": "VnModel", + "options": { + "mysql": { + "table": "expeditionState" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "created": { + "type": "date" + }, + "expeditionFk": { + "type": "number" + }, + "typeFk": { + "type": "number" + }, + "userFk": { + "type": "number" + } + } +} diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index bdbb2c3e8d..a41d368f6b 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -19,10 +19,9 @@ Package type Counter externalId - Packager Created - Palletizer - Scanned + State + @@ -33,7 +32,7 @@ vn-tooltip="Delete expedition"> - {{expedition.id | zeroFill:6}} + {{expedition.id | zeroFill:6}} {{::expedition.freightItemName}} {{::expedition.counter}} {{::expedition.externalId}} - - - {{::expedition.userName | dashIfEmpty}} - - {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} + {{::expedition.state}} - - {{::expedition.scannerUserName | dashIfEmpty}} - + + - {{::expedition.scanned | date:'dd/MM/yyyy HH:mm'}} @@ -77,4 +71,44 @@ on-accept="$ctrl.onDialogAccept($data)" question="Delete expedition" message="Are you sure you want to delete this expedition?"> - \ No newline at end of file + + + + + + + + + + + State + Worker + Created + + + + + {{::expeditionState.state}} + + + {{::expeditionState.name || 'System' | translate}} + + + {{::expeditionState.created | date:'dd/MM/yyyy HH:mm'}} + + + + + + + + \ No newline at end of file diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 0c395e6ce1..120d89bb29 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -6,6 +6,11 @@ class Controller extends Section { return this.$http.delete(`Expeditions/${id}`) .then(() => this.$.model.refresh()); } + + showLog(expedition) { + this.expedition = expedition; + this.$.statusLog.show(); + } } ngModule.vnComponent('vnTicketExpedition', { diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js index 425539aef0..586ef21096 100644 --- a/modules/ticket/front/expedition/index.spec.js +++ b/modules/ticket/front/expedition/index.spec.js @@ -5,10 +5,12 @@ describe('Ticket', () => { let controller; let $scope; let $httpBackend; + let $window; beforeEach(ngModule('ticket')); - beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => { + beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$window_) => { + $window = _$window_; $httpBackend = _$httpBackend_; $scope = $rootScope.$new(); $scope.model = { @@ -30,5 +32,23 @@ describe('Ticket', () => { expect($scope.model.refresh).toHaveBeenCalledWith(); }); }); + + describe('showLog()', () => { + it('should show the popover status log', () => { + controller.$.statusLog = {show: () => {}}; + jest.spyOn(controller.$.statusLog, 'show'); + + const expedition = {id: 1}; + + const event = new MouseEvent('click', { + view: $window, + bubbles: true, + cancelable: true + }); + controller.showLog(event, expedition); + + expect(controller.$.statusLog.show).toHaveBeenCalledWith(); + }); + }); }); }); diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml new file mode 100644 index 0000000000..d23cf25af3 --- /dev/null +++ b/modules/ticket/front/expedition/locale/es.yml @@ -0,0 +1 @@ +Status log: Hitorial de estados \ No newline at end of file diff --git a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js index b4bfecbfbf..aa92fa3086 100644 --- a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js +++ b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js @@ -1,7 +1,6 @@ const app = require('vn-loopback/server/server'); -// #2687 - Cannot make a data rollback because of the triggers -xdescribe('Travel cloneWithEntries()', () => { +describe('Travel cloneWithEntries()', () => { const models = app.models; const travelId = 5; const currentUserId = 1102; @@ -9,44 +8,45 @@ xdescribe('Travel cloneWithEntries()', () => { let travelBefore; let newTravelId; - afterAll(async() => { - try { - const entries = await models.Entry.find({ - where: { - travelFk: newTravelId - } - }); - const entriesId = entries.map(entry => entry.id); + // afterAll(async() => { + // try { + // const entries = await models.Entry.find({ + // where: { + // travelFk: newTravelId + // } + // }); + // const entriesId = entries.map(entry => entry.id); - // Destroy all entries buys - await models.Buy.destroyAll({ - where: { - entryFk: {inq: entriesId} - } - }); + // // Destroy all entries buys + // await models.Buy.destroyAll({ + // where: { + // entryFk: {inq: entriesId} + // } + // }); - // Destroy travel entries - await models.Entry.destroyAll({ - where: { - travelFk: newTravelId - } - }); + // // Destroy travel entries + // await models.Entry.destroyAll({ + // where: { + // travelFk: newTravelId + // } + // }); - // Destroy new travel - await models.Travel.destroyById(newTravelId); + // // Destroy new travel + // await models.Travel.destroyById(newTravelId); - // Restore original travel shipped & landed - const travel = await models.Travel.findById(travelId); - await travel.updateAttributes({ - shipped: travelBefore.shipped, - landed: travelBefore.landed - }); - } catch (error) { - console.error(error); - } - }); + // // Restore original travel shipped & landed + // const travel = await models.Travel.findById(travelId); + // await travel.updateAttributes({ + // shipped: travelBefore.shipped, + // landed: travelBefore.landed + // }); + // } catch (error) { + // console.error(error); + // } + // }); it(`should clone the travel and the containing entries`, async() => { + pending('#2687 - Cannot make a data rollback because of the triggers'); const warehouseThree = 3; const agencyModeOne = 1; const yesterday = new Date(); diff --git a/package.json b/package.json index 35c27fc0c4..52741641ab 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "gulp-env": "^0.4.0", "gulp-file": "^0.4.0", "gulp-install": "^1.1.0", - "gulp-jasmine": "^4.0.0", "gulp-merge-json": "^1.3.1", "gulp-nodemon": "^2.5.0", "gulp-print": "^2.0.1", @@ -78,7 +77,7 @@ "html-loader-jest": "^0.2.1", "html-webpack-plugin": "^4.0.0-beta.11", "identity-obj-proxy": "^3.0.0", - "jasmine": "^3.10.0", + "jasmine": "^4.1.0", "jasmine-reporters": "^2.4.0", "jasmine-spec-reporter": "^7.0.0", "jest": "^26.0.1", @@ -88,7 +87,7 @@ "minimist": "^1.2.5", "mysql2": "^1.7.0", "node-sass": "^4.14.1", - "nodemon": "^1.19.4", + "nodemon": "^2.0.16", "plugin-error": "^1.0.1", "raw-loader": "^1.0.0", "regenerator-runtime": "^0.13.7", @@ -102,7 +101,10 @@ }, "scripts": { "dbtest": "nodemon -q db/tests.js -w db/tests", - "test": "jest --watch", + "test:back": "nodemon -q back/tests.js --config back/nodemonConfig.json", + "test:back:ci": "node back/tests.js ci", + "test:e2e": "node e2e/helpers/tests.js", + "test:front": "jest --watch", "back": "nodemon --inspect -w modules ./node_modules/gulp/bin/gulp.js back", "lint": "eslint ./ --cache --ignore-pattern .gitignore", "docker": "docker build --progress=plain -t salix-db ./db" diff --git a/print/templates/email/osticket-report/osticket-report.html b/print/templates/email/osticket-report/osticket-report.html index 76973c8e17..a9cf9a24aa 100644 --- a/print/templates/email/osticket-report/osticket-report.html +++ b/print/templates/email/osticket-report/osticket-report.html @@ -35,8 +35,8 @@