diff --git a/back/methods/vn-user/addAlias.js b/back/methods/vn-user/addAlias.js new file mode 100644 index 000000000..9fe43e713 --- /dev/null +++ b/back/methods/vn-user/addAlias.js @@ -0,0 +1,68 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('addAlias', { + description: 'Add an alias if the user has the grant', + accessType: 'WRITE', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, + { + arg: 'id', + type: 'number', + required: true, + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'mailAlias', + type: 'number', + description: 'The new alias for user', + required: true + } + ], + http: { + path: `/:id/addAlias`, + verb: 'POST' + } + }); + + Self.addAlias = async function(ctx, id, mailAlias, options) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions); + + if (!user.hasGrant) + throw new UserError(`You don't have grant privilege`); + + const account = await models.Account.findById(userId, { + fields: ['id'], + include: { + relation: 'aliases', + scope: { + fields: ['mailAlias'] + } + } + }, myOptions); + + const aliases = account.aliases().map(alias => alias.mailAlias); + + const hasAlias = aliases.includes(mailAlias); + if (!hasAlias) + throw new UserError(`You cannot assign an alias that you are not assigned to`); + + return models.MailAliasAccount.create({ + mailAlias: mailAlias, + account: id + }, myOptions); + }; +}; diff --git a/back/methods/vn-user/removeAlias.js b/back/methods/vn-user/removeAlias.js new file mode 100644 index 000000000..0424c3e96 --- /dev/null +++ b/back/methods/vn-user/removeAlias.js @@ -0,0 +1,55 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('removeAlias', { + description: 'Remove alias if the user has the grant', + accessType: 'WRITE', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + }, + { + arg: 'id', + type: 'number', + required: true, + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'mailAlias', + type: 'number', + description: 'The alias to delete', + required: true + } + ], + http: { + path: `/:id/removeAlias`, + verb: 'POST' + } + }); + + Self.removeAlias = async function(ctx, id, mailAlias, options) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE'); + + if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`); + + const mailAliasAccount = await models.MailAliasAccount.findOne({ + where: { + mailAlias: mailAlias, + account: id + } + }, myOptions); + + await mailAliasAccount.destroy(myOptions); + }; +}; diff --git a/back/models/vn-user.js b/back/models/vn-user.js index a7ce12073..11d4bf250 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -12,6 +12,8 @@ module.exports = function(Self) { require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); + require('../methods/vn-user/addAlias')(Self); + require('../methods/vn-user/removeAlias')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); diff --git a/db/changes/232401/00-ACLgetVehiclesSorted.sql b/db/changes/232401/00-ACLgetVehiclesSorted.sql new file mode 100644 index 000000000..6625f0d5c --- /dev/null +++ b/db/changes/232401/00-ACLgetVehiclesSorted.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) + VALUES + ('Vehicle','sorted','WRITE','ALLOW','employee'); \ No newline at end of file diff --git a/db/changes/232601/00-aclAddAlias.sql b/db/changes/232601/00-aclAddAlias.sql new file mode 100644 index 000000000..cc96f5ad8 --- /dev/null +++ b/db/changes/232601/00-aclAddAlias.sql @@ -0,0 +1,11 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232601/00-aclInvoiceTickets.sql b/db/changes/232601/00-aclInvoiceTickets.sql new file mode 100644 index 000000000..2c221950e --- /dev/null +++ b/db/changes/232601/00-aclInvoiceTickets.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES('Ticket', 'invoiceTickets', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/232801/00-roadmap.sql b/db/changes/232801/00-roadmap.sql new file mode 100644 index 000000000..a2835160f --- /dev/null +++ b/db/changes/232801/00-roadmap.sql @@ -0,0 +1,10 @@ +ALTER TABLE `vn`.`roadmap` COMMENT='Troncales diarios que se contratan'; +ALTER TABLE `vn`.`roadmap` ADD price decimal(10,2) NULL; +ALTER TABLE `vn`.`roadmap` ADD driverName varchar(45) NULL; +ALTER TABLE `vn`.`roadmap` ADD name varchar(45) NOT NULL; +ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL AFTER id; +ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL; + +ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales'; +ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2; +ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/changes/232801/00-roadmapACL.sql b/db/changes/232801/00-roadmapACL.sql new file mode 100644 index 000000000..4fc116f86 --- /dev/null +++ b/db/changes/232801/00-roadmapACL.sql @@ -0,0 +1,6 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'), + ('Roadmap', '*', '*', 'ALLOW', 'ROLE', 'productionBoss'), + ('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'palletizerBoss'), + ('ExpeditionTruck', '*', '*', 'ALLOW', 'ROLE', 'productionBoss'); diff --git a/db/changes/233001/00-itemRecycle.sql b/db/changes/233001/00-itemRecycle.sql new file mode 100644 index 000000000..c191e5d1c --- /dev/null +++ b/db/changes/233001/00-itemRecycle.sql @@ -0,0 +1,2 @@ +ALTER TABLE `vn`.`item` ADD recycledPlastic INT NULL; +ALTER TABLE `vn`.`item` ADD nonRecycledPlastic INT NULL; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 7649e3594..fe11d5b64 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -188,13 +188,13 @@ INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAd UPDATE `vn`.`sector` SET mainPrinterFk = 1 WHERE id = 1; -INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`) +INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`) VALUES - (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106, NULL, NULL), - (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107, NULL, NULL), - (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108, 1, NULL), - (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109, 1, NULL), - (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110, 2, NULL); + (1106, 'LGN', 'David Charles', 'Haller', 1106, 19, 432978106), + (1107, 'ANT', 'Hank' , 'Pym' , 1107, 19, 432978107), + (1108, 'DCX', 'Charles' , 'Xavier', 1108, 19, 432978108), + (1109, 'HLK', 'Bruce' , 'Banner', 1109, 19, 432978109), + (1110, 'JJJ', 'Jessica' , 'Jones' , 1110, 19, 432978110); INSERT INTO `vn`.`parking` (`id`, `column`, `row`, `sectorFk`, `code`, `pickingOrder`) VALUES @@ -2606,9 +2606,18 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`) (3, 6, 5), (4, 7, 1); -INSERT INTO `vn`.`expeditionTruck` (`id`, `eta`, `description`) +INSERT INTO `vn`.`roadmap` (`id`, `name`, `tractorPlate`, `trailerPlate`, `phone`, `supplierFk`, `etd`, `observations`, `userFk`, `price`, `driverName`) VALUES - (1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet'); + (1, 'val-algemesi', 'RE-001', 'PO-001', '111111111', 1, util.VN_NOW(), 'this is test observation', 1, 15, 'Batman'), + (2, 'alg-valencia', 'RE-002', 'PO-002', '111111111', 1, util.VN_NOW(), 'test observation', 1, 20, 'Robin'), + (3, 'alz-algemesi', 'RE-003', 'PO-003', '222222222', 2, DATE_ADD(util.VN_NOW(), INTERVAL 2 DAY), 'observations...', 2, 25, 'Driverman'); + +INSERT INTO `vn`.`expeditionTruck` (`id`, `roadmapFk`, `warehouseFk`, `eta`, `description`, `userFk`) + VALUES + (1, 1, 1, DATE_ADD(util.VN_NOW(), INTERVAL 1 DAY), 'Best truck in fleet', 1), + (2, 1, 2, DATE_ADD(util.VN_NOW(), INTERVAL '1 2' DAY_HOUR), 'Second truck in fleet', 1), + (3, 1, 3, DATE_ADD(util.VN_NOW(), INTERVAL '1 4' DAY_HOUR), 'Third truck in fleet', 1), + (4, 2, 1, DATE_ADD(util.VN_NOW(), INTERVAL 3 DAY), 'Truck red', 1); INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`) VALUES diff --git a/db/dump/structure.sql b/db/dump/structure.sql index ee5fb40a1..4e7127310 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -77831,7 +77831,7 @@ BEGIN LEAVE cur1Loop; END IF; - CALL zone_getLeaves2(vZoneFk, NULL, NULL); + CALL zone_getLeaves(vZoneFk, NULL, NULL, TRUE); myLoop: LOOP SET vGeoFk = NULL; @@ -77844,7 +77844,7 @@ BEGIN LEAVE myLoop; END IF; - CALL zone_getLeaves2(vZoneFk, vGeoFk, NULL); + CALL zone_getLeaves(vZoneFk, vGeoFk, NULL, TRUE); UPDATE tmp.zoneNodes SET isChecked = TRUE WHERE geoFk = vGeoFk; @@ -78130,55 +78130,58 @@ DELIMITER ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; -CREATE DEFINER=`root`@`localhost` PROCEDURE `zone_getLeaves`(vSelf INT, vParentFk INT, vSearch VARCHAR(255)) -BEGIN +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLeaves`( + vSelf INT, + vParentFk INT, + vSearch VARCHAR(255), + vHasInsert BOOL +) +BEGIN /** * Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk. * @param vSelf Id de la zona * @param vParentFk Id del geo a calcular - * @param vSearch cadena a buscar + * @param vSearch Cadena a buscar + * @param vHasInsert Indica si inserta en tmp.zoneNodes + * Optional @table tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk) */ DECLARE vIsNumber BOOL; - DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != ''; + DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch <> ''; - DROP TEMPORARY TABLE IF EXISTS tNodes; - CREATE TEMPORARY TABLE tNodes + CREATE OR REPLACE TEMPORARY TABLE tNodes (UNIQUE (id)) ENGINE = MEMORY - SELECT id - FROM zoneGeo + SELECT id + FROM zoneGeo LIMIT 0; IF vIsSearch THEN SET vIsNumber = vSearch REGEXP '^[0-9]+$'; - + INSERT INTO tNodes - SELECT id + SELECT id FROM zoneGeo WHERE (vIsNumber AND `name` = vSearch) OR (!vIsNumber AND `name` LIKE CONCAT('%', vSearch, '%')) LIMIT 1000; - + ELSEIF vParentFk IS NULL THEN INSERT INTO tNodes - SELECT geoFk + SELECT geoFk FROM zoneIncluded WHERE zoneFk = vSelf; END IF; IF vParentFk IS NULL THEN - DROP TEMPORARY TABLE IF EXISTS tChilds; - CREATE TEMPORARY TABLE tChilds + CREATE OR REPLACE TEMPORARY TABLE tChilds + (INDEX(id)) ENGINE = MEMORY - SELECT id - FROM tNodes; + SELECT id FROM tNodes; - DROP TEMPORARY TABLE IF EXISTS tParents; - CREATE TEMPORARY TABLE tParents + CREATE OR REPLACE TEMPORARY TABLE tParents + (INDEX(id)) ENGINE = MEMORY - SELECT id - FROM zoneGeo - LIMIT 0; + SELECT id FROM zoneGeo LIMIT 0; myLoop: LOOP DELETE FROM tParents; @@ -78186,43 +78189,67 @@ BEGIN SELECT parentFk id FROM zoneGeo g JOIN tChilds c ON c.id = g.id - WHERE g.parentFk IS NOT NULL; - + WHERE g.parentFk IS NOT NULL; + INSERT IGNORE INTO tNodes - SELECT id - FROM tParents; - - IF ROW_COUNT() = 0 THEN + SELECT id FROM tParents; + + IF NOT ROW_COUNT() THEN LEAVE myLoop; END IF; - + DELETE FROM tChilds; INSERT INTO tChilds - SELECT id - FROM tParents; + SELECT id FROM tParents; END LOOP; - + DROP TEMPORARY TABLE tChilds, tParents; END IF; - IF !vIsSearch THEN + IF NOT vIsSearch THEN INSERT IGNORE INTO tNodes - SELECT id + SELECT id FROM zoneGeo WHERE parentFk <=> vParentFk; END IF; - SELECT g.id, - g.name, - g.parentFk, - g.sons, - isIncluded selected - FROM zoneGeo g - JOIN tNodes n ON n.id = g.id - LEFT JOIN zoneIncluded i ON i.geoFk = g.id AND i.zoneFk = vSelf - ORDER BY `depth`, selected DESC, name; + CREATE OR REPLACE TEMPORARY TABLE tZones + SELECT g.id, + g.name, + g.parentFk, + g.sons, + NOT g.sons OR `type` = 'country' isChecked, + i.isIncluded selected, + g.`depth`, + vSelf + FROM zoneGeo g + JOIN tNodes n ON n.id = g.id + LEFT JOIN zoneIncluded i ON i.geoFk = g.id + AND i.zoneFk = vSelf + ORDER BY g.`depth`, selected DESC, g.name; - DROP TEMPORARY TABLE tNodes; + IF vHasInsert THEN + INSERT IGNORE INTO tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk) + SELECT id, + name, + parentFk, + sons, + isChecked, + vSelf + FROM tZones + WHERE selected + OR (selected IS NULL AND vParentFk IS NOT NULL); + ELSE + SELECT id, + name, + parentFk, + sons, + selected + FROM tZones + ORDER BY `depth`, selected DESC, name; + END IF; + + DROP TEMPORARY TABLE tNodes, tZones; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; @@ -78540,7 +78567,7 @@ BEGIN INDEX(geoFk)) ENGINE = MEMORY; - CALL zone_getLeaves2(vSelf, NULL , NULL); + CALL zone_getLeaves(vSelf, NULL , NULL, TRUE); UPDATE tmp.zoneNodes zn SET isChecked = 0 @@ -78553,7 +78580,7 @@ BEGIN WHERE NOT isChecked LIMIT 1; - CALL zone_getLeaves2(vSelf, vGeoFk, NULL); + CALL zone_getLeaves(vSelf, vGeoFk, NULL, TRUE); UPDATE tmp.zoneNodes SET isChecked = TRUE WHERE geoFk = vGeoFk; diff --git a/front/core/services/token.js b/front/core/services/token.js index c4b644a89..c16cc3c4f 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -83,7 +83,6 @@ export default class Token { this.renewPeriod = data.renewPeriod; this.stopRenewer(); this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000); - this.checkValidity(); }); } diff --git a/front/core/styles/icons/salixfont.css b/front/core/styles/icons/salixfont.css index 6f9b1fe15..37f489fe2 100644 --- a/front/core/styles/icons/salixfont.css +++ b/front/core/styles/icons/salixfont.css @@ -1,402 +1,411 @@ @font-face { - font-family: 'salixfont'; - src: - url('./salixfont.ttf?wtrl3') format('truetype'), - url('./salixfont.woff?wtrl3') format('woff'), - url('./salixfont.svg?wtrl3#salixfont') format('svg'); - font-weight: normal; - font-style: normal; -} + font-family: 'salixfont'; + src: + url('./salixfont.ttf?wtrl3') format('truetype'), + url('./salixfont.woff?wtrl3') format('woff'), + url('./salixfont.svg?wtrl3#salixfont') format('svg'); + font-weight: normal; + font-style: normal; + } -[class^="icon-"], [class*=" icon-"] { - /* use !important to prevent issues with browser extensions that change fonts */ - font-family: 'salixfont' !important; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; + [class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'salixfont' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } -.icon-agency-term:before { - content: "\e950"; -} -.icon-defaulter:before { - content: "\e94b"; -} -.icon-100:before { - content: "\e95a"; -} -.icon-clientUnpaid:before { - content: "\e95b"; -} -.icon-history:before { - content: "\e968"; -} -.icon-Person:before { - content: "\e901"; -} -.icon-accessory:before { - content: "\e90a"; -} -.icon-account:before { - content: "\e92a"; -} -.icon-actions:before { - content: "\e960"; -} -.icon-addperson:before { - content: "\e90e"; -} -.icon-agency:before { - content: "\e938"; -} -.icon-albaran:before { - content: "\e94d"; -} -.icon-anonymous:before { - content: "\e930"; -} -.icon-apps:before { - content: "\e951"; -} -.icon-artificial:before { - content: "\e90b"; -} -.icon-attach:before { - content: "\e92e"; -} -.icon-barcode:before { - content: "\e971"; -} -.icon-basket:before { - content: "\e914"; -} -.icon-basketadd:before { - content: "\e913"; -} -.icon-bin:before { - content: "\e96f"; -} -.icon-botanical:before { - content: "\e972"; -} -.icon-bucket:before { - content: "\e97a"; -} -.icon-buscaman:before { - content: "\e93b"; -} -.icon-buyrequest:before { - content: "\e932"; -} -.icon-calc_volum .path1:before { - content: "\e915"; -} -.icon-calc_volum .path2:before { - content: "\e916"; - margin-left: -1em; -} -.icon-calc_volum .path3:before { - content: "\e917"; - margin-left: -1em; -} -.icon-calc_volum .path4:before { - content: "\e918"; - margin-left: -1em; -} -.icon-calc_volum .path5:before { - content: "\e919"; - margin-left: -1em; -} -.icon-calc_volum .path6:before { - content: "\e91a"; - margin-left: -1em; -} -.icon-calendar:before { - content: "\e93d"; -} -.icon-catalog:before { - content: "\e937"; -} -.icon-claims:before { - content: "\e963"; -} -.icon-client:before { - content: "\e928"; -} -.icon-clone:before { - content: "\e973"; -} -.icon-columnadd:before { - content: "\e954"; -} -.icon-columndelete:before { - content: "\e953"; -} -.icon-components:before { - content: "\e946"; -} -.icon-consignatarios:before { - content: "\e93f"; -} -.icon-control:before { - content: "\e949"; -} -.icon-credit:before { - content: "\e927"; -} -.icon-deletedTicket:before { - content: "\e935"; -} -.icon-deleteline:before { - content: "\e955"; -} -.icon-delivery:before { - content: "\e939"; -} -.icon-deliveryprices:before { - content: "\e91c"; -} -.icon-details:before { - content: "\e961"; -} -.icon-dfiscales:before { - content: "\e984"; -} -.icon-disabled:before { - content: "\e921"; -} -.icon-doc:before { - content: "\e977"; -} -.icon-entry:before { - content: "\e934"; -} -.icon-exit:before { - content: "\e92f"; -} -.icon-eye:before { - content: "\e976"; -} -.icon-fixedPrice:before { - content: "\e90d"; -} -.icon-flower:before { - content: "\e90c"; -} -.icon-frozen:before { - content: "\e900"; -} -.icon-fruit:before { - content: "\e903"; -} -.icon-funeral:before { - content: "\e904"; -} -.icon-greenery:before { - content: "\e907"; -} -.icon-greuge:before { - content: "\e944"; -} -.icon-grid:before { - content: "\e980"; -} -.icon-handmade:before { - content: "\e909"; -} -.icon-handmadeArtificial:before { - content: "\e902"; -} -.icon-headercol:before { - content: "\e958"; -} -.icon-info:before { - content: "\e952"; -} -.icon-inventory:before { - content: "\e92b"; -} -.icon-invoice:before { - content: "\e923"; -} -.icon-invoice-in:before { - content: "\e911"; -} -.icon-invoice-in-create:before { - content: "\e912"; -} -.icon-invoice-out:before { - content: "\e910"; -} -.icon-isTooLittle:before { - content: "\e91b"; -} -.icon-item:before { - content: "\e956"; -} -.icon-languaje:before { - content: "\e926"; -} -.icon-lines:before { - content: "\e942"; -} -.icon-linesprepaired:before { - content: "\e948"; -} -.icon-logout:before { - content: "\e936"; -} -.icon-mana:before { - content: "\e96a"; -} -.icon-mandatory:before { - content: "\e97b"; -} -.icon-net:before { - content: "\e931"; -} -.icon-niche:before { - content: "\e96c"; -} -.icon-no036:before { - content: "\e920"; -} -.icon-noPayMethod:before { - content: "\e905"; -} -.icon-notes:before { - content: "\e941"; -} -.icon-noweb:before { - content: "\e91f"; -} -.icon-onlinepayment:before { - content: "\e91d"; -} -.icon-package:before { - content: "\e978"; -} -.icon-payment:before { - content: "\e97e"; -} -.icon-pbx:before { - content: "\e93c"; -} -.icon-pets:before { - content: "\e947"; -} -.icon-photo:before { - content: "\e924"; -} -.icon-plant:before { - content: "\e908"; -} -.icon-polizon:before { - content: "\e95e"; -} -.icon-preserved:before { - content: "\e906"; -} -.icon-recovery:before { - content: "\e97c"; -} -.icon-regentry:before { - content: "\e964"; -} -.icon-reserva:before { - content: "\e959"; -} -.icon-revision:before { - content: "\e94a"; -} -.icon-risk:before { - content: "\e91e"; -} -.icon-services:before { - content: "\e94c"; -} -.icon-settings:before { - content: "\e979"; -} -.icon-shipment-01:before { - content: "\e929"; -} -.icon-sign:before { - content: "\e95d"; -} -.icon-sms:before { - content: "\e975"; -} -.icon-solclaim:before { - content: "\e95f"; -} -.icon-solunion:before { - content: "\e94e"; -} -.icon-stowaway:before { - content: "\e94f"; -} -.icon-splitline:before { - content: "\e93e"; -} -.icon-splur:before { - content: "\e970"; -} -.icon-supplier:before { - content: "\e925"; -} -.icon-supplierfalse:before { - content: "\e90f"; -} -.icon-tags:before { - content: "\e96d"; -} -.icon-tax:before { - content: "\e940"; -} -.icon-thermometer:before { - content: "\e933"; -} -.icon-ticket:before { - content: "\e96b"; -} -.icon-ticketAdd:before { - content: "\e945"; -} -.icon-traceability:before { - content: "\e962"; -} -.icon-transaction:before { - content: "\e966"; -} -.icon-treatments:before { - content: "\e922"; -} -.icon-unavailable:before { - content: "\e92c"; -} -.icon-volume:before { - content: "\e96e"; -} -.icon-wand:before { - content: "\e93a"; -} -.icon-web:before { - content: "\e982"; -} -.icon-wiki:before { - content: "\e92d"; -} -.icon-worker:before { - content: "\e957"; -} -.icon-zone:before { - content: "\e943"; -} + .icon-trailer:before { + content: "\e967"; + } + .icon-grafana:before { + content: "\e965"; + } + .icon-trolley:before { + content: "\e95c"; + } + .icon-agency-term:before { + content: "\e950"; + } + .icon-defaulter:before { + content: "\e94b"; + } + .icon-100:before { + content: "\e95a"; + } + .icon-clientUnpaid:before { + content: "\e95b"; + } + .icon-history:before { + content: "\e968"; + } + .icon-Person:before { + content: "\e901"; + } + .icon-accessory:before { + content: "\e90a"; + } + .icon-account:before { + content: "\e92a"; + } + .icon-actions:before { + content: "\e960"; + } + .icon-addperson:before { + content: "\e90e"; + } + .icon-agency:before { + content: "\e938"; + } + .icon-albaran:before { + content: "\e94d"; + } + .icon-anonymous:before { + content: "\e930"; + } + .icon-apps:before { + content: "\e951"; + } + .icon-artificial:before { + content: "\e90b"; + } + .icon-attach:before { + content: "\e92e"; + } + .icon-barcode:before { + content: "\e971"; + } + .icon-basket:before { + content: "\e914"; + } + .icon-basketadd:before { + content: "\e913"; + } + .icon-bin:before { + content: "\e96f"; + } + .icon-botanical:before { + content: "\e972"; + } + .icon-bucket:before { + content: "\e97a"; + } + .icon-buscaman:before { + content: "\e93b"; + } + .icon-buyrequest:before { + content: "\e932"; + } + .icon-calc_volum .path1:before { + content: "\e915"; + } + .icon-calc_volum .path2:before { + content: "\e916"; + margin-left: -1em; + } + .icon-calc_volum .path3:before { + content: "\e917"; + margin-left: -1em; + } + .icon-calc_volum .path4:before { + content: "\e918"; + margin-left: -1em; + } + .icon-calc_volum .path5:before { + content: "\e919"; + margin-left: -1em; + } + .icon-calc_volum .path6:before { + content: "\e91a"; + margin-left: -1em; + } + .icon-calendar:before { + content: "\e93d"; + } + .icon-catalog:before { + content: "\e937"; + } + .icon-claims:before { + content: "\e963"; + } + .icon-client:before { + content: "\e928"; + } + .icon-clone:before { + content: "\e973"; + } + .icon-columnadd:before { + content: "\e954"; + } + .icon-columndelete:before { + content: "\e953"; + } + .icon-components:before { + content: "\e946"; + } + .icon-consignatarios:before { + content: "\e93f"; + } + .icon-control:before { + content: "\e949"; + } + .icon-credit:before { + content: "\e927"; + } + .icon-deletedTicket:before { + content: "\e935"; + } + .icon-deleteline:before { + content: "\e955"; + } + .icon-delivery:before { + content: "\e939"; + } + .icon-deliveryprices:before { + content: "\e91c"; + } + .icon-details:before { + content: "\e961"; + } + .icon-dfiscales:before { + content: "\e984"; + } + .icon-disabled:before { + content: "\e921"; + } + .icon-doc:before { + content: "\e977"; + } + .icon-entry:before { + content: "\e934"; + } + .icon-exit:before { + content: "\e92f"; + } + .icon-eye:before { + content: "\e976"; + } + .icon-fixedPrice:before { + content: "\e90d"; + } + .icon-flower:before { + content: "\e90c"; + } + .icon-frozen:before { + content: "\e900"; + } + .icon-fruit:before { + content: "\e903"; + } + .icon-funeral:before { + content: "\e904"; + } + .icon-greenery:before { + content: "\e907"; + } + .icon-greuge:before { + content: "\e944"; + } + .icon-grid:before { + content: "\e980"; + } + .icon-handmade:before { + content: "\e909"; + } + .icon-handmadeArtificial:before { + content: "\e902"; + } + .icon-headercol:before { + content: "\e958"; + } + .icon-info:before { + content: "\e952"; + } + .icon-inventory:before { + content: "\e92b"; + } + .icon-invoice:before { + content: "\e923"; + } + .icon-invoice-in:before { + content: "\e911"; + } + .icon-invoice-in-create:before { + content: "\e912"; + } + .icon-invoice-out:before { + content: "\e910"; + } + .icon-isTooLittle:before { + content: "\e91b"; + } + .icon-item:before { + content: "\e956"; + } + .icon-languaje:before { + content: "\e926"; + } + .icon-lines:before { + content: "\e942"; + } + .icon-linesprepaired:before { + content: "\e948"; + } + .icon-logout:before { + content: "\e936"; + } + .icon-mana:before { + content: "\e96a"; + } + .icon-mandatory:before { + content: "\e97b"; + } + .icon-net:before { + content: "\e931"; + } + .icon-niche:before { + content: "\e96c"; + } + .icon-no036:before { + content: "\e920"; + } + .icon-noPayMethod:before { + content: "\e905"; + } + .icon-notes:before { + content: "\e941"; + } + .icon-noweb:before { + content: "\e91f"; + } + .icon-onlinepayment:before { + content: "\e91d"; + } + .icon-package:before { + content: "\e978"; + } + .icon-payment:before { + content: "\e97e"; + } + .icon-pbx:before { + content: "\e93c"; + } + .icon-pets:before { + content: "\e947"; + } + .icon-photo:before { + content: "\e924"; + } + .icon-plant:before { + content: "\e908"; + } + .icon-polizon:before { + content: "\e95e"; + } + .icon-preserved:before { + content: "\e906"; + } + .icon-recovery:before { + content: "\e97c"; + } + .icon-regentry:before { + content: "\e964"; + } + .icon-reserva:before { + content: "\e959"; + } + .icon-revision:before { + content: "\e94a"; + } + .icon-risk:before { + content: "\e91e"; + } + .icon-services:before { + content: "\e94c"; + } + .icon-settings:before { + content: "\e979"; + } + .icon-shipment-01:before { + content: "\e929"; + } + .icon-sign:before { + content: "\e95d"; + } + .icon-sms:before { + content: "\e975"; + } + .icon-solclaim:before { + content: "\e95f"; + } + .icon-solunion:before { + content: "\e94e"; + } + .icon-stowaway:before { + content: "\e94f"; + } + .icon-splitline:before { + content: "\e93e"; + } + .icon-splur:before { + content: "\e970"; + } + .icon-supplier:before { + content: "\e925"; + } + .icon-supplierfalse:before { + content: "\e90f"; + } + .icon-tags:before { + content: "\e96d"; + } + .icon-tax:before { + content: "\e940"; + } + .icon-thermometer:before { + content: "\e933"; + } + .icon-ticket:before { + content: "\e96b"; + } + .icon-ticketAdd:before { + content: "\e945"; + } + .icon-traceability:before { + content: "\e962"; + } + .icon-transaction:before { + content: "\e966"; + } + .icon-treatments:before { + content: "\e922"; + } + .icon-unavailable:before { + content: "\e92c"; + } + .icon-volume:before { + content: "\e96e"; + } + .icon-wand:before { + content: "\e93a"; + } + .icon-web:before { + content: "\e982"; + } + .icon-wiki:before { + content: "\e92d"; + } + .icon-worker:before { + content: "\e957"; + } + .icon-zone:before { + content: "\e943"; + } diff --git a/front/core/styles/icons/salixfont.eot b/front/core/styles/icons/salixfont.eot index 61a3be8b7..6a158c806 100644 Binary files a/front/core/styles/icons/salixfont.eot and b/front/core/styles/icons/salixfont.eot differ diff --git a/front/core/styles/icons/salixfont.svg b/front/core/styles/icons/salixfont.svg index d6bfd5d74..da5404842 100644 --- a/front/core/styles/icons/salixfont.svg +++ b/front/core/styles/icons/salixfont.svg @@ -98,7 +98,8 @@ - + + @@ -107,7 +108,9 @@ + + @@ -131,4 +134,4 @@ - + \ No newline at end of file diff --git a/front/core/styles/icons/salixfont.ttf b/front/core/styles/icons/salixfont.ttf index 89d3bd57d..b484f2a60 100644 Binary files a/front/core/styles/icons/salixfont.ttf and b/front/core/styles/icons/salixfont.ttf differ diff --git a/front/core/styles/icons/salixfont.woff b/front/core/styles/icons/salixfont.woff index bfc9e075d..087d63f5e 100644 Binary files a/front/core/styles/icons/salixfont.woff and b/front/core/styles/icons/salixfont.woff differ diff --git a/loopback/locale/en.json b/loopback/locale/en.json index d33b3b3c3..030afbe9e 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -177,5 +177,6 @@ "Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address", "The renew period has not been exceeded": "The renew period has not been exceeded", "You can not use the same password": "You can not use the same password", - "Valid priorities": "Valid priorities: %d" + "Valid priorities": "Valid priorities: %d", + "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 106df379e..784ff5e6e 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -303,5 +303,7 @@ "Error when sending mail to client": "Error al enviar el correo al cliente", "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", - "Valid priorities": "Prioridades válidas: %d" + "Valid priorities": "Prioridades válidas: %d", + "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", + "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado" } diff --git a/modules/account/front/aliases/index.html b/modules/account/front/aliases/index.html index 11d546afb..4a73ec873 100644 --- a/modules/account/front/aliases/index.html +++ b/modules/account/front/aliases/index.html @@ -17,9 +17,7 @@ + ng-click="removeConfirm.show(row)"> @@ -32,9 +30,7 @@ translate-attr="{title: 'Add'}" vn-bind="+" ng-click="$ctrl.onAddClick()" - fixed-bottom-right - vn-acl="itManagement" - vn-acl-action="remove"> + fixed-bottom-right> this.refresh()) .then(() => this.vnApp.showSuccess( this.$t('Subscribed to alias!')) @@ -34,11 +33,12 @@ export default class Controller extends Section { } onRemove(row) { - return this.$http.delete(`MailAliasAccounts/${row.id}`) - .then(() => { - this.$.data.splice(this.$.data.indexOf(row), 1); - this.vnApp.showSuccess(this.$t('Unsubscribed from alias!')); - }); + const params = { + mailAlias: row.mailAlias + }; + return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params) + .then(() => this.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); } } diff --git a/modules/account/front/aliases/index.spec.js b/modules/account/front/aliases/index.spec.js index 466f1e1e9..61f71949c 100644 --- a/modules/account/front/aliases/index.spec.js +++ b/modules/account/front/aliases/index.spec.js @@ -25,8 +25,9 @@ describe('component vnUserAliases', () => { describe('onAddSave()', () => { it('should add the new row', () => { controller.addData = {account: 1}; + controller.$params = {id: 1}; - $httpBackend.expectPOST('MailAliasAccounts').respond(); + $httpBackend.expectPOST('VnUsers/1/addAlias').respond(); $httpBackend.expectGET('MailAliasAccounts').respond('foo'); controller.onAddSave(); $httpBackend.flush(); @@ -41,12 +42,14 @@ describe('component vnUserAliases', () => { {id: 1, alias: 'foo'}, {id: 2, alias: 'bar'} ]; + controller.$params = {id: 1}; - $httpBackend.expectDELETE('MailAliasAccounts/1').respond(); + $httpBackend.expectPOST('VnUsers/1/removeAlias').respond(); + $httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]); controller.onRemove(controller.$.data[0]); $httpBackend.flush(); - expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]); + expect(controller.$.data).toEqual({id: 2, alias: 'bar'}); expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index e6e240931..ca279ef71 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -89,7 +89,7 @@ module.exports = Self => { }; const country = await Self.app.models.Country.findOne(filter); const code = country ? country.code.toLowerCase() : null; - const countryCode = this.fi.toLowerCase().substring(0, 2); + const countryCode = this.fi?.toLowerCase().substring(0, 2); if (!this.fi || !validateTin(this.fi, code) || (this.isVies && countryCode == code)) err(); diff --git a/modules/client/front/credit-management/index.html b/modules/client/front/credit-management/index.html index b9064ff69..f66b988f5 100644 --- a/modules/client/front/credit-management/index.html +++ b/modules/client/front/credit-management/index.html @@ -68,7 +68,7 @@ {{::clientInforma.rating}} - {{::clientInforma.recommendedCredit | currency: 'EUR': 2}} + {{::clientInforma.recommendedCredit | currency: 'EUR'}} diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index a0d4b918a..15a55ec8c 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -64,7 +64,7 @@ - @@ -270,7 +270,7 @@ info="Invoices minus payments plus orders not yet invoiced"> @@ -296,6 +296,9 @@ value="{{$ctrl.summary.rating}}" info="Value from 1 to 20. The higher the better value"> + + diff --git a/modules/client/front/summary/style.scss b/modules/client/front/summary/style.scss index 77fc020ef..7dc1cc928 100644 --- a/modules/client/front/summary/style.scss +++ b/modules/client/front/summary/style.scss @@ -8,7 +8,7 @@ vn-client-summary .summary { } vn-horizontal h4 .grafana:after { - content: 'contact_support'; - font-size: 17px; + font-family: 'salixfont' !important; + content: "\e965"; } } diff --git a/modules/entry/front/buy/index/style.scss b/modules/entry/front/buy/index/style.scss index 04c8d130a..3fad252df 100644 --- a/modules/entry/front/buy/index/style.scss +++ b/modules/entry/front/buy/index/style.scss @@ -3,14 +3,13 @@ vn-entry-buy-index vn-card { max-width: $width-xl; - + .dark-row { background-color: lighten($color-marginal, 10%); } thead tr { - border-left: 1px solid white; - border-right: 1px solid white; + border: 1px solid white;; } tbody tr:nth-child(1), @@ -22,7 +21,7 @@ vn-entry-buy-index vn-card { tbody tr:nth-child(2) { border-bottom: 1px solid $color-spacer; } - + tbody{ border-bottom: 1px solid $color-spacer; } @@ -40,4 +39,4 @@ vn-entry-buy-index vn-card { } } -$color-font-link-medium: lighten($color-font-link, 20%) \ No newline at end of file +$color-font-link-medium: lighten($color-font-link, 20%) diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ddd008942..fa22dab1e 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -1,5 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('invoiceClient', { description: 'Make a invoice of a client', @@ -56,7 +54,6 @@ module.exports = Self => { const minShipped = Date.vnNew(); minShipped.setFullYear(args.maxShipped.getFullYear() - 1); - let invoiceId; try { const client = await models.Client.findById(args.clientId, { fields: ['id', 'hasToInvoiceByAddress'] @@ -77,56 +74,21 @@ module.exports = Self => { ], options); } - // Check negative bases - - let query = - `SELECT COUNT(*) isSpanishCompany - FROM supplier s - JOIN country c ON c.id = s.countryFk - AND c.code = 'ES' - WHERE s.id = ?`; - const [supplierCompany] = await Self.rawSql(query, [ - args.companyFk - ], options); - - const isSpanishCompany = supplierCompany?.isSpanishCompany; - - query = 'SELECT hasAnyNegativeBase() AS base'; - const [result] = await Self.rawSql(query, null, options); - - const hasAnyNegativeBase = result?.base; - if (hasAnyNegativeBase && isSpanishCompany) - throw new UserError('Negative basis'); - - // Invoicing - - query = `SELECT invoiceSerial(?, ?, ?) AS serial`; - const [invoiceSerial] = await Self.rawSql(query, [ - client.id, + const invoiceType = 'G'; + const invoiceId = await models.Ticket.makeInvoice( + ctx, + invoiceType, args.companyFk, - 'G' - ], options); - const serialLetter = invoiceSerial.serial; - - query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`; - await Self.rawSql(query, [ - serialLetter, - args.invoiceDate - ], options); - - const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, options); - if (!newInvoice) - throw new UserError('No tickets to invoice', 'notInvoiced'); - - await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], options); - invoiceId = newInvoice.id; + args.invoiceDate, + options + ); if (tx) await tx.commit(); + + return invoiceId; } catch (e) { if (tx) await tx.rollback(); throw e; } - - return invoiceId; }; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js index a999437c0..a48664b30 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js +++ b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js @@ -14,8 +14,7 @@ module.exports = Self => { }, { arg: 'printerFk', type: 'number', - description: 'The printer to print', - required: true + description: 'The printer to print' } ], http: { @@ -51,7 +50,7 @@ module.exports = Self => { const ref = invoiceOut.ref; const client = invoiceOut.client(); - if (client.isToBeMailed) { + if (client.isToBeMailed || !printerFk) { try { ctx.args = { reference: ref, diff --git a/modules/item/back/methods/item/filter.js b/modules/item/back/methods/item/filter.js index c0b1cc0d9..d5de6a954 100644 --- a/modules/item/back/methods/item/filter.js +++ b/modules/item/back/methods/item/filter.js @@ -145,7 +145,7 @@ module.exports = Self => { const stmts = []; const stmt = new ParameterizedSQL( - `SELECT + `SELECT i.id, i.image, i.name, @@ -164,6 +164,8 @@ module.exports = Self => { i.stemMultiplier, i.typeFk, i.isFloramondo, + i.recycledPlastic, + i.nonRecycledPlastic, pr.name AS producer, it.name AS typeName, it.workerFk AS buyerFk, diff --git a/modules/item/back/models/item.json b/modules/item/back/models/item.json index 4a7e04002..e99dcd996 100644 --- a/modules/item/back/models/item.json +++ b/modules/item/back/models/item.json @@ -125,6 +125,12 @@ "minPrice": { "type": "number" }, + "recycledPlastic": { + "type": "number" + }, + "nonRecycledPlastic": { + "type": "number" + }, "packingOut": { "type": "number" }, diff --git a/modules/item/front/basic-data/index.html b/modules/item/front/basic-data/index.html index a7e603bd1..fba4d679c 100644 --- a/modules/item/front/basic-data/index.html +++ b/modules/item/front/basic-data/index.html @@ -82,6 +82,8 @@ vn-name="expence" initial-data="$ctrl.item.expense"> + + - - - - - - + + + + + + - - - - - - + + + + + + + + + + - - El nombre completo se calcula - basado en los tags 1-3. +Full name calculates based on tags 1-3. Is not recommended to change it manually: >- + El nombre completo se calcula + basado en los tags 1-3. No se recomienda cambiarlo manualmente Is active: Activo Expense: Gasto @@ -13,4 +13,6 @@ Is shown at website, app that this item cannot travel (wreath, palms, ...): Se m Multiplier: Multiplicador Generic: Genérico This item does need a photo: Este artículo necesita una foto -Do photo: Hacer foto \ No newline at end of file +Do photo: Hacer foto +Recycled Plastic: Plástico reciclado +Non recycled plastic: Plástico no reciclado diff --git a/modules/item/front/summary/index.html b/modules/item/front/summary/index.html index 46a2baef4..fcc267b4d 100644 --- a/modules/item/front/summary/index.html +++ b/modules/item/front/summary/index.html @@ -113,9 +113,21 @@ + + + + + + + +

diff --git a/modules/route/back/methods/roadmap/clone.js b/modules/route/back/methods/roadmap/clone.js new file mode 100644 index 000000000..456ed823d --- /dev/null +++ b/modules/route/back/methods/roadmap/clone.js @@ -0,0 +1,81 @@ +const UserError = require('vn-loopback/util/user-error'); +module.exports = Self => { + Self.remoteMethod('clone', { + description: 'Clones the selected routes', + accessType: 'WRITE', + accepts: [ + { + arg: 'ids', + type: ['number'], + required: true, + description: 'The routes ids to clone' + }, + { + arg: 'etd', + type: 'date', + required: true, + description: 'The estimated time of departure for all roadmaps' + } + ], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/clone`, + verb: 'POST' + } + }); + + Self.clone = async(ids, etd) => { + const tx = await Self.beginTransaction({}); + try { + const models = Self.app.models; + const options = {transaction: tx}; + const originalRoadmaps = await models.Roadmap.find({ + where: {id: {inq: ids}}, + fields: [ + 'id', + 'name', + 'tractorPlate', + 'trailerPlate', + 'phone', + 'supplierFk', + 'etd', + 'observations', + 'price'], + include: [{ + relation: 'expeditionTruck', + scope: { + fields: ['roadmapFk', 'warehouseFk', 'eta', 'description'] + } + }] + + }, options); + + if (ids.length != originalRoadmaps.length) + throw new UserError(`The amount of roadmaps found don't match`); + + for (const roadmap of originalRoadmaps) { + roadmap.id = undefined; + roadmap.etd = etd; + + const clone = await models.Roadmap.create(roadmap, options); + + const expeditionTrucks = roadmap.expeditionTruck(); + expeditionTrucks.map(expeditionTruck => { + expeditionTruck.roadmapFk = clone.id; + return expeditionTruck; + }); + await models.ExpeditionTruck.create(expeditionTrucks, options); + } + + await tx.commit(); + + return true; + } catch (e) { + await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/route/back/methods/roadmap/specs/clone.spec.js b/modules/route/back/methods/roadmap/specs/clone.spec.js new file mode 100644 index 000000000..41e696157 --- /dev/null +++ b/modules/route/back/methods/roadmap/specs/clone.spec.js @@ -0,0 +1,109 @@ +const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; + +describe('AgencyTerm filter()', () => { + const authUserId = 9; + const today = Date.vnNew(); + today.setHours(2, 0, 0, 0); + + it('should return all results matching the filter', async() => { + const tx = await models.AgencyTerm.beginTransaction({}); + + try { + const options = {transaction: tx}; + const filter = {}; + const ctx = {req: {accessToken: {userId: authUserId}}}; + + const agencyTerms = await models.AgencyTerm.filter(ctx, filter, options); + const firstAgencyTerm = agencyTerms[0]; + + expect(firstAgencyTerm.routeFk).toEqual(1); + expect(agencyTerms.length).toEqual(5); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return results matching "search" searching by integer', async() => { + let ctx = { + args: { + search: 1, + } + }; + + let result = await app.models.AgencyTerm.filter(ctx); + + expect(result.length).toEqual(1); + expect(result[0].routeFk).toEqual(1); + }); + + it('should return results matching "search" searching by string', async() => { + let ctx = { + args: { + search: 'Plants SL', + } + }; + + let result = await app.models.AgencyTerm.filter(ctx); + + expect(result.length).toEqual(2); + }); + + it('should return results matching "from" and "to"', async() => { + const tx = await models.Buy.beginTransaction({}); + const options = {transaction: tx}; + + try { + const from = Date.vnNew(); + from.setHours(0, 0, 0, 0); + + const to = Date.vnNew(); + to.setHours(23, 59, 59, 999); + + const ctx = { + args: { + from: from, + to: to + } + }; + + const results = await models.AgencyTerm.filter(ctx, options); + + expect(results.length).toBe(5); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return results matching "agencyModeFk"', async() => { + let ctx = { + args: { + agencyModeFk: 1, + } + }; + + let result = await app.models.AgencyTerm.filter(ctx); + + expect(result.length).toEqual(1); + expect(result[0].routeFk).toEqual(1); + }); + + it('should return results matching "agencyFk"', async() => { + let ctx = { + args: { + agencyFk: 2, + } + }; + + let result = await app.models.AgencyTerm.filter(ctx); + + expect(result.length).toEqual(1); + expect(result[0].routeFk).toEqual(2); + }); +}); diff --git a/modules/route/back/methods/vehicle/sorted.js b/modules/route/back/methods/vehicle/sorted.js new file mode 100644 index 000000000..b379743bc --- /dev/null +++ b/modules/route/back/methods/vehicle/sorted.js @@ -0,0 +1,27 @@ +module.exports = Self => { + Self.remoteMethod('sorted', { + description: 'Sort the vehicles by warehouse', + accessType: 'WRITE', + accepts: [{ + arg: 'warehouseFk', + type: 'number' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/sorted`, + verb: `POST` + } + }); + + Self.sorted = async warehouseFk => { + return Self.rawSql(` + SELECT v.id, v.warehouseFk, v.numberPlate, w.name + FROM vehicle v + JOIN warehouse w ON w.id = v.warehouseFk + ORDER BY v.warehouseFk = ? DESC, w.id, v.numberPlate ASC; + `, [warehouseFk]); + }; +}; diff --git a/modules/route/back/model-config.json b/modules/route/back/model-config.json index 31aaad9f5..6688a243a 100644 --- a/modules/route/back/model-config.json +++ b/modules/route/back/model-config.json @@ -5,18 +5,22 @@ "AgencyTermConfig": { "dataSource": "vn" }, - "Route": { + "DeliveryPoint": { "dataSource": "vn" }, - "Vehicle": { + "ExpeditionTruck": { + "dataSource": "vn" + }, + "Roadmap": { + "dataSource": "vn" + }, + "Route": { "dataSource": "vn" }, "RouteLog": { "dataSource": "vn" }, - "DeliveryPoint": { + "Vehicle": { "dataSource": "vn" } } - - diff --git a/modules/route/back/models/expedition-truck.json b/modules/route/back/models/expedition-truck.json new file mode 100644 index 000000000..8edc7347f --- /dev/null +++ b/modules/route/back/models/expedition-truck.json @@ -0,0 +1,43 @@ +{ + "name": "ExpeditionTruck", + "base": "VnModel", + "options": { + "mysql": { + "table": "expeditionTruck" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "roadmapFk": { + "type": "number" + }, + "warehouseFk": { + "type": "number" + }, + "eta": { + "type": "date" + }, + "description": { + "type": "string" + }, + "userFk": { + "type": "number" + } + }, + "relations": { + "roadmap": { + "type": "belongsTo", + "model": "Roadmap", + "foreignKey": "roadmapFk" + }, + "warehouse": { + "type": "belongsTo", + "model": "Warehouse", + "foreignKey": "warehouseFk" + } + } +} diff --git a/modules/route/back/models/roadmap.js b/modules/route/back/models/roadmap.js new file mode 100644 index 000000000..4a2a02022 --- /dev/null +++ b/modules/route/back/models/roadmap.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/roadmap/clone')(Self); +}; diff --git a/modules/route/back/models/roadmap.json b/modules/route/back/models/roadmap.json new file mode 100644 index 000000000..7ca8fe0f6 --- /dev/null +++ b/modules/route/back/models/roadmap.json @@ -0,0 +1,63 @@ +{ + "name": "Roadmap", + "base": "VnModel", + "options": { + "mysql": { + "table": "roadmap" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "name": { + "type": "string" + }, + "tractorPlate": { + "type": "string" + }, + "trailerPlate": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "supplierFk": { + "type": "number" + }, + "etd": { + "type": "date" + }, + "observations": { + "type": "string" + }, + "userFk": { + "type": "number" + }, + "price": { + "type": "number" + }, + "driverName": { + "type": "string" + } + }, + "relations": { + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "userFk" + }, + "supplier": { + "type": "belongsTo", + "model": "Supplier", + "foreignKey": "supplierFk" + }, + "expeditionTruck": { + "type": "hasMany", + "model": "ExpeditionTruck", + "foreignKey": "roadmapFk" + } + } +} diff --git a/modules/route/back/models/vehicle.js b/modules/route/back/models/vehicle.js new file mode 100644 index 000000000..73e321443 --- /dev/null +++ b/modules/route/back/models/vehicle.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/vehicle/sorted')(Self); +}; diff --git a/modules/route/front/basic-data/index.html b/modules/route/front/basic-data/index.html index c63fee53b..ade9230e8 100644 --- a/modules/route/front/basic-data/index.html +++ b/modules/route/front/basic-data/index.html @@ -23,19 +23,21 @@ + {{::numberPlate}} - {{::name}} + vn-name="created"> { + this.vehicles = res.data; + }); + } + onSubmit() { this.$.watcher.submit().then(() => this.card.reload() diff --git a/modules/route/front/index.js b/modules/route/front/index.js index c43048df5..803fc1045 100644 --- a/modules/route/front/index.js +++ b/modules/route/front/index.js @@ -16,3 +16,4 @@ import './agency-term/createInvoiceIn'; import './agency-term-search-panel'; import './ticket-popup'; import './sms'; +import './roadmap'; diff --git a/modules/route/front/roadmap/basic-data/index.html b/modules/route/front/roadmap/basic-data/index.html new file mode 100644 index 000000000..28c67eb47 --- /dev/null +++ b/modules/route/front/roadmap/basic-data/index.html @@ -0,0 +1,98 @@ + + + +
+ + + + + + + + + + + + + + + + + + + {{::id}} - {{::nickname}} + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/modules/route/front/roadmap/basic-data/index.js b/modules/route/front/roadmap/basic-data/index.js new file mode 100644 index 000000000..d5b39b76e --- /dev/null +++ b/modules/route/front/roadmap/basic-data/index.js @@ -0,0 +1,16 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + this.$.watcher.submit(); + } +} + +ngModule.component('vnRoadmapBasicData', { + template: require('./index.html'), + controller: Controller, + bindings: { + roadmap: '<' + } +}); diff --git a/modules/route/front/roadmap/card/index.html b/modules/route/front/roadmap/card/index.html new file mode 100644 index 000000000..97ca40f95 --- /dev/null +++ b/modules/route/front/roadmap/card/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/modules/route/front/roadmap/card/index.js b/modules/route/front/roadmap/card/index.js new file mode 100644 index 000000000..ff2d13616 --- /dev/null +++ b/modules/route/front/roadmap/card/index.js @@ -0,0 +1,19 @@ +import ngModule from '../../module'; +import ModuleCard from 'salix/components/module-card'; + +class Controller extends ModuleCard { + reload() { + const filter = { + include: [ + {relation: 'supplier'} + ] + }; + this.$http.get(`Roadmaps/${this.$params.id}`, {filter}) + .then(res => this.roadmap = res.data); + } +} + +ngModule.vnComponent('vnRoadmapCard', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/route/front/roadmap/create/index.html b/modules/route/front/roadmap/create/index.html new file mode 100644 index 000000000..f5a26566e --- /dev/null +++ b/modules/route/front/roadmap/create/index.html @@ -0,0 +1,37 @@ + + +
+ + + + + + + + + + + + + + + + +
diff --git a/modules/route/front/roadmap/create/index.js b/modules/route/front/roadmap/create/index.js new file mode 100644 index 000000000..7e638da94 --- /dev/null +++ b/modules/route/front/roadmap/create/index.js @@ -0,0 +1,23 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $, $transclude, vnReport, vnEmail) { + super($element, $, $transclude); + this.roadmap = {etd: Date.vnNew()}; + } + + onSubmit() { + this.$.watcher.submit().then( + res => this.$state.go('route.roadmap.card.summary', {id: res.data.id}) + ); + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnRoadmapCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/route/front/roadmap/create/style.scss b/modules/route/front/roadmap/create/style.scss new file mode 100644 index 000000000..8ee7ecb09 --- /dev/null +++ b/modules/route/front/roadmap/create/style.scss @@ -0,0 +1,6 @@ +vn-ticket-request { + .vn-textfield { + margin: 0!important; + max-width: 100px; + } +} diff --git a/modules/route/front/roadmap/descriptor/index.html b/modules/route/front/roadmap/descriptor/index.html new file mode 100644 index 000000000..92ae8eab1 --- /dev/null +++ b/modules/route/front/roadmap/descriptor/index.html @@ -0,0 +1,39 @@ + + + + Delete roadmap + + + +
+ + + + + + + {{$ctrl.roadmap.supplier.nickname}} + + +
+
+
+ + + + diff --git a/modules/route/front/roadmap/descriptor/index.js b/modules/route/front/roadmap/descriptor/index.js new file mode 100644 index 000000000..2846b073a --- /dev/null +++ b/modules/route/front/roadmap/descriptor/index.js @@ -0,0 +1,26 @@ +import ngModule from '../../module'; +import Descriptor from 'salix/components/descriptor'; + +class Controller extends Descriptor { + get roadmap() { + return this.entity; + } + + set roadmap(value) { + this.entity = value; + } + + onDelete() { + return this.$http.delete(`Roadmaps/${this.roadmap.id}`) + .then(() => this.$state.go('route.roadmap')) + .then(() => this.vnApp.showSuccess(this.$t('Roadmap removed'))); + } +} + +ngModule.component('vnRoadmapDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + roadmap: '<' + } +}); diff --git a/modules/route/front/roadmap/descriptor/locale/es.yml b/modules/route/front/roadmap/descriptor/locale/es.yml new file mode 100644 index 000000000..376209694 --- /dev/null +++ b/modules/route/front/roadmap/descriptor/locale/es.yml @@ -0,0 +1,3 @@ +Delete roadmap: Eliminar troncal +The roadmap will be removed: La troncal será eliminada +Roadmap removed: Troncal eliminada diff --git a/modules/route/front/roadmap/index.js b/modules/route/front/roadmap/index.js new file mode 100644 index 000000000..91b782a9b --- /dev/null +++ b/modules/route/front/roadmap/index.js @@ -0,0 +1,9 @@ +import './main'; +import './index/'; +import './summary'; +import './card'; +import './descriptor'; +import './create'; +import './basic-data'; +import './search-panel'; +import './stops'; diff --git a/modules/route/front/roadmap/index/index.html b/modules/route/front/roadmap/index/index.html new file mode 100644 index 000000000..6f8cbecc4 --- /dev/null +++ b/modules/route/front/roadmap/index/index.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + Roadmap + ETD + Carrier + Plate + Price + Observations + + + + + + + + + + {{::roadmap.name}} + {{::roadmap.etd | date:'dd/MM/yyyy HH:mm'}} + + + {{::roadmap.supplier.nickname}} + + + {{::roadmap.tractorPlate | dashIfEmpty}} + {{::roadmap.price | currency: 'EUR':2 | dashIfEmpty}} + {{::roadmap.observations | dashIfEmpty}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/route/front/roadmap/index/index.js b/modules/route/front/roadmap/index/index.js new file mode 100644 index 000000000..3ffc5b4b1 --- /dev/null +++ b/modules/route/front/roadmap/index/index.js @@ -0,0 +1,62 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +class Controller extends Section { + get checked() { + const roadmaps = this.$.model.data || []; + const checkedRoadmap = []; + for (let roadmap of roadmaps) { + if (roadmap.checked) + checkedRoadmap.push(roadmap); + } + + return checkedRoadmap; + } + + get totalChecked() { + return this.checked.length; + } + + preview(roadmap) { + this.roadmapSelected = roadmap; + this.$.summary.show(); + } + + openClonationDialog() { + this.$.clonationDialog.show(); + this.etd = Date.vnNew(); + } + + cloneSelectedRoadmaps() { + try { + if (!this.etd) + throw new Error(`The date can't be empty`); + + const roadmapsIds = []; + for (let roadmap of this.checked) + roadmapsIds.push(roadmap.id); + + return this.$http.post('Roadmaps/clone', {ids: roadmapsIds, etd: this.etd}).then(() => { + this.$.model.refresh(); + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } catch (e) { + this.vnApp.showError(this.$t(e.message)); + } + } + + deleteRoadmaps() { + console.log(this.checked); + + for (const roadmap of this.checked) { + this.$http.delete(`Roadmaps/${roadmap.id}`) + .then(() => this.$.model.refresh()) + .then(() => this.vnApp.showSuccess(this.$t('Roadmaps removed'))); + } + } +} + +ngModule.vnComponent('vnRoadmapIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/route/front/roadmap/index/locale/es.yml b/modules/route/front/roadmap/index/locale/es.yml new file mode 100644 index 000000000..dd93eac6e --- /dev/null +++ b/modules/route/front/roadmap/index/locale/es.yml @@ -0,0 +1,3 @@ +Delete roadmap(s): Eliminar troncal(es) +Selected roadmaps will be removed: Los troncales seleccionados serán eliminados +Roadmaps removed: Troncales eliminados diff --git a/modules/route/front/roadmap/locale/es.yml b/modules/route/front/roadmap/locale/es.yml new file mode 100644 index 000000000..e136eca31 --- /dev/null +++ b/modules/route/front/roadmap/locale/es.yml @@ -0,0 +1,14 @@ +Roadmaps: Troncales +Roadmap: Troncal +Driver name: Nombre conductor +Plate: Matrícula +Price: Precio +Observations: Observaciones +Clone selected roadmaps: Clonar troncales seleccionadas +Select the estimated time of departure (ETD): Seleccione la hora estimada de salida (ETD) +Create roadmap: Crear troncal +Tractor plate: Matrícula tractor +Trailer plate: Matrícula trailer +Carrier: Transportista +ETD date: Fecha ETD +ETD hour: Hora ETD diff --git a/modules/route/front/roadmap/main/index.html b/modules/route/front/roadmap/main/index.html new file mode 100644 index 000000000..3a8eb2599 --- /dev/null +++ b/modules/route/front/roadmap/main/index.html @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/modules/route/front/roadmap/main/index.js b/modules/route/front/roadmap/main/index.js new file mode 100644 index 000000000..e7b3366f2 --- /dev/null +++ b/modules/route/front/roadmap/main/index.js @@ -0,0 +1,61 @@ +import ngModule from '../../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class Roadmap extends ModuleMain { + constructor($element, $) { + super($element, $); + + this.include = { + relation: 'supplier', + scope: { + fields: ['nickname'] + } + }; + } + + $postLink() { + const from = Date.vnNew(); + from.setHours(0, 0, 0, 0); + + const to = Date.vnNew(); + to.setHours(23, 59, 59, 999); + + this.filterParams = { + from: from, + to: to + }; + + this.$.model.addFilter({where: { + and: [ + {etd: {gte: from}}, + {etd: {lte: to}} + ] + }}); + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {name: {like: `%${value}%`}}; + case 'from': + return {etd: {gte: value}}; + case 'to': + return {etd: {lte: value}}; + case 'supplierFk': + case 'price': + return {[param]: value}; + case 'tractorPlate': + case 'trailerPlate': + case 'phone': + case 'driverName': + return {[param]: {like: `%${value}%`}}; + } + } +} + +ngModule.vnComponent('vnRoadmap', { + controller: Roadmap, + template: require('./index.html') +}); diff --git a/modules/route/front/roadmap/main/locale/es.yml b/modules/route/front/roadmap/main/locale/es.yml new file mode 100644 index 000000000..78342bce8 --- /dev/null +++ b/modules/route/front/roadmap/main/locale/es.yml @@ -0,0 +1 @@ +Search roadmap by id or trunk: Buscar troncales por id o troncal diff --git a/modules/route/front/roadmap/search-panel/index.html b/modules/route/front/roadmap/search-panel/index.html new file mode 100644 index 000000000..53fd37344 --- /dev/null +++ b/modules/route/front/roadmap/search-panel/index.html @@ -0,0 +1,74 @@ +
+
+ + + + +
+ + + + + + +
+ + + + + + + + + + {{::id}} - {{::nickname}} + + + + + + + + + + + + + + +
+
diff --git a/modules/route/front/roadmap/search-panel/index.js b/modules/route/front/roadmap/search-panel/index.js new file mode 100644 index 000000000..499027d14 --- /dev/null +++ b/modules/route/front/roadmap/search-panel/index.js @@ -0,0 +1,7 @@ +import ngModule from '../../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +ngModule.component('vnRoadmapSearchPanel', { + template: require('./index.html'), + controller: SearchPanel +}); diff --git a/modules/route/front/roadmap/stops/index.html b/modules/route/front/roadmap/stops/index.html new file mode 100644 index 000000000..b69492a21 --- /dev/null +++ b/modules/route/front/roadmap/stops/index.html @@ -0,0 +1,71 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/modules/route/front/roadmap/stops/index.js b/modules/route/front/roadmap/stops/index.js new file mode 100644 index 000000000..075a1c8a4 --- /dev/null +++ b/modules/route/front/roadmap/stops/index.js @@ -0,0 +1,39 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + add() { + const filter = { + fields: ['etd'] + }; + this.$http.get(`Roadmaps/${this.$params.id}`, {filter}) + .then(res => { + this.roadmap = res.data; + + const eta = new Date(this.roadmap.etd); + eta.setDate(eta.getDate() + 1); + + this.$.model.insert({ + roadmapFk: this.$params.id, + eta: eta + }); + }); + } + + onSubmit() { + this.$.watcher.check(); + this.$.model.save().then(() => { + this.$.watcher.notifySaved(); + this.$.watcher.updateOriginalData(); + this.$.model.refresh(); + }); + } +} + +ngModule.component('vnRoadmapStops', { + template: require('./index.html'), + controller: Controller, + bindings: { + roadmap: '<' + } +}); diff --git a/modules/route/front/roadmap/stops/locale/es.yml b/modules/route/front/roadmap/stops/locale/es.yml new file mode 100644 index 000000000..1db275949 --- /dev/null +++ b/modules/route/front/roadmap/stops/locale/es.yml @@ -0,0 +1,4 @@ +Remove stop: Eliminar parada +Add stop: Añadir parada +ETA date: Fecha ETA +ETA hour: Hora ETA diff --git a/modules/route/front/roadmap/summary/index.html b/modules/route/front/roadmap/summary/index.html new file mode 100644 index 000000000..e6b50601e --- /dev/null +++ b/modules/route/front/roadmap/summary/index.html @@ -0,0 +1,113 @@ + +
+ {{summary.id}} - {{summary.name}} +
+ + + + + {{summary.supplier.nickname}} + + + + + + + + + + + + + + + + + + +

+ + Stops + + + +

+ + + + Wharehouse + ETA + + + + + {{expeditionTruck.warehouse.name}} + {{expeditionTruck.eta | date:'dd/MM/yyyy HH:mm'}} + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/route/front/roadmap/summary/index.js b/modules/route/front/roadmap/summary/index.js new file mode 100644 index 000000000..041b43ce3 --- /dev/null +++ b/modules/route/front/roadmap/summary/index.js @@ -0,0 +1,68 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; +import './style.scss'; + +class Controller extends Component { + set roadmap(value) { + this._roadmap = value; + this.$.summary = null; + if (!value) return; + + this.loadData(); + } + + get roadmap() { + return this._roadmap; + } + + loadData() { + const filter = { + include: [ + {relation: 'supplier'}, + {relation: 'worker'}, + {relation: 'expeditionTruck', + scope: { + include: [ + {relation: 'warehouse'} + ] + }} + ] + }; + this.$http.get(`Roadmaps/${this.roadmap.id}`, {filter}) + .then(res => this.$.summary = res.data); + } + + getETD() { + const eta = new Date(this.roadmap.etd); + eta.setDate(eta.getDate() + 1); + + this.expeditionTruck = {eta: eta}; + } + + onAddAccept() { + try { + const data = { + roadmapFk: this.roadmap.id, + warehouseFk: this.expeditionTruck.warehouseFk, + eta: this.expeditionTruck.eta, + description: this.expeditionTruck.description + }; + + this.$http.post(`ExpeditionTrucks`, data) + .then(() => { + this.loadData(); + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } catch (e) { + this.vnApp.showError(this.$t(e.message)); + } + } +} + +ngModule.component('vnRoadmapSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + roadmap: '<' + } +}); diff --git a/modules/route/front/roadmap/summary/locale/es.yml b/modules/route/front/roadmap/summary/locale/es.yml new file mode 100644 index 000000000..f2d82438a --- /dev/null +++ b/modules/route/front/roadmap/summary/locale/es.yml @@ -0,0 +1,3 @@ +Stops: Paradas +Wharehouse: Almacén +You must fill all the fields: Debes rellenar todos los campos diff --git a/modules/route/front/roadmap/summary/style.scss b/modules/route/front/roadmap/summary/style.scss new file mode 100644 index 000000000..63b3f625a --- /dev/null +++ b/modules/route/front/roadmap/summary/style.scss @@ -0,0 +1,10 @@ +@import "variables"; + +vn-roadmap-summary .summary { + a { + display: flex; + align-items: center; + height: 18.328px; + } + +} diff --git a/modules/route/front/routes.json b/modules/route/front/routes.json index 75e1fdc57..3b866581d 100644 --- a/modules/route/front/routes.json +++ b/modules/route/front/routes.json @@ -7,12 +7,17 @@ "menus": { "main": [ {"state": "route.index", "icon": "icon-delivery"}, - {"state": "route.agencyTerm.index", "icon": "icon-agency-term"} + {"state": "route.agencyTerm.index", "icon": "icon-agency-term"}, + {"state": "route.roadmap", "icon": "icon-trailer"} ], "card": [ {"state": "route.card.basicData", "icon": "settings"}, {"state": "route.card.tickets", "icon": "icon-ticket"}, {"state": "route.card.log", "icon": "history"} + ], + "roadmap": [ + {"state": "route.roadmap.card.basicData", "icon": "settings"}, + {"state": "route.roadmap.card.stops", "icon": "icon-lines"} ] }, "routes": [ @@ -90,6 +95,46 @@ "route": "$ctrl.route" }, "acl": ["delivery"] + }, { + "url": "/roadmap?q", + "state": "route.roadmap", + "component": "vn-roadmap", + "description": "Roadmaps" + }, { + "url": "/create", + "state": "route.roadmap.create", + "component": "vn-roadmap-create", + "description": "Create roadmap" + },{ + "url": "/:id", + "state": "route.roadmap.card", + "component": "vn-roadmap-card", + "abstract": true, + "description": "Detail" + },{ + "url": "/summary", + "state": "route.roadmap.card.summary", + "component": "vn-roadmap-summary", + "description": "Summary", + "params": { + "roadmap": "$ctrl.roadmap" + } + },{ + "url": "/basic-data", + "state": "route.roadmap.card.basicData", + "component": "vn-roadmap-basic-data", + "description": "Basic data", + "params": { + "roadmap": "$ctrl.roadmap" + } + }, { + "url": "/stops", + "state": "route.roadmap.card.stops", + "component": "vn-roadmap-stops", + "description": "Stops", + "params": { + "route": "$ctrl.roadmap" + } } ] } diff --git a/modules/shelving/front/descriptor/index.html b/modules/shelving/front/descriptor/index.html index 1344bb52a..53dd37258 100644 --- a/modules/shelving/front/descriptor/index.html +++ b/modules/shelving/front/descriptor/index.html @@ -7,7 +7,7 @@ ng-click="deleteShelving.show()" name="deleteShelving" translate> - Delete + Delete shelving @@ -32,7 +32,7 @@ @@ -40,6 +40,6 @@ - - \ No newline at end of file + diff --git a/modules/shelving/front/descriptor/locale/es.yml b/modules/shelving/front/descriptor/locale/es.yml new file mode 100644 index 000000000..e7fafc320 --- /dev/null +++ b/modules/shelving/front/descriptor/locale/es.yml @@ -0,0 +1,3 @@ +Delete shelving: Eliminar carro +Shelving will be removed: El carro será eliminado +Shelving removed: Carro eliminado diff --git a/modules/supplier/back/models/supplier.js b/modules/supplier/back/models/supplier.js index 9c78e8590..e88fadc0b 100644 --- a/modules/supplier/back/models/supplier.js +++ b/modules/supplier/back/models/supplier.js @@ -103,7 +103,7 @@ module.exports = Self => { const changes = ctx.data || ctx.instance; const orgData = ctx.currentInstance; const loopBackContext = LoopBackContext.getCurrentContext(); - const accessToken = {req: loopBackContext.active.accessToken}; + const accessToken = {req: loopBackContext.active}; const editPayMethodCheck = await Self.app.models.ACL.checkAccessAcl(accessToken, 'Supplier', 'editPayMethodCheck', 'WRITE'); diff --git a/modules/ticket/back/methods/ticket/canBeInvoiced.js b/modules/ticket/back/methods/ticket/canBeInvoiced.js index 6b8f9e71a..0f6cb476b 100644 --- a/modules/ticket/back/methods/ticket/canBeInvoiced.js +++ b/modules/ticket/back/methods/ticket/canBeInvoiced.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = function(Self) { Self.remoteMethodCtx('canBeInvoiced', { description: 'Whether the ticket can or not be invoiced', @@ -21,8 +23,9 @@ module.exports = function(Self) { } }); - Self.canBeInvoiced = async(ticketsIds, options) => { + Self.canBeInvoiced = async(ctx, ticketsIds, options) => { const myOptions = {}; + const $t = ctx.req.__; // $translate if (typeof options == 'object') Object.assign(myOptions, options); @@ -31,29 +34,43 @@ module.exports = function(Self) { where: { id: {inq: ticketsIds} }, - fields: ['id', 'refFk', 'shipped', 'totalWithVat'] + fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk'] }, myOptions); + const [firstTicket] = tickets; + const companyFk = firstTicket.companyFk; - const query = ` - SELECT vn.hasSomeNegativeBase(t.id) AS hasSomeNegativeBase - FROM ticket t - WHERE id IN(?)`; - const ticketBases = await Self.rawSql(query, [ticketsIds], myOptions); - const hasSomeNegativeBase = ticketBases.some( - ticketBases => ticketBases.hasSomeNegativeBase - ); + const query = + `SELECT COUNT(*) isSpanishCompany + FROM supplier s + JOIN country c ON c.id = s.countryFk + AND c.code = 'ES' + WHERE s.id = ?`; + const [supplierCompany] = await Self.rawSql(query, [companyFk], options); + + const isSpanishCompany = supplierCompany?.isSpanishCompany; + + const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options); + const hasAnyNegativeBase = result?.base && isSpanishCompany; + if (hasAnyNegativeBase) + throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds})); const today = Date.vnNew(); - const invalidTickets = tickets.some(ticket => { + tickets.some(ticket => { const shipped = new Date(ticket.shipped); const shippingInFuture = shipped.getTime() > today.getTime(); - const isInvoiced = ticket.refFk; - const priceZero = ticket.totalWithVat == 0; + if (shippingInFuture) + throw new UserError(`Can't invoice to future`); - return isInvoiced || priceZero || shippingInFuture; + const isInvoiced = ticket.refFk; + if (isInvoiced) + throw new UserError(`This ticket is already invoiced`); + + const priceZero = ticket.totalWithVat == 0; + if (priceZero) + throw new UserError(`A ticket with an amount of zero can't be invoiced`); }); - return !(invalidTickets || hasSomeNegativeBase); + return true; }; }; diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js new file mode 100644 index 000000000..ca1bf15fb --- /dev/null +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -0,0 +1,104 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = function(Self) { + Self.remoteMethodCtx('invoiceTickets', { + description: 'Make out an invoice from one or more tickets', + accessType: 'WRITE', + accepts: [ + { + arg: 'ticketsIds', + description: 'The tickets id', + type: ['number'], + required: true + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/invoiceTickets`, + verb: 'POST' + } + }); + + Self.invoiceTickets = async(ctx, ticketsIds, options) => { + const models = Self.app.models; + const date = Date.vnNew(); + date.setHours(0, 0, 0, 0); + + const myOptions = {userId: ctx.req.accessToken.userId}; + let tx; + + if (typeof options === 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + let invoicesIds = []; + try { + const tickets = await models.Ticket.find({ + where: { + id: {inq: ticketsIds} + }, + fields: ['id', 'clientFk', 'companyFk'] + }, myOptions); + + const [firstTicket] = tickets; + const clientId = firstTicket.clientFk; + const companyId = firstTicket.companyFk; + + const isSameClient = tickets.every(ticket => ticket.clientFk === clientId); + if (!isSameClient) + throw new UserError(`You can't invoice tickets from multiple clients`); + + const client = await models.Client.findById(clientId, { + fields: ['id', 'hasToInvoiceByAddress'] + }, myOptions); + + if (client.hasToInvoiceByAddress) { + const query = ` + SELECT DISTINCT addressFk + FROM ticket t + WHERE id IN (?)`; + const result = await Self.rawSql(query, [ticketsIds], myOptions); + + const addressIds = result.map(address => address.addressFk); + for (const address of addressIds) + await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions); + } else + await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + + for (const invoiceId of invoicesIds) + await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null); + + return invoicesIds; + }; + + async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions) { + const models = Self.app.models; + + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + ${address ? `AND addressFk = ${address}` : ''} + `, [ticketsIds], myOptions); + + const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), myOptions); + invoicesIds.push(invoiceId); + } +}; + diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index ee82b874f..22fe7b3f5 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -6,15 +6,26 @@ module.exports = function(Self) { accessType: 'WRITE', accepts: [ { - arg: 'ticketsIds', - description: 'The tickets id', - type: ['number'], + arg: 'invoiceType', + description: 'The invoice type', + type: 'string', + required: true + }, + { + arg: 'companyFk', + description: 'The company id', + type: 'string', + required: true + }, + { + arg: 'invoiceDate', + description: 'The invoice date', + type: 'date', required: true } ], returns: { - arg: 'data', - type: 'boolean', + type: ['object'], root: true }, http: { @@ -23,10 +34,9 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, ticketsIds, options) => { + Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => { const models = Self.app.models; - const date = Date.vnNew(); - date.setHours(0, 0, 0, 0); + invoiceDate.setHours(0, 0, 0, 0); const myOptions = {userId: ctx.req.accessToken.userId}; let tx; @@ -40,81 +50,50 @@ module.exports = function(Self) { } let serial; - let invoiceId; - let invoiceOut; try { + const ticketToInvoice = await Self.rawSql(` + SELECT id + FROM tmp.ticketToInvoice`, null, myOptions); + + const ticketsIds = ticketToInvoice.map(ticket => ticket.id); const tickets = await models.Ticket.find({ where: { id: {inq: ticketsIds} }, - fields: ['id', 'clientFk', 'companyFk'] + fields: ['id', 'clientFk'] }, myOptions); + await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); + const [firstTicket] = tickets; const clientId = firstTicket.clientFk; - const companyId = firstTicket.companyFk; - - const isSameClient = tickets.every(ticket => ticket.clientFk == clientId); - if (!isSameClient) - throw new UserError(`You can't invoice tickets from multiple clients`); - const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions); if (!clientCanBeInvoiced) throw new UserError(`This client can't be invoiced`); - const ticketCanBeInvoiced = await models.Ticket.canBeInvoiced(ticketsIds, myOptions); - if (!ticketCanBeInvoiced) - throw new UserError(`Some of the selected tickets are not billable`); - const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`; const [result] = await Self.rawSql(query, [ clientId, - companyId, - 'R' + companyFk, + invoiceType, ], myOptions); serial = result.serial; - await Self.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; - CREATE TEMPORARY TABLE tmp.ticketToInvoice - (PRIMARY KEY (id)) - ENGINE = MEMORY - SELECT id FROM vn.ticket - WHERE id IN(?) AND refFk IS NULL - `, [ticketsIds], myOptions); - - await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, date], myOptions); + await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); + if (!resultInvoice) + throw new UserError('No tickets to invoice', 'notInvoiced'); - invoiceId = resultInvoice.id; + if (serial != 'R' && resultInvoice.id) + await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); - if (serial != 'R' && invoiceId) - await Self.rawSql('CALL invoiceOutBooking(?)', [invoiceId], myOptions); - - invoiceOut = await models.InvoiceOut.findById(invoiceId, { - include: { - relation: 'client' - } - }, myOptions); if (tx) await tx.commit(); + + return resultInvoice.id; } catch (e) { if (tx) await tx.rollback(); throw e; } - - if (serial != 'R' && invoiceId) - await models.InvoiceOut.createPdf(ctx, invoiceId); - - if (invoiceId) { - ctx.args = { - reference: invoiceOut.ref, - recipientId: invoiceOut.clientFk, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); - } - - return {invoiceFk: invoiceId, serial: serial}; }; }; diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 806f80227..538dbc49f 100644 --- a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -1,61 +1,81 @@ const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); describe('ticket canBeInvoiced()', () => { const userId = 19; const ticketId = 11; - const activeCtx = { - accessToken: {userId: userId} + const ctx = {req: {accessToken: {userId: userId}}}; + ctx.req.__ = value => { + return value; }; - beforeAll(async() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - }); - it('should return falsy for an already invoiced ticket', async() => { const tx = await models.Ticket.beginTransaction({}); + let error; try { const options = {transaction: tx}; const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('refFk', 'T1111111', options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`This ticket is already invoiced`); }); it('should return falsy for a ticket with a price of zero', async() => { const tx = await models.Ticket.beginTransaction({}); + let error; try { const options = {transaction: tx}; const ticket = await models.Ticket.findById(ticketId, null, options); await ticket.updateAttribute('totalWithVat', 0, options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`A ticket with an amount of zero can't be invoiced`); }); it('should return falsy for a ticket shipping in future', async() => { const tx = await models.Ticket.beginTransaction({}); + + let error; try { const options = {transaction: tx}; @@ -66,15 +86,26 @@ describe('ticket canBeInvoiced()', () => { await ticket.updateAttribute('shipped', shipped, options); - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(false); await tx.rollback(); } catch (e) { + error = e; await tx.rollback(); - throw e; } + + expect(error.message).toEqual(`Can't invoice to future`); }); it('should return truthy for an invoiceable ticket', async() => { @@ -83,7 +114,16 @@ describe('ticket canBeInvoiced()', () => { try { const options = {transaction: tx}; - const canBeInvoiced = await models.Ticket.canBeInvoiced([ticketId], options); + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); expect(canBeInvoiced).toEqual(true); diff --git a/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js new file mode 100644 index 000000000..8971fb24a --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js @@ -0,0 +1,115 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('ticket invoiceTickets()', () => { + const userId = 19; + const clientId = 1102; + const activeCtx = { + getLocale: () => { + return 'en'; + }, + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + }; + const ctx = {req: activeCtx}; + + beforeAll(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should throw an error when invoicing tickets from multiple clients', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const ticketsIds = [11, 16]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`You can't invoice tickets from multiple clients`); + }); + + it(`should throw an error when invoicing a client without tax data checked`, async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const client = await models.Client.findById(clientId, null, options); + await client.updateAttribute('isTaxDataChecked', false, options); + + const ticketsIds = [11]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This client can't be invoiced`); + }); + + it('should invoice a ticket, then try again to fail', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + let error; + + try { + const options = {transaction: tx}; + + const ticketsIds = [11]; + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`This ticket is already invoiced`); + }); + + it('should success to invoice a ticket', async() => { + const invoiceOutModel = models.InvoiceOut; + spyOn(invoiceOutModel, 'makePdfAndNotify'); + + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ticketsIds = [11]; + const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + + expect(invoicesIds.length).toBeGreaterThan(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 270ba5c93..9af6ad557 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -3,8 +3,9 @@ const LoopBackContext = require('loopback-context'); describe('ticket makeInvoice()', () => { const userId = 19; - const ticketId = 11; - const clientId = 1102; + const invoiceType = 'R'; + const companyFk = 442; + const invoiceDate = Date.vnNew(); const activeCtx = { getLocale: () => { return 'en'; @@ -20,77 +21,6 @@ describe('ticket makeInvoice()', () => { }); }); - it('should throw an error when invoicing tickets from multiple clients', async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - const otherClientTicketId = 16; - await models.Ticket.makeInvoice(ctx, [ticketId, otherClientTicketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`You can't invoice tickets from multiple clients`); - }); - - it(`should throw an error when invoicing a client without tax data checked`, async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - - const client = await models.Client.findById(clientId, null, options); - await client.updateAttribute('isTaxDataChecked', false, options); - - await models.Ticket.makeInvoice(ctx, [ticketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`This client can't be invoiced`); - }); - - it('should invoice a ticket, then try again to fail', async() => { - const invoiceOutModel = models.InvoiceOut; - spyOn(invoiceOutModel, 'createPdf'); - spyOn(invoiceOutModel, 'invoiceEmail'); - - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - - await models.Ticket.makeInvoice(ctx, [ticketId], options); - await models.Ticket.makeInvoice(ctx, [ticketId], options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error.message).toEqual(`Some of the selected tickets are not billable`); - }); - it('should success to invoice a ticket', async() => { const invoiceOutModel = models.InvoiceOut; spyOn(invoiceOutModel, 'createPdf'); @@ -101,10 +31,20 @@ describe('ticket makeInvoice()', () => { try { const options = {transaction: tx}; - const invoice = await models.Ticket.makeInvoice(ctx, [ticketId], options); + const ticketsIds = [11, 16]; + await models.Ticket.rawSql(` + DROP TEMPORARY TABLE IF EXISTS tmp.ticketToInvoice; + CREATE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketsIds], options); - expect(invoice.invoiceFk).toBeDefined(); - expect(invoice.serial).toEqual('T'); + const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); + + expect(invoiceId).toBeDefined(); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index e5a8e8d94..f0c85ecc4 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -39,4 +39,5 @@ module.exports = function(Self) { require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/saveSign')(Self); + require('../methods/ticket/invoiceTickets')(Self); }; diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index c849b59eb..0fc8488ca 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -270,8 +270,7 @@ class Controller extends Section { }); } - return this.$http.post(`Tickets/makeInvoice`, {ticketsIds: [this.id]}) - .then(() => this.reload()) + return this.$http.post(`Tickets/invoiceTickets`, {ticketsIds: [this.id]}) .then(() => this.reload()) .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 953de3962..914fe486c 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -191,7 +191,7 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { jest.spyOn(controller.vnApp, 'showSuccess'); const expectedParams = {ticketsIds: [ticket.id]}; - $httpBackend.expectPOST(`Tickets/makeInvoice`, expectedParams).respond(); + $httpBackend.expectPOST(`Tickets/invoiceTickets`, expectedParams).respond(); controller.makeInvoice(); $httpBackend.flush(); diff --git a/modules/ticket/front/index/index.js b/modules/ticket/front/index/index.js index 55229eabb..b3afc838c 100644 --- a/modules/ticket/front/index/index.js +++ b/modules/ticket/front/index/index.js @@ -163,7 +163,7 @@ export default class Controller extends Section { makeInvoice() { const ticketsIds = this.checked.map(ticket => ticket.id); - return this.$http.post(`Tickets/makeInvoice`, {ticketsIds}) + return this.$http.post(`Tickets/invoiceTickets`, {ticketsIds}) .then(() => this.$.model.refresh()) .then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced'))); } diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index c888f97da..8132bddb1 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -163,7 +163,7 @@ {{::entry.invoiceAmount | currency: 'EUR': 2}} - {{::entry.invoiceNumber}} + {{::entry.reference}} {{::entry.stickers}} {{::entry.loadedkg}} diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 978d613e9..6d23c1b66 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -43,9 +43,6 @@ "SSN": { "type" : "string" }, - "labelerFk": { - "type" : "number" - }, "mobileExtension": { "type" : "number" }, @@ -86,11 +83,6 @@ "type": "hasMany", "model": "WorkerTeamCollegues", "foreignKey": "workerFk" - }, - "sector": { - "type": "belongsTo", - "model": "Sector", - "foreignKey": "sectorFk" } } } diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index ace2f4e27..877add57b 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -31,7 +31,7 @@
-
{{'Contract' | translate}} #{{$ctrl.card.worker.hasWorkCenter}}
+
{{'Contract' | translate}} #{{$ctrl.businessId}}
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}} diff --git a/modules/worker/front/card/index.js b/modules/worker/front/card/index.js index e1eb97327..35f331764 100644 --- a/modules/worker/front/card/index.js +++ b/modules/worker/front/card/index.js @@ -33,11 +33,12 @@ class Controller extends ModuleCard { }; this.$http.get(`Workers/${this.$params.id}`, {filter}) - .then(res => this.worker = res.data); - this.$http.get(`Workers/${this.$params.id}/activeContract`) - .then(res => { - if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; - }); + .then(res => this.worker = res.data) + .then(() => + this.$http.get(`Workers/${this.$params.id}/activeContract`) + .then(res => { + if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; + })); } } diff --git a/modules/worker/front/create/index.html b/modules/worker/front/create/index.html index eb45704a7..481746761 100644 --- a/modules/worker/front/create/index.html +++ b/modules/worker/front/create/index.html @@ -38,7 +38,7 @@ { Object.assign(myOptions, options); const [res] = await Self.rawSql( - `CALL zone_getLeaves(?, ?, ?)`, + `CALL zone_getLeaves(?, ?, ?, FALSE)`, [id, parentId, search], myOptions ); diff --git a/print/templates/email/auth-code/assets/css/style.css b/print/templates/email/auth-code/assets/css/style.css index d3bfa2aea..7fbccdc3f 100644 --- a/print/templates/email/auth-code/assets/css/style.css +++ b/print/templates/email/auth-code/assets/css/style.css @@ -1,5 +1,6 @@ .code { border: 2px dashed #8dba25; border-radius: 3px; - text-align: center -} \ No newline at end of file + text-align: center; + font-size: 24px; +} diff --git a/print/templates/email/auth-code/auth-code.html b/print/templates/email/auth-code/auth-code.html index 0d303682a..ae9aa6478 100644 --- a/print/templates/email/auth-code/auth-code.html +++ b/print/templates/email/auth-code/auth-code.html @@ -1,23 +1,45 @@ - -
-
-

{{ $t('title') }}

-

{{ $t('description') }}

-

- {{ $t('device') }}: {{ device }} -

-

- {{$t('ip')}}: {{ ip }} -

-
-
-
-
-

{{ $t('Enter the following code to continue to your account') }}

-
- {{ code }} -
-

{{ $t('It expires in 5 minutes') }}

-
-
-
+ + + + + + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+

{{ $t('title') }}

+

{{ $t('description') }}

+

+ {{ $t('device') }}: {{ device }} +

+

+ {{$t('ip')}}: {{ ip }} +

+
+
+
+
+

{{ $t('Enter the following code to continue to your account. It expires in 5 minutes.') }}

+
+ {{ code }} +
+
+
+
+ + diff --git a/print/templates/email/auth-code/auth-code.js b/print/templates/email/auth-code/auth-code.js index 362b307d8..b16833036 100755 --- a/print/templates/email/auth-code/auth-code.js +++ b/print/templates/email/auth-code/auth-code.js @@ -1,10 +1,10 @@ const Component = require(`vn-print/core/component`); -const emailBody = new Component('email-body'); +const emailHeader = new Component('email-header'); module.exports = { name: 'auth-code', components: { - 'email-body': emailBody.build(), + 'email-header': emailHeader.build(), }, props: { code: { diff --git a/print/templates/email/auth-code/locale/en.yml b/print/templates/email/auth-code/locale/en.yml index 27b493e20..74ea87e32 100644 --- a/print/templates/email/auth-code/locale/en.yml +++ b/print/templates/email/auth-code/locale/en.yml @@ -3,5 +3,4 @@ title: Verification code description: Somebody did request a verification code for login. If you didn't request it, please ignore this email. device: 'Device' ip: 'IP' -Enter the following code to continue to your account: Enter the following code to continue to your account -It expires in 5 minutes: It expires in 5 minutes +Enter the following code to continue to your account. It expires in 5 minutes.: Enter the following code to continue to your account. It expires in 5 minutes. diff --git a/print/templates/email/auth-code/locale/es.yml b/print/templates/email/auth-code/locale/es.yml index 463ea95fa..92150c4ed 100644 --- a/print/templates/email/auth-code/locale/es.yml +++ b/print/templates/email/auth-code/locale/es.yml @@ -3,5 +3,4 @@ title: Código de verificación description: Alguien ha solicitado un código de verificación para poder iniciar sesión. Si no lo has solicitado tu, ignora este email. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account: Introduce el siguiente código para poder continuar con tu cuenta -It expires in 5 minutes: Expira en 5 minutos +Enter the following code to continue to your account. It expires in 5 minutes.: Introduce el siguiente código para poder continuar con tu cuenta. Expira en 5 minutos. diff --git a/print/templates/email/auth-code/locale/fr.yml b/print/templates/email/auth-code/locale/fr.yml index 454bb80ae..0b71d4228 100644 --- a/print/templates/email/auth-code/locale/fr.yml +++ b/print/templates/email/auth-code/locale/fr.yml @@ -3,5 +3,4 @@ title: Code de vérification description: Quelqu'un a demandé un code de vérification pour se connecter. Si ce n'était pas toi, ignore cet email. device: 'Appareil' ip: 'IP' -Enter the following code to continue to your account: Entrez le code suivant pour continuer avec votre compte -It expires in 5 minutes: Il expire dans 5 minutes. +Enter the following code to continue to your account. It expires in 5 minutes.: Entrez le code suivant pour continuer avec votre compte. Il expire dans 5 minutes. diff --git a/print/templates/email/auth-code/locale/pt.yml b/print/templates/email/auth-code/locale/pt.yml index 7f9eb85f5..1cf5d23be 100644 --- a/print/templates/email/auth-code/locale/pt.yml +++ b/print/templates/email/auth-code/locale/pt.yml @@ -3,5 +3,4 @@ title: Código de verificação description: Alguém solicitou um código de verificação para entrar. Se você não fez essa solicitação, ignore este e-mail. device: 'Dispositivo' ip: 'IP' -Enter the following code to continue to your account: Insira o seguinte código para continuar com sua conta. -It expires in 5 minutes: Expira em 5 minutos. +Enter the following code to continue to your account. It expires in 5 minutes.: Insira o seguinte código para continuar com sua conta. Expira em 5 minutos.