diff --git a/back/methods/account/recover-password.js b/back/methods/account/recover-password.js new file mode 100644 index 0000000000..ddea76829c --- /dev/null +++ b/back/methods/account/recover-password.js @@ -0,0 +1,30 @@ +module.exports = Self => { + Self.remoteMethod('recoverPassword', { + description: 'Send email to the user', + accepts: [ + { + arg: 'email', + type: 'string', + description: 'The email of user', + required: true + } + ], + http: { + path: `/recoverPassword`, + verb: 'POST' + } + }); + + Self.recoverPassword = async function(email) { + const models = Self.app.models; + + try { + await models.user.resetPassword({email, emailTemplate: 'recover-password'}); + } catch (err) { + if (err.code === 'EMAIL_NOT_FOUND') + return; + else + throw err; + } + }; +}; diff --git a/back/methods/account/specs/set-password.spec.js b/back/methods/account/specs/set-password.spec.js index c76fd52b83..fe71873de2 100644 --- a/back/methods/account/specs/set-password.spec.js +++ b/back/methods/account/specs/set-password.spec.js @@ -1,6 +1,6 @@ const app = require('vn-loopback/server/server'); -describe('account changePassword()', () => { +describe('account setPassword()', () => { it('should throw an error when password does not meet requirements', async() => { let req = app.models.Account.setPassword(1, 'insecurePass'); diff --git a/back/methods/dms/deleteTrashFiles.js b/back/methods/dms/deleteTrashFiles.js index 63d7021c5f..f14e65e9f6 100644 --- a/back/methods/dms/deleteTrashFiles.js +++ b/back/methods/dms/deleteTrashFiles.js @@ -51,7 +51,7 @@ module.exports = Self => { const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file); await fs.unlink(dstFile); } catch (err) { - if (err.code != 'ENOENT') + if (err.code != 'ENOENT' && dms.file) throw err; } diff --git a/back/models/account.js b/back/models/account.js index f74052b5c9..c2502380a1 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -1,4 +1,7 @@ +/* eslint max-len: ["error", { "code": 150 }]*/ const md5 = require('md5'); +const LoopBackContext = require('loopback-context'); +const {Email} = require('vn-print'); module.exports = Self => { require('../methods/account/login')(Self); @@ -6,6 +9,7 @@ module.exports = Self => { require('../methods/account/acl')(Self); require('../methods/account/change-password')(Self); require('../methods/account/set-password')(Self); + require('../methods/account/recover-password')(Self); require('../methods/account/validate-token')(Self); require('../methods/account/privileges')(Self); @@ -27,17 +31,62 @@ module.exports = Self => { ctx.data.password = md5(ctx.data.password); }); + Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { + if (!ctx.args || !ctx.args.data.email) return; + const models = Self.app.models; + + const loopBackContext = LoopBackContext.getCurrentContext(); + const httpCtx = {req: loopBackContext.active}; + const httpRequest = httpCtx.req.http.req; + const headers = httpRequest.headers; + const origin = headers.origin; + const url = origin.split(':'); + + const userId = ctx.instance.id; + const user = await models.user.findById(userId); + + class Mailer { + async send(verifyOptions, cb) { + const params = { + url: verifyOptions.verifyHref, + recipient: verifyOptions.to, + lang: ctx.req.getLocale() + }; + + const email = new Email('email-verify', params); + email.send(); + + cb(null, verifyOptions.to); + } + } + + const options = { + type: 'email', + to: instance.email, + from: {}, + redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`, + template: false, + mailer: new Mailer, + host: url[1].split('/')[2], + port: url[2], + protocol: url[0], + user: Self + }; + + await user.verify(options); + }); + Self.remoteMethod('getCurrentUserData', { description: 'Gets the current user data', accepts: [ { arg: 'ctx', - type: 'Object', + type: 'object', http: {source: 'context'} } ], returns: { - type: 'Object', + type: 'object', root: true }, http: { @@ -58,7 +107,7 @@ module.exports = Self => { * * @param {Integer} userId The user id * @param {String} name The role name - * @param {Object} options Options + * @param {object} options Options * @return {Boolean} %true if user has the role, %false otherwise */ Self.hasRole = async function(userId, name, options) { @@ -70,8 +119,8 @@ module.exports = Self => { * Get all user roles. * * @param {Integer} userId The user id - * @param {Object} options Options - * @return {Object} User role list + * @param {object} options Options + * @return {object} User role list */ Self.getRoles = async(userId, options) => { let result = await Self.rawSql( diff --git a/back/models/account.json b/back/models/account.json index d0c17e70f6..5e35c711a7 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -40,6 +40,9 @@ "email": { "type": "string" }, + "emailVerified": { + "type": "boolean" + }, "created": { "type": "date" }, @@ -88,16 +91,23 @@ "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - }, + }, + { + "property": "recoverPassword", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, { - "property": "logout", + "property": "logout", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" }, { - "property": "validateToken", + "property": "validateToken", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$authenticated", diff --git a/back/models/specs/account.spec.js b/back/models/specs/account.spec.js index c52bc43782..f31c81b75e 100644 --- a/back/models/specs/account.spec.js +++ b/back/models/specs/account.spec.js @@ -1,14 +1,14 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; describe('loopback model Account', () => { it('should return true if the user has the given role', async() => { - let result = await app.models.Account.hasRole(1, 'employee'); + let result = await models.Account.hasRole(1, 'employee'); expect(result).toBeTruthy(); }); it('should return false if the user doesnt have the given role', async() => { - let result = await app.models.Account.hasRole(1, 'administrator'); + let result = await models.Account.hasRole(1, 'administrator'); expect(result).toBeFalsy(); }); diff --git a/back/models/specs/user.spec.js b/back/models/specs/user.spec.js new file mode 100644 index 0000000000..124afdc0cc --- /dev/null +++ b/back/models/specs/user.spec.js @@ -0,0 +1,32 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('account recoverPassword()', () => { + const userId = 1107; + + const activeCtx = { + accessToken: {userId: userId}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should send email with token', async() => { + const userId = 1107; + const user = await models.Account.findById(userId); + + await models.Account.recoverPassword(user.email); + + const result = await models.AccessToken.findOne({where: {userId: userId}}); + + expect(result).toBeDefined(); + }); +}); diff --git a/back/models/user.js b/back/models/user.js new file mode 100644 index 0000000000..b24d702b32 --- /dev/null +++ b/back/models/user.js @@ -0,0 +1,27 @@ +const LoopBackContext = require('loopback-context'); +const {Email} = require('vn-print'); + +module.exports = function(Self) { + Self.on('resetPasswordRequest', async function(info) { + const loopBackContext = LoopBackContext.getCurrentContext(); + const httpCtx = {req: loopBackContext.active}; + const httpRequest = httpCtx.req.http.req; + const headers = httpRequest.headers; + const origin = headers.origin; + + const user = await Self.app.models.Account.findById(info.user.id); + const params = { + recipient: info.email, + lang: user.lang, + url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}` + }; + + const options = Object.assign({}, info.options); + for (const param in options) + params[param] = options[param]; + + const email = new Email(options.emailTemplate, params); + + return email.send(); + }); +}; diff --git a/db/changes/10470-family/00-accountingType.sql b/db/changes/10470-family/00-accountingType.sql deleted file mode 100644 index 964027e3a4..0000000000 --- a/db/changes/10470-family/00-accountingType.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE `vn`.`accountingType` ADD daysInFuture INT NULL; -ALTER TABLE `vn`.`accountingType` MODIFY COLUMN daysInFuture int(11) DEFAULT 0 NULL; -UPDATE `vn`.`accountingType` SET daysInFuture=1 WHERE id=8; \ No newline at end of file diff --git a/db/changes/10470-family/00-aclItemType.sql b/db/changes/10470-family/00-aclItemType.sql deleted file mode 100644 index 836a69dfd2..0000000000 --- a/db/changes/10470-family/00-aclItemType.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('ItemType', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), - ('ItemType', '*', 'WRITE', 'ALLOW', 'ROLE', 'buyer'); \ No newline at end of file diff --git a/db/changes/10470-family/00-aclMdb.sql b/db/changes/10470-family/00-aclMdb.sql deleted file mode 100644 index b02ddc4514..0000000000 --- a/db/changes/10470-family/00-aclMdb.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS `vn`.`mdbBranch` ( - `name` VARCHAR(255), - PRIMARY KEY(`name`) -); - -CREATE TABLE IF NOT EXISTS `vn`.`mdbVersion` ( - `app` VARCHAR(255) NOT NULL, - `branchFk` VARCHAR(255) NOT NULL, - `version` INT, - CONSTRAINT `mdbVersion_branchFk` FOREIGN KEY (`branchFk`) REFERENCES `vn`.`mdbBranch` (`name`) ON DELETE CASCADE ON UPDATE CASCADE -); - -INSERT IGNORE INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES(318, 'MdbVersion', '*', '*', 'ALLOW', 'ROLE', 'developer'); diff --git a/db/changes/10470-family/00-chat.sql b/db/changes/10470-family/00-chat.sql deleted file mode 100644 index d4a8f068a7..0000000000 --- a/db/changes/10470-family/00-chat.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE `vn`.`chat` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `senderFk` int(10) unsigned DEFAULT NULL, - `recipient` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, - `dated` date DEFAULT NULL, - `checkUserStatus` tinyint(1) DEFAULT NULL, - `message` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL, - `status` tinyint(1) DEFAULT NULL, - `attempts` int(1) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `chat_FK` (`senderFk`), - CONSTRAINT `chat_FK` FOREIGN KEY (`senderFk`) REFERENCES `account`.`user` (`id`) ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; \ No newline at end of file diff --git a/db/changes/10470-family/00-creditInsurance.sql b/db/changes/10470-family/00-creditInsurance.sql deleted file mode 100644 index 9d4db470b8..0000000000 --- a/db/changes/10470-family/00-creditInsurance.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE `vn`.`creditInsurance` ADD creditClassificationFk int(11) NULL; - -UPDATE `vn`.`creditInsurance` AS `destiny` - SET `destiny`.`creditClassificationFk`= (SELECT creditClassification FROM `vn`.`creditInsurance` AS `origin` WHERE `origin`.id = `destiny`.id); - -ALTER TABLE `vn`.`creditInsurance` - ADD CONSTRAINT `creditInsurance_creditClassificationFk` FOREIGN KEY (`creditClassificationFk`) - REFERENCES `vn`.`creditClassification` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/db/changes/10470-family/00-defaultViewConfig.sql b/db/changes/10470-family/00-defaultViewConfig.sql deleted file mode 100644 index d423599b17..0000000000 --- a/db/changes/10470-family/00-defaultViewConfig.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns) -VALUES ('clientsDetail', '{"id":true,"phone":true,"city":true,"socialName":true,"salesPersonFk":true,"email":true,"name":false,"fi":false,"credit":false,"creditInsurance":false,"mobile":false,"street":false,"countryFk":false,"provinceFk":false,"postcode":false,"created":false,"businessTypeFk":false,"payMethodFk":false,"sageTaxTypeFk":false,"sageTransactionTypeFk":false,"isActive":false,"isVies":false,"isTaxDataChecked":false,"isEqualizated":false,"isFreezed":false,"hasToInvoice":false,"hasToInvoiceByAddress":false,"isToBeMailed":false,"hasLcr":false,"hasCoreVnl":false,"hasSepaVnl":false}'); - diff --git a/db/changes/10470-family/00-ticket_doRefund.sql b/db/changes/10470-family/00-ticket_doRefund.sql deleted file mode 100644 index f4ecf29d71..0000000000 --- a/db/changes/10470-family/00-ticket_doRefund.sql +++ /dev/null @@ -1,127 +0,0 @@ -DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`; - -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(OUT vNewTicket INT) -BEGIN -/** - * Crea un ticket de abono a partir de tmp.sale y/o tmp.ticketService - * - * @return vNewTicket - */ - DECLARE vDone BIT DEFAULT 0; - DECLARE vClientFk MEDIUMINT; - DECLARE vWarehouse TINYINT; - DECLARE vCompany MEDIUMINT; - DECLARE vAddress MEDIUMINT; - DECLARE vRefundAgencyMode INT; - DECLARE vItemFk INT; - DECLARE vQuantity DECIMAL (10,2); - DECLARE vConcept VARCHAR(50); - DECLARE vPrice DECIMAL (10,2); - DECLARE vDiscount TINYINT; - DECLARE vSaleNew INT; - DECLARE vSaleMain INT; - DECLARE vZoneFk INT; - DECLARE vDescription VARCHAR(50); - DECLARE vTaxClassFk INT; - DECLARE vTicketServiceTypeFk INT; - DECLARE vOriginTicket INT; - - DECLARE cSales CURSOR FOR - SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount - FROM tmp.sale s; - - DECLARE cTicketServices CURSOR FOR - SELECT ts.description, - ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk - FROM tmp.ticketService ts; - - DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; - - SELECT sub.ticketFk INTO vOriginTicket - FROM ( - SELECT s.ticketFk - FROM tmp.sale s - UNION ALL - SELECT ts.ticketFk - FROM tmp.ticketService ts - ) sub - LIMIT 1; - - SELECT id INTO vRefundAgencyMode - FROM agencyMode WHERE `name` = 'ABONO'; - - SELECT clientFk, warehouseFk, companyFk, addressFk - INTO vClientFk, vWarehouse, vCompany, vAddress - FROM ticket - WHERE id = vOriginTicket; - - SELECT id INTO vZoneFk - FROM zone WHERE agencyModeFk = vRefundAgencyMode - LIMIT 1; - - INSERT INTO vn.ticket ( - clientFk, - shipped, - addressFk, - agencyModeFk, - nickname, - warehouseFk, - companyFk, - landed, - zoneFk - ) - SELECT - vClientFk, - CURDATE(), - vAddress, - vRefundAgencyMode, - a.nickname, - vWarehouse, - vCompany, - CURDATE(), - vZoneFk - FROM address a - WHERE a.id = vAddress; - - SET vNewTicket = LAST_INSERT_ID(); - - SET vDone := FALSE; - OPEN cSales; - FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount; - - WHILE NOT vDone DO - - INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount) - VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount ); - - SET vSaleNew = LAST_INSERT_ID(); - - INSERT INTO vn.saleComponent(saleFk,componentFk,`value`) - SELECT vSaleNew,componentFk,`value` - FROM vn.saleComponent - WHERE saleFk = vSaleMain; - - FETCH cSales INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount; - - END WHILE; - CLOSE cSales; - - SET vDone := FALSE; - OPEN cTicketServices; - FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk; - - WHILE NOT vDone DO - - INSERT INTO vn.ticketService(description, quantity, price, taxClassFk, ticketFk, ticketServiceTypeFk) - VALUES(vDescription, vQuantity, vPrice, vTaxClassFk, vNewTicket, vTicketServiceTypeFk); - - FETCH cTicketServices INTO vDescription, vQuantity, vPrice, vTaxClassFk, vTicketServiceTypeFk; - - END WHILE; - CLOSE cTicketServices; - - INSERT INTO vn.ticketRefund(refundTicketFk, originalTicketFk) - VALUES(vNewTicket, vOriginTicket); -END$$ -DELIMITER ; diff --git a/db/changes/10470-family/01-creditInsuranceTriggers.sql b/db/changes/10470-family/01-creditInsuranceTriggers.sql deleted file mode 100644 index 53ba3ba11e..0000000000 --- a/db/changes/10470-family/01-creditInsuranceTriggers.sql +++ /dev/null @@ -1,11 +0,0 @@ -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`creditInsurance_beforeInsert` - BEFORE INSERT ON `creditInsurance` - FOR EACH ROW -BEGIN - IF NEW.creditClassificationFk THEN - SET NEW.creditClassification = NEW.creditClassificationFk; - END IF; -END$$ -DELIMITER ; \ No newline at end of file diff --git a/db/changes/10470-family/01-tableConfig.sql b/db/changes/10470-family/01-tableConfig.sql deleted file mode 100644 index 685981d902..0000000000 --- a/db/changes/10470-family/01-tableConfig.sql +++ /dev/null @@ -1 +0,0 @@ -RENAME TABLE `edi`.`fileConfig` to `edi`.`tableConfig`; \ No newline at end of file diff --git a/db/changes/10470-family/02-fileConfig.sql b/db/changes/10470-family/02-fileConfig.sql deleted file mode 100644 index 3109a46166..0000000000 --- a/db/changes/10470-family/02-fileConfig.sql +++ /dev/null @@ -1,22 +0,0 @@ -CREATE TABLE `edi`.`fileConfig` -( - name varchar(25) NOT NULL, - checksum text NULL, - keyValue tinyint(1) default true NOT NULL, - constraint fileConfig_pk - primary key (name) -); - -create unique index fileConfig_name_uindex - on `edi`.`fileConfig` (name); - - -INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue) -VALUES ('FEC010104', null, 0); - -INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue) -VALUES ('VBN020101', null, 1); - -INSERT INTO `edi`.`fileConfig` (name, checksum, keyValue) -VALUES ('florecompc2', null, 1); - diff --git a/db/changes/10471-family/00-chat.sql b/db/changes/10471-family/00-chat.sql deleted file mode 100644 index 6f3c9d437f..0000000000 --- a/db/changes/10471-family/00-chat.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE `vn`.`chat` MODIFY COLUMN message TEXT CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; -ALTER TABLE `vn`.`chat` MODIFY COLUMN dated DATETIME DEFAULT NULL NULL; -ALTER TABLE `vn`.`chat` ADD error TEXT NULL; diff --git a/db/changes/10472-family/00-creditInsurance.sql b/db/changes/10472-family/00-creditInsurance.sql deleted file mode 100644 index 4731cfb2a9..0000000000 --- a/db/changes/10472-family/00-creditInsurance.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE `vn`.`creditInsurance` - ADD CONSTRAINT `creditInsurance_creditClassificationFk` FOREIGN KEY (`creditClassificationFk`) - REFERENCES `vn`.`creditClassification` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/db/changes/10480-june/00-ACL.sql b/db/changes/10480-june/00-ACL.sql deleted file mode 100644 index b13e56e21a..0000000000 --- a/db/changes/10480-june/00-ACL.sql +++ /dev/null @@ -1,21 +0,0 @@ -INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) - VALUES - ('InvoiceOut','refund','WRITE','ALLOW','ROLE','invoicing'), - ('InvoiceOut','refund','WRITE','ALLOW','ROLE','salesAssistant'), - ('InvoiceOut','refund','WRITE','ALLOW','ROLE','claimManager'), - ('Ticket','refund','WRITE','ALLOW','ROLE','invoicing'), - ('Ticket','refund','WRITE','ALLOW','ROLE','salesAssistant'), - ('Ticket','refund','WRITE','ALLOW','ROLE','claimManager'), - ('Sale','refund','WRITE','ALLOW','ROLE','salesAssistant'), - ('Sale','refund','WRITE','ALLOW','ROLE','claimManager'), - ('TicketRefund','*','WRITE','ALLOW','ROLE','invoicing'), - ('ClaimObservation','*','WRITE','ALLOW','ROLE','salesPerson'), - ('ClaimObservation','*','READ','ALLOW','ROLE','salesPerson'), - ('Client','setPassword','WRITE','ALLOW','ROLE','salesPerson'), - ('Client','updateUser','WRITE','ALLOW','ROLE','salesPerson'); - -DELETE FROM `salix`.`ACL` WHERE id=313; - -UPDATE `salix`.`ACL` - SET principalId='invoicing' - WHERE id=297; \ No newline at end of file diff --git a/db/changes/10480-june/00-aclShelvingLog.sql b/db/changes/10480-june/00-aclShelvingLog.sql deleted file mode 100644 index dc75142d12..0000000000 --- a/db/changes/10480-june/00-aclShelvingLog.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) - VALUES - ('ShelvingLog','*','READ','ALLOW','ROLE','employee'); \ No newline at end of file diff --git a/db/changes/10480-june/00-aclZoneExclusionGeos.sql b/db/changes/10480-june/00-aclZoneExclusionGeos.sql deleted file mode 100644 index 4c0f6c9916..0000000000 --- a/db/changes/10480-june/00-aclZoneExclusionGeos.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('ZoneExclusionGeo', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), - ('ZoneExclusionGeo', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); \ No newline at end of file diff --git a/db/changes/10480-june/00-albaran_gestdoc.sql b/db/changes/10480-june/00-albaran_gestdoc.sql deleted file mode 100644 index a0ba93bd3a..0000000000 --- a/db/changes/10480-june/00-albaran_gestdoc.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE `vn2008`.`albaran_gestdoc` DROP FOREIGN KEY fk_albaran_gestdoc_gestdoc1; -ALTER TABLE `vn2008`.`albaran_gestdoc` ADD CONSTRAINT albaran_gestdoc_FK FOREIGN KEY (gestdoc_id) REFERENCES `vn`.`dms`(id) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/db/changes/10480-june/00-client.sql b/db/changes/10480-june/00-client.sql deleted file mode 100644 index 4a39bbdf95..0000000000 --- a/db/changes/10480-june/00-client.sql +++ /dev/null @@ -1,3 +0,0 @@ -alter table `vn`.`client` - add hasIncoterms tinyint(1) default 0 not null comment 'Received incoterms authorization from client'; - diff --git a/db/changes/10480-june/00-deprecations.sql b/db/changes/10480-june/00-deprecations.sql deleted file mode 100644 index 68becd13e9..0000000000 --- a/db/changes/10480-june/00-deprecations.sql +++ /dev/null @@ -1,13 +0,0 @@ -DROP FUNCTION `account`.`userGetId`; -DROP FUNCTION `account`.`myUserGetName`; -DROP FUNCTION `account`.`myUserGetId`; -DROP FUNCTION `account`.`myUserHasRole`; -DROP FUNCTION `account`.`myUserHasRoleId`; -DROP FUNCTION `account`.`userGetName`; -DROP FUNCTION `account`.`userHasRole`; -DROP FUNCTION `account`.`userHasRoleId`; -DROP PROCEDURE `account`.`myUserLogout`; -DROP PROCEDURE `account`.`userLogin`; -DROP PROCEDURE `account`.`userLoginWithKey`; -DROP PROCEDURE `account`.`userLoginWithName`; -DROP PROCEDURE `account`.`userSetPassword`; \ No newline at end of file diff --git a/db/changes/10480-june/00-item.sql b/db/changes/10480-june/00-item.sql deleted file mode 100644 index a08d3f4c14..0000000000 --- a/db/changes/10480-june/00-item.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `vn`.`item` MODIFY COLUMN description TEXT CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; diff --git a/db/changes/10480-june/00-route.sql b/db/changes/10480-june/00-route.sql deleted file mode 100644 index beb7d5e414..0000000000 --- a/db/changes/10480-june/00-route.sql +++ /dev/null @@ -1,10 +0,0 @@ -UPDATE `vn`.`route` r - JOIN(SELECT r.id, wl.workcenterFk - FROM `vn`.`route` r - JOIN `vn`.`routeLog` rl ON rl.originFk = r.id - JOIN `vn`.`workerLabour` wl ON wl.workerFk = rl.userFk - AND r.created BETWEEN wl.started AND IFNULL(wl.ended, r.created) - WHERE r.created BETWEEN '2021-12-01' AND CURDATE() - AND rl.action = 'insert' - )sub ON sub.id = r.id - SET r.commissionWorkCenterFk = sub.workcenterFk; \ No newline at end of file diff --git a/db/changes/10480-june/00-sample.sql b/db/changes/10480-june/00-sample.sql deleted file mode 100644 index 18beb736da..0000000000 --- a/db/changes/10480-june/00-sample.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `vn`.`sample` (code, description, isVisible, hasCompany, hasPreview, datepickerEnabled) -VALUES ('incoterms-authorization', 'Autorización de incoterms', 1, 1, 1, 0); \ No newline at end of file diff --git a/db/changes/10480-june/00-shelving.sql b/db/changes/10480-june/00-shelving.sql deleted file mode 100644 index c66d164c4f..0000000000 --- a/db/changes/10480-june/00-shelving.sql +++ /dev/null @@ -1,18 +0,0 @@ -ALTER TABLE `vn`.`itemShelving` DROP FOREIGN KEY itemShelving_fk2; -ALTER TABLE `vn`.`shelvingLog` DROP FOREIGN KEY shelvingLog_FK_ibfk_1; -ALTER TABLE `vn`.`smartTag` DROP FOREIGN KEY smartTag_shelving_fk; -ALTER TABLE `vn`.`workerShelving` DROP FOREIGN KEY workerShelving_shelving_fk; - -ALTER TABLE `vn`.`shelving` DROP PRIMARY KEY; -ALTER TABLE `vn`.`shelving` ADD id INT auto_increment PRIMARY KEY NULL; -ALTER TABLE `vn`.`shelving` CHANGE id id int(11) auto_increment NOT NULL FIRST; -ALTER TABLE `vn`.`shelving` ADD CONSTRAINT shelving_UN UNIQUE KEY (code); - -ALTER TABLE `vn`.`itemShelving` ADD CONSTRAINT itemShelving_fk2 FOREIGN KEY (shelvingFk) REFERENCES `vn`.`shelving`(code) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `vn`.`shelvingLog` ADD CONSTRAINT shelvingLog_FK_ibfk_1 FOREIGN KEY (originFk) REFERENCES `vn`.`shelving`(code) ON DELETE CASCADE ON UPDATE CASCADE; -ALTER TABLE `vn`.`smartTag` ADD CONSTRAINT smartTag_FK FOREIGN KEY (shelvingFk) REFERENCES `vn`.`shelving`(code) ON DELETE RESTRICT ON UPDATE CASCADE; -ALTER TABLE `vn`.`workerShelving` ADD CONSTRAINT workerShelving_FK_1 FOREIGN KEY (shelvingFk) REFERENCES `vn`.`shelving`(code) ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE vn.shelvingLog DROP FOREIGN KEY shelvingLog_FK_ibfk_1; -ALTER TABLE vn.shelvingLog MODIFY COLUMN originFk INT NOT NULL; -ALTER TABLE vn.shelvingLog ADD CONSTRAINT shelvingLog_FK FOREIGN KEY (originFk) REFERENCES vn.shelving(id) ON DELETE CASCADE ON UPDATE CASCADE; \ No newline at end of file diff --git a/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql b/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql deleted file mode 100644 index e6506c5d78..0000000000 --- a/db/changes/10480-june/00-ticketRefund_beforeUpsert.sql +++ /dev/null @@ -1,21 +0,0 @@ -DROP PROCEDURE IF EXISTS `vn`.`ticketRefund_beforeUpsert`; - -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketRefund_beforeUpsert`(vRefundTicketFk INT, vOriginalTicketFk INT) -BEGIN - DECLARE vAlreadyExists BOOLEAN DEFAULT FALSE; - - IF vRefundTicketFk = vOriginalTicketFk THEN - CALL util.throw('Original ticket and refund ticket has same id'); - END IF; - - SELECT COUNT(*) INTO vAlreadyExists - FROM ticketRefund - WHERE originalTicketFk = vOriginalTicketFk; - - IF vAlreadyExists > 0 THEN - CALL util.throw('This ticket is already a refund'); - END IF; -END$$ -DELIMITER ; diff --git a/db/changes/10480-june/01-claimObservation.sql b/db/changes/10480-june/01-claimObservation.sql deleted file mode 100644 index 8dc126a9ea..0000000000 --- a/db/changes/10480-june/01-claimObservation.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE `vn`.`claimObservation` ( - `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, - `claimFk` int(10) unsigned NOT NULL, - `workerFk` int(10) unsigned DEFAULT NULL, - `text` text COLLATE utf8_unicode_ci NOT NULL, - `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - KEY `worker_key` (`workerFk`), - KEY `claim_key` (`claimFk`), - KEY `claimObservation_created_IDX` (`created`) USING BTREE, - CONSTRAINT `claimObservation_ibfk_1` FOREIGN KEY (`claimFk`) REFERENCES `vn`.`claim` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `claimObservation_ibfk_2` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE -) COMMENT='Todas las observaciones referentes a una reclamación' \ No newline at end of file diff --git a/db/changes/10480-june/02-claimTextMigration.sql b/db/changes/10480-june/02-claimTextMigration.sql deleted file mode 100644 index fa5f6fe834..0000000000 --- a/db/changes/10480-june/02-claimTextMigration.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `vn`.`claimObservation` (`claimFk`, `text`, `created`) -SELECT `id`, `observation`, `created` FROM `vn`.`claim` \ No newline at end of file diff --git a/db/changes/10480-june/04-aclParking.sql b/db/changes/10480-june/04-aclParking.sql deleted file mode 100644 index 05acd68b1c..0000000000 --- a/db/changes/10480-june/04-aclParking.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) - VALUES ('Parking','*','*','ALLOW','ROLE','employee') \ No newline at end of file diff --git a/db/changes/10480-june/04-aclShelving.sql b/db/changes/10480-june/04-aclShelving.sql deleted file mode 100644 index b237dfe0da..0000000000 --- a/db/changes/10480-june/04-aclShelving.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) - VALUES ('Shelving','*','*','ALLOW','ROLE','employee') \ No newline at end of file diff --git a/db/changes/10481-june/00-aclOsTicket.sql b/db/changes/10481-june/00-aclOsTicket.sql deleted file mode 100644 index ae2a121f56..0000000000 --- a/db/changes/10481-june/00-aclOsTicket.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('OsTicket', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10481-june/00-aclOsTicketConfig.sql b/db/changes/10481-june/00-aclOsTicketConfig.sql deleted file mode 100644 index ad53ea6ae5..0000000000 --- a/db/changes/10481-june/00-aclOsTicketConfig.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('OsTicketConfig', '*', '*', 'ALLOW', 'ROLE', 'it'); \ No newline at end of file diff --git a/db/changes/10481-june/00-osTicketConfig.sql b/db/changes/10481-june/00-osTicketConfig.sql deleted file mode 100644 index 8727c816dd..0000000000 --- a/db/changes/10481-june/00-osTicketConfig.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE TABLE `vn`.`osTicketConfig` ( - `id` int(11) NOT NULL, - `host` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `user` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `password` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `oldStatus` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `newStatusId` int(11) DEFAULT NULL, - `action` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `day` int(11) DEFAULT NULL, - `comment` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `hostDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `userDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `passwordDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL, - `portDb` int(11) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; \ No newline at end of file diff --git a/db/changes/10490-august/00-ACL.sql b/db/changes/224701/00-ACL.sql similarity index 100% rename from db/changes/10490-august/00-ACL.sql rename to db/changes/224701/00-ACL.sql diff --git a/db/changes/10490-august/00-acl_receiptPdf.sql b/db/changes/224701/00-acl_receiptPdf.sql similarity index 100% rename from db/changes/10490-august/00-acl_receiptPdf.sql rename to db/changes/224701/00-acl_receiptPdf.sql diff --git a/db/changes/10490-august/00-clientConsumptionQueue.sql b/db/changes/224701/00-clientConsumptionQueue.sql similarity index 100% rename from db/changes/10490-august/00-clientConsumptionQueue.sql rename to db/changes/224701/00-clientConsumptionQueue.sql diff --git a/db/changes/10490-august/00-invoiceOutQueue.sql b/db/changes/224701/00-invoiceOutQueue.sql similarity index 100% rename from db/changes/10490-august/00-invoiceOutQueue.sql rename to db/changes/224701/00-invoiceOutQueue.sql diff --git a/db/changes/10490-august/00-itemConfig.sql b/db/changes/224701/00-itemConfig.sql similarity index 100% rename from db/changes/10490-august/00-itemConfig.sql rename to db/changes/224701/00-itemConfig.sql diff --git a/db/changes/10490-august/00-printConfig.sql b/db/changes/224701/00-printConfig.sql similarity index 100% rename from db/changes/10490-august/00-printConfig.sql rename to db/changes/224701/00-printConfig.sql diff --git a/db/changes/10490-august/00-sale_afterUpdate.sql b/db/changes/224701/00-sale_afterUpdate.sql similarity index 100% rename from db/changes/10490-august/00-sale_afterUpdate.sql rename to db/changes/224701/00-sale_afterUpdate.sql diff --git a/db/changes/10490-august/00-sample.sql b/db/changes/224701/00-sample.sql similarity index 100% rename from db/changes/10490-august/00-sample.sql rename to db/changes/224701/00-sample.sql diff --git a/db/changes/10490-august/00-user_hasGrant.sql b/db/changes/224701/00-user_hasGrant.sql similarity index 100% rename from db/changes/10490-august/00-user_hasGrant.sql rename to db/changes/224701/00-user_hasGrant.sql diff --git a/db/changes/10491-august/00-ACL_workerDisableExcluded.sql b/db/changes/224702/00-ACL_workerDisableExcluded.sql similarity index 100% rename from db/changes/10491-august/00-ACL_workerDisableExcluded.sql rename to db/changes/224702/00-ACL_workerDisableExcluded.sql diff --git a/db/changes/10491-august/00-aclBusiness.sql b/db/changes/224702/00-aclBusiness.sql similarity index 100% rename from db/changes/10491-august/00-aclBusiness.sql rename to db/changes/224702/00-aclBusiness.sql diff --git a/db/changes/10491-august/00-aclUsesMana.sql b/db/changes/224702/00-aclUsesMana.sql similarity index 100% rename from db/changes/10491-august/00-aclUsesMana.sql rename to db/changes/224702/00-aclUsesMana.sql diff --git a/db/changes/10491-august/00-defaultPayDem_sameAs_production.sql b/db/changes/224702/00-defaultPayDem_sameAs_production.sql similarity index 100% rename from db/changes/10491-august/00-defaultPayDem_sameAs_production.sql rename to db/changes/224702/00-defaultPayDem_sameAs_production.sql diff --git a/db/changes/10491-august/00-invoiceInPdf.sql b/db/changes/224702/00-invoiceInPdf.sql similarity index 100% rename from db/changes/10491-august/00-invoiceInPdf.sql rename to db/changes/224702/00-invoiceInPdf.sql diff --git a/db/changes/10491-august/00-newSupplier_ACL.sql b/db/changes/224702/00-newSupplier_ACL.sql similarity index 100% rename from db/changes/10491-august/00-newSupplier_ACL.sql rename to db/changes/224702/00-newSupplier_ACL.sql diff --git a/db/changes/10491-august/00-notificationProc.sql b/db/changes/224702/00-notificationProc.sql similarity index 100% rename from db/changes/10491-august/00-notificationProc.sql rename to db/changes/224702/00-notificationProc.sql diff --git a/db/changes/10491-august/00-notificationTables.sql b/db/changes/224702/00-notificationTables.sql similarity index 100% rename from db/changes/10491-august/00-notificationTables.sql rename to db/changes/224702/00-notificationTables.sql diff --git a/db/changes/10491-august/00-payMethodFk_Allow_Null.sql b/db/changes/224702/00-payMethodFk_Allow_Null.sql similarity index 100% rename from db/changes/10491-august/00-payMethodFk_Allow_Null.sql rename to db/changes/224702/00-payMethodFk_Allow_Null.sql diff --git a/db/changes/10491-august/00-supplierActivityFk_Allow_Null.sql b/db/changes/224702/00-supplierActivityFk_Allow_Null.sql similarity index 100% rename from db/changes/10491-august/00-supplierActivityFk_Allow_Null.sql rename to db/changes/224702/00-supplierActivityFk_Allow_Null.sql diff --git a/db/changes/10491-august/00-ticket_closeByTicket.sql b/db/changes/224702/00-ticket_closeByTicket.sql similarity index 100% rename from db/changes/10491-august/00-ticket_closeByTicket.sql rename to db/changes/224702/00-ticket_closeByTicket.sql diff --git a/db/changes/10491-august/00-zipConfig.sql b/db/changes/224702/00-zipConfig.sql similarity index 100% rename from db/changes/10491-august/00-zipConfig.sql rename to db/changes/224702/00-zipConfig.sql diff --git a/db/changes/10500-november/00-ACL.sql b/db/changes/224801/00-ACL.sql similarity index 100% rename from db/changes/10500-november/00-ACL.sql rename to db/changes/224801/00-ACL.sql diff --git a/db/changes/10500-november/00-claim.sql b/db/changes/224801/00-claim.sql similarity index 100% rename from db/changes/10500-november/00-claim.sql rename to db/changes/224801/00-claim.sql diff --git a/db/changes/10500-november/00-claimRma.sql b/db/changes/224801/00-claimRma.sql similarity index 100% rename from db/changes/10500-november/00-claimRma.sql rename to db/changes/224801/00-claimRma.sql diff --git a/db/changes/10501-november/00-aclNotification.sql b/db/changes/224901/00-aclNotification.sql similarity index 100% rename from db/changes/10501-november/00-aclNotification.sql rename to db/changes/224901/00-aclNotification.sql diff --git a/db/changes/10501-november/00-packingSiteConfig.sql b/db/changes/224901/00-packingSiteConfig.sql similarity index 100% rename from db/changes/10501-november/00-packingSiteConfig.sql rename to db/changes/224901/00-packingSiteConfig.sql diff --git a/db/changes/10501-november/00-packingSiteUpdate.sql b/db/changes/224901/00-packingSiteUpdate.sql similarity index 100% rename from db/changes/10501-november/00-packingSiteUpdate.sql rename to db/changes/224901/00-packingSiteUpdate.sql diff --git a/db/changes/10501-november/00-salix_url.sql b/db/changes/224901/00-salix_url.sql similarity index 100% rename from db/changes/10501-november/00-salix_url.sql rename to db/changes/224901/00-salix_url.sql diff --git a/db/changes/224902/00-aclUserPassword.sql b/db/changes/224902/00-aclUserPassword.sql new file mode 100644 index 0000000000..b92b54c283 --- /dev/null +++ b/db/changes/224902/00-aclUserPassword.sql @@ -0,0 +1,2 @@ +DELETE FROM `salix`.`ACL` + WHERE model = 'UserPassword'; diff --git a/db/changes/10502-november/00-deletePickupContact.sql b/db/changes/224902/00-deletePickupContact.sql similarity index 100% rename from db/changes/10502-november/00-deletePickupContact.sql rename to db/changes/224902/00-deletePickupContact.sql diff --git a/db/changes/10502-november/00-itemShelvingACL.sql b/db/changes/224902/00-itemShelvingACL.sql similarity index 100% rename from db/changes/10502-november/00-itemShelvingACL.sql rename to db/changes/224902/00-itemShelvingACL.sql diff --git a/db/changes/10502-november/00-itemShelvingPlacementSupplyStockACL.sql b/db/changes/224902/00-itemShelvingPlacementSupplyStockACL.sql similarity index 100% rename from db/changes/10502-november/00-itemShelvingPlacementSupplyStockACL.sql rename to db/changes/224902/00-itemShelvingPlacementSupplyStockACL.sql diff --git a/db/changes/10502-november/00-workerTimeControlMail.sql b/db/changes/224902/00-workerTimeControlMail.sql similarity index 100% rename from db/changes/10502-november/00-workerTimeControlMail.sql rename to db/changes/224902/00-workerTimeControlMail.sql diff --git a/db/changes/10502-november/00-zone_getPostalCode.sql b/db/changes/224902/00-zone_getPostalCode.sql similarity index 100% rename from db/changes/10502-november/00-zone_getPostalCode.sql rename to db/changes/224902/00-zone_getPostalCode.sql diff --git a/db/changes/10503-november/00-ACL_notification_InvoiceE.sql b/db/changes/224903/00-ACL_notification_InvoiceE.sql similarity index 100% rename from db/changes/10503-november/00-ACL_notification_InvoiceE.sql rename to db/changes/224903/00-ACL_notification_InvoiceE.sql diff --git a/db/changes/10503-november/00-aclInvoiceOut.sql b/db/changes/224903/00-aclInvoiceOut.sql similarity index 100% rename from db/changes/10503-november/00-aclInvoiceOut.sql rename to db/changes/224903/00-aclInvoiceOut.sql diff --git a/db/changes/10503-november/00-alter_expedition_itemFk.sql b/db/changes/224903/00-alter_expedition_itemFk.sql similarity index 100% rename from db/changes/10503-november/00-alter_expedition_itemFk.sql rename to db/changes/224903/00-alter_expedition_itemFk.sql diff --git a/db/changes/10503-november/00-clientHasInvoiceElectronic.sql b/db/changes/224903/00-clientHasInvoiceElectronic.sql similarity index 100% rename from db/changes/10503-november/00-clientHasInvoiceElectronic.sql rename to db/changes/224903/00-clientHasInvoiceElectronic.sql diff --git a/db/changes/10503-november/00-collection_missingTrash.sql b/db/changes/224903/00-collection_missingTrash.sql similarity index 100% rename from db/changes/10503-november/00-collection_missingTrash.sql rename to db/changes/224903/00-collection_missingTrash.sql diff --git a/db/changes/10503-november/00-deleteInvoiceOutQueue.sql b/db/changes/224903/00-deleteInvoiceOutQueue.sql similarity index 100% rename from db/changes/10503-november/00-deleteInvoiceOutQueue.sql rename to db/changes/224903/00-deleteInvoiceOutQueue.sql diff --git a/db/changes/10503-november/00-editTrackedACL.sql b/db/changes/224903/00-editTrackedACL.sql similarity index 100% rename from db/changes/10503-november/00-editTrackedACL.sql rename to db/changes/224903/00-editTrackedACL.sql diff --git a/db/changes/10503-november/00-greuge.sql b/db/changes/224903/00-greuge.sql similarity index 100% rename from db/changes/10503-november/00-greuge.sql rename to db/changes/224903/00-greuge.sql diff --git a/db/changes/10503-november/00-insert_notification_invoiceE.sql b/db/changes/224903/00-insert_notification_invoiceE.sql similarity index 100% rename from db/changes/10503-november/00-insert_notification_invoiceE.sql rename to db/changes/224903/00-insert_notification_invoiceE.sql diff --git a/db/changes/10503-november/00-isCompensationACL.sql b/db/changes/224903/00-isCompensationACL.sql similarity index 100% rename from db/changes/10503-november/00-isCompensationACL.sql rename to db/changes/224903/00-isCompensationACL.sql diff --git a/db/changes/10503-november/00-osTicketConfig.sql b/db/changes/224903/00-osTicketConfig.sql similarity index 100% rename from db/changes/10503-november/00-osTicketConfig.sql rename to db/changes/224903/00-osTicketConfig.sql diff --git a/db/changes/10503-november/00-ticket_canMerge.sql b/db/changes/224903/00-ticket_canMerge.sql similarity index 100% rename from db/changes/10503-november/00-ticket_canMerge.sql rename to db/changes/224903/00-ticket_canMerge.sql diff --git a/db/changes/10503-november/00-ticket_canbePostponed.sql b/db/changes/224903/00-ticket_canbePostponed.sql similarity index 100% rename from db/changes/10503-november/00-ticket_canbePostponed.sql rename to db/changes/224903/00-ticket_canbePostponed.sql diff --git a/db/changes/10503-november/00-timeBusiness_calculate.sql b/db/changes/224903/00-timeBusiness_calculate.sql similarity index 100% rename from db/changes/10503-november/00-timeBusiness_calculate.sql rename to db/changes/224903/00-timeBusiness_calculate.sql diff --git a/db/changes/10503-november/01-updateClientHasInvoiceElectronic.sql b/db/changes/224903/01-updateClientHasInvoiceElectronic.sql similarity index 100% rename from db/changes/10503-november/01-updateClientHasInvoiceElectronic.sql rename to db/changes/224903/01-updateClientHasInvoiceElectronic.sql diff --git a/db/changes/10510-december/deleteMe.keep b/db/changes/225001/.gitkeep similarity index 100% rename from db/changes/10510-december/deleteMe.keep rename to db/changes/225001/.gitkeep diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index c9c265ae78..a4870e2df0 100644 --- a/db/dump/dumpedFixtures.sql +++ b/db/dump/dumpedFixtures.sql @@ -22,7 +22,7 @@ USE `util`; LOCK TABLES `config` WRITE; /*!40000 ALTER TABLE `config` DISABLE KEYS */; -INSERT INTO `config` VALUES (1,'10480',0,'production',NULL); +INSERT INTO `config` VALUES (1,'224602',0,'production',NULL); /*!40000 ALTER TABLE `config` ENABLE KEYS */; UNLOCK TABLES; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 482218ed24..92b84be43f 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2564,10 +2564,6 @@ UPDATE `vn`.`route` UPDATE `vn`.`route` SET `invoiceInFk`=2 WHERE `id`=2; -INSERT INTO `bs`.`salesPerson` (`workerFk`, `year`, `month`, `portfolioWeight`) - VALUES - (18, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE()), 807.23), - (19, YEAR(util.VN_CURDATE()), MONTH(util.VN_CURDATE()), 34.40); INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`) VALUES diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 6d85d0511f..403534787b 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -81044,4 +81044,3 @@ USE `vncontrol`; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2022-09-16 10:44:31 - diff --git a/db/import-changes.sh b/db/import-changes.sh index 2b80654d3e..5461f003bb 100755 --- a/db/import-changes.sh +++ b/db/import-changes.sh @@ -81,9 +81,9 @@ N_CHANGES=0 for DIR_PATH in "$DIR/changes/"*; do DIR_NAME=$(basename $DIR_PATH) - DIR_VERSION=${DIR_NAME:0:5} + DIR_VERSION=${DIR_NAME:0:6} - if [[ ! "$DIR_NAME" =~ ^[0-9]{5}(-[a-zA-Z0-9]+)?$ ]]; then + if [[ ! "$DIR_NAME" =~ ^[0-9]{6}$ ]]; then echo "[WARN] Ignoring wrong directory name: $DIR_NAME" continue fi diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 06fabe3e61..f550e3a9da 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -29,6 +29,11 @@ export default { firstModulePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="push_pin"]', firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]' }, + recoverPassword: { + recoverPasswordButton: 'vn-login a[ui-sref="recoverPassword"]', + email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]', + sendEmailButton: 'vn-recover-password vn-submit', + }, accountIndex: { addAccount: 'vn-user-index button vn-icon[icon="add"]', newName: 'vn-user-create vn-textfield[ng-model="$ctrl.user.name"]', diff --git a/e2e/paths/01-salix/04_recoverPassword.spec.js b/e2e/paths/01-salix/04_recoverPassword.spec.js new file mode 100644 index 0000000000..80ef32cb51 --- /dev/null +++ b/e2e/paths/01-salix/04_recoverPassword.spec.js @@ -0,0 +1,40 @@ +import selectors from '../../helpers/selectors'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Login path', async() => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + + await page.waitToClick(selectors.recoverPassword.recoverPasswordButton); + await page.waitForState('recoverPassword'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should not throw error if not exist user', async() => { + await page.write(selectors.recoverPassword.email, 'fakeEmail@mydomain.com'); + await page.waitToClick(selectors.recoverPassword.sendEmailButton); + + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Notification sent!'); + }); + + it('should send email', async() => { + await page.waitForState('login'); + await page.waitToClick(selectors.recoverPassword.recoverPasswordButton); + + await page.write(selectors.recoverPassword.email, 'BruceWayne@mydomain.com'); + await page.waitToClick(selectors.recoverPassword.sendEmailButton); + const message = await page.waitForSnackbar(); + await page.waitForState('login'); + + expect(message.text).toContain('Notification sent!'); + }); +}); diff --git a/e2e/paths/05-ticket/20_future.spec.js b/e2e/paths/05-ticket/20_future.spec.js index 4fee9523bd..6db2bf4f00 100644 --- a/e2e/paths/05-ticket/20_future.spec.js +++ b/e2e/paths/05-ticket/20_future.spec.js @@ -5,80 +5,78 @@ describe('Ticket Future path', () => { let browser; let page; - beforeAll(async () => { + beforeAll(async() => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('employee', 'ticket'); await page.accessToSection('ticket.future'); }); - afterAll(async () => { + afterAll(async() => { await browser.close(); }); const now = new Date(); const tomorrow = new Date(now.getDate() + 1); - const ticket = { - originDated: now, - futureDated: now, - linesMax: '9999', - litersMax: '9999', - warehouseFk: 'Warehouse One' - }; - it('should show errors snackbar because of the required data', async () => { + it('should show errors snackbar because of the required data', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.warehouseFk); await page.waitToClick(selectors.ticketFuture.submit); let message = await page.waitForSnackbar(); + expect(message.text).toContain('warehouseFk is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.litersMax); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); + expect(message.text).toContain('litersMax is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.linesMax); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); + expect(message.text).toContain('linesMax is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.futureDated); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); + expect(message.text).toContain('futureDated is a required argument'); await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.originDated); await page.waitToClick(selectors.ticketFuture.submit); message = await page.waitForSnackbar(); + expect(message.text).toContain('originDated is a required argument'); }); - it('should search with the required data', async () => { + it('should search with the required data', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search with the origin shipped today', async () => { + it('should search with the origin shipped today', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.pickDate(selectors.ticketFuture.shipped, now); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search with the origin shipped tomorrow', async () => { + it('should search with the origin shipped tomorrow', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.pickDate(selectors.ticketFuture.shipped, tomorrow); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); }); - it('should search with the destination shipped today', async () => { + it('should search with the destination shipped today', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.shipped); await page.pickDate(selectors.ticketFuture.tfShipped, now); @@ -86,14 +84,14 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search with the destination shipped tomorrow', async () => { + it('should search with the destination shipped tomorrow', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow); await page.waitToClick(selectors.ticketFuture.submit); await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); }); - it('should search with the origin IPT', async () => { + it('should search with the origin IPT', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.shipped); @@ -108,7 +106,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); }); - it('should search with the destination IPT', async () => { + it('should search with the destination IPT', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.shipped); @@ -123,7 +121,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); }); - it('should search with the origin grouped state', async () => { + it('should search with the origin grouped state', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.shipped); @@ -138,7 +136,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 3); }); - it('should search with the destination grouped state', async () => { + it('should search with the destination grouped state', async() => { await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton); await page.clearInput(selectors.ticketFuture.shipped); @@ -164,10 +162,10 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with an ID Origin', async () => { + it('should search in smart-table with an ID Origin', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableId, "13"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableId, '13'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 2); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -176,10 +174,10 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with an ID Destination', async () => { + it('should search in smart-table with an ID Destination', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableTfId, "12"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableTfId, '12'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -188,7 +186,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with an IPT Origin', async () => { + it('should search in smart-table with an IPT Origin', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.autocompleteSearch(selectors.ticketFuture.tableIpt, 'Vertical'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); @@ -199,7 +197,7 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with an IPT Destination', async () => { + it('should search in smart-table with an IPT Destination', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); @@ -210,10 +208,10 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with especified Lines', async () => { + it('should search in smart-table with especified Lines', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLines, "0"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableLines, '0'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -222,8 +220,8 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLines, "1"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableLines, '1'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -232,10 +230,10 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should search in smart-table with especified Liters', async () => { + it('should search in smart-table with especified Liters', async() => { await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLiters, "0"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableLiters, '0'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 1); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -244,8 +242,8 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); - await page.write(selectors.ticketFuture.tableLiters, "28"); - await page.keyboard.press("Enter"); + await page.write(selectors.ticketFuture.tableLiters, '28'); + await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.ticketFuture.table, 5); await page.waitToClick(selectors.ticketFuture.tableButtonSearch); @@ -254,13 +252,13 @@ describe('Ticket Future path', () => { await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); }); - it('should check the three last tickets and move to the future', async () => { + it('should check the three last tickets and move to the future', async() => { await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.firstCheck); await page.waitToClick(selectors.ticketFuture.moveButton); await page.waitToClick(selectors.ticketFuture.acceptButton); const message = await page.waitForSnackbar(); + expect(message.text).toContain('Tickets moved successfully!'); }); - }); diff --git a/front/core/lib/component.js b/front/core/lib/component.js index f17db68a2a..5695d9449c 100644 --- a/front/core/lib/component.js +++ b/front/core/lib/component.js @@ -12,9 +12,10 @@ export default class Component extends EventEmitter { * @param {HTMLElement} $element The main component element * @param {$rootScope.Scope} $scope The element scope * @param {Function} $transclude The transclusion function + * @param {Function} $location The location function */ - constructor($element, $scope, $transclude) { - super(); + constructor($element, $scope, $transclude, $location) { + super($element, $scope, $transclude, $location); this.$ = $scope; if (!$element) return; @@ -164,7 +165,7 @@ export default class Component extends EventEmitter { $transclude.$$boundTransclude.$$slots[slot]; } } -Component.$inject = ['$element', '$scope']; +Component.$inject = ['$element', '$scope', '$location', '$state']; /* * Automatically adds the most used services to the prototype, so they are diff --git a/front/core/services/auth.js b/front/core/services/auth.js index a1dcfa3953..04520cd0b5 100644 --- a/front/core/services/auth.js +++ b/front/core/services/auth.js @@ -23,7 +23,10 @@ export default class Auth { initialize() { let criteria = { - to: state => state.name != 'login' + to: state => { + const outLayout = ['login', 'recoverPassword', 'resetPassword']; + return !outLayout.some(ol => ol == state.name); + } }; this.$transitions.onStart(criteria, transition => { if (this.loggedIn) diff --git a/front/salix/components/app/app.html b/front/salix/components/app/app.html index d32c9f68bc..f14fab2dd3 100644 --- a/front/salix/components/app/app.html +++ b/front/salix/components/app/app.html @@ -1,9 +1,8 @@ - - + diff --git a/front/salix/components/app/app.js b/front/salix/components/app/app.js index 1f8cdb46e7..20f0ad969b 100644 --- a/front/salix/components/app/app.js +++ b/front/salix/components/app/app.js @@ -9,13 +9,20 @@ import Component from 'core/lib/component'; * @property {SideMenu} rightMenu The left menu, if it's present */ export default class App extends Component { + constructor($element, $, $location, $state) { + super($element, $, $location, $state); + this.$location = $location; + this.$state = $state; + } + $postLink() { this.vnApp.logger = this; } get showLayout() { - let state = this.$state.current.name; - return state && state != 'login'; + const state = this.$state.current.name || this.$location.$$path.substring(1).replace('/', '.'); + const outLayout = ['login', 'recoverPassword', 'resetPassword']; + return state && !outLayout.some(ol => ol == state); } $onDestroy() { diff --git a/front/salix/components/index.js b/front/salix/components/index.js index ce4ad585aa..dbe9fe81ae 100644 --- a/front/salix/components/index.js +++ b/front/salix/components/index.js @@ -5,7 +5,10 @@ import './descriptor-popover'; import './home/home'; import './layout'; import './left-menu/left-menu'; +import './login/index'; import './login/login'; +import './login/recover-password'; +import './login/reset-password'; import './module-card'; import './module-main'; import './side-menu/side-menu'; diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index 2796931a07..f30878b9f8 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -37,7 +37,7 @@ export default class Controller extends Section { const validations = window.validations; value.forEach(log => { - const locale = validations[log.changedModel].locale ? validations[log.changedModel].locale : {}; + const locale = validations[log.changedModel] && validations[log.changedModel].locale ? validations[log.changedModel].locale : {}; log.oldProperties = this.getInstance(log.oldInstance, locale); log.newProperties = this.getInstance(log.newInstance, locale); diff --git a/front/salix/components/login/index.html b/front/salix/components/login/index.html new file mode 100644 index 0000000000..186979f8ce --- /dev/null +++ b/front/salix/components/login/index.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/front/salix/components/login/index.js b/front/salix/components/login/index.js new file mode 100644 index 0000000000..f0e21fa299 --- /dev/null +++ b/front/salix/components/login/index.js @@ -0,0 +1,16 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; +import './style.scss'; + +export default class OutLayout extends Component { + constructor($element, $scope) { + super($element, $scope); + } +} + +OutLayout.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnOutLayout', { + template: require('./index.html'), + controller: OutLayout +}); diff --git a/front/salix/components/login/locale/en.yml b/front/salix/components/login/locale/en.yml index c59a6dd8e6..1ddd454b70 100644 --- a/front/salix/components/login/locale/en.yml +++ b/front/salix/components/login/locale/en.yml @@ -1,4 +1,8 @@ User: User Password: Password Do not close session: Do not close session -Enter: Enter \ No newline at end of file +Enter: Enter +Password requirements: > + The password must have at least {{ length }} length characters, + {{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}} + digits and {{nPunct}} symbols (Ex: $%&.) diff --git a/front/salix/components/login/locale/es.yml b/front/salix/components/login/locale/es.yml index 9c9ba59058..e3a5815c1c 100644 --- a/front/salix/components/login/locale/es.yml +++ b/front/salix/components/login/locale/es.yml @@ -1,4 +1,16 @@ User: Usuario Password: Contraseña +Email: Correo electrónico Do not close session: No cerrar sesión -Enter: Entrar \ No newline at end of file +Enter: Entrar +I do not remember my password: No recuerdo mi contraseña +Recover password: Recuperar contraseña +We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña +Notification sent!: ¡Notificación enviada! +Reset password: Restrablecer contraseña +New password: Nueva contraseña +Repeat password: Repetir contraseña +Password requirements: > + La contraseña debe tener al menos {{ length }} caracteres de longitud, + {{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}} + dígitos y {{nPunct}} símbolos (Ej: $%&.) diff --git a/front/salix/components/login/login.html b/front/salix/components/login/login.html index b15714a232..a078fa0af0 100644 --- a/front/salix/components/login/login.html +++ b/front/salix/components/login/login.html @@ -1,27 +1,27 @@ -
- -
- - - - - - - -
+ + + + + + + diff --git a/front/salix/components/login/recover-password.html b/front/salix/components/login/recover-password.html new file mode 100644 index 0000000000..73f5401d9e --- /dev/null +++ b/front/salix/components/login/recover-password.html @@ -0,0 +1,17 @@ +
Recover password
+ + +
+ We will sent you an email to recover your password +
+ diff --git a/front/salix/components/login/recover-password.js b/front/salix/components/login/recover-password.js new file mode 100644 index 0000000000..fa9bfc4599 --- /dev/null +++ b/front/salix/components/login/recover-password.js @@ -0,0 +1,37 @@ +import ngModule from '../../module'; +import './style.scss'; + +export default class Controller { + constructor($scope, $element, $http, vnApp, $translate, $state) { + Object.assign(this, { + $scope, + $element, + $http, + vnApp, + $translate, + $state + }); + } + + goToLogin() { + this.vnApp.showSuccess(this.$translate.instant('Notification sent!')); + this.$state.go('login'); + } + + submit() { + const params = { + email: this.email + }; + + this.$http.post('Accounts/recoverPassword', params) + .then(() => { + this.goToLogin(); + }); + } +} +Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state']; + +ngModule.vnComponent('vnRecoverPassword', { + template: require('./recover-password.html'), + controller: Controller +}); diff --git a/front/salix/components/login/reset-password.html b/front/salix/components/login/reset-password.html new file mode 100644 index 0000000000..bdbdc113ed --- /dev/null +++ b/front/salix/components/login/reset-password.html @@ -0,0 +1,19 @@ +
Reset password
+ + + + + diff --git a/front/salix/components/login/reset-password.js b/front/salix/components/login/reset-password.js new file mode 100644 index 0000000000..9ee1fdb620 --- /dev/null +++ b/front/salix/components/login/reset-password.js @@ -0,0 +1,48 @@ +import ngModule from '../../module'; +import './style.scss'; + +export default class Controller { + constructor($scope, $element, $http, vnApp, $translate, $state, $location) { + Object.assign(this, { + $scope, + $element, + $http, + vnApp, + $translate, + $state, + $location + }); + } + + $onInit() { + this.$http.get('UserPasswords/findOne') + .then(res => { + this.passRequirements = res.data; + }); + } + + submit() { + if (!this.newPassword) + throw new UserError(`You must enter a new password`); + if (this.newPassword != this.repeatPassword) + throw new UserError(`Passwords don't match`); + + const headers = { + Authorization: this.$location.$$search.access_token + }; + + const newPassword = this.newPassword; + + this.$http.post('users/reset-password', {newPassword}, {headers}) + .then(() => { + this.vnApp.showSuccess(this.$translate.instant('Password changed!')); + this.$state.go('login'); + }); + } +} +Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location']; + +ngModule.vnComponent('vnResetPassword', { + template: require('./reset-password.html'), + controller: Controller +}); diff --git a/front/salix/components/login/style.scss b/front/salix/components/login/style.scss index 8ebf2a68c5..8985893f2a 100644 --- a/front/salix/components/login/style.scss +++ b/front/salix/components/login/style.scss @@ -1,6 +1,31 @@ @import "variables"; -vn-login { +vn-login, +vn-reset-password, +vn-recover-password{ + .footer { + margin-top: 32px; + text-align: center; + position: relative; + & > .vn-submit { + display: block; + + & > input { + display: block; + width: 100%; + } + } + & > .spinner-wrapper { + position: absolute; + width: 0; + top: 3px; + right: -8px; + overflow: visible; + } + } +} + +vn-out-layout{ position: absolute; height: 100%; width: 100%; @@ -39,28 +64,17 @@ vn-login { white-space: inherit; } } - & > .footer { - margin-top: 32px; - text-align: center; - position: relative; - - & > vn-submit { - display: block; - - & > input { - display: block; - width: 100%; - } - } - & > .spinner-wrapper { - position: absolute; - width: 0; - top: 3px; - right: -8px; - overflow: visible; - } - } } + + h5{ + color: $color-primary; + } + + .text-secondary{ + text-align: center; + padding-bottom: 16px; + } + } @media screen and (max-width: 600px) { @@ -71,4 +85,8 @@ vn-login { box-shadow: none; } } + + a{ + color: $color-primary; + } } diff --git a/front/salix/module.js b/front/salix/module.js index a8de61ae07..01df01a672 100644 --- a/front/salix/module.js +++ b/front/salix/module.js @@ -112,7 +112,7 @@ function $exceptionHandler(vnApp, $window, $state, $injector) { switch (exception.status) { case 401: - if ($state.current.name != 'login') { + if (!$state.current.name.includes('login')) { messageT = 'Session has expired'; let params = {continue: $window.location.hash}; $state.go('login', params); diff --git a/front/salix/routes.js b/front/salix/routes.js index 600907ff1e..be893800f9 100644 --- a/front/salix/routes.js +++ b/front/salix/routes.js @@ -9,9 +9,17 @@ function config($stateProvider, $urlRouterProvider) { .state('login', { url: '/login?continue', description: 'Login', - views: { - login: {template: ''} - } + template: '' + }) + .state('recoverPassword', { + url: '/recover-password', + description: 'Recover-password', + template: 'asd' + }) + .state('resetPassword', { + url: '/reset-password', + description: 'Reset-password', + template: '' }) .state('home', { url: '/', diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 2abf17b4b9..d4695f72c2 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -131,12 +131,15 @@ "Fichadas impares": "Odd signs", "Descanso diario 9h.": "Daily rest 9h.", "Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.", + "Verify email": "Verify email", + "Click on the following link to verify this email. If you haven't requested this email, just ignore it": "Click on the following link to verify this email. If you haven't requested this email, just ignore it", "Password does not meet requirements": "Password does not meet requirements", "You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies", "Not enough privileges to edit a client": "Not enough privileges to edit a client", "Claim pickup order sent": "Claim pickup order sent [{{claimId}}]({{{claimUrl}}}) to client *{{clientName}}*", "You don't have grant privilege": "You don't have grant privilege", "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user", + "Email verify": "Email verify", "Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})", "Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production", "Receipt's bank was not found": "Receipt's bank was not found", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index c1e09e9f62..1b0a504336 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -245,6 +245,8 @@ "Already has this status": "Ya tiene este estado", "There aren't records for this week": "No existen registros para esta semana", "Empty data source": "Origen de datos vacio", + "Email verify": "Correo de verificación", + "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente" diff --git a/loopback/server/datasources.json b/loopback/server/datasources.json index 4db642058f..00f6bf624e 100644 --- a/loopback/server/datasources.json +++ b/loopback/server/datasources.json @@ -113,4 +113,4 @@ "application/x-7z-compressed" ] } -} \ No newline at end of file +} diff --git a/modules/account/back/models/user-password.json b/modules/account/back/models/user-password.json index 1b7e49edd7..53909ad1fa 100644 --- a/modules/account/back/models/user-password.json +++ b/modules/account/back/models/user-password.json @@ -30,5 +30,13 @@ "type": "number", "required": true } - } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] } diff --git a/modules/account/front/basic-data/index.js b/modules/account/front/basic-data/index.js index 342297e458..77d3eab26c 100644 --- a/modules/account/front/basic-data/index.js +++ b/modules/account/front/basic-data/index.js @@ -2,6 +2,11 @@ import ngModule from '../module'; import Section from 'salix/components/section'; export default class Controller extends Section { + $onInit() { + if (this.$params.emailConfirmed) + this.vnApp.showSuccess(this.$t('Email verified successfully!')); + } + onSubmit() { this.$.watcher.submit() .then(() => this.card.reload()); diff --git a/modules/account/front/basic-data/locale/es.yml b/modules/account/front/basic-data/locale/es.yml new file mode 100644 index 0000000000..2ca7bf6987 --- /dev/null +++ b/modules/account/front/basic-data/locale/es.yml @@ -0,0 +1 @@ +Email verified successfully!: Correo verificado correctamente! diff --git a/modules/account/front/routes.json b/modules/account/front/routes.json index b96c931c95..a6f2f5d3f5 100644 --- a/modules/account/front/routes.json +++ b/modules/account/front/routes.json @@ -74,7 +74,7 @@ } }, { - "url": "/basic-data", + "url": "/basic-data?emailConfirmed", "state": "account.card.basicData", "component": "vn-user-basic-data", "description": "Basic data", diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js index 3e1ea43bb8..1ae569fd37 100644 --- a/modules/client/back/methods/client/filter.js +++ b/modules/client/back/methods/client/filter.js @@ -91,7 +91,18 @@ module.exports = Self => { case 'search': return /^\d+$/.test(value) ? {'c.id': {inq: value}} - : {'c.name': {like: `%${value}%`}}; + : {or: [ + {'c.name': {like: `%${value}%`}}, + {'c.socialName': {like: `%${value}%`}}, + ]}; + case 'phone': + return {or: [ + {'c.phone': {like: `%${value}%`}}, + {'c.mobile': {like: `%${value}%`}}, + ]}; + case 'zoneFk': + param = 'a.postalCode'; + return {[param]: {inq: postalCode}}; case 'name': case 'salesPersonFk': case 'fi': @@ -100,12 +111,8 @@ module.exports = Self => { case 'postcode': case 'provinceFk': case 'email': - case 'phone': param = `c.${param}`; - return {[param]: value}; - case 'zoneFk': - param = 'a.postalCode'; - return {[param]: {inq: postalCode}}; + return {[param]: {like: `%${value}%`}}; } }); @@ -119,6 +126,7 @@ module.exports = Self => { c.fi, c.socialName, c.phone, + c.mobile, c.city, c.postcode, c.email, @@ -132,7 +140,7 @@ module.exports = Self => { LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN province p ON p.id = c.provinceFk JOIN vn.address a ON a.clientFk = c.id - ` + ` ); stmt.merge(conn.makeWhere(filter.where)); diff --git a/modules/client/back/methods/client/specs/updatePortfolio.spec.js b/modules/client/back/methods/client/specs/updatePortfolio.spec.js deleted file mode 100644 index bf681eb2ed..0000000000 --- a/modules/client/back/methods/client/specs/updatePortfolio.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); - -describe('Client updatePortfolio', () => { - const activeCtx = { - accessToken: {userId: 9}, - http: { - req: { - headers: {origin: 'http://localhost'}, - [`__`]: value => { - return value; - } - } - } - }; - - beforeAll(() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - }); - - it('should update the portfolioWeight when the salesPerson of a client changes', async() => { - const clientId = 1108; - const salesPersonId = 18; - - const tx = await models.Client.beginTransaction({}); - - try { - const options = {transaction: tx}; - const expectedResult = 841.63; - - const client = await models.Client.findById(clientId, null, options); - await client.updateAttribute('salesPersonFk', salesPersonId, options); - - await models.Client.updatePortfolio(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); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should keep the same portfolioWeight when a salesperson is unassigned of a client', async() => { - const clientId = 1107; - const salesPersonId = 19; - const tx = await models.Client.beginTransaction({}); - - try { - const options = {transaction: tx}; - const expectedResult = 34.40; - - const client = await models.Client.findById(clientId, null, options); - await client.updateAttribute('salesPersonFk', null, options); - - await models.Client.updatePortfolio(); - - const portfolioQuery = `SELECT portfolioWeight FROM bs.salesPerson WHERE workerFk = ${salesPersonId}; `; - const [salesPerson] = await models.Client.rawSql(portfolioQuery); - - expect(salesPerson.portfolioWeight).toEqual(expectedResult); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/client/back/methods/client/updatePortfolio.js b/modules/client/back/methods/client/updatePortfolio.js deleted file mode 100644 index 809a84636b..0000000000 --- a/modules/client/back/methods/client/updatePortfolio.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = function(Self) { - Self.remoteMethodCtx('updatePortfolio', { - description: 'Update salesPeson potfolio weight', - accessType: 'READ', - accepts: [], - returns: { - type: 'Object', - root: true - }, - http: { - path: `/updatePortfolio`, - verb: 'GET' - } - }); - - Self.updatePortfolio = async options => { - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - query = `CALL bs.salesPerson_updatePortfolio()`; - return Self.rawSql(query, null, myOptions); - }; -}; diff --git a/modules/client/back/methods/receipt/filter.js b/modules/client/back/methods/receipt/filter.js index 9549fb0011..6df5e73f8a 100644 --- a/modules/client/back/methods/receipt/filter.js +++ b/modules/client/back/methods/receipt/filter.js @@ -57,14 +57,16 @@ module.exports = Self => { r.clientFk, FALSE hasPdf, FALSE isInvoice, - CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation + at2.id IS NOT NULL as isCompensation FROM vn.receipt r LEFT JOIN vn.worker w ON w.id = r.workerFk LEFT JOIN account.user u ON u.id = w.userFk JOIN vn.company c ON c.id = r.companyFk - JOIN vn.accounting a ON a.id = r.bankFk - JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk - WHERE r.clientFk = ? AND r.companyFk = ? + LEFT JOIN vn.accounting a ON a.id = r.bankFk + LEFT JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk AND at2.code = 'compensation' + WHERE + r.clientFk = ? + AND r.companyFk = ? UNION ALL SELECT i.id, @@ -81,13 +83,10 @@ module.exports = Self => { i.clientFk, i.hasPdf, TRUE isInvoice, - CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation + NULL FROM vn.invoiceOut i JOIN vn.company c ON c.id = i.companyFk - JOIN vn.accounting a ON a.id = i.bankFk - JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk WHERE i.clientFk = ? AND i.companyFk = ? - ORDER BY payed DESC, created DESC ) t ORDER BY payed DESC, created DESC`, [ clientId, diff --git a/modules/client/back/models/client-methods.js b/modules/client/back/models/client-methods.js index 5134e39422..4b20a822c1 100644 --- a/modules/client/back/models/client-methods.js +++ b/modules/client/back/models/client-methods.js @@ -22,7 +22,6 @@ module.exports = Self => { require('../methods/client/summary')(Self); require('../methods/client/updateAddress')(Self); require('../methods/client/updateFiscalData')(Self); - require('../methods/client/updatePortfolio')(Self); require('../methods/client/updateUser')(Self); require('../methods/client/uploadFile')(Self); require('../methods/client/campaignMetricsPdf')(Self); diff --git a/modules/client/front/basic-data/index.js b/modules/client/front/basic-data/index.js index 418663952c..b08d642d11 100644 --- a/modules/client/front/basic-data/index.js +++ b/modules/client/front/basic-data/index.js @@ -10,8 +10,6 @@ export default class Controller extends Section { onSubmit() { return this.$.watcher.submit().then(() => { - const query = `Clients/updatePortfolio`; - this.$http.get(query); this.$http.get(`Clients/${this.$params.id}/checkDuplicatedData`); }); } diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js index 803338ef33..ee3310368e 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createPdf.spec.js @@ -11,6 +11,7 @@ describe('InvoiceOut createPdf()', () => { const ctx = {req: activeCtx}; it('should create a new PDF file and set true the hasPdf property', async() => { + pending('https://redmine.verdnatura.es/issues/4875'); const invoiceId = 1; spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js index 08f0497837..536fa07a00 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/downloadZip.spec.js @@ -30,6 +30,7 @@ describe('InvoiceOut downloadZip()', () => { }); it('should return an error if the size of the files is too large', async() => { + pending('https://redmine.verdnatura.es/issues/4875'); const tx = await models.InvoiceOut.beginTransaction({}); let error; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js index 02f982011c..7b58862365 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/filter.spec.js @@ -51,6 +51,7 @@ describe('InvoiceOut filter()', () => { }); it('should return the invoice out matching hasPdf', async() => { + pending('https://redmine.verdnatura.es/issues/4875'); const tx = await models.InvoiceOut.beginTransaction({}); const options = {transaction: tx}; diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index 18e5abaf2e..708644c1a2 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -33,36 +33,36 @@ module.exports = Self => { const stmt = new ParameterizedSQL( `SELECT - t.id, - t.packages, - t.warehouseFk, - t.nickname, - t.clientFk, - t.priority, - t.addressFk, - st.code AS ticketStateCode, - st.name AS ticketStateName, - wh.name AS warehouseName, - tob.description AS ticketObservation, - a.street, - a.postalCode, - a.city, - am.name AS agencyModeName, - u.nickname AS userNickname, - vn.ticketTotalVolume(t.id) AS volume, - tob.description - FROM route r - JOIN ticket t ON t.routeFk = r.id - LEFT JOIN ticketState ts ON ts.ticketFk = t.id - LEFT JOIN state st ON st.id = ts.stateFk - LEFT JOIN warehouse wh ON wh.id = t.warehouseFk - LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id - LEFT JOIN observationType ot ON tob.observationTypeFk = ot.id - AND ot.code = 'delivery' - LEFT JOIN address a ON a.id = t.addressFk - LEFT JOIN agencyMode am ON am.id = t.agencyModeFk - LEFT JOIN account.user u ON u.id = r.workerFk - LEFT JOIN vehicle v ON v.id = r.vehicleFk` + t.id, + t.packages, + t.warehouseFk, + t.nickname, + t.clientFk, + t.priority, + t.addressFk, + st.code AS ticketStateCode, + st.name AS ticketStateName, + wh.name AS warehouseName, + tob.description AS ticketObservation, + a.street, + a.postalCode, + a.city, + am.name AS agencyModeName, + u.nickname AS userNickname, + vn.ticketTotalVolume(t.id) AS volume, + tob.description + FROM vn.route r + JOIN ticket t ON t.routeFk = r.id + LEFT JOIN ticketState ts ON ts.ticketFk = t.id + LEFT JOIN state st ON st.id = ts.stateFk + LEFT JOIN warehouse wh ON wh.id = t.warehouseFk + LEFT JOIN observationType ot ON ot.code = 'delivery' + LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id + AND tob.observationTypeFk = ot.id + LEFT JOIN address a ON a.id = t.addressFk + LEFT JOIN agencyMode am ON am.id = t.agencyModeFk + LEFT JOIN account.user u ON u.id = r.workerFk + LEFT JOIN vehicle v ON v.id = r.vehicleFk` ); if (!filter.where) filter.where = {}; diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index baa6a0b41b..e33428bf03 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -137,9 +137,11 @@ module.exports = Self => { const params = [args.id, args.shipped, args.warehouseFk]; const [salesMovable] = await Self.rawSql(query, params, myOptions); + const sales = await models.Sale.find({ticketFk: args.id}, myOptions); const salesNewTicket = salesMovable.filter(sale => (sale.movable ? sale.movable : 0) >= sale.quantity); - if (salesNewTicket.length) { + const salesNewTicketLength = salesNewTicket.length; + if (salesNewTicketLength && sales.length != salesNewTicketLength) { const newTicket = await models.Ticket.transferSales(ctx, args.id, null, salesNewTicket, myOptions); args.id = newTicket.id; } diff --git a/modules/ticket/front/basic-data/step-two/index.js b/modules/ticket/front/basic-data/step-two/index.js index 32d6b2cd69..4ac9f292e6 100644 --- a/modules/ticket/front/basic-data/step-two/index.js +++ b/modules/ticket/front/basic-data/step-two/index.js @@ -67,6 +67,7 @@ class Controller extends Component { ticketHaveNegatives() { let haveNegatives = false; let haveNotNegatives = false; + this.ticket.withoutNegatives = false; const haveDifferences = this.ticket.sale.haveDifferences; this.ticket.sale.items.forEach(item => { @@ -76,8 +77,9 @@ class Controller extends Component { haveNotNegatives = true; }); - this.ticket.withoutNegatives = true; this.haveNegatives = (haveNegatives && haveNotNegatives && haveDifferences); + if (this.haveNegatives) + this.ticket.withoutNegatives = true; } onSubmit() { diff --git a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js new file mode 100644 index 0000000000..7825f38b84 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js @@ -0,0 +1,181 @@ +const Imap = require('imap'); +module.exports = Self => { + Self.remoteMethod('checkInbox', { + description: 'Check an email inbox and process it', + accessType: 'READ', + returns: + { + arg: 'body', + type: 'file', + root: true + }, + http: { + path: `/checkInbox`, + verb: 'POST' + } + }); + + Self.checkInbox = async() => { + let imapConfig = await Self.app.models.WorkerTimeControlParams.findOne(); + let imap = new Imap({ + user: imapConfig.mailUser, + password: imapConfig.mailPass, + host: imapConfig.mailHost, + port: 993, + tls: true + }); + let isEmailOk; + let uid; + let emailBody; + + function openInbox(cb) { + imap.openBox('INBOX', true, cb); + } + + imap.once('ready', function() { + openInbox(function(err, box) { + if (err) throw err; + const totalMessages = box.messages.total; + if (totalMessages == 0) + imap.end(); + + let f = imap.seq.fetch('1:*', { + bodies: ['HEADER.FIELDS (FROM SUBJECT)', '1'], + struct: true + }); + f.on('message', function(msg, seqno) { + isEmailOk = false; + msg.on('body', function(stream, info) { + let buffer = ''; + let bufferCopy = ''; + stream.on('data', function(chunk) { + buffer = chunk.toString('utf8'); + if (info.which === '1' && bufferCopy.length == 0) + bufferCopy = buffer.replace(/\s/g, ' '); + }); + stream.on('end', function() { + if (bufferCopy.length > 0) { + emailBody = bufferCopy.toUpperCase().trim(); + + const bodyPositionOK = emailBody.match(/\bOK\b/i); + if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122)) + isEmailOk = true; + else + isEmailOk = false; + } + }); + msg.once('attributes', function(attrs) { + uid = attrs.uid; + }); + msg.once('end', function() { + if (info.which === 'HEADER.FIELDS (FROM SUBJECT)') { + if (isEmailOk) { + imap.move(uid, 'exito', function(err) { + }); + emailConfirm(buffer); + } else { + imap.move(uid, 'error', function(err) { + }); + emailReply(buffer, emailBody); + } + } + }); + }); + }); + f.once('end', function() { + imap.end(); + }); + }); + }); + + imap.connect(); + return 'Leer emails de gestion horaria'; + }; + + async function emailConfirm(buffer) { + const now = new Date(); + const from = JSON.stringify(Imap.parseHeader(buffer).from); + const subject = JSON.stringify(Imap.parseHeader(buffer).subject); + + const timeControlDate = await getEmailDate(subject); + const week = timeControlDate[0]; + const year = timeControlDate[1]; + const user = await getUser(from); + let workerMail; + + if (user.id != null) { + workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ + where: { + week: week, + year: year, + workerFk: user.id + } + }); + } + if (workerMail != null) { + await workerMail.updateAttributes({ + updated: now, + state: 'CONFIRMED' + }); + } + } + + async function emailReply(buffer, emailBody) { + const now = new Date(); + const from = JSON.stringify(Imap.parseHeader(buffer).from); + const subject = JSON.stringify(Imap.parseHeader(buffer).subject); + + const timeControlDate = await getEmailDate(subject); + const week = timeControlDate[0]; + const year = timeControlDate[1]; + const user = await getUser(from); + let workerMail; + + if (user.id != null) { + workerMail = await Self.app.models.WorkerTimeControlMail.findOne({ + where: { + week: week, + year: year, + workerFk: user.id + } + }); + + if (workerMail != null) { + await workerMail.updateAttributes({ + updated: now, + state: 'REVISE', + reason: emailBody + }); + } else + await sendMail(user, subject, emailBody); + } + } + + async function getUser(workerEmail) { + const userEmail = workerEmail.match(/(?<=<)(.*?)(?=>)/); + + let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u + LEFT JOIN account.mailForward m on m.account = u.id + WHERE forwardTo =? OR + CONCAT(u.name,'@verdnatura.es') = ?`, + [userEmail[0], userEmail[0]]); + + return user; + } + + async function getEmailDate(subject) { + const date = subject.match(/\d+/g); + return date; + } + + async function sendMail(user, subject, emailBody) { + const sendTo = 'rrhh@verdnatura.es'; + const emailSubject = subject + ' ' + user.name; + + await Self.app.models.Mail.create({ + receiver: sendTo, + subject: emailSubject, + body: emailBody + }); + } +}; diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js index 2f9559b3a1..b38405c1d5 100644 --- a/modules/worker/back/methods/worker-time-control/sendMail.js +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -133,7 +133,7 @@ module.exports = Self => { tb.permissionRate, d.isTeleworking FROM tmp.timeBusinessCalculate tb - JOIN user u ON u.id = tb.userFk + JOIN account.user u ON u.id = tb.userFk JOIN department d ON d.id = tb.departmentFk JOIN business b ON b.id = tb.businessFk LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated @@ -143,7 +143,7 @@ module.exports = Self => { IF(tc.timeWorkDecimal > 0, FALSE, IF(tb.timeWorkDecimal > 0, TRUE, FALSE)), TRUE))isTeleworkingWeek FROM tmp.timeBusinessCalculate tb - LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk + LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated GROUP BY tb.userFk HAVING isTeleworkingWeek > 0 @@ -332,18 +332,9 @@ module.exports = Self => { }, myOptions); const timestamp = started.getTime() / 1000; - await models.Mail.create({ - receiver: previousReceiver, - subject: $t('Record of hours week', { - week: args.week, - year: args.year - }), - body: `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}` - }, myOptions); + const url = `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`; - query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week) - VALUES (?, ?, ?);`; - await Self.rawSql(query, [previousWorkerFk, args.year, args.week], myOptions); + await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, previousReceiver, args.week, args.year, url); previousWorkerFk = day.workerFk; previousReceiver = day.receiver; diff --git a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js index 4cc6e54e3b..24bfd69041 100644 --- a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js @@ -2,15 +2,12 @@ const models = require('vn-loopback/server/server').models; describe('workerTimeControl sendMail()', () => { const workerId = 18; - const ctx = { - req: { - __: value => { - return value; - } - }, - args: {} - + const activeCtx = { + getLocale: () => { + return 'en'; + } }; + const ctx = {req: activeCtx, args: {}}; it('should fill time control of a worker without records in Journey and with rest', async() => { const tx = await models.WorkerTimeControl.beginTransaction({}); diff --git a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js new file mode 100644 index 0000000000..0cf614e572 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js @@ -0,0 +1,53 @@ +const {Email} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('weeklyHourRecordEmail', { + description: 'Sends the buyer waste email', + accessType: 'WRITE', + accepts: [ + { + arg: 'recipient', + type: 'string', + description: 'The recipient email', + required: true, + }, + { + arg: 'week', + type: 'number', + required: true, + }, + { + arg: 'year', + type: 'number', + required: true + }, + { + arg: 'url', + type: 'string', + required: true + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/weekly-hour-hecord-email', + verb: 'POST' + } + }); + + Self.weeklyHourRecordEmail = async(ctx, recipient, week, year, url) => { + const params = { + recipient: recipient, + lang: ctx.req.getLocale(), + week: week, + year: year, + url: url + }; + + const email = new Email('weekly-hour-record', params); + + return email.send(); + }; +}; diff --git a/modules/worker/back/models/worker-time-control-mail.js b/modules/worker/back/models/worker-time-control-mail.js new file mode 100644 index 0000000000..36f3851b6d --- /dev/null +++ b/modules/worker/back/models/worker-time-control-mail.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/worker-time-control-mail/checkInbox')(Self); +}; diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 9f802511aa..7339f5d157 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -7,6 +7,7 @@ module.exports = Self => { require('../methods/worker-time-control/updateTimeEntry')(Self); require('../methods/worker-time-control/sendMail')(Self); require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); + require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/package-lock.json b/package-lock.json index 07b0a95fd1..2ed6cb2752 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "jsdom": "^16.7.0", "jszip": "^3.10.0", "ldapjs": "^2.2.0", - "loopback": "^3.26.0", + "loopback": "^3.28.0", "loopback-boot": "3.3.1", "loopback-component-explorer": "^6.5.0", "loopback-component-storage": "3.6.1", @@ -4878,13 +4878,20 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001299", + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==", "dev": true, - "license": "CC-BY-4.0", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/canonical-json": { "version": "0.0.4", @@ -12881,6 +12888,66 @@ "xmlcreate": "^1.0.1" } }, + "node_modules/jsbarcode": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz", + "integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==", + "bin": { + "auto.js": "bin/barcodes/CODE128/auto.js", + "Barcode.js": "bin/barcodes/Barcode.js", + "barcodes": "bin/barcodes", + "canvas.js": "bin/renderers/canvas.js", + "checksums.js": "bin/barcodes/MSI/checksums.js", + "codabar": "bin/barcodes/codabar", + "CODE128": "bin/barcodes/CODE128", + "CODE128_AUTO.js": "bin/barcodes/CODE128/CODE128_AUTO.js", + "CODE128.js": "bin/barcodes/CODE128/CODE128.js", + "CODE128A.js": "bin/barcodes/CODE128/CODE128A.js", + "CODE128B.js": "bin/barcodes/CODE128/CODE128B.js", + "CODE128C.js": "bin/barcodes/CODE128/CODE128C.js", + "CODE39": "bin/barcodes/CODE39", + "constants.js": "bin/barcodes/ITF/constants.js", + "defaults.js": "bin/options/defaults.js", + "EAN_UPC": "bin/barcodes/EAN_UPC", + "EAN.js": "bin/barcodes/EAN_UPC/EAN.js", + "EAN13.js": "bin/barcodes/EAN_UPC/EAN13.js", + "EAN2.js": "bin/barcodes/EAN_UPC/EAN2.js", + "EAN5.js": "bin/barcodes/EAN_UPC/EAN5.js", + "EAN8.js": "bin/barcodes/EAN_UPC/EAN8.js", + "encoder.js": "bin/barcodes/EAN_UPC/encoder.js", + "ErrorHandler.js": "bin/exceptions/ErrorHandler.js", + "exceptions": "bin/exceptions", + "exceptions.js": "bin/exceptions/exceptions.js", + "fixOptions.js": "bin/help/fixOptions.js", + "GenericBarcode": "bin/barcodes/GenericBarcode", + "getOptionsFromElement.js": "bin/help/getOptionsFromElement.js", + "getRenderProperties.js": "bin/help/getRenderProperties.js", + "help": "bin/help", + "index.js": "bin/renderers/index.js", + "index.tmp.js": "bin/barcodes/index.tmp.js", + "ITF": "bin/barcodes/ITF", + "ITF.js": "bin/barcodes/ITF/ITF.js", + "ITF14.js": "bin/barcodes/ITF/ITF14.js", + "JsBarcode.js": "bin/JsBarcode.js", + "linearizeEncodings.js": "bin/help/linearizeEncodings.js", + "merge.js": "bin/help/merge.js", + "MSI": "bin/barcodes/MSI", + "MSI.js": "bin/barcodes/MSI/MSI.js", + "MSI10.js": "bin/barcodes/MSI/MSI10.js", + "MSI1010.js": "bin/barcodes/MSI/MSI1010.js", + "MSI11.js": "bin/barcodes/MSI/MSI11.js", + "MSI1110.js": "bin/barcodes/MSI/MSI1110.js", + "object.js": "bin/renderers/object.js", + "options": "bin/options", + "optionsFromStrings.js": "bin/help/optionsFromStrings.js", + "pharmacode": "bin/barcodes/pharmacode", + "renderers": "bin/renderers", + "shared.js": "bin/renderers/shared.js", + "svg.js": "bin/renderers/svg.js", + "UPC.js": "bin/barcodes/EAN_UPC/UPC.js", + "UPCE.js": "bin/barcodes/EAN_UPC/UPCE.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "license": "MIT" @@ -23759,6 +23826,14 @@ "version": "1.0.2", "license": "Apache-2.0" }, + "node_modules/xmldom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "license": "MIT", @@ -23938,6 +24013,7 @@ "fs-extra": "^7.0.1", "intl": "^1.2.5", "js-yaml": "^3.13.1", + "jsbarcode": "^3.11.5", "jsonexport": "^3.2.0", "juice": "^5.2.0", "log4js": "^6.7.0", @@ -23948,7 +24024,8 @@ "strftime": "^0.10.0", "vue": "^2.6.10", "vue-i18n": "^8.15.0", - "vue-server-renderer": "^2.6.10" + "vue-server-renderer": "^2.6.10", + "xmldom": "^0.6.0" } }, "print/node_modules/fs-extra": { @@ -28107,7 +28184,9 @@ "version": "1.0.0" }, "caniuse-lite": { - "version": "1.0.30001299", + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==", "dev": true }, "canonical-json": { @@ -39828,6 +39907,11 @@ "xmlcreate": "^1.0.1" } }, + "jsbarcode": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz", + "integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==" + }, "jsbn": { "version": "0.1.1" }, @@ -57805,6 +57889,7 @@ "fs-extra": "^7.0.1", "intl": "^1.2.5", "js-yaml": "^3.13.1", + "jsbarcode": "^3.11.5", "jsonexport": "^3.2.0", "juice": "^5.2.0", "log4js": "^6.7.0", @@ -57815,7 +57900,8 @@ "strftime": "^0.10.0", "vue": "^2.6.10", "vue-i18n": "^8.15.0", - "vue-server-renderer": "^2.6.10" + "vue-server-renderer": "^2.6.10", + "xmldom": "^0.6.0" }, "dependencies": { "fs-extra": { @@ -59678,6 +59764,11 @@ "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", "integrity": "sha512-Mbe56Dvj00onbnSo9J0qj/XlY5bfN9KidsOnpd5tRCsR3ekB3hyyNU9fGrTdqNT5ZNvv4BsA2TcQlignsZyVcw==" }, + "xmldom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==" + }, "xtend": { <<<<<<< HEAD "version": "1.0.3", diff --git a/print/common/css/misc.css b/print/common/css/misc.css index df8bf571ae..ce6c641a01 100644 --- a/print/common/css/misc.css +++ b/print/common/css/misc.css @@ -49,4 +49,10 @@ .page-break-after { page-break-after: always; +} + +.ellipsize { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } \ No newline at end of file diff --git a/print/core/smtp.js b/print/core/smtp.js index a55ba448dd..61b115b5af 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -8,10 +8,12 @@ module.exports = { this.transporter = nodemailer.createTransport(config.smtp); }, - send(options) { + async send(options) { options.from = `${config.app.senderName} <${config.app.senderEmail}>`; if (process.env.NODE_ENV !== 'production') { + const notProductionError = {message: 'This not production, this email not sended'}; + await this.mailLog(options, notProductionError); if (!config.smtp.auth.user) return Promise.resolve(true); @@ -24,29 +26,35 @@ module.exports = { throw err; }).finally(async() => { - const attachments = []; - if (options.attachments) { - for (let attachment of options.attachments) { - const fileName = attachment.filename; - const filePath = attachment.path; - if (fileName.includes('.png')) continue; - - if (fileName || filePath) - attachments.push(filePath ? filePath : fileName); - } - } - - const fileNames = attachments.join(',\n'); - await db.rawSql(` - INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status) - VALUES (?, ?, 1, ?, ?, ?, ?)`, [ - options.to, - options.replyTo, - options.subject, - options.text || options.html, - fileNames, - error && error.message || 'Sent' - ]); + await this.mailLog(options, error); }); + }, + + async mailLog(options, error) { + const attachments = []; + if (options.attachments) { + for (let attachment of options.attachments) { + const fileName = attachment.filename; + const filePath = attachment.path; + if (fileName.includes('.png')) continue; + + if (fileName || filePath) + attachments.push(filePath ? filePath : fileName); + } + } + + const fileNames = attachments.join(',\n'); + + await db.rawSql(` + INSERT INTO vn.mail (receiver, replyTo, sent, subject, body, attachment, status) + VALUES (?, ?, 1, ?, ?, ?, ?)`, [ + options.to, + options.replyTo, + options.subject, + options.text || options.html, + fileNames, + error && error.message || 'Sent' + ]); } + }; diff --git a/print/templates/email/email-verify/assets/css/import.js b/print/templates/email/email-verify/assets/css/import.js new file mode 100644 index 0000000000..7360587f7b --- /dev/null +++ b/print/templates/email/email-verify/assets/css/import.js @@ -0,0 +1,13 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/email.css`, + `${__dirname}/style.css`]) + .mergeStyles(); + diff --git a/print/templates/email/email-verify/assets/css/style.css b/print/templates/email/email-verify/assets/css/style.css new file mode 100644 index 0000000000..5db85befa4 --- /dev/null +++ b/print/templates/email/email-verify/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/email-verify/email-verify.html b/print/templates/email/email-verify/email-verify.html new file mode 100644 index 0000000000..b8a6e263c9 --- /dev/null +++ b/print/templates/email/email-verify/email-verify.html @@ -0,0 +1,48 @@ + + + + + + {{ $t('subject') }} + + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+

+ {{ $t(`click`) }} + {{ $t('subject') }} +

+
+
+ + +
+
+ +
+
+ +
+
+
+
+ + diff --git a/print/templates/email/email-verify/email-verify.js b/print/templates/email/email-verify/email-verify.js new file mode 100755 index 0000000000..7f0b80a13c --- /dev/null +++ b/print/templates/email/email-verify/email-verify.js @@ -0,0 +1,17 @@ +const Component = require(`vn-print/core/component`); +const emailHeader = new Component('email-header'); +const emailFooter = new Component('email-footer'); + +module.exports = { + name: 'email-verify', + components: { + 'email-header': emailHeader.build(), + 'email-footer': emailFooter.build() + }, + props: { + url: { + type: [String], + required: true + } + } +}; diff --git a/print/templates/email/email-verify/locale/en.yml b/print/templates/email/email-verify/locale/en.yml new file mode 100644 index 0000000000..0298f53b4b --- /dev/null +++ b/print/templates/email/email-verify/locale/en.yml @@ -0,0 +1,3 @@ +subject: Email Verify +title: Email Verify +click: Click on the following link to verify this email. If you haven't requested this email, just ignore it diff --git a/print/templates/email/email-verify/locale/es.yml b/print/templates/email/email-verify/locale/es.yml new file mode 100644 index 0000000000..37bd6ef273 --- /dev/null +++ b/print/templates/email/email-verify/locale/es.yml @@ -0,0 +1,3 @@ +subject: Verificar correo +title: Verificar correo +click: Pulsa en el siguiente link para verificar este correo. Si no has pedido este correo, simplemente ignóralo diff --git a/print/templates/email/recover-password/assets/css/import.js b/print/templates/email/recover-password/assets/css/import.js new file mode 100644 index 0000000000..7360587f7b --- /dev/null +++ b/print/templates/email/recover-password/assets/css/import.js @@ -0,0 +1,13 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/email.css`, + `${__dirname}/style.css`]) + .mergeStyles(); + diff --git a/print/templates/email/recover-password/assets/css/style.css b/print/templates/email/recover-password/assets/css/style.css new file mode 100644 index 0000000000..5db85befa4 --- /dev/null +++ b/print/templates/email/recover-password/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/recover-password/locale/es.yml b/print/templates/email/recover-password/locale/es.yml new file mode 100644 index 0000000000..c72b108ee9 --- /dev/null +++ b/print/templates/email/recover-password/locale/es.yml @@ -0,0 +1,3 @@ +subject: Recuperar contraseña +title: Recuperar contraseña +Click on the following link to change your password.: Pulsa en el siguiente link para cambiar tu contraseña. diff --git a/print/templates/email/recover-password/recover-password.html b/print/templates/email/recover-password/recover-password.html new file mode 100644 index 0000000000..a654b3d5f0 --- /dev/null +++ b/print/templates/email/recover-password/recover-password.html @@ -0,0 +1,48 @@ + + + + + + {{ $t('subject') }} + + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+

+ {{ $t('Click on the following link to change your password.') }} + {{ $t('subject') }} +

+
+
+ + +
+
+ +
+
+ +
+
+
+
+ + diff --git a/print/templates/email/recover-password/recover-password.js b/print/templates/email/recover-password/recover-password.js new file mode 100755 index 0000000000..b589411a9a --- /dev/null +++ b/print/templates/email/recover-password/recover-password.js @@ -0,0 +1,17 @@ +const Component = require(`vn-print/core/component`); +const emailHeader = new Component('email-header'); +const emailFooter = new Component('email-footer'); + +module.exports = { + name: 'recover-password', + components: { + 'email-header': emailHeader.build(), + 'email-footer': emailFooter.build() + }, + props: { + url: { + type: [String], + required: true + } + } +}; diff --git a/print/templates/email/weekly-hour-record/assets/css/import.js b/print/templates/email/weekly-hour-record/assets/css/import.js new file mode 100644 index 0000000000..1582b82c50 --- /dev/null +++ b/print/templates/email/weekly-hour-record/assets/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/email.css`]) + .mergeStyles(); + diff --git a/print/templates/email/weekly-hour-record/locale/en.yml b/print/templates/email/weekly-hour-record/locale/en.yml new file mode 100644 index 0000000000..817e5451e4 --- /dev/null +++ b/print/templates/email/weekly-hour-record/locale/en.yml @@ -0,0 +1,6 @@ +subject: Weekly time log +title: Record of hours week {0} year {1} +dear: Dear worker +description: Access the following link:

+ {0}

+ Click 'SATISFIED' if you agree with the hours worked. Otherwise, press 'NOT SATISFIED', detailing the cause of the disagreement. diff --git a/print/templates/email/weekly-hour-record/locale/es.yml b/print/templates/email/weekly-hour-record/locale/es.yml new file mode 100644 index 0000000000..b70862f16a --- /dev/null +++ b/print/templates/email/weekly-hour-record/locale/es.yml @@ -0,0 +1,6 @@ +subject: Registro de horas semanal +title: Registro de horas semana {0} año {1} +dear: Estimado trabajador +description: Acceda al siguiente enlace:

+ {0}

+ Pulse 'CONFORME' si esta de acuerdo con las horas trabajadas. En caso contrario pulse 'NO CONFORME', detallando la causa de la disconformidad. diff --git a/print/templates/email/weekly-hour-record/weekly-hour-record.html b/print/templates/email/weekly-hour-record/weekly-hour-record.html new file mode 100644 index 0000000000..84abb4c61c --- /dev/null +++ b/print/templates/email/weekly-hour-record/weekly-hour-record.html @@ -0,0 +1,9 @@ + +
+
+

{{ $t('title', [week, year]) }}

+

{{$t('dear')}},

+

+
+
+
diff --git a/print/templates/email/weekly-hour-record/weekly-hour-record.js b/print/templates/email/weekly-hour-record/weekly-hour-record.js new file mode 100755 index 0000000000..8fdaea0ce1 --- /dev/null +++ b/print/templates/email/weekly-hour-record/weekly-hour-record.js @@ -0,0 +1,23 @@ +const Component = require(`vn-print/core/component`); +const emailBody = new Component('email-body'); + +module.exports = { + name: 'weekly-hour-record', + components: { + 'email-body': emailBody.build() + }, + props: { + week: { + type: Number, + required: true + }, + year: { + type: Number, + required: true + }, + url: { + type: String, + required: true + } + } +}; diff --git a/print/templates/reports/collection-label/assets/css/style.css b/print/templates/reports/collection-label/assets/css/style.css index fe1975445d..f92d5bf1e8 100644 --- a/print/templates/reports/collection-label/assets/css/style.css +++ b/print/templates/reports/collection-label/assets/css/style.css @@ -1,6 +1,6 @@ html { - font-family: "Roboto"; - margin-top: -7px; + font-family: Arial, Helvetica, sans-serif; + margin-top: -6px; } * { box-sizing: border-box; @@ -9,29 +9,36 @@ html { } #vertical { writing-mode: vertical-rl; - height: 226px; + height: 230px; + font-size: 29px; margin-left: -13px; } .outline { border: 1px solid black; padding: 5px; + height: 37px; + width: 100px; } #nickname { font-size: 22px; + max-width: 50px; } #agencyDescripton { font-size: 32px; + width: 375px; font-weight: bold; } #bold { font-weight: bold; } #barcode{ - width: 390px; + width: 370px; } #shipped { font-weight: bold; + width: 50px; + max-width: 100px; } -#ticketFk, #vertical { - font-size: 34px; +#ticketFk { + font-size: 32px; } \ No newline at end of file diff --git a/print/templates/reports/collection-label/collection-label.html b/print/templates/reports/collection-label/collection-label.html index eeb82ac4a5..b7f783a88c 100644 --- a/print/templates/reports/collection-label/collection-label.html +++ b/print/templates/reports/collection-label/collection-label.html @@ -1,35 +1,34 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{labelData.levelV}}{{labelData.ticketFk}} ⬸ {{labelData.clientFk}}{{labelData.shipped}}
{{labelData.workerCode}}
{{labelData.labelCount}}
{{labelData.size}}
{{labelData.agencyDescription}}
{{labelData.lineCount}}
{{labelData.nickName}}{{labelData.agencyHour}}
- -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{getVertical(labelData)}} + {{labelData.clientFk ? `${labelData.ticketFk} « ${labelData.clientFk}` : labelData.ticketFk}} + {{labelData.shipped || '---'}}
{{labelData.workerCode || '---'}}
{{labelData.labelCount || 0}}
{{labelData.code == 'V' ? (labelData.size || 0) + 'cm' : (labelData.volume || 0) + 'm³'}}
{{labelData.agencyDescription ? labelData.agencyDescription.toUpperCase() : '---'}}
{{labelData.lineCount || 0}}
{{labelData.nickName ? labelData.nickName.toUpperCase() : '---'}}{{labelData.shippedHour || labelData.zoneHour}}
+ + \ No newline at end of file diff --git a/print/templates/reports/collection-label/collection-label.js b/print/templates/reports/collection-label/collection-label.js index 7bc25ad790..fd412791b9 100644 --- a/print/templates/reports/collection-label/collection-label.js +++ b/print/templates/reports/collection-label/collection-label.js @@ -25,7 +25,6 @@ module.exports = { ticketIds = [this.id]; this.labelsData = await this.rawSqlFromDef('labelsData', [ticketIds]); - if (!this.labelsData.length) throw new UserError('Empty data source'); }, @@ -40,10 +39,23 @@ module.exports = { format: 'code128', displayValue: false, width: 3.8, - height: 110, + height: 115, }); return xmlSerializer.serializeToString(svgNode); }, + getVertical(labelData) { + let value; + if (labelData.collectionFk) { + value = `${labelData.collectionFk} ~ `; + if (labelData.code == 'V') + value = value + `${labelData.wagon}-${labelData.level}`; + else + value = value + `${labelData.color.substring(0, 4)}`; + } else + value = '-'.repeat(19); + + return value; + }, }, components: { 'report-body': reportBody.build() diff --git a/print/templates/reports/collection-label/options.json b/print/templates/reports/collection-label/options.json index 175b3c1db8..a555c5723e 100644 --- a/print/templates/reports/collection-label/options.json +++ b/print/templates/reports/collection-label/options.json @@ -1,9 +1,9 @@ { "width": "10.4cm", - "height": "4.8cm", + "height": "4.9cm", "margin": { - "top": "0cm", - "right": "0.5cm", + "top": "0.3cm", + "right": "0.6cm", "bottom": "0cm", "left": "0cm" }, diff --git a/print/templates/reports/collection-label/sql/labelsData.sql b/print/templates/reports/collection-label/sql/labelsData.sql index 6f5b47a54d..9afac79d09 100644 --- a/print/templates/reports/collection-label/sql/labelsData.sql +++ b/print/templates/reports/collection-label/sql/labelsData.sql @@ -1,18 +1,20 @@ -SELECT c.itemPackingTypeFk, - CONCAT(tc.collectionFk, ' ', LEFT(cc.code, 4)) color, - CONCAT(tc.collectionFk, ' ', SUBSTRING('ABCDEFGH',tc.wagon, 1), '-', tc.`level`) levelV, - tc.ticketFk, - LEFT(COALESCE(et.description, zo.name, am.name),12) agencyDescription, - am.name, +SELECT c.itemPackingTypeFk code, + tc.collectionFk, + SUBSTRING('ABCDEFGH', tc.wagon, 1) wagon, + tc.`level`, + t.id ticketFk, + COALESCE(et.description, zo.name, am.name) agencyDescription, + cc.code color, t.clientFk, - CONCAT(CAST(SUM(sv.volume) AS DECIMAL(5, 2)), 'm³') m3 , - CAST(IF(ic.code = 'plant', CONCAT(MAX(i.`size`),' cm'), COUNT(*)) AS CHAR) size, + CAST(SUM(sv.volume) AS DECIMAL(5, 2)) volume, + MAX(i.`size`) `size`, w.code workerCode, - tt.labelCount, - IF(HOUR(t.shipped), TIME_FORMAT(t.shipped, '%H:%i'), TIME_FORMAT(zo.`hour`, '%H:%i')) agencyHour, + TIME_FORMAT(t.shipped, '%H:%i') shippedHour, + TIME_FORMAT(zo.`hour`, '%H:%i') zoneHour, DATE_FORMAT(t.shipped, '%d/%m/%y') shipped, - COUNT(*) lineCount, - t.nickName + t.nickName, + tt.labelCount, + COUNT(*) lineCount FROM vn.ticket t JOIN vn.ticketCollection tc ON tc.ticketFk = t.id JOIN vn.collection c ON c.id = tc.collectionFk @@ -30,6 +32,6 @@ SELECT c.itemPackingTypeFk, LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk - WHERE tc.ticketFk IN (?) + WHERE t.id IN (?) GROUP BY t.id ORDER BY cc.`code`; \ No newline at end of file