From 05b0a5342fb80ebc9c23050b6de258867505e73c Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 11 May 2022 14:53:52 +0200 Subject: [PATCH 001/150] feat(zone.events): new dialog when choose 'exclude' --- modules/zone/front/events/index.html | 69 +++++++++++++++++++++++++ modules/zone/front/events/index.js | 61 +++++++++++++++++++--- modules/zone/front/events/locale/es.yml | 3 ++ 3 files changed, 127 insertions(+), 6 deletions(-) diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index e71a1ae26..7bcbba256 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -198,3 +198,72 @@ message="This item will be deleted" question="Are you sure you want to continue?"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/zone/front/events/index.js b/modules/zone/front/events/index.js index 0df16a42a..c5843e362 100644 --- a/modules/zone/front/events/index.js +++ b/modules/zone/front/events/index.js @@ -5,7 +5,7 @@ class Controller extends Section { constructor($element, $, vnWeekDays) { super($element, $); this.vnWeekDays = vnWeekDays; - this.editMode = 'include'; + this.editMode = 'exclude'; } $onInit() { @@ -52,14 +52,23 @@ class Controller extends Section { if (this.editMode == 'include') { if (events.length) this.edit(events[0]); - else + else { this.create(type, days, weekday); + console.log(type); + } } else { - if (exclusions.length) - this.exclusionDelete(exclusions); - else - this.exclusionCreate(days); + console.log(type); + this.selected = { + type: 'all', + dated: days[0] + }; + this.$.excludeDialog.show(); } + + // if (exclusions.length) + // this.exclusionDelete(exclusions); + // else + // this.exclusionCreate(days); } onEditClick(row, event) { @@ -170,6 +179,46 @@ class Controller extends Section { this.$q.all(reqs) .then(() => this.refresh()); } + + onSearch(params) { + this.$.model.applyFilter({}, params).then(() => { + const data = this.$.model.data; + this.$.treeview.data = data; + }); + } + + onFetch(item) { + const params = item ? {parentId: item.id} : null; + return this.$.model.applyFilter({}, params) + .then(() => this.$.model.data); + } + + onSort(a, b) { + if (b.selected !== a.selected) { + if (a.selected == null) + return 1; + if (b.selected == null) + return -1; + return b.selected - a.selected; + } + + return a.name.localeCompare(b.name); + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return {name: {like: `%${value}%`}}; + } + } + + onSelection2(value, item) { + if (value == null) + value = undefined; + const params = {geoId: item.id, isIncluded: value}; + const path = `zones/${this.zone.id}/toggleIsIncluded`; + this.$http.post(path, params); + } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; diff --git a/modules/zone/front/events/locale/es.yml b/modules/zone/front/events/locale/es.yml index eb581a719..59b05e696 100644 --- a/modules/zone/front/events/locale/es.yml +++ b/modules/zone/front/events/locale/es.yml @@ -4,3 +4,6 @@ Exclude: Excluir Events: Eventos Add event: Añadir evento Edit event: Editar evento +All: Todo +Specific locations: Localizaciones concretas +Locations where it is not distributed: Localizaciones en las que no se reparte \ No newline at end of file From b96d9ea4c2bb3ed8d194bbc1d2f96df182a3e22a Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 12 May 2022 13:53:13 +0200 Subject: [PATCH 002/150] feat(zone.events): add dialog --- .../00-aclZoneExclusionGeos.sql | 2 + .../10460-mothersDay/00-zoneHoliday.sql | 398 ++++++++++++++++++ modules/zone/back/model-config.json | 3 + .../zone/back/models/zone-exclusion-geo.json | 19 + modules/zone/front/events/index.html | 10 +- modules/zone/front/events/index.js | 60 ++- 6 files changed, 465 insertions(+), 27 deletions(-) create mode 100644 db/changes/10460-mothersDay/00-aclZoneExclusionGeos.sql create mode 100644 db/changes/10460-mothersDay/00-zoneHoliday.sql create mode 100644 modules/zone/back/models/zone-exclusion-geo.json diff --git a/db/changes/10460-mothersDay/00-aclZoneExclusionGeos.sql b/db/changes/10460-mothersDay/00-aclZoneExclusionGeos.sql new file mode 100644 index 000000000..3a157947d --- /dev/null +++ b/db/changes/10460-mothersDay/00-aclZoneExclusionGeos.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL`(`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) +VALUES('ZoneExclusionGeo', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10460-mothersDay/00-zoneHoliday.sql b/db/changes/10460-mothersDay/00-zoneHoliday.sql new file mode 100644 index 000000000..cacf5ba84 --- /dev/null +++ b/db/changes/10460-mothersDay/00-zoneHoliday.sql @@ -0,0 +1,398 @@ +CREATE TABLE `vn`.`zoneExclusionGeo` ( + `zoneExclusionFk` int(11) NOT NULL, + `geoFk` int(11) NOT NULL, + PRIMARY KEY (`zoneExclusionFk`,`geoFk`), + KEY `zoneExclusionGeo2_FK_1` (`geoFk`), + CONSTRAINT `zoneExclusionGeo2_FK` FOREIGN KEY (`zoneExclusionFk`) REFERENCES `zoneExclusion` (`id`) ON UPDATE CASCADE, + CONSTRAINT `zoneExclusionGeo2_FK_1` FOREIGN KEY (`geoFk`) REFERENCES `zoneGeo` (`id`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; + + +DROP PROCEDURE IF EXISTS `vn`.`zone_excludeFromGeo`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_excludeFromGeo`(vZoneGeo INT) +BEGIN +/** + * Excluye zonas a partir un geoFk. + * + * @table tmp.zoneOption(zoneFk, hour, travelingDays, price, bonus, landed, shipped) The computed options + * @param vZoneGeo The zone geo + * @return tmp.zoneOption The computed options + */ + DELETE t FROM tmp.zoneOption t + JOIN zoneExclusion e ON e.zoneFk = t.zoneFk AND e.`dated` = t.landed + LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + JOIN zoneGeo zg1 ON zg1.id = eg.geoFk + JOIN zoneGeo zg2 ON zg2.id = vZoneGeo + WHERE zg2.`path` LIKE CONCAT(zg1.`path`,'%'); +END$$ +DELIMITER ; + + +DROP PROCEDURE IF EXISTS `vn`.`zone_getEvents`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getEvents`( + vGeoFk INT, + vAgencyModeFk INT) +BEGIN +/** + * Returns available events for the passed province/postcode and agency. + * + * @param vGeoFk The geo id + * @param vAgencyModeFk The agency mode id + */ + DECLARE vDeliveryMethodFk VARCHAR(255); + + DROP TEMPORARY TABLE IF EXISTS tZone; + CREATE TEMPORARY TABLE tZone + (id INT PRIMARY KEY) + ENGINE = MEMORY; + + SELECT dm.`code` INTO vDeliveryMethodFk + FROM agencyMode am + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + WHERE am.id = vAgencyModeFk; + + IF vDeliveryMethodFk = 'PICKUP' THEN + INSERT INTO tZone + SELECT id + FROM zone + WHERE agencyModeFk = vAgencyModeFk; + ELSE + CALL zone_getFromGeo(vGeoFk); + IF vAgencyModeFk IS NOT NULL THEN + INSERT INTO tZone + SELECT t.id + FROM tmp.zone t + JOIN zone z ON z.id = t.id + WHERE z.agencyModeFk = vAgencyModeFk; + ELSE + INSERT INTO tZone + SELECT t.id + FROM tmp.zone t + JOIN zone z ON z.id = t.id + JOIN agencyMode am ON am.id = z.agencyModeFk + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + WHERE dm.`code` IN ('AGENCY', 'DELIVERY'); + END IF; + DROP TEMPORARY TABLE tmp.zone; + END IF; + + SELECT e.zoneFk, e.`type`, e.dated, e.`started`, e.`ended`, e.weekDays + FROM tZone t + JOIN zoneEvent e ON e.zoneFk = t.id; + SELECT e.zoneFk, e.dated + FROM tZone t + JOIN zoneExclusion e ON e.zoneFk = t.id + LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE eg.zoneExclusionFk IS NULL; + + DROP TEMPORARY TABLE tZone; +END$$ +DELIMITER ; + +DROP PROCEDURE IF EXISTS `vn`.`zone_getLanded`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLanded`(vShipped DATE, vAddressFk INT, vAgencyModeFk INT, vWarehouseFk INT, vShowExpiredZones BOOLEAN) +BEGIN +/** + * Devuelve una tabla temporal con el dia de recepcion para vShipped. + * Excluye las que tengan cajas preparadas + * + * @param vShipped Fecha de preparacion de mercancia + * @param vAddressFk Id de consignatario, %NULL para recogida + * @param vAgencyModeFk Id agencia + * @param vWarehouseFk vWarehouseFk + * @table tmp.zoneGetLanded Datos de recepción + */ + DECLARE vZoneGeo INT; + SELECT address_getGeo(vAddressFk) INTO vZoneGeo; + CALL vn.zone_getFromGeo(vZoneGeo); + CALL vn.zone_getOptionsForShipment(vShipped, vShowExpiredZones); + CALL vn.zone_excludeFromGeo(vZoneGeo); + CALL vn.zone_getClosed(); + DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetLanded; + CREATE TEMPORARY TABLE tmp.zoneGetLanded + ENGINE = MEMORY + SELECT vWarehouseFk warehouseFk, + TIMESTAMPADD(DAY,zo.travelingDays, vShipped) landed, + zo.zoneFk + FROM tmp.zoneOption zo + JOIN vn.`zone` z ON z.id = zo.zoneFk + JOIN vn.zoneWarehouse zw ON zw.zoneFk = z.id + LEFT JOIN tmp.closedZones cz + ON cz.warehouseFk = zw.warehouseFk + AND cz.zoneFk = zw.zoneFk + AND zo.shipped = CURDATE() + WHERE z.agencyModeFk = vAgencyModeFk + AND zw.warehouseFk = vWarehouseFk + AND (ISNULL(cz.zoneFk) OR vShowExpiredZones); + DROP TEMPORARY TABLE + tmp.`zone`, + tmp.zoneOption, + tmp.closedZones; +END$$ +DELIMITER ; + +DROP PROCEDURE IF EXISTS `vn`.`zone_getOptionsForLanding`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getOptionsForLanding`(vLanded DATE, vShowExpiredZones BOOLEAN) +BEGIN +/** + * Gets computed options for the passed zones and delivery date. + * + * @table tmp.zone(id) The zones ids + * @param vLanded The delivery date + * @return tmp.zoneOption The computed options + */ + DROP TEMPORARY TABLE IF EXISTS tmp.zoneOption; + CREATE TEMPORARY TABLE tmp.zoneOption + ENGINE = MEMORY + SELECT + zoneFk, + `hour`, + travelingDays, + price, + bonus, + vLanded landed, + TIMESTAMPADD(DAY, -travelingDays, vLanded) shipped + FROM ( + SELECT t.id zoneFk, + TIME(IFNULL(e.`hour`, z.`hour`)) `hour`, + IFNULL(e.travelingDays, z.travelingDays) travelingDays, + IFNULL(e.price, z.price) price, + IFNULL(e.bonus, z.bonus) bonus + FROM tmp.zone t + JOIN zone z ON z.id = t.id + JOIN zoneEvent e ON e.zoneFk = t.id + WHERE ( + e.`type` = 'day' + AND e.dated = vLanded + ) OR ( + e.`type` != 'day' + AND e.weekDays & (1 << WEEKDAY(vLanded)) + AND (e.`started` IS NULL OR vLanded >= e.`started`) + AND (e.`ended` IS NULL OR vLanded <= e.`ended`) + ) + ORDER BY + zoneFk, + CASE + WHEN e.`type` = 'day' + THEN 1 + WHEN e.`type` = 'range' + THEN 2 + ELSE 3 + END + ) t + GROUP BY zoneFk; + + DELETE t FROM tmp.zoneOption t + JOIN zoneExclusion e ON e.zoneFk = t.zoneFk AND e.`dated` = t.landed + LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE eg.zoneExclusionFk IS NULL; + + IF NOT vShowExpiredZones THEN + DELETE FROM tmp.zoneOption + WHERE shipped < CURDATE() + OR (shipped = CURDATE() AND CURTIME() > `hour`); + END IF; +END$$ +DELIMITER ; + + +DROP PROCEDURE IF EXISTS `vn`.`zone_getOptionsForShipment`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getOptionsForShipment`(vShipped DATE, vShowExpiredZones BOOLEAN) +BEGIN +/** + * Gets computed options for the passed zones and shipping date. + * + * @table tmp.zones(id) The zones ids + * @param vShipped The shipping date + * @return tmp.zoneOption(zoneFk, hour, travelingDays, price, bonus, specificity) The computed options + */ + DECLARE vHour TIME DEFAULT TIME(NOW()); + + DROP TEMPORARY TABLE IF EXISTS tLandings; + CREATE TEMPORARY TABLE tLandings + (INDEX (eventFk)) + ENGINE = MEMORY + SELECT e.id eventFk, + @travelingDays := IFNULL(e.travelingDays, z.travelingDays) travelingDays, + TIMESTAMPADD(DAY, @travelingDays, vShipped) landed + FROM tmp.zone t + JOIN zone z ON z.id = t.id + JOIN zoneEvent e ON e.zoneFk = t.id; + DROP TEMPORARY TABLE IF EXISTS tmp.zoneOption; + CREATE TEMPORARY TABLE tmp.zoneOption + ENGINE = MEMORY + SELECT * + FROM ( + SELECT t.id zoneFk, + TIME(IFNULL(e.`hour`, z.`hour`)) `hour`, + l.travelingDays, + IFNULL(e.price, z.price) price, + IFNULL(e.bonus, z.bonus) bonus, + l.landed, + vShipped shipped + FROM tmp.zone t + JOIN zone z ON z.id = t.id + JOIN zoneEvent e ON e.zoneFk = t.id + JOIN tLandings l ON l.eventFk = e.id + WHERE ( + e.`type` = 'day' + AND e.`dated` = l.landed + ) OR ( + e.`type` != 'day' + AND e.weekDays & (1 << WEEKDAY(l.landed)) + AND (e.`started` IS NULL OR l.landed >= e.`started`) + AND (e.`ended` IS NULL OR l.landed <= e.`ended`) + ) + ORDER BY + zoneFk, + CASE + WHEN e.`type` = 'day' + THEN 1 + WHEN e.`type` = 'range' + THEN 2 + ELSE 3 + END + ) t + GROUP BY zoneFk; + DROP TEMPORARY TABLE tLandings; + DELETE t FROM tmp.zoneOption t + JOIN zoneExclusion e ON e.zoneFk = t.zoneFk AND e.`dated` = t.landed + LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE eg.zoneExclusionFk IS NULL; + IF NOT vShowExpiredZones THEN + DELETE FROM tmp.zoneOption + WHERE vShipped < CURDATE() + OR (vShipped = CURDATE() AND CURTIME() > `hour`); + END IF; +END$$ +DELIMITER ; + + +DROP PROCEDURE IF EXISTS `vn`.`zone_getShipped`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getShipped`(vLanded DATE, vAddressFk INT, vAgencyModeFk INT, vShowExpiredZones BOOLEAN) +BEGIN +/** + * Devuelve la mínima fecha de envío para cada warehouse + * Excluye aquellas zonas que ya tienen cajas preparadas en ese almacén + * + * @param vLanded La fecha de recepcion + * @param vAddressFk Id del consignatario + * @param vAgencyModeFk Id de la agencia + * @return tmp.zoneGetShipped + */ + DECLARE vZoneGeo INT; + SELECT address_getGeo(vAddressFk) INTO vZoneGeo; + + CALL vn.zone_getFromGeo(vZoneGeo); + CALL vn.zone_getOptionsForLanding(vLanded, vShowExpiredZones); + CALL vn.zone_excludeFromGeo(vZoneGeo); + CALL vn.zone_getClosed(); + DROP TEMPORARY TABLE IF EXISTS tmp.zoneGetShipped; + CREATE TEMPORARY TABLE tmp.zoneGetShipped + ENGINE = MEMORY + SELECT * FROM ( + SELECT zo.zoneFk, + zo.shipped, + zo.`hour`, + zw.warehouseFk, + z.agencyModeFk, + zo.price, + zo.bonus + FROM tmp.zoneOption zo + JOIN vn.zoneWarehouse zw ON zw.zoneFk = zo.zoneFk + JOIN vn.`zone` z ON z.id = zo.zoneFk + LEFT JOIN tmp.closedZones cz + ON cz.warehouseFk = zw.warehouseFk + AND cz.zoneFk = zw.zoneFk + AND zo.shipped = CURDATE() + WHERE z.agencyModeFk = vAgencyModeFk + AND (ISNULL(cz.zoneFk) OR vShowExpiredZones) + ORDER BY shipped) t + GROUP BY warehouseFk; + DROP TEMPORARY TABLE + tmp.`zone`, + tmp.zoneOption, + tmp.closedZones; +END$$ +DELIMITER ; + + +DROP PROCEDURE IF EXISTS `vn`.`zone_upcomingDeliveries`; +DELIMITER $$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_upcomingDeliveries`() +BEGIN + DECLARE vForwardDays INT; + + SELECT forwardDays INTO vForwardDays FROM zoneConfig; + CALL util.time_createTable(CURDATE(), DATE_ADD(CURDATE(), INTERVAL vForwardDays DAY)); + DROP TEMPORARY TABLE IF EXISTS tLandings; + CREATE TEMPORARY TABLE tLandings + (INDEX (eventFk)) + ENGINE = MEMORY + SELECT e.id eventFk, + @travelingDays := IFNULL(e.travelingDays, z.travelingDays) travelingDays, + TIMESTAMPADD(DAY, @travelingDays, ti.dated) landed, + ti.dated shipped + FROM zone z + JOIN zoneEvent e ON e.zoneFk = z.id + JOIN tmp.time ti ON ti.dated BETWEEN curdate() AND TIMESTAMPADD(DAY, vForwardDays, curdate()); + DROP TEMPORARY TABLE IF EXISTS tmp.zoneOption; + CREATE TEMPORARY TABLE tmp.zoneOption + ENGINE = MEMORY + SELECT * + FROM ( + SELECT z.id zoneFk, + TIME(IFNULL(e.`hour`, z.`hour`)) `hour`, + l.travelingDays, + IFNULL(e.price, z.price) price, + IFNULL(e.bonus, z.bonus) bonus, + l.landed, + l.shipped + FROM zone z + JOIN zoneEvent e ON e.zoneFk = z.id + JOIN tLandings l ON l.eventFk = e.id + WHERE ( + e.`type` = 'day' + AND e.`dated` = l.landed + ) OR ( + e.`type` != 'day' + AND e.weekDays & (1 << WEEKDAY(l.landed)) + AND (e.`started` IS NULL OR l.landed >= e.`started`) + AND (e.`ended` IS NULL OR l.landed <= e.`ended`) + ) + ORDER BY + zoneFk, + CASE + WHEN e.`type` = 'day' + THEN 1 + WHEN e.`type` = 'range' + THEN 2 + ELSE 3 + END + ) t + GROUP BY zoneFk, landed; + DELETE t FROM tmp.zoneOption t + JOIN zoneExclusion e ON e.zoneFk = t.zoneFk AND e.`dated` = t.landed + LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE eg.zoneExclusionFk IS NULL; + + SELECT MAX(zo.`hour`) `hour`, zg.`name`, zo.shipped, zo.zoneFk + FROM tmp.zoneOption zo + JOIN `zone` z ON z.id = zo.zoneFk + JOIN agencyMode am ON am.id = z.agencyModeFk + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + JOIN zoneIncluded zi ON zi.zoneFk = z.id + JOIN zoneGeo zg ON zg.id = zi.geoFk AND zg.type = 'province' + WHERE dm.code = 'DELIVERY' + GROUP BY shipped, zg.`name` + ORDER BY shipped, zg.`name`; + + DROP TEMPORARY TABLE tmp.time, tLandings; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/modules/zone/back/model-config.json b/modules/zone/back/model-config.json index ee555f3f4..261a89902 100644 --- a/modules/zone/back/model-config.json +++ b/modules/zone/back/model-config.json @@ -23,6 +23,9 @@ "ZoneExclusion": { "dataSource": "vn" }, + "ZoneExclusionGeo": { + "dataSource": "vn" + }, "ZoneGeo": { "dataSource": "vn" }, diff --git a/modules/zone/back/models/zone-exclusion-geo.json b/modules/zone/back/models/zone-exclusion-geo.json new file mode 100644 index 000000000..a578a07d0 --- /dev/null +++ b/modules/zone/back/models/zone-exclusion-geo.json @@ -0,0 +1,19 @@ +{ + "name": "ZoneExclusionGeo", + "base": "VnModel", + "options": { + "mysql": { + "table": "zoneExclusionGeo" + } + }, + "properties": { + "zoneExclusionFk": { + "type": "number", + "required": true + }, + "geoFk": { + "type": "date", + "required": true + } + } +} \ No newline at end of file diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index 7bcbba256..3a3eaf1b3 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -98,7 +98,7 @@ fixed-bottom-right> @@ -201,7 +201,7 @@ @@ -233,17 +235,17 @@ - + diff --git a/modules/zone/front/events/index.js b/modules/zone/front/events/index.js index c5843e362..ef0fad9e9 100644 --- a/modules/zone/front/events/index.js +++ b/modules/zone/front/events/index.js @@ -6,6 +6,8 @@ class Controller extends Section { super($element, $); this.vnWeekDays = vnWeekDays; this.editMode = 'exclude'; + this.exclusions; + this.days; } $onInit() { @@ -52,23 +54,18 @@ class Controller extends Section { if (this.editMode == 'include') { if (events.length) this.edit(events[0]); - else { + else this.create(type, days, weekday); - console.log(type); - } } else { - console.log(type); this.selected = { type: 'all', dated: days[0] }; + this.exclusions = exclusions; + this.days = days; + this.$.excludeDialog.show(); } - - // if (exclusions.length) - // this.exclusionDelete(exclusions); - // else - // this.exclusionCreate(days); } onEditClick(row, event) { @@ -80,7 +77,7 @@ class Controller extends Section { this.isNew = false; this.selected = angular.copy(row); this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays); - this.$.dialog.show(); + this.$.includeDialog.show(); } create(type, days, weekday) { @@ -101,7 +98,7 @@ class Controller extends Section { }; } - this.$.dialog.show(); + this.$.includeDialog.show(); } onIncludeResponse(response) { @@ -141,6 +138,29 @@ class Controller extends Section { } } + onExcludeResponse(response) { + switch (response) { + case 'accept': { + let selected = this.selected; + let type = selected.type; + if (type == 'all') + this.exclusionCreate(this.days); + else { + const params = [{ + zoneExclusionFk: 1, + geoFk: 1 + }]; + this.$http.post(`ZoneExclusionGeos`, params).then(() => this.refresh()); + } + // inserta en zoneExclusionGeo el zoneGeo seleccionado + + return 1; + } + case 'delete': + return this.exclusionDelete(this.exclusions); + } + } + onDeleteClick(id, event) { if (event.defaultPrevented) return; event.preventDefault(); @@ -181,10 +201,12 @@ class Controller extends Section { } onSearch(params) { - this.$.model.applyFilter({}, params).then(() => { - const data = this.$.model.data; - this.$.treeview.data = data; - }); + if (this.selected.type == 'specificLocations') { + this.$.model.applyFilter({}, params).then(() => { + const data = this.$.model.data; + this.$.treeview.data = data; + }); + } } onFetch(item) { @@ -211,14 +233,6 @@ class Controller extends Section { return {name: {like: `%${value}%`}}; } } - - onSelection2(value, item) { - if (value == null) - value = undefined; - const params = {geoId: item.id, isIncluded: value}; - const path = `zones/${this.zone.id}/toggleIsIncluded`; - this.$http.post(path, params); - } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; From 06e9a22f405a824eca4a73e4bca1477f58f3b31d Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 12 May 2022 15:02:07 +0200 Subject: [PATCH 003/150] added search --- modules/zone/front/events/index.html | 49 +++++++++++++------------- modules/zone/front/events/index.js | 49 ++++++++++++++++++++++++-- modules/zone/front/location/index.js | 2 ++ modules/zone/front/location/style.scss | 30 ++++++++-------- modules/zone/front/routes.json | 7 ++-- 5 files changed, 93 insertions(+), 44 deletions(-) diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index 3a3eaf1b3..67657e665 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -198,7 +198,6 @@ message="This item will be deleted" question="Are you sure you want to continue?"> - - + @@ -228,27 +227,27 @@ url="Zones/{{$ctrl.$params.id}}/getLeaves" filter="$ctrl.filter"> - - - - - - - - - - +
+ + +
+ + + + +
+
diff --git a/modules/zone/front/events/index.js b/modules/zone/front/events/index.js index ef0fad9e9..d7d54498c 100644 --- a/modules/zone/front/events/index.js +++ b/modules/zone/front/events/index.js @@ -22,6 +22,17 @@ class Controller extends Section { return `Zones/${this.$params.id}/exclusions`; } + get checked() { + const geos = this.$.model.data || []; + const checkedLines = []; + for (let geo of geos) { + if (geo.checked) + checkedLines.push(geo); + } + + return checkedLines; + } + refresh() { this.$.data = null; this.$.$applyAsync(() => { @@ -200,8 +211,25 @@ class Controller extends Section { .then(() => this.refresh()); } - onSearch(params) { - if (this.selected.type == 'specificLocations') { + set excludeSearch(value) { + this._excludeSearch = value; + if (!value) this.onSearch(); + } + + get excludeSearch() { + return this._excludeSearch; + } + + onKeyDown(event) { + if (event.key == 'Enter') { + event.preventDefault(); + this.onSearch(); + } + } + + onSearch() { + const params = {search: this._excludeSearch}; + if (this.excludeType == 'specificLocations') { this.$.model.applyFilter({}, params).then(() => { const data = this.$.model.data; this.$.treeview.data = data; @@ -233,10 +261,25 @@ class Controller extends Section { return {name: {like: `%${value}%`}}; } } + + onSelection2(value, item) { + console.log(item, this.zone.id); + if (value == null) + value = undefined; + const params = {geoId: item.id, isIncluded: value}; + const path = `zones/${this.zone.id}/toggleIsIncluded`; + this.$http.post(path, params); + } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; ngModule.vnComponent('vnZoneEvents', { template: require('./index.html'), - controller: Controller + controller: Controller, + bindings: { + zone: '<' + }, + require: { + card: '^vnZoneCard' + } }); diff --git a/modules/zone/front/location/index.js b/modules/zone/front/location/index.js index 0f92f37de..8f3a24b65 100644 --- a/modules/zone/front/location/index.js +++ b/modules/zone/front/location/index.js @@ -36,6 +36,8 @@ class Controller extends Section { } onSelection(value, item) { + console.log(item, this.zone.id); + if (value == null) value = undefined; const params = {geoId: item.id, isIncluded: value}; diff --git a/modules/zone/front/location/style.scss b/modules/zone/front/location/style.scss index 2316a2622..24d685a51 100644 --- a/modules/zone/front/location/style.scss +++ b/modules/zone/front/location/style.scss @@ -1,19 +1,21 @@ @import "variables"; -vn-treeview-child { - .content > .vn-check:not(.indeterminate):not(.checked) { - color: $color-alert; +vn-zone-location { + vn-treeview-child { + .content > .vn-check:not(.indeterminate):not(.checked) { + color: $color-alert; - & > .btn { - border-color: $color-alert; + & > .btn { + border-color: $color-alert; + } + } + .content > .vn-check.checked { + color: $color-notice; + + & > .btn { + background-color: $color-notice; + border-color: $color-notice + } } } - .content > .vn-check.checked { - color: $color-notice; - - & > .btn { - background-color: $color-notice; - border-color: $color-notice - } - } -} \ No newline at end of file +} diff --git a/modules/zone/front/routes.json b/modules/zone/front/routes.json index e08f97314..7f67260da 100644 --- a/modules/zone/front/routes.json +++ b/modules/zone/front/routes.json @@ -85,10 +85,13 @@ "description": "Warehouses" }, { - "url": "/events", + "url": "/events?q", "state": "zone.card.events", "component": "vn-zone-events", - "description": "Calendar" + "description": "Calendar", + "params": { + "zone": "$ctrl.zone" + } }, { "url": "/location?q", From ab13c16948111fbc42bc2ccacc3ebf76d0be82d6 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 16 May 2022 07:17:29 +0200 Subject: [PATCH 004/150] feat: add dialog --- .../10460-mothersDay/00-zoneHoliday.sql | 5 +- .../zone/back/methods/zone/exclusionGeo.js | 48 +++++++++++++++++++ .../back/methods/zone/getEventsFiltered.js | 17 +++++-- .../zone/back/models/zone-exclusion-geo.json | 2 +- modules/zone/back/models/zone.js | 1 + modules/zone/front/calendar/index.js | 20 ++++++-- modules/zone/front/calendar/style.scss | 3 ++ modules/zone/front/events/index.html | 23 +++++---- modules/zone/front/events/index.js | 35 ++++++++------ modules/zone/front/events/style.scss | 11 +++++ modules/zone/front/location/index.js | 2 - 11 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 modules/zone/back/methods/zone/exclusionGeo.js create mode 100644 modules/zone/front/events/style.scss diff --git a/db/changes/10460-mothersDay/00-zoneHoliday.sql b/db/changes/10460-mothersDay/00-zoneHoliday.sql index cacf5ba84..badae1c3e 100644 --- a/db/changes/10460-mothersDay/00-zoneHoliday.sql +++ b/db/changes/10460-mothersDay/00-zoneHoliday.sql @@ -81,11 +81,14 @@ BEGIN SELECT e.zoneFk, e.`type`, e.dated, e.`started`, e.`ended`, e.weekDays FROM tZone t JOIN zoneEvent e ON e.zoneFk = t.id; + SELECT e.zoneFk, e.dated FROM tZone t JOIN zoneExclusion e ON e.zoneFk = t.id LEFT JOIN zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id - WHERE eg.zoneExclusionFk IS NULL; + JOIN zoneGeo zg1 ON zg1.id = eg.geoFk + JOIN zoneGeo zg2 ON zg2.id = vGeoFk + WHERE eg.zoneExclusionFk IS NULL OR zg2.`path` LIKE CONCAT(zg1.`path`,'%'); DROP TEMPORARY TABLE tZone; END$$ diff --git a/modules/zone/back/methods/zone/exclusionGeo.js b/modules/zone/back/methods/zone/exclusionGeo.js new file mode 100644 index 000000000..0bd6075b9 --- /dev/null +++ b/modules/zone/back/methods/zone/exclusionGeo.js @@ -0,0 +1,48 @@ + +module.exports = Self => { + Self.remoteMethod('exclusionGeo', { + description: 'Exclude a zoneGeo for a zone', + accepts: [ + { + arg: 'zoneFk', + type: 'number', + description: 'The zone id' + }, + { + arg: 'date', + type: 'date', + description: 'The date to exclude' + }, + { + arg: 'geoIds', + type: 'any', + description: 'The geos id' + } + + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/exclusionGeo`, + verb: 'POST' + } + }); + + Self.exclusionGeo = async(zoneFk, date, geoIds, options) => { + const models = Self.app.models; + + const newZoneExclusion = await models.ZoneExclusion.create({ + zoneFk: zoneFk, + dated: date + }); + + for (let geoId of geoIds) { + await models.ZoneExclusionGeo.create({ + zoneExclusionFk: newZoneExclusion.id, + geoFk: geoId.id + }); + } + }; +}; diff --git a/modules/zone/back/methods/zone/getEventsFiltered.js b/modules/zone/back/methods/zone/getEventsFiltered.js index 5e9cbae5a..b97e49b1c 100644 --- a/modules/zone/back/methods/zone/getEventsFiltered.js +++ b/modules/zone/back/methods/zone/getEventsFiltered.js @@ -53,11 +53,22 @@ module.exports = Self => { query = ` SELECT * - FROM vn.zoneExclusion + FROM vn.zoneExclusion e + LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id WHERE zoneFk = ? - AND dated BETWEEN ? AND ?;`; + AND dated BETWEEN ? AND ? + AND eg.zoneExclusionFk IS NULL;`; const exclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions); - return {events, exclusions}; + query = ` + SELECT * + FROM vn.zoneExclusion e + LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE zoneFk = ? + AND dated BETWEEN ? AND ? + AND eg.zoneExclusionFk IS NOT NULL;`; + const geoExclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions); + + return {events, exclusions, geoExclusions}; }; }; diff --git a/modules/zone/back/models/zone-exclusion-geo.json b/modules/zone/back/models/zone-exclusion-geo.json index a578a07d0..7c77c5458 100644 --- a/modules/zone/back/models/zone-exclusion-geo.json +++ b/modules/zone/back/models/zone-exclusion-geo.json @@ -12,7 +12,7 @@ "required": true }, "geoFk": { - "type": "date", + "type": "number", "required": true } } diff --git a/modules/zone/back/models/zone.js b/modules/zone/back/models/zone.js index ef1c8c5d9..c580a2f15 100644 --- a/modules/zone/back/models/zone.js +++ b/modules/zone/back/models/zone.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/zone/deleteZone')(Self); require('../methods/zone/includingExpired')(Self); require('../methods/zone/getZoneClosing')(Self); + require('../methods/zone/exclusionGeo')(Self); Self.validatesPresenceOf('agencyModeFk', { message: `Agency cannot be blank` diff --git a/modules/zone/front/calendar/index.js b/modules/zone/front/calendar/index.js index a24d10aef..bdec1b949 100644 --- a/modules/zone/front/calendar/index.js +++ b/modules/zone/front/calendar/index.js @@ -75,6 +75,17 @@ class Controller extends Component { } } + this.geoExclusions = {}; + let geoExclusions = value.geoExclusions; + + if (geoExclusions) { + for (let geoExclusion of geoExclusions) { + let stamp = toStamp(geoExclusion.dated); + if (!this.geoExclusions[stamp]) this.geoExclusions[stamp] = []; + this.geoExclusions[stamp].push(geoExclusion); + } + } + let events = value.events; if (events) { @@ -154,13 +165,16 @@ class Controller extends Component { hasEvents(day) { let stamp = day.getTime(); - return this.days[stamp] || this.exclusions[stamp]; + return this.days[stamp] || this.exclusions[stamp] || this.geoExclusions[stamp]; } getClass(day) { let stamp = day.getTime(); - return this.exclusions[stamp] && !this.days[stamp] - ? 'excluded' : ''; + if (this.geoExclusions[stamp] && !this.days[stamp]) + return 'geoExcluded'; + else if (this.exclusions[stamp] && !this.days[stamp]) + return 'excluded'; + else return ''; } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; diff --git a/modules/zone/front/calendar/style.scss b/modules/zone/front/calendar/style.scss index 25b6a87d1..d6853e33c 100644 --- a/modules/zone/front/calendar/style.scss +++ b/modules/zone/front/calendar/style.scss @@ -33,6 +33,9 @@ vn-zone-calendar { &.excluded .day-number { background-color: $color-alert; } + &.geoExcluded .day-number { + background-color: #33CAFF; + } } } } diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index 67657e665..45e2ec671 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -205,18 +205,18 @@ + ng-model="$ctrl.excludeSelected.dated"> - + @@ -227,20 +227,23 @@ url="Zones/{{$ctrl.$params.id}}/getLeaves" filter="$ctrl.filter"> -
+
+ + + -
+
@@ -254,13 +257,15 @@ + translate-attr="{value: 'Cancel'}" + tabindex="0"> + translate-attr="{value: 'Delete'}" + tabindex="0"> -
diff --git a/front/salix/components/layout/index.js b/front/salix/components/layout/index.js index 986f61622..372e8e828 100644 --- a/front/salix/components/layout/index.js +++ b/front/salix/components/layout/index.js @@ -26,6 +26,10 @@ export class Layout extends Component { const token = this.vnToken.token; return `/api/Images/user/160x160/${userId}/download?access_token=${token}`; } + + refresh() { + window.location.reload(); + } } Layout.$inject = ['$element', '$scope', 'vnModules']; diff --git a/front/salix/components/layout/style.scss b/front/salix/components/layout/style.scss index 36522bc3a..612366228 100644 --- a/front/salix/components/layout/style.scss +++ b/front/salix/components/layout/style.scss @@ -60,6 +60,9 @@ vn-layout { font-size: 1.05rem; padding: 0; } + .vn-icon-button.refresh { + color: $color-alert; + } } & > vn-side-menu > .menu { display: flex; diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml index 4e93ae18e..e5dc82b16 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -17,6 +17,7 @@ Go to module summary: Ir a la vista previa del módulo Show summary: Mostrar vista previa What is new: Novedades de la versión Settings: Ajustes +There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar # Actions diff --git a/jest-front.js b/jest-front.js index 7a692f57c..6d7532260 100644 --- a/jest-front.js +++ b/jest-front.js @@ -13,6 +13,7 @@ import './modules/route/front/module.js'; import './modules/ticket/front/module.js'; import './modules/travel/front/module.js'; import './modules/worker/front/module.js'; +import './modules/shelving/front/module.js'; core.run(vnInterceptor => { vnInterceptor.setApiPath(null); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index b7e9b43d3..ccf16cce0 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -54,7 +54,6 @@ "You can't delete a confirmed order": "You can't delete a confirmed order", "Value has an invalid format": "Value has an invalid format", "The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one", - "Can't create stowaway for this ticket": "Can't create stowaway for this ticket", "Swift / BIC can't be empty": "Swift / BIC can't be empty", "Deleted sales from ticket": "I have deleted the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}", "Added sale to ticket": "I have added the following line to the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", @@ -70,7 +69,6 @@ "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", "Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", "Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", - "This ticket is not an stowaway anymore": "The ticket id [{{ticketId}}]({{{ticketUrl}}}) is not an stowaway anymore", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member", "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}", @@ -123,5 +121,14 @@ "The worker has hours recorded that day": "The worker has hours recorded that day", "isWithoutNegatives": "isWithoutNegatives", "routeFk": "routeFk", - "Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data" + "Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data", + "Can't change the password of another worker": "Can't change the password of another worker", + "No hay un contrato en vigor": "There is no existing contract", + "No está permitido trabajar": "Not allowed to work", + "Dirección incorrecta": "Wrong direction", + "No se permite fichar a futuro": "It is not allowed to sign in the future", + "Descanso diario 12h.": "Daily rest 12h.", + "Fichadas impares": "Odd signs", + "Descanso diario 9h.": "Daily rest 9h.", + "Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h." } \ No newline at end of file diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 37e014678..07a00024a 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -101,7 +101,6 @@ "Ticket id cannot be blank": "El id de ticket no puede quedar en blanco", "Weekday cannot be blank": "El día de la semana no puede quedar en blanco", "You can't delete a confirmed order": "No puedes borrar un pedido confirmado", - "Can't create stowaway for this ticket": "No se puede crear un polizon para este ticket", "The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto", "Invalid quantity": "Cantidad invalida", "This postal code is not valid": "This postal code is not valid", @@ -138,7 +137,6 @@ "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", - "This ticket is not an stowaway anymore": "El ticket id [{{ticketId}}]({{{ticketUrl}}}) ha dejado de ser un polizón", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", @@ -226,5 +224,13 @@ "reference duplicated": "Referencia duplicada", "This ticket is already a refund": "Este ticket ya es un abono", "isWithoutNegatives": "isWithoutNegatives", - "routeFk": "routeFk" + "routeFk": "routeFk", + "Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador", + "No hay un contrato en vigor": "No hay un contrato en vigor", + "No se permite fichar a futuro": "No se permite fichar a futuro", + "No está permitido trabajar": "No está permitido trabajar", + "Fichadas impares": "Fichadas impares", + "Descanso diario 12h.": "Descanso diario 12h.", + "Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.", + "Dirección incorrecta": "Dirección incorrecta" } \ No newline at end of file diff --git a/loopback/server/middleware.json b/loopback/server/middleware.json index 60bedcdb3..d65b8b34a 100644 --- a/loopback/server/middleware.json +++ b/loopback/server/middleware.json @@ -31,7 +31,8 @@ "loopback#token": {} }, "auth:after": { - "./middleware/current-user": {} + "./middleware/current-user": {}, + "./middleware/salix-version": {} }, "parse": { "body-parser#json":{} diff --git a/loopback/server/middleware/salix-version.js b/loopback/server/middleware/salix-version.js new file mode 100644 index 000000000..988a3b39c --- /dev/null +++ b/loopback/server/middleware/salix-version.js @@ -0,0 +1,8 @@ +const packageJson = require('../../../package.json'); + +module.exports = function(options) { + return function(req, res, next) { + res.set('Salix-Version', packageJson.version); + next(); + }; +}; diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index de812417a..22a48f83e 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethodCtx('importToNewRefundTicket', { - description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company', + description: 'Import lines of claimBeginning to new ticket with shipped, landed dates, agency and company', accessType: 'WRITE', accepts: [{ arg: 'id', diff --git a/modules/claim/back/methods/claim/getSummary.js b/modules/claim/back/methods/claim/getSummary.js index 0accbf920..8ab39eb45 100644 --- a/modules/claim/back/methods/claim/getSummary.js +++ b/modules/claim/back/methods/claim/getSummary.js @@ -84,6 +84,20 @@ module.exports = Self => { }; promises.push(Self.app.models.ClaimBeginning.find(filter, myOptions)); + // Claim observations + filter = { + where: {claimFk: id}, + include: [ + { + relation: 'worker', + scope: { + fields: ['id', 'firstName', 'lastName'] + } + } + ] + }; + promises.push(Self.app.models.ClaimObservation.find(filter, myOptions)); + // Claim developments filter = { where: {claimFk: id}, @@ -138,8 +152,9 @@ module.exports = Self => { summary.isEditable = await Self.isEditable(ctx, id, myOptions); [summary.claim] = res[0]; summary.salesClaimed = res[1]; - summary.developments = res[2]; - summary.actions = res[3]; + summary.observations = res[2]; + summary.developments = res[3]; + summary.actions = res[4]; return summary; }; diff --git a/modules/claim/back/methods/claim/specs/getSummary.spec.js b/modules/claim/back/methods/claim/specs/getSummary.spec.js index 6eb920b29..c4d01ecdc 100644 --- a/modules/claim/back/methods/claim/specs/getSummary.spec.js +++ b/modules/claim/back/methods/claim/specs/getSummary.spec.js @@ -19,6 +19,7 @@ describe('claim getSummary()', () => { expect(keys).toContain('claim'); expect(keys).toContain('salesClaimed'); expect(keys).toContain('developments'); + expect(keys).toContain('observations'); expect(keys).toContain('actions'); expect(keys).toContain('isEditable'); diff --git a/modules/claim/back/model-config.json b/modules/claim/back/model-config.json index d4d772b58..e99a455ac 100644 --- a/modules/claim/back/model-config.json +++ b/modules/claim/back/model-config.json @@ -38,6 +38,9 @@ "ClaimLog": { "dataSource": "vn" }, + "ClaimObservation": { + "dataSource": "vn" + }, "ClaimContainer": { "dataSource": "claimStorage" } diff --git a/modules/claim/back/models/claim-observation.json b/modules/claim/back/models/claim-observation.json new file mode 100644 index 000000000..e882ad09d --- /dev/null +++ b/modules/claim/back/models/claim-observation.json @@ -0,0 +1,43 @@ +{ + "name": "ClaimObservation", + "base": "Loggable", + "log": { + "model": "ClaimLog", + "relation": "claim" + }, + "options": { + "mysql": { + "table": "claimObservation" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "claimFk": { + "type": "number", + "required": true + }, + "text": { + "type": "string", + "required": true + }, + "created": { + "type": "date" + } + }, + "relations": { + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "claim": { + "type": "belongsTo", + "model": "Claim", + "foreignKey": "claimFk" + } + } +} \ No newline at end of file diff --git a/modules/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html index 28fd92472..7a91e180a 100644 --- a/modules/claim/front/basic-data/index.html +++ b/modules/claim/front/basic-data/index.html @@ -51,15 +51,6 @@ label="Packages received" ng-model="$ctrl.claim.packages"> - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/claim/front/note/create/index.js b/modules/claim/front/note/create/index.js new file mode 100644 index 000000000..40ae9309b --- /dev/null +++ b/modules/claim/front/note/create/index.js @@ -0,0 +1,22 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.note = { + claimFk: parseInt(this.$params.id), + workerFk: window.localStorage.currentUserWorkerId, + text: null + }; + } + + cancel() { + this.$state.go('claim.card.note.index', {id: this.$params.id}); + } +} + +ngModule.vnComponent('vnClaimNoteCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/claim/front/note/index/index.html b/modules/claim/front/note/index/index.html new file mode 100644 index 000000000..8ffe19c2b --- /dev/null +++ b/modules/claim/front/note/index/index.html @@ -0,0 +1,32 @@ + + + + +
+ + {{::note.worker.firstName}} {{::note.worker.lastName}} + {{::note.created | date:'dd/MM/yyyy HH:mm'}} + + + {{::note.text}} + +
+
+
+ + + \ No newline at end of file diff --git a/modules/claim/front/note/index/index.js b/modules/claim/front/note/index/index.js new file mode 100644 index 000000000..5a2fd96d3 --- /dev/null +++ b/modules/claim/front/note/index/index.js @@ -0,0 +1,25 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.filter = { + order: 'created DESC', + }; + this.include = { + relation: 'worker', + scope: { + fields: ['id', 'firstName', 'lastName'] + } + }; + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnClaimNote', { + template: require('./index.html'), + controller: Controller, +}); diff --git a/modules/claim/front/note/index/style.scss b/modules/claim/front/note/index/style.scss new file mode 100644 index 000000000..44ae2cee7 --- /dev/null +++ b/modules/claim/front/note/index/style.scss @@ -0,0 +1,5 @@ +vn-client-note { + .note:last-child { + margin-bottom: 0; + } +} \ No newline at end of file diff --git a/modules/claim/front/routes.json b/modules/claim/front/routes.json index 5f08255be..d02ea6f6c 100644 --- a/modules/claim/front/routes.json +++ b/modules/claim/front/routes.json @@ -12,6 +12,7 @@ {"state": "claim.card.basicData", "icon": "settings"}, {"state": "claim.card.detail", "icon": "icon-details"}, {"state": "claim.card.photos", "icon": "image"}, + {"state": "claim.card.note.index", "icon": "insert_drive_file"}, {"state": "claim.card.development", "icon": "icon-traceability"}, {"state": "claim.card.action", "icon": "icon-actions"}, {"state": "claim.card.log", "icon": "history"} @@ -27,17 +28,20 @@ "abstract": true, "component": "vn-claim", "description": "Claims" - }, { + }, + { "url": "/index?q", "state": "claim.index", "component": "vn-claim-index", "description": "Claims" - }, { + }, + { "url": "/:id", "state": "claim.card", "abstract": true, "component": "vn-claim-card" - }, { + }, + { "url": "/summary", "state": "claim.card.summary", "component": "vn-claim-summary", @@ -45,7 +49,8 @@ "params": { "claim": "$ctrl.claim" } - }, { + }, + { "url": "/basic-data", "state": "claim.card.basicData", "component": "vn-claim-basic-data", @@ -54,7 +59,8 @@ "claim": "$ctrl.claim" }, "acl": ["salesPerson"] - }, { + }, + { "url": "/detail", "state": "claim.card.detail", "component": "vn-claim-detail", @@ -63,7 +69,32 @@ "claim": "$ctrl.claim" }, "acl": ["salesPerson"] - }, { + }, + { + "url": "/note", + "state": "claim.card.note", + "component": "ui-view", + "abstract": true, + "acl": ["salesPerson"] + }, + { + "url": "/index", + "state": "claim.card.note.index", + "component": "vn-claim-note", + "description": "Notes", + "params": { + "claim": "$ctrl.claim" + }, + "acl": ["salesPerson"] + }, + { + "url": "/create", + "state": "claim.card.note.create", + "component": "vn-claim-note-create", + "description": "New note", + "acl": ["salesPerson"] + }, + { "url": "/development", "state": "claim.card.development", "component": "vn-claim-development", @@ -72,7 +103,8 @@ "claim": "$ctrl.claim" }, "acl": ["claimManager"] - }, { + }, + { "url": "/action", "state": "claim.card.action", "component": "vn-claim-action", @@ -81,7 +113,8 @@ "claim": "$ctrl.claim" }, "acl": ["claimManager"] - }, { + }, + { "url": "/photos", "state": "claim.card.photos", "component": "vn-claim-photos", @@ -89,7 +122,8 @@ "params": { "claim": "$ctrl.claim" } - }, { + }, + { "url" : "/log", "state": "claim.card.log", "component": "vn-claim-log", diff --git a/modules/claim/front/summary/index.html b/modules/claim/front/summary/index.html index 5d90da516..0c12aa2e6 100644 --- a/modules/claim/front/summary/index.html +++ b/modules/claim/front/summary/index.html @@ -1,11 +1,12 @@ -
- {{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}} - - - - - + +

+ + Observations + +

+

+ Observations +

+
+ + {{::note.worker.firstName}} {{::note.worker.lastName}} + {{::note.created | date:'dd/MM/yyyy HH:mm'}} + + + {{::note.text}} + +
+

{ const models = Self.app.models; const args = ctx.args; + const date = new Date(); + date.setHours(0, 0, 0, 0); + let tx; const myOptions = {}; @@ -92,8 +95,9 @@ module.exports = function(Self) { throw new UserError('Invalid account'); await Self.rawSql( - `CALL vn.ledger_doCompensation(CURDATE(), ?, ?, ?, ?, ?, ?)`, + `CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`, [ + date, args.compensationAccount, args.bankFk, accountingType.receiptDescription + originalClient.accountingAccount, @@ -106,9 +110,10 @@ module.exports = function(Self) { } else if (accountingType.isAutoConciliated == true) { const description = `${originalClient.id} : ${originalClient.socialName} - ${accountingType.receiptDescription}`; const [xdiarioNew] = await Self.rawSql( - `SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`, + `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ledger;`, [ null, + date, bank.account, originalClient.accountingAccount, description, @@ -126,9 +131,10 @@ module.exports = function(Self) { ); await Self.rawSql( - `SELECT xdiario_new(?, CURDATE(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, + `SELECT xdiario_new(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`, [ xdiarioNew.ledger, + date, originalClient.accountingAccount, bank.account, description, diff --git a/modules/client/back/methods/client/getCard.js b/modules/client/back/methods/client/getCard.js index 34fba0984..21b3140bd 100644 --- a/modules/client/back/methods/client/getCard.js +++ b/modules/client/back/methods/client/getCard.js @@ -62,7 +62,7 @@ module.exports = function(Self) { { relation: 'account', scope: { - fields: ['id', 'name', 'active'] + fields: ['id', 'name', 'email', 'active'] } }, { @@ -74,8 +74,10 @@ module.exports = function(Self) { ] }, myOptions); - const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; - const data = await Self.rawSql(query, [id], myOptions); + const date = new Date(); + date.setHours(0, 0, 0, 0); + const query = `SELECT vn.clientGetDebt(?, ?) AS debt`; + const data = await Self.rawSql(query, [id, date], myOptions); client.debt = data[0].debt; diff --git a/modules/client/back/methods/client/getDebt.js b/modules/client/back/methods/client/getDebt.js index 8eb0f2480..aafdbfa48 100644 --- a/modules/client/back/methods/client/getDebt.js +++ b/modules/client/back/methods/client/getDebt.js @@ -25,8 +25,10 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`; - const [debt] = await Self.rawSql(query, [clientFk], myOptions); + const date = new Date(); + date.setHours(0, 0, 0, 0); + const query = `SELECT vn.clientGetDebt(?, ?) AS debt`; + const [debt] = await Self.rawSql(query, [clientFk, date], myOptions); return debt; }; diff --git a/modules/client/back/methods/client/lastActiveTickets.js b/modules/client/back/methods/client/lastActiveTickets.js index da38dbee8..03616a0e3 100644 --- a/modules/client/back/methods/client/lastActiveTickets.js +++ b/modules/client/back/methods/client/lastActiveTickets.js @@ -32,6 +32,8 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const ticket = await Self.app.models.Ticket.findById(ticketId, null, myOptions); const query = ` SELECT @@ -50,12 +52,12 @@ module.exports = Self => { JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.address ad ON t.addressFk = ad.id JOIN vn.province pr ON ad.provinceFk = pr.id - WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 + WHERE t.shipped >= ? AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ? AND t.warehouseFk = ? ORDER BY t.shipped LIMIT 10`; - return Self.rawSql(query, [id, ticketId, ticket.warehouseFk], myOptions); + return Self.rawSql(query, [date, id, ticketId, ticket.warehouseFk], myOptions); }; }; diff --git a/modules/client/back/methods/client/setPassword.js b/modules/client/back/methods/client/setPassword.js new file mode 100644 index 000000000..19675d0e8 --- /dev/null +++ b/modules/client/back/methods/client/setPassword.js @@ -0,0 +1,32 @@ +module.exports = Self => { + Self.remoteMethod('setPassword', { + description: 'Sets the password of a non-worker client', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The user id', + http: {source: 'path'} + }, { + arg: 'newPassword', + type: 'string', + description: 'The new password', + required: true + } + ], + http: { + path: `/:id/setPassword`, + verb: 'PATCH' + } + }); + + Self.setPassword = async function(id, newPassword) { + const models = Self.app.models; + + const isWorker = await models.Worker.findById(id); + if (isWorker) + throw new Error(`Can't change the password of another worker`); + + await models.Account.setPassword(id, newPassword); + }; +}; diff --git a/modules/client/back/methods/client/specs/extendedListFilter.spec.js b/modules/client/back/methods/client/specs/extendedListFilter.spec.js index 907c03ef9..9a0441656 100644 --- a/modules/client/back/methods/client/specs/extendedListFilter.spec.js +++ b/modules/client/back/methods/client/specs/extendedListFilter.spec.js @@ -1,4 +1,4 @@ -const { models } = require('vn-loopback/server/server'); +const {models} = require('vn-loopback/server/server'); describe('client extendedListFilter()', () => { it('should return the clients matching the filter with a limit of 20 rows', async() => { @@ -99,7 +99,7 @@ describe('client extendedListFilter()', () => { const randomIndex = Math.floor(Math.random() * result.length); const randomResultClient = result[randomIndex]; - + expect(result.length).toBeGreaterThanOrEqual(5); expect(randomResultClient.salesPersonFk).toEqual(salesPersonId); @@ -121,7 +121,7 @@ describe('client extendedListFilter()', () => { const result = await models.Client.extendedListFilter(ctx, filter, options); const firstClient = result[0]; - + expect(result.length).toEqual(1); expect(firstClient.name).toEqual('Max Eisenhardt'); @@ -138,15 +138,15 @@ describe('client extendedListFilter()', () => { try { const options = {transaction: tx}; - const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Silla'}}; + const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Gotham'}}; const filter = {}; const result = await models.Client.extendedListFilter(ctx, filter, options); const randomIndex = Math.floor(Math.random() * result.length); const randomResultClient = result[randomIndex]; - + expect(result.length).toBeGreaterThanOrEqual(20); - expect(randomResultClient.city.toLowerCase()).toEqual('silla'); + expect(randomResultClient.city.toLowerCase()).toEqual('gotham'); await tx.rollback(); } catch (e) { @@ -167,7 +167,7 @@ describe('client extendedListFilter()', () => { const randomIndex = Math.floor(Math.random() * result.length); const randomResultClient = result[randomIndex]; - + expect(result.length).toBeGreaterThanOrEqual(20); expect(randomResultClient.postcode).toEqual('46460'); diff --git a/modules/client/back/methods/client/specs/setPassword.spec.js b/modules/client/back/methods/client/specs/setPassword.spec.js new file mode 100644 index 000000000..e0de20249 --- /dev/null +++ b/modules/client/back/methods/client/specs/setPassword.spec.js @@ -0,0 +1,27 @@ +const models = require('vn-loopback/server/server').models; + +describe('Client setPassword', () => { + it('should throw an error the setPassword target is not just a client but a worker', async() => { + let error; + + try { + await models.Client.setPassword(1106, 'newPass?'); + } catch (e) { + error = e; + } + + expect(error.message).toEqual(`Can't change the password of another worker`); + }); + + it('should change the password of the client', async() => { + let error; + + try { + await models.Client.setPassword(1101, 't0pl3v3l.p455w0rd!'); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + }); +}); diff --git a/modules/client/back/methods/client/specs/summary.spec.js b/modules/client/back/methods/client/specs/summary.spec.js index 1eef35024..f2d576e39 100644 --- a/modules/client/back/methods/client/specs/summary.spec.js +++ b/modules/client/back/methods/client/specs/summary.spec.js @@ -120,7 +120,6 @@ describe('client summary()', () => { const result = await models.Client.summary(clientId, options); expect(result.recovery.id).toEqual(3); - await tx.rollback(); } catch (e) { await tx.rollback(); diff --git a/modules/client/back/methods/client/specs/updateUser.spec.js b/modules/client/back/methods/client/specs/updateUser.spec.js new file mode 100644 index 000000000..4dc969906 --- /dev/null +++ b/modules/client/back/methods/client/specs/updateUser.spec.js @@ -0,0 +1,58 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); +describe('Client updateUser', () => { + const employeeId = 1; + const activeCtx = { + accessToken: {userId: employeeId}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + const ctx = { + req: {accessToken: {userId: employeeId}}, + args: {name: 'test', active: true} + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should throw an error the target user is not just a client but a worker', async() => { + let error; + try { + const clientID = 1106; + await models.Client.updateUser(ctx, clientID); + } catch (e) { + error = e; + } + + expect(error.message).toEqual(`Can't update the user details of another worker`); + }); + + it('should update the user data', async() => { + let error; + + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const clientID = 1105; + await models.Client.updateUser(ctx, clientID, options); + const client = await models.Account.findById(clientID, null, options); + + expect(client.name).toEqual('test'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(error).toBeUndefined(); + }); +}); diff --git a/modules/client/back/methods/client/summary.js b/modules/client/back/methods/client/summary.js index dccf7f39c..48cb75f31 100644 --- a/modules/client/back/methods/client/summary.js +++ b/modules/client/back/methods/client/summary.js @@ -122,7 +122,6 @@ module.exports = Self => { return clientModel.findOne(filter, options); } - async function getRecoveries(recoveryModel, clientId, options) { const filter = { where: { diff --git a/modules/client/back/methods/client/updateFiscalData.js b/modules/client/back/methods/client/updateFiscalData.js index 7ae842c6e..9bb572fb3 100644 --- a/modules/client/back/methods/client/updateFiscalData.js +++ b/modules/client/back/methods/client/updateFiscalData.js @@ -95,6 +95,10 @@ module.exports = Self => { { arg: 'despiteOfClient', type: 'number' + }, + { + arg: 'hasIncoterms', + type: 'boolean' } ], returns: { diff --git a/modules/client/back/methods/client/updateUser.js b/modules/client/back/methods/client/updateUser.js new file mode 100644 index 000000000..ef23b92fd --- /dev/null +++ b/modules/client/back/methods/client/updateUser.js @@ -0,0 +1,61 @@ +module.exports = Self => { + Self.remoteMethodCtx('updateUser', { + description: 'Updates the user information', + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The user id', + http: {source: 'path'} + }, + { + arg: 'name', + type: 'string', + description: 'the user name' + }, + { + arg: 'email', + type: 'string', + description: 'the user email' + }, + { + arg: 'active', + type: 'boolean', + description: 'whether the user is active or not' + }, + ], + http: { + path: '/:id/updateUser', + verb: 'PATCH' + } + }); + + Self.updateUser = async function(ctx, id, options) { + const models = Self.app.models; + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await models.Account.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const isWorker = await models.Worker.findById(id, null, myOptions); + if (isWorker) + throw new Error(`Can't update the user details of another worker`); + + const user = await models.Account.findById(id, null, myOptions); + + await user.updateAttributes(ctx.args, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js index ec38c0821..ce1d89818 100644 --- a/modules/client/back/methods/defaulter/filter.js +++ b/modules/client/back/methods/defaulter/filter.js @@ -51,6 +51,8 @@ module.exports = Self => { const stmts = []; + const date = new Date(); + date.setHours(0, 0, 0, 0); const stmt = new ParameterizedSQL( `SELECT * FROM ( @@ -58,12 +60,12 @@ module.exports = Self => { DISTINCT c.id clientFk, c.name clientName, c.salesPersonFk, - u.nickname salesPersonName, + u.name salesPersonName, d.amount, co.created, co.text observation, uw.id workerFk, - uw.nickname workerName, + uw.name workerName, c.creditInsurance, d.defaulterSinced FROM vn.defaulter d @@ -72,10 +74,10 @@ module.exports = Self => { LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user uw ON uw.id = co.workerFk WHERE - d.created = CURDATE() + d.created = ? AND d.amount > 0 ORDER BY co.created DESC) d` - ); + , [date]); stmt.merge(conn.makeWhere(filter.where)); stmt.merge(`GROUP BY d.clientFk`); diff --git a/modules/client/back/methods/recovery/hasActiveRecovery.js b/modules/client/back/methods/recovery/hasActiveRecovery.js index a0d0064eb..6b69d99d7 100644 --- a/modules/client/back/methods/recovery/hasActiveRecovery.js +++ b/modules/client/back/methods/recovery/hasActiveRecovery.js @@ -27,13 +27,15 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const query = ` SELECT count(*) AS hasActiveRecovery FROM vn.recovery WHERE clientFk = ? - AND IFNULL(finished,CURDATE()) >= CURDATE(); + AND IFNULL(finished, ?) >= ?; `; - const [result] = await Self.rawSql(query, [id], myOptions); + const [result] = await Self.rawSql(query, [id, date, date], myOptions); return result.hasActiveRecovery != 0; }; diff --git a/modules/client/back/models/client-sample.json b/modules/client/back/models/client-sample.json index 920758217..fc64cd949 100644 --- a/modules/client/back/models/client-sample.json +++ b/modules/client/back/models/client-sample.json @@ -32,10 +32,10 @@ "model": "Sample", "foreignKey": "typeFk" }, - "worker": { + "user": { "type": "belongsTo", - "model": "Worker", - "foreignKey": "workerFk" + "model": "Account", + "foreignKey": "userFk" }, "account": { "type": "belongsTo", diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 90a9b9e23..746261626 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -8,30 +8,32 @@ const LoopBackContext = require('loopback-context'); module.exports = Self => { // Methods - require('../methods/client/getCard')(Self); - require('../methods/client/createWithUser')(Self); - require('../methods/client/hasCustomerRole')(Self); - require('../methods/client/canCreateTicket')(Self); - require('../methods/client/isValidClient')(Self); require('../methods/client/addressesPropagateRe')(Self); + require('../methods/client/canBeInvoiced')(Self); + require('../methods/client/canCreateTicket')(Self); + require('../methods/client/checkDuplicated')(Self); + require('../methods/client/confirmTransaction')(Self); + require('../methods/client/consumption')(Self); + require('../methods/client/createAddress')(Self); + require('../methods/client/createReceipt')(Self); + require('../methods/client/createWithUser')(Self); + require('../methods/client/extendedListFilter')(Self); + require('../methods/client/getAverageInvoiced')(Self); + require('../methods/client/getCard')(Self); require('../methods/client/getDebt')(Self); require('../methods/client/getMana')(Self); - require('../methods/client/getAverageInvoiced')(Self); - require('../methods/client/summary')(Self); - require('../methods/client/updateFiscalData')(Self); require('../methods/client/getTransactions')(Self); - require('../methods/client/confirmTransaction')(Self); - require('../methods/client/canBeInvoiced')(Self); - require('../methods/client/uploadFile')(Self); + require('../methods/client/hasCustomerRole')(Self); + require('../methods/client/isValidClient')(Self); require('../methods/client/lastActiveTickets')(Self); require('../methods/client/sendSms')(Self); - require('../methods/client/createAddress')(Self); + require('../methods/client/setPassword')(Self); + require('../methods/client/summary')(Self); require('../methods/client/updateAddress')(Self); - require('../methods/client/consumption')(Self); - require('../methods/client/createReceipt')(Self); + require('../methods/client/updateFiscalData')(Self); require('../methods/client/updatePortfolio')(Self); - require('../methods/client/checkDuplicated')(Self); - require('../methods/client/extendedListFilter')(Self); + require('../methods/client/updateUser')(Self); + require('../methods/client/uploadFile')(Self); // Validations @@ -446,7 +448,7 @@ module.exports = Self => { const app = require('vn-loopback/server/server'); app.on('started', function() { - let account = app.models.Account; + const account = app.models.Account; account.observe('before save', async ctx => { if (ctx.isNewInstance) return; @@ -454,22 +456,26 @@ module.exports = Self => { }); account.observe('after save', async ctx => { - let changes = ctx.data || ctx.instance; + const changes = ctx.data || ctx.instance; if (!ctx.isNewInstance && changes) { - let oldData = ctx.hookState.oldInstance; - let hasChanges = oldData.name != changes.name || oldData.active != changes.active; + const oldData = ctx.hookState.oldInstance; + const hasChanges = oldData.name != changes.name || oldData.active != changes.active; if (!hasChanges) return; - let userId = ctx.options.accessToken.userId; - let logRecord = { - originFk: oldData.id, - userFk: userId, - action: 'update', - changedModel: 'Account', - oldInstance: {name: oldData.name, active: oldData.active}, - newInstance: {name: changes.name, active: changes.active} - }; - await Self.app.models.ClientLog.create(logRecord); + const isClient = await Self.app.models.Client.count({id: oldData.id}); + if (isClient) { + const loopBackContext = LoopBackContext.getCurrentContext(); + const userId = loopBackContext.active.accessToken.userId; + const logRecord = { + originFk: oldData.id, + userFk: userId, + action: 'update', + changedModel: 'Account', + oldInstance: {name: oldData.name, active: oldData.active}, + newInstance: {name: changes.name, active: changes.active} + }; + await Self.app.models.ClientLog.create(logRecord); + } } }); }); diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json index 1426152a4..d383ae907 100644 --- a/modules/client/back/models/client.json +++ b/modules/client/back/models/client.json @@ -98,6 +98,9 @@ "hasCoreVnh": { "type": "boolean" }, + "hasIncoterms": { + "type": "boolean" + }, "isTaxDataChecked":{ "type": "boolean" }, diff --git a/modules/client/front/defaulter/index.html b/modules/client/front/defaulter/index.html index 92664079c..22b78594a 100644 --- a/modules/client/front/defaulter/index.html +++ b/modules/client/front/defaulter/index.html @@ -3,6 +3,7 @@ url="Defaulters/filter" filter="::$ctrl.filter" limit="20" + order="amount DESC" data="defaulters" auto-load="true"> @@ -70,13 +71,13 @@ - Last observation D. + field="created"> + L. O. Date + field="creditInsurance" + shrink> Credit I. @@ -124,13 +125,13 @@ ng-model="defaulter.observation"> - + {{::defaulter.created | date: 'dd/MM/yyyy'}} - {{::defaulter.creditInsurance | currency: 'EUR': 2}} - {{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}} + {{::defaulter.creditInsurance | currency: 'EUR': 2}} + {{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}} diff --git a/modules/client/front/defaulter/index.js b/modules/client/front/defaulter/index.js index 80f510bf2..4beadfda6 100644 --- a/modules/client/front/defaulter/index.js +++ b/modules/client/front/defaulter/index.js @@ -26,7 +26,7 @@ export default class Controller extends Section { url: 'Workers/activeWithInheritedRole', where: `{role: 'salesPerson'}`, searchFunction: '{firstName: $search}', - showField: 'nickname', + showField: 'name', valueField: 'id', } }, @@ -35,7 +35,7 @@ export default class Controller extends Section { autocomplete: { url: 'Workers/activeWithInheritedRole', searchFunction: '{firstName: $search}', - showField: 'nickname', + showField: 'name', valueField: 'id', } }, @@ -53,16 +53,8 @@ export default class Controller extends Section { } ] }; - } - get balanceDueTotal() { - let balanceDueTotal = 0; - const defaulters = this.$.model.data || []; - - for (let defaulter of defaulters) - balanceDueTotal += defaulter.amount; - - return balanceDueTotal; + this.getBalanceDueTotal(); } get checked() { @@ -76,6 +68,18 @@ export default class Controller extends Section { return checkedLines; } + getBalanceDueTotal() { + this.$http.get('Defaulters/filter') + .then(res => { + if (!res.data) return 0; + + this.balanceDueTotal = res.data.reduce( + (accumulator, currentValue) => { + return accumulator + (currentValue['amount'] || 0); + }, 0); + }); + } + chipColor(date) { const day = 24 * 60 * 60 * 1000; const today = new Date(); diff --git a/modules/client/front/defaulter/index.spec.js b/modules/client/front/defaulter/index.spec.js index 5801fa1f8..0732c68a1 100644 --- a/modules/client/front/defaulter/index.spec.js +++ b/modules/client/front/defaulter/index.spec.js @@ -36,17 +36,6 @@ describe('client defaulter', () => { }); }); - describe('balanceDueTotal() getter', () => { - it('should return balance due total', () => { - const data = controller.$.model.data; - const expectedAmount = data[0].amount + data[1].amount + data[2].amount; - - const result = controller.balanceDueTotal; - - expect(result).toEqual(expectedAmount); - }); - }); - describe('chipColor()', () => { it('should return undefined when the date is the present', () => { let today = new Date(); @@ -93,6 +82,7 @@ describe('client defaulter', () => { const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}]; jest.spyOn(controller.vnApp, 'showMessage'); + $httpBackend.expect('GET', `Defaulters/filter`).respond(200); $httpBackend.expect('POST', `ClientObservations`, params).respond(200, params); controller.onResponse(); @@ -115,5 +105,17 @@ describe('client defaulter', () => { expect(expr).toEqual({'d.clientFk': '5'}); }); }); + + describe('getBalanceDueTotal()', () => { + it('should return balance due total', () => { + const defaulters = controller.$.model.data; + $httpBackend.when('GET', `Defaulters/filter`).respond(defaulters); + + controller.getBalanceDueTotal(); + $httpBackend.flush(); + + expect(controller.balanceDueTotal).toEqual(875); + }); + }); }); }); diff --git a/modules/client/front/defaulter/locale/es.yml b/modules/client/front/defaulter/locale/es.yml index 3f046e8d6..c3e1d4e19 100644 --- a/modules/client/front/defaulter/locale/es.yml +++ b/modules/client/front/defaulter/locale/es.yml @@ -3,7 +3,7 @@ Add observation to all selected clients: Añadir observación a {{total}} client Balance D.: Saldo V. Credit I.: Crédito A. Last observation: Última observación -Last observation D.: Fecha última O. +L. O. Date: Fecha Ú. O. Last observation date: Fecha última observación Search client: Buscar clientes Worker who made the last observation: Trabajador que ha realizado la última observación \ No newline at end of file diff --git a/modules/client/front/extended-list/index.html b/modules/client/front/extended-list/index.html index b45a0bc5f..784e88b8a 100644 --- a/modules/client/front/extended-list/index.html +++ b/modules/client/front/extended-list/index.html @@ -162,7 +162,7 @@ {{::client.province | dashIfEmpty}} {{::client.city | dashIfEmpty}} {{::client.postcode | dashIfEmpty}} - {{::client.email | dashIfEmpty}} + {{::client.email | dashIfEmpty}} {{::client.created | date:'dd/MM/yyyy'}} {{::client.businessType | dashIfEmpty}} {{::client.payMethod | dashIfEmpty}} diff --git a/modules/client/front/fiscal-data/index.html b/modules/client/front/fiscal-data/index.html index 1f3533327..bc5898635 100644 --- a/modules/client/front/fiscal-data/index.html +++ b/modules/client/front/fiscal-data/index.html @@ -185,6 +185,14 @@ vn-acl="salesAssistant"> + + + + - {{::sample.worker.user.name}} + ng-click="workerDescriptor.show($event, sample.user.id)" + ng-class="{'link': sample.user}"> + {{::sample.user.name || 'System' | translate}} {{::sample.company.code}} diff --git a/modules/client/front/sample/index/index.js b/modules/client/front/sample/index/index.js index 132704de0..7aa44c67f 100644 --- a/modules/client/front/sample/index/index.js +++ b/modules/client/front/sample/index/index.js @@ -12,15 +12,9 @@ class Controller extends Section { fields: ['code', 'description'] } }, { - relation: 'worker', + relation: 'user', scope: { - fields: ['userFk'], - include: { - relation: 'user', - scope: { - fields: ['name'] - } - } + fields: ['id', 'name'] } }, { relation: 'company', diff --git a/modules/client/front/web-access/index.html b/modules/client/front/web-access/index.html index 610497994..b776fa577 100644 --- a/modules/client/front/web-access/index.html +++ b/modules/client/front/web-access/index.html @@ -1,11 +1,16 @@ -
+ + + + + + + { + this.$http.patch(`Clients/${this.client.id}/setPassword`, data).then(() => { this.vnApp.showSuccess(this.$t('Data saved!')); }); } catch (e) { @@ -59,6 +59,18 @@ export default class Controller extends Section { return true; } + + onSubmit() { + const data = { + name: this.account.name, + email: this.account.email, + active: this.account.active + }; + this.$http.patch(`Clients/${this.client.id}/updateUser`, data).then(() => { + this.$.watcher.notifySaved(); + this.$.watcher.updateOriginalData(); + }); + } } Controller.$inject = ['$element', '$scope']; diff --git a/modules/client/front/web-access/index.spec.js b/modules/client/front/web-access/index.spec.js index 00fa12781..c1bb47a8e 100644 --- a/modules/client/front/web-access/index.spec.js +++ b/modules/client/front/web-access/index.spec.js @@ -52,7 +52,7 @@ describe('Component VnClientWebAccess', () => { }); describe('checkConditions()', () => { - it(`should perform a query to check if the client is valid and then store a boolean into the controller`, () => { + it('should perform a query to check if the client is valid', () => { controller.client = {id: '1234'}; expect(controller.canEnableCheckBox).toBeTruthy(); @@ -82,7 +82,9 @@ describe('Component VnClientWebAccess', () => { controller.newPassword = 'm24x8'; controller.repeatPassword = 'm24x8'; controller.canChangePassword = true; - $httpBackend.expectPATCH('Accounts/1234', {password: 'm24x8'}).respond('done'); + + const query = `Clients/${controller.client.id}/setPassword`; + $httpBackend.expectPATCH(query, {newPassword: controller.newPassword}).respond('done'); controller.onPassChange(); $httpBackend.flush(); }); diff --git a/modules/client/front/web-access/locale/es.yml b/modules/client/front/web-access/locale/es.yml index 4a3ac0ab4..5090ed020 100644 --- a/modules/client/front/web-access/locale/es.yml +++ b/modules/client/front/web-access/locale/es.yml @@ -4,4 +4,6 @@ New password: Nueva contraseña Repeat password: Repetir contraseña Change password: Cambiar contraseña Passwords don't match: Las contraseñas no coinciden -You must enter a new password: Debes introducir una nueva contraseña \ No newline at end of file +You must enter a new password: Debes introducir una nueva contraseña +Recovery email: Correo de recuperación +This email is used for user to regain access their account.: Este correo electrónico se usa para que el usuario recupere el acceso a su cuenta. \ No newline at end of file diff --git a/modules/entry/back/methods/entry/getEntry.js b/modules/entry/back/methods/entry/getEntry.js index 66238d0dc..cac3c65f8 100644 --- a/modules/entry/back/methods/entry/getEntry.js +++ b/modules/entry/back/methods/entry/getEntry.js @@ -43,7 +43,7 @@ module.exports = Self => { 'name', 'shipped', 'landed', - 'agencyFk', + 'agencyModeFk', 'warehouseOutFk', 'warehouseInFk', 'isReceived', diff --git a/modules/entry/back/methods/entry/lastItemBuys.js b/modules/entry/back/methods/entry/lastItemBuys.js index 7e83f1e5f..87b99c229 100644 --- a/modules/entry/back/methods/entry/lastItemBuys.js +++ b/modules/entry/back/methods/entry/lastItemBuys.js @@ -63,7 +63,6 @@ module.exports = Self => { stmt = new ParameterizedSQL( `CREATE TEMPORARY TABLE tmp.item (PRIMARY KEY (id)) - ENGINE = MEMORY SELECT i.*, p.name AS producerName, diff --git a/modules/entry/back/methods/entry/latestBuysFilter.js b/modules/entry/back/methods/entry/latestBuysFilter.js index 6399faa52..9a21d4472 100644 --- a/modules/entry/back/methods/entry/latestBuysFilter.js +++ b/modules/entry/back/methods/entry/latestBuysFilter.js @@ -96,6 +96,7 @@ module.exports = Self => { }); Self.latestBuysFilter = async(ctx, filter, options) => { + const models = Self.app.models; const myOptions = {}; if (typeof options == 'object') @@ -143,8 +144,12 @@ module.exports = Self => { const stmts = []; let stmt; - stmts.push('CALL cache.visible_refresh(@calc_id, FALSE, 1)'); + const warehouse = await models.Warehouse.findOne({where: {code: 'ALG'}}, myOptions); + stmt = new ParameterizedSQL(`CALL cache.visible_refresh(@calc_id, FALSE, ?)`, [warehouse.id]); + stmts.push(stmt); + const date = new Date(); + date.setHours(0, 0, 0, 0); stmt = new ParameterizedSQL(` SELECT i.image, @@ -202,9 +207,9 @@ module.exports = Self => { LEFT JOIN itemType t ON t.id = i.typeFk LEFT JOIN intrastat intr ON intr.id = i.intrastatFk LEFT JOIN origin ori ON ori.id = i.originFk - LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(CURDATE(),INTERVAL 1 YEAR) + LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(? ,INTERVAL 1 YEAR) LEFT JOIN supplier s ON s.id = e.supplierFk` - ); + , [date]); if (ctx.args.tags) { let i = 1; diff --git a/modules/entry/front/basic-data/index.html b/modules/entry/front/basic-data/index.html index 8787853a5..423e9d70d 100644 --- a/modules/entry/front/basic-data/index.html +++ b/modules/entry/front/basic-data/index.html @@ -137,7 +137,7 @@ diff --git a/modules/entry/front/basic-data/index.js b/modules/entry/front/basic-data/index.js index 80870c3f3..564a3df5c 100644 --- a/modules/entry/front/basic-data/index.js +++ b/modules/entry/front/basic-data/index.js @@ -46,7 +46,7 @@ class Controller extends Section { if (!value) continue; switch (key) { - case 'agencyFk': + case 'agencyModeFk': case 'warehouseInFk': case 'warehouseOutFk': case 'shipped': diff --git a/modules/entry/front/card/index.js b/modules/entry/front/card/index.js index eafed171b..96f4702e2 100644 --- a/modules/entry/front/card/index.js +++ b/modules/entry/front/card/index.js @@ -14,7 +14,7 @@ class Controller extends ModuleCard { { relation: 'travel', scope: { - fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'], + fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'], include: [ { relation: 'agency', diff --git a/modules/entry/front/descriptor/index.js b/modules/entry/front/descriptor/index.js index fed3787d4..34aa162f9 100644 --- a/modules/entry/front/descriptor/index.js +++ b/modules/entry/front/descriptor/index.js @@ -14,9 +14,9 @@ class Controller extends Descriptor { let travelFilter; const entryTravel = this.entry && this.entry.travel; - if (entryTravel && entryTravel.agencyFk) { + if (entryTravel && entryTravel.agencyModeFk) { travelFilter = this.entry && JSON.stringify({ - agencyFk: entryTravel.agencyFk + agencyModeFk: entryTravel.agencyModeFk }); } return travelFilter; @@ -49,7 +49,7 @@ class Controller extends Descriptor { { relation: 'travel', scope: { - fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'], + fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'], include: [ { relation: 'agency', diff --git a/modules/invoiceIn/front/tax/index.spec.js b/modules/invoiceIn/front/tax/index.spec.js index c62ada9ca..52114afe5 100644 --- a/modules/invoiceIn/front/tax/index.spec.js +++ b/modules/invoiceIn/front/tax/index.spec.js @@ -1,7 +1,6 @@ import './index.js'; import watcher from 'core/mocks/watcher'; import crudModel from 'core/mocks/crud-model'; -const UserError = require('vn-loopback/util/user-error'); describe('InvoiceIn', () => { describe('Component tax', () => { diff --git a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js index 116e8b3fc..c2fdbcbba 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js @@ -61,7 +61,7 @@ module.exports = Self => { responseType: 'stream', params: { authorization: auth.id, - invoiceId: id + refFk: invoiceOut.ref } }).then(async response => { const issued = invoiceOut.issued; diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index f1138dd51..19dea5b1a 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -9,7 +9,7 @@ module.exports = Self => { accepts: [ { arg: 'id', - type: 'String', + type: 'string', description: 'The invoice id', http: {source: 'path'} } @@ -21,16 +21,16 @@ module.exports = Self => { root: true }, { arg: 'Content-Type', - type: 'String', + type: 'string', http: {target: 'header'} }, { arg: 'Content-Disposition', - type: 'String', + type: 'string', http: {target: 'header'} } ], http: { - path: `/:id/download`, + path: '/:id/download', verb: 'GET' } }); diff --git a/modules/invoiceOut/back/methods/invoiceOut/refund.js b/modules/invoiceOut/back/methods/invoiceOut/refund.js new file mode 100644 index 000000000..7ad6b03ec --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/refund.js @@ -0,0 +1,49 @@ +module.exports = Self => { + Self.remoteMethod('refund', { + description: 'Create refund tickets with sales and services if provided', + accessType: 'WRITE', + accepts: [{ + arg: 'ref', + type: 'string', + description: 'The invoice reference' + }], + returns: { + type: ['number'], + root: true + }, + http: { + path: '/refund', + verb: 'post' + } + }); + + Self.refund = async(ref, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const filter = {where: {refFk: ref}}; + const tickets = await models.Ticket.find(filter, myOptions); + + const ticketsIds = tickets.map(ticket => ticket.id); + + const refundedTickets = await models.Ticket.refund(ticketsIds, myOptions); + + if (tx) await tx.commit(); + + return refundedTickets; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js new file mode 100644 index 000000000..628318d42 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/refund.spec.js @@ -0,0 +1,28 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('InvoiceOut refund()', () => { + const userId = 5; + const activeCtx = { + accessToken: {userId: userId}, + }; + + it('should return the ids for the created refund tickets', async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + const result = await models.InvoiceOut.refund('T1111111', options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 3b2822ada..c8c97702f 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -8,4 +8,5 @@ module.exports = Self => { require('../methods/invoiceOut/createPdf')(Self); require('../methods/invoiceOut/createManualInvoice')(Self); require('../methods/invoiceOut/globalInvoicing')(Self); + require('../methods/invoiceOut/refund')(Self); }; diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html index ef4c9a62e..1c0919288 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.html +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -80,6 +80,8 @@ ng-click="refundConfirmation.show()" name="refundInvoice" vn-tooltip="Create a single ticket with all the content of the current invoice" + vn-acl="invoicing, claimManager, salesAssistant" + vn-acl-action="remove" translate> Refund diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index b884e50cb..2b6d90ebf 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -84,7 +84,7 @@ class Controller extends Section { showCsvInvoice() { this.vnReport.showCsv('invoice', { recipientId: this.invoiceOut.client.id, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } @@ -95,7 +95,7 @@ class Controller extends Section { return this.vnEmail.send('invoice', { recipientId: this.invoiceOut.client.id, recipient: $data.email, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } @@ -106,43 +106,23 @@ class Controller extends Section { return this.vnEmail.sendCsv('invoice', { recipientId: this.invoiceOut.client.id, recipient: $data.email, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } showExportationLetter() { this.vnReport.show('exportation', { recipientId: this.invoiceOut.client.id, - invoiceId: this.id + refFk: this.invoiceOut.ref }); } - async refundInvoiceOut() { - let filter = { - where: {refFk: this.invoiceOut.ref} - }; - const tickets = await this.$http.get('Tickets', {filter}); - this.tickets = tickets.data; - this.ticketsIds = []; - for (let ticket of this.tickets) - this.ticketsIds.push(ticket.id); - - filter = { - where: {ticketFk: {inq: this.ticketsIds}} - }; - const sales = await this.$http.get('Sales', {filter}); - this.sales = sales.data; - - const ticketServices = await this.$http.get('TicketServices', {filter}); - this.services = ticketServices.data; - - const params = { - sales: this.sales, - services: this.services - }; - const query = `Sales/refund`; - return this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + refundInvoiceOut() { + const query = 'InvoiceOuts/refund'; + const params = {ref: this.invoiceOut.ref}; + this.$http.post(query, params).then(res => { + const ticketIds = res.data.map(ticket => ticket.id).join(', '); + this.vnApp.showSuccess(this.$t('The following refund tickets have been created', {ticketIds})); }); } } diff --git a/modules/invoiceOut/front/descriptor-menu/index.spec.js b/modules/invoiceOut/front/descriptor-menu/index.spec.js index c84c97a57..da7c87894 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.spec.js +++ b/modules/invoiceOut/front/descriptor-menu/index.spec.js @@ -6,7 +6,8 @@ describe('vnInvoiceOutDescriptorMenu', () => { let $httpParamSerializer; const invoiceOut = { id: 1, - client: {id: 1101} + client: {id: 1101}, + ref: 'T1111111' }; beforeEach(ngModule('invoiceOut')); @@ -15,14 +16,17 @@ describe('vnInvoiceOutDescriptorMenu', () => { $httpBackend = _$httpBackend_; $httpParamSerializer = _$httpParamSerializer_; controller = $componentController('vnInvoiceOutDescriptorMenu', {$element: null}); + controller.invoiceOut = { + id: 1, + ref: 'T1111111', + client: {id: 1101} + }; })); describe('createPdfInvoice()', () => { it('should make a query to the createPdf() endpoint and show a success snackbar', () => { jest.spyOn(controller.vnApp, 'showSuccess'); - controller.invoiceOut = invoiceOut; - $httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond(); $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond(); controller.createPdfInvoice(); @@ -36,11 +40,9 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the csv invoice download endpoint and show a message snackbar', () => { jest.spyOn(window, 'open').mockReturnThis(); - controller.invoiceOut = invoiceOut; - const expectedParams = { - invoiceId: invoiceOut.id, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); const expectedPath = `api/csv/invoice/download?${serializedParams}`; @@ -52,7 +54,6 @@ describe('vnInvoiceOutDescriptorMenu', () => { describe('deleteInvoiceOut()', () => { it(`should make a query and call showSuccess()`, () => { - controller.invoiceOut = invoiceOut; controller.$state.reload = jest.fn(); jest.spyOn(controller.vnApp, 'showSuccess'); @@ -65,7 +66,6 @@ describe('vnInvoiceOutDescriptorMenu', () => { }); it(`should make a query and call showSuccess() after state.go if the state wasn't in invoiceOut module`, () => { - controller.invoiceOut = invoiceOut; jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); jest.spyOn(controller.vnApp, 'showSuccess'); controller.$state.current.name = 'invoiceOut.card.something'; @@ -83,13 +83,11 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the email invoice endpoint and show a message snackbar', () => { jest.spyOn(controller.vnApp, 'showMessage'); - controller.invoiceOut = invoiceOut; - const $data = {email: 'brucebanner@gothamcity.com'}; const expectedParams = { - invoiceId: invoiceOut.id, recipient: $data.email, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); @@ -105,13 +103,11 @@ describe('vnInvoiceOutDescriptorMenu', () => { it('should make a query to the csv invoice send endpoint and show a message snackbar', () => { jest.spyOn(controller.vnApp, 'showMessage'); - controller.invoiceOut = invoiceOut; - const $data = {email: 'brucebanner@gothamcity.com'}; const expectedParams = { - invoiceId: invoiceOut.id, recipient: $data.email, - recipientId: invoiceOut.client.id + recipientId: invoiceOut.client.id, + refFk: invoiceOut.ref }; const serializedParams = $httpParamSerializer(expectedParams); @@ -123,33 +119,16 @@ describe('vnInvoiceOutDescriptorMenu', () => { }); }); - // #4084 review with Juan - xdescribe('refundInvoiceOut()', () => { - it('should make a query and go to ticket.card.sale', () => { - controller.$state.go = jest.fn(); + describe('refundInvoiceOut()', () => { + it('should make a query and show a success message', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + const params = {ref: controller.invoiceOut.ref}; - const invoiceOut = { - id: 1, - ref: 'T1111111' - }; - controller.invoiceOut = invoiceOut; - const tickets = [{id: 1}]; - const sales = [{id: 1}]; - const services = [{id: 2}]; - - $httpBackend.expectGET(`Tickets`).respond(tickets); - $httpBackend.expectGET(`Sales`).respond(sales); - $httpBackend.expectGET(`TicketServices`).respond(services); - - const expectedParams = { - sales: sales, - services: services - }; - $httpBackend.expectPOST(`Sales/refund`, expectedParams).respond(); + $httpBackend.expectPOST(`InvoiceOuts/refund`, params).respond([{id: 1}, {id: 2}]); controller.refundInvoiceOut(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined}); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); }); }); }); diff --git a/modules/invoiceOut/front/descriptor-menu/locale/en.yml b/modules/invoiceOut/front/descriptor-menu/locale/en.yml new file mode 100644 index 000000000..d299155d7 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/locale/en.yml @@ -0,0 +1 @@ +The following refund tickets have been created: "The following refund tickets have been created: {{ticketIds}}" diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml index 8949f1f91..488c1a3f8 100644 --- a/modules/invoiceOut/front/descriptor-menu/locale/es.yml +++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml @@ -17,3 +17,4 @@ Create a single ticket with all the content of the current invoice: Crear un tic Regenerate PDF invoice: Regenerar PDF factura The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado The email can't be empty: El correo no puede estar vacío +The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}" diff --git a/modules/invoiceOut/front/index/global-invoicing/index.spec.js b/modules/invoiceOut/front/index/global-invoicing/index.spec.js index 35252c406..916364007 100644 --- a/modules/invoiceOut/front/index/global-invoicing/index.spec.js +++ b/modules/invoiceOut/front/index/global-invoicing/index.spec.js @@ -66,7 +66,9 @@ describe('InvoiceOut', () => { controller.responseHandler('accept'); - expect(controller.vnApp.showError).toHaveBeenCalledWith(`Invoice date and the max date should be filled`); + const expectedError = 'Invoice date and the max date should be filled'; + + expect(controller.vnApp.showError).toHaveBeenCalledWith(expectedError); }); it('should throw an error when fromClientId or toClientId properties are not filled in', () => { diff --git a/modules/item/back/methods/item/getWasteByItem.js b/modules/item/back/methods/item/getWasteByItem.js index a797667f5..b93d534da 100644 --- a/modules/item/back/methods/item/getWasteByItem.js +++ b/modules/item/back/methods/item/getWasteByItem.js @@ -32,6 +32,8 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const wastes = await Self.rawSql(` SELECT *, 100 * dwindle / total AS percentage FROM ( @@ -42,11 +44,11 @@ module.exports = Self => { sum(ws.saleWaste) AS dwindle FROM bs.waste ws WHERE buyer = ? AND family = ? - AND year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE())) - AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1) + AND year = YEAR(TIMESTAMPADD(WEEK,-1, ?)) + AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) GROUP BY buyer, itemFk ) sub - ORDER BY family, percentage DESC`, [buyer, family], myOptions); + ORDER BY family, percentage DESC`, [buyer, family, date, date], myOptions); const details = []; diff --git a/modules/item/back/methods/item/getWasteByWorker.js b/modules/item/back/methods/item/getWasteByWorker.js index 5e40831b9..2b0b78974 100644 --- a/modules/item/back/methods/item/getWasteByWorker.js +++ b/modules/item/back/methods/item/getWasteByWorker.js @@ -19,6 +19,8 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const wastes = await Self.rawSql(` SELECT *, 100 * dwindle / total AS percentage FROM ( @@ -27,11 +29,11 @@ module.exports = Self => { sum(ws.saleTotal) AS total, sum(ws.saleWaste) AS dwindle FROM bs.waste ws - WHERE year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE())) - AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1) + WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?)) + AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) GROUP BY buyer, family ) sub - ORDER BY percentage DESC`, null, myOptions); + ORDER BY percentage DESC`, [date, date], myOptions); const details = []; diff --git a/modules/item/back/model-config.json b/modules/item/back/model-config.json index 004aeb4b9..c31f472be 100644 --- a/modules/item/back/model-config.json +++ b/modules/item/back/model-config.json @@ -32,9 +32,6 @@ "ItemPackingType": { "dataSource": "vn" }, - "ItemPlacement": { - "dataSource": "vn" - }, "ItemTag": { "dataSource": "vn" }, diff --git a/modules/item/back/models/item-placement.json b/modules/item/back/models/item-placement.json deleted file mode 100644 index 0c036ef14..000000000 --- a/modules/item/back/models/item-placement.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "ItemPlacement", - "base": "VnModel", - "options": { - "mysql": { - "table": "itemPlacement" - } - }, - "properties": { - "id": { - "type": "number", - "id": true - }, - "code": { - "type": "string" - }, - "itemFk": { - "type": "number" - }, - "warehouseFk": { - "type": "number" - } - }, - "relations": { - "item": { - "type": "belongsTo", - "model": "Item", - "foreignKey": "itemFk" - }, - "warehouse": { - "type": "belongsTo", - "model": "Warehouse", - "foreignKey": "warehouseFk" - } - } -} \ No newline at end of file diff --git a/modules/item/front/summary/index.html b/modules/item/front/summary/index.html index dfc0b6e01..5264977f3 100644 --- a/modules/item/front/summary/index.html +++ b/modules/item/front/summary/index.html @@ -117,6 +117,23 @@ value="{{tag.value}}"> + +

+ + Description + +

+

+ Description +

+

+ {{$ctrl.summary.item.description}} +

+

{ if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const stmt = new ParameterizedSQL(` SELECT u.name AS salesPerson, @@ -47,9 +49,10 @@ module.exports = Self => { JOIN client c ON c.id = v.userFk JOIN account.user u ON c.salesPersonFk = u.id LEFT JOIN sharingCart sc ON sc.workerFk = c.salesPersonFk - AND CURDATE() BETWEEN sc.started AND sc.ended + AND ? BETWEEN sc.started AND sc.ended LEFT JOIN workerTeamCollegues wtc - ON wtc.collegueFk = IFNULL(sc.workerSubstitute, c.salesPersonFk)`); + ON wtc.collegueFk = IFNULL(sc.workerSubstitute, c.salesPersonFk)`, + [date]); if (!filter.where) filter.where = {}; diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index 9b6030e9f..9a7415055 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -104,6 +104,8 @@ module.exports = Self => { const userId = ctx.req.accessToken.userId; const conn = Self.dataSource.connector; const models = Self.app.models; + const date = new Date(); + date.setHours(0, 0, 0, 0); const args = ctx.args; const myOptions = {}; @@ -264,15 +266,18 @@ module.exports = Self => { `); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); - stmts.push(` + + stmt = new ParameterizedSQL(` CREATE TEMPORARY TABLE tmp.sale_getProblems - (INDEX (ticketFk)) - ENGINE = MEMORY - SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped - FROM tmp.filter f - LEFT JOIN alertLevel al ON al.id = f.alertLevel - WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) - AND f.shipped >= CURDATE()`); + (INDEX (ticketFk)) + ENGINE = MEMORY + SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped + FROM tmp.filter f + LEFT JOIN alertLevel al ON al.id = f.alertLevel + WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) + AND f.shipped >= ?`, [date]); + stmts.push(stmt); + stmts.push('CALL ticket_getProblems(FALSE)'); stmts.push(` diff --git a/modules/route/back/methods/agency-term/filter.js b/modules/route/back/methods/agency-term/filter.js index 5a9cbb564..0ecec7e88 100644 --- a/modules/route/back/methods/agency-term/filter.js +++ b/modules/route/back/methods/agency-term/filter.js @@ -74,6 +74,8 @@ module.exports = Self => { filter = mergeFilters(filter, {where}); + const date = new Date(); + date.setHours(0, 0, 0, 0); const stmts = []; const stmt = new ParameterizedSQL( `SELECT * @@ -101,10 +103,10 @@ module.exports = Self => { LEFT JOIN vn.ticket t ON t.routeFk = r.id LEFT JOIN vn.supplierAgencyTerm sat ON sat.agencyFk = a.id LEFT JOIN vn.supplier s ON s.id = sat.supplierFk - WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND sat.supplierFk IS NOT NULL + WHERE r.created > DATE_ADD(?, INTERVAL -2 MONTH) AND sat.supplierFk IS NOT NULL GROUP BY r.id ) a` - ); + , [date]); stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makePagination(filter)); diff --git a/modules/route/back/methods/route/specs/filter.spec.js b/modules/route/back/methods/route/specs/filter.spec.js index 9b3c6edf4..9d481f21e 100644 --- a/modules/route/back/methods/route/specs/filter.spec.js +++ b/modules/route/back/methods/route/specs/filter.spec.js @@ -2,17 +2,17 @@ const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models; describe('Route filter()', () => { - let today = new Date(); + const today = new Date(); today.setHours(2, 0, 0, 0); it('should return the routes matching "search"', async() => { - let ctx = { + const ctx = { args: { search: 1, } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(1); expect(result[0].id).toEqual(1); @@ -28,7 +28,6 @@ describe('Route filter()', () => { const to = new Date(); to.setHours(23, 59, 59, 999); - const ctx = { args: { from: from, @@ -48,43 +47,43 @@ describe('Route filter()', () => { }); it('should return the routes matching "m3"', async() => { - let ctx = { + const ctx = { args: { m3: 0.1, } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(1); }); it('should return the routes matching "description"', async() => { - let ctx = { + const ctx = { args: { description: 'third route', } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(1); }); it('should return the routes matching "workerFk"', async() => { - let ctx = { + const ctx = { args: { workerFk: 56, } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(5); }); it('should return the routes matching "warehouseFk"', async() => { - let ctx = { + const ctx = { args: { warehouseFk: 1, } @@ -102,25 +101,25 @@ describe('Route filter()', () => { }); it('should return the routes matching "vehicleFk"', async() => { - let ctx = { + const ctx = { args: { vehicleFk: 2, } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(1); }); it('should return the routes matching "agencyModeFk"', async() => { - let ctx = { + const ctx = { args: { agencyModeFk: 7, } }; - let result = await app.models.Route.filter(ctx); + const result = await app.models.Route.filter(ctx); expect(result.length).toEqual(1); }); diff --git a/modules/route/back/methods/route/specs/getDeliveryPoint.spec.js b/modules/route/back/methods/route/specs/getDeliveryPoint.spec.js index 4025b04d6..b08051c0a 100644 --- a/modules/route/back/methods/route/specs/getDeliveryPoint.spec.js +++ b/modules/route/back/methods/route/specs/getDeliveryPoint.spec.js @@ -2,7 +2,7 @@ const app = require('vn-loopback/server/server'); describe('route getDeliveryPoint()', () => { const routeId = 1; - const deliveryPointAddress = '46460 Av Espioca 100-Silla'; + const deliveryPointAddress = '1007 Mountain Drive, Gotham'; it('should get the delivery point addres of a route with assigned vehicle', async() => { let route = await app.models.Route.findById(routeId); diff --git a/modules/route/back/methods/route/specs/getSuggestedTickets.spec.js b/modules/route/back/methods/route/specs/getSuggestedTickets.spec.js index beb494d43..bb38cb50e 100644 --- a/modules/route/back/methods/route/specs/getSuggestedTickets.spec.js +++ b/modules/route/back/methods/route/specs/getSuggestedTickets.spec.js @@ -1,30 +1,32 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('route getSuggestedTickets()', () => { const routeID = 1; const ticketId = 12; + it('should return an array of suggested tickets', async() => { const activeCtx = { accessToken: {userId: 19}, headers: {origin: 'http://localhost'} }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); - const tx = await app.models.Ticket.beginTransaction({}); + const tx = await models.Ticket.beginTransaction({}); try { const options = {transaction: tx}; - const ticketInRoute = await app.models.Ticket.findById(ticketId, null, options); + const ticketInRoute = await models.Ticket.findById(ticketId, null, options); await ticketInRoute.updateAttributes({ routeFk: null, landed: new Date() }, options); - const result = await app.models.Route.getSuggestedTickets(routeID, options); + const result = await models.Route.getSuggestedTickets(routeID, options); const length = result.length; const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; diff --git a/modules/route/back/methods/route/specs/updateWorkCenter.spec.js b/modules/route/back/methods/route/specs/updateWorkCenter.spec.js new file mode 100644 index 000000000..5328dc240 --- /dev/null +++ b/modules/route/back/methods/route/specs/updateWorkCenter.spec.js @@ -0,0 +1,51 @@ +const models = require('vn-loopback/server/server').models; + +describe('route updateWorkCenter()', () => { + const routeId = 1; + + it('should set the commission work center if the worker has workCenter', async() => { + const tx = await models.Route.beginTransaction({}); + try { + const developerId = 9; + const ctx = { + req: { + accessToken: {userId: developerId} + } + }; + const options = {transaction: tx}; + + const expectedResult = 1; + const updatedRoute = await models.Route.updateWorkCenter(ctx, routeId, options); + + expect(updatedRoute.commissionWorkCenterFk).toEqual(expectedResult); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it(`shoul set the default commision work center if that worker didn't have one yet`, async() => { + const tx = await models.Route.beginTransaction({}); + try { + const userWithoutWorkCenter = 2; + const ctx = { + req: { + accessToken: {userId: userWithoutWorkCenter} + } + }; + const options = {transaction: tx}; + + const expectedResult = 9; + const updatedRoute = await models.Route.updateWorkCenter(ctx, routeId, options); + + expect(updatedRoute.commissionWorkCenterFk).toEqual(expectedResult); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/route/back/methods/route/updateWorkCenter.js b/modules/route/back/methods/route/updateWorkCenter.js new file mode 100644 index 000000000..7796fba41 --- /dev/null +++ b/modules/route/back/methods/route/updateWorkCenter.js @@ -0,0 +1,54 @@ +module.exports = Self => { + Self.remoteMethodCtx('updateWorkCenter', { + description: 'Update the commission work center through user salix connected', + accessType: 'WRITE', + accepts: { + arg: 'id', + type: 'number', + description: 'Route Id', + http: {source: 'path'} + }, + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/updateWorkCenter`, + verb: 'POST' + } + }); + + Self.updateWorkCenter = async(ctx, id, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + const userId = ctx.req.accessToken.userId; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const [result] = await Self.rawSql(` + SELECT IFNULL(wl.workCenterFk, r.defaultWorkCenterFk) AS commissionWorkCenter + FROM vn.routeConfig r + LEFT JOIN vn.workerLabour wl ON wl.workerFk = ? + AND CURDATE() BETWEEN wl.started AND IFNULL(wl.ended, CURDATE()); + `, [userId], myOptions); + + const route = await models.Route.findById(id, null, myOptions); + await route.updateAttribute('commissionWorkCenterFk', result.commissionWorkCenter, myOptions); + + if (tx) await tx.commit(); + + return route; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js index c82d1722e..4050e62fe 100644 --- a/modules/route/back/models/route.js +++ b/modules/route/back/models/route.js @@ -9,6 +9,7 @@ module.exports = Self => { require('../methods/route/clone')(Self); require('../methods/route/getSuggestedTickets')(Self); require('../methods/route/unlink')(Self); + require('../methods/route/updateWorkCenter')(Self); Self.validate('kmStart', validateDistance, { message: 'Distance must be lesser than 1000' diff --git a/modules/route/back/models/route.json b/modules/route/back/models/route.json index 46fb6b76f..3b33cc028 100644 --- a/modules/route/back/models/route.json +++ b/modules/route/back/models/route.json @@ -47,6 +47,9 @@ }, "description": { "type": "string" + }, + "commissionWorkCenterFk": { + "type": "number" } }, "relations": { diff --git a/modules/route/front/create/index.js b/modules/route/front/create/index.js index 56c8cc25a..c81394c10 100644 --- a/modules/route/front/create/index.js +++ b/modules/route/front/create/index.js @@ -4,7 +4,12 @@ import Section from 'salix/components/section'; export default class Controller extends Section { onSubmit() { this.$.watcher.submit().then( - res => this.$state.go('route.card.summary', {id: res.data.id}) + res => { + this.$http.post(`Routes/${res.data.id}/updateWorkCenter`, null) + .then(() => { + this.$state.go('route.card.summary', {id: res.data.id}); + }); + } ); } } diff --git a/modules/shelving/back/methods/shelving/getSummary.js b/modules/shelving/back/methods/shelving/getSummary.js new file mode 100644 index 000000000..da357c7bf --- /dev/null +++ b/modules/shelving/back/methods/shelving/getSummary.js @@ -0,0 +1,52 @@ +module.exports = Self => { + Self.remoteMethod('getSummary', { + description: 'Returns the shelving summary', + accessType: 'READ', + accepts: { + arg: 'code', + type: 'string', + required: true, + description: 'The shelving code', + http: {source: 'path'} + }, + returns: { + type: 'object', + root: true + }, + http: { + path: `/:code/getSummary`, + verb: 'GET' + } + }); + Self.getSummary = async code => { + let filter = { + where: {code: code}, + fields: [ + 'code', + 'parkingFk', + 'priority', + 'userFk', + 'isRecyclable' + ], + include: [ + { + relation: 'parking' + }, + { + relation: 'worker', + scope: { + fields: ['id', 'userFk'], + include: { + relation: 'user', + scope: { + fields: ['id', 'nickname'] + } + } + } + } + ] + }; + + return Self.app.models.Shelving.findOne(filter); + }; +}; diff --git a/modules/shelving/back/model-config.json b/modules/shelving/back/model-config.json new file mode 100644 index 000000000..b5619d8c5 --- /dev/null +++ b/modules/shelving/back/model-config.json @@ -0,0 +1,11 @@ +{ + "Parking": { + "dataSource": "vn" + }, + "Shelving": { + "dataSource": "vn" + }, + "ShelvingLog": { + "dataSource": "vn" + } +} diff --git a/modules/shelving/back/models/parking.json b/modules/shelving/back/models/parking.json new file mode 100644 index 000000000..7efcf72d3 --- /dev/null +++ b/modules/shelving/back/models/parking.json @@ -0,0 +1,33 @@ +{ + "name": "Parking", + "base": "VnModel", + "options": { + "mysql": { + "table": "parking" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "column": { + "type": "string", + "required": true + }, + "row": { + "type": "string", + "required": true + }, + "sectorFk": { + "type": "number" + }, + "code": { + "type": "string" + }, + "pickingOrder": { + "type": "number" + } + } +} diff --git a/modules/shelving/back/models/shelving-log.json b/modules/shelving/back/models/shelving-log.json new file mode 100644 index 000000000..a2267394e --- /dev/null +++ b/modules/shelving/back/models/shelving-log.json @@ -0,0 +1,58 @@ +{ + "name": "ShelvingLog", + "base": "VnModel", + "options": { + "mysql": { + "table": "shelvingLog" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "forceId": false + }, + "originFk": { + "type": "number", + "required": true + }, + "userFk": { + "type": "number" + }, + "action": { + "type": "string", + "required": true + }, + "changedModel": { + "type": "string" + }, + "oldInstance": { + "type": "object" + }, + "newInstance": { + "type": "object" + }, + "creationDate": { + "type": "date" + }, + "changedModelId": { + "type": "number" + }, + "changedModelValue": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userFk" + } + }, + "scope": { + "order": ["creationDate DESC", "id DESC"] + } +} diff --git a/modules/shelving/back/models/shelving.js b/modules/shelving/back/models/shelving.js new file mode 100644 index 000000000..3e27f5863 --- /dev/null +++ b/modules/shelving/back/models/shelving.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/shelving/getSummary')(Self); +}; diff --git a/modules/shelving/back/models/shelving.json b/modules/shelving/back/models/shelving.json new file mode 100644 index 000000000..508ac428f --- /dev/null +++ b/modules/shelving/back/models/shelving.json @@ -0,0 +1,51 @@ +{ + "name": "Shelving", + "base": "Loggable", + "log": { + "model": "ShelvingLog", + "showField": "id" + }, + "options": { + "mysql": { + "table": "shelving" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "code": { + "type": "string", + "required": true + }, + "parkingFk": { + "type": "number" + }, + "isPrinted": { + "type": "boolean" + }, + "priority": { + "type": "number" + }, + "userFk": { + "type": "number" + }, + "isRecyclable": { + "type": "boolean" + } + }, + "relations": { + "parking": { + "type": "belongsTo", + "model": "Parking", + "foreignKey": "parkingFk" + }, + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "userFk" + } + } +} diff --git a/modules/shelving/front/basic-data/index.html b/modules/shelving/front/basic-data/index.html new file mode 100644 index 000000000..68d61e169 --- /dev/null +++ b/modules/shelving/front/basic-data/index.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/shelving/front/basic-data/index.js b/modules/shelving/front/basic-data/index.js new file mode 100644 index 000000000..e17c9feee --- /dev/null +++ b/modules/shelving/front/basic-data/index.js @@ -0,0 +1,10 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +ngModule.vnComponent('vnShelvingBasicData', { + template: require('./index.html'), + controller: Section, + bindings: { + shelving: '<' + } +}); diff --git a/modules/shelving/front/card/index.html b/modules/shelving/front/card/index.html new file mode 100644 index 000000000..c83afc288 --- /dev/null +++ b/modules/shelving/front/card/index.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/modules/shelving/front/card/index.js b/modules/shelving/front/card/index.js new file mode 100644 index 000000000..5e2ea9b12 --- /dev/null +++ b/modules/shelving/front/card/index.js @@ -0,0 +1,29 @@ +import ngModule from '../module'; +import ModuleCard from 'salix/components/module-card'; + +class Controller extends ModuleCard { + reload() { + const filter = { + include: [ + {relation: 'worker', + scope: { + fields: ['userFk'], + include: { + relation: 'user', + scope: { + fields: ['nickname'] + } + } + }}, + {relation: 'parking'} + ] + }; + this.$http.get(`Shelvings/${this.$params.id}`, {filter}) + .then(res => this.shelving = res.data); + } +} + +ngModule.vnComponent('vnShelvingCard', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/shelving/front/card/index.spec.js b/modules/shelving/front/card/index.spec.js new file mode 100644 index 000000000..85b1bd269 --- /dev/null +++ b/modules/shelving/front/card/index.spec.js @@ -0,0 +1,26 @@ +import './index'; + +describe('component vnShelvingCard', () => { + let controller; + let $httpBackend; + const data = {id: 1, code: 'AAA'}; + + beforeEach(ngModule('shelving')); + + beforeEach(inject(($componentController, _$httpBackend_, $stateParams) => { + $httpBackend = _$httpBackend_; + + let $element = angular.element('
'); + controller = $componentController('vnShelvingCard', {$element}); + + $stateParams.id = data.id; + $httpBackend.whenRoute('GET', 'Shelvings/:id').respond(data); + })); + + it('should reload the controller data', () => { + controller.reload(); + $httpBackend.flush(); + + expect(controller.shelving).toEqual(data); + }); +}); diff --git a/modules/shelving/front/create/index.html b/modules/shelving/front/create/index.html new file mode 100644 index 000000000..edb3a7d3b --- /dev/null +++ b/modules/shelving/front/create/index.html @@ -0,0 +1,51 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/modules/shelving/front/create/index.js b/modules/shelving/front/create/index.js new file mode 100644 index 000000000..bb0e441eb --- /dev/null +++ b/modules/shelving/front/create/index.js @@ -0,0 +1,15 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + return this.$.watcher.submit().then(res => + this.$state.go('shelving.card.basicData', {id: res.data.id}) + ); + } +} + +ngModule.vnComponent('vnShelvingCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/shelving/front/create/index.spec.js b/modules/shelving/front/create/index.spec.js new file mode 100644 index 000000000..0bdde9145 --- /dev/null +++ b/modules/shelving/front/create/index.spec.js @@ -0,0 +1,38 @@ +import './index'; + +describe('Shelving', () => { + describe('Component vnShelvingCreate', () => { + let $scope; + let $state; + let controller; + + beforeEach(ngModule('shelving')); + + beforeEach(inject(($componentController, $rootScope, _$state_) => { + $scope = $rootScope.$new(); + $state = _$state_; + $scope.watcher = { + submit: () => { + return { + then: callback => { + callback({data: {id: 1}}); + } + }; + } + }; + const $element = angular.element(''); + controller = $componentController('vnShelvingCreate', {$element, $scope}); + controller.$params = {}; + })); + + describe('onSubmit()', () => { + it(`should redirect to basic data by calling the $state.go function`, () => { + jest.spyOn(controller.$state, 'go'); + + controller.onSubmit(); + + expect(controller.$state.go).toHaveBeenCalledWith('shelving.card.basicData', {id: 1}); + }); + }); + }); +}); diff --git a/modules/shelving/front/descriptor/index.html b/modules/shelving/front/descriptor/index.html new file mode 100644 index 000000000..1344bb52a --- /dev/null +++ b/modules/shelving/front/descriptor/index.html @@ -0,0 +1,45 @@ + + + + Delete + + + +
+ + + + + + + {{::$ctrl.shelving.worker.user.nickname}} + + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/modules/shelving/front/descriptor/index.js b/modules/shelving/front/descriptor/index.js new file mode 100644 index 000000000..931dbb6dc --- /dev/null +++ b/modules/shelving/front/descriptor/index.js @@ -0,0 +1,26 @@ +import ngModule from '../module'; +import Descriptor from 'salix/components/descriptor'; + +class Controller extends Descriptor { + get shelving() { + return this.entity; + } + + set shelving(value) { + this.entity = value; + } + + onDelete() { + return this.$http.delete(`Shelvings/${this.shelving.id}`) + .then(() => this.$state.go('shelving.index')) + .then(() => this.vnApp.showSuccess(this.$t('Shelving removed'))); + } +} + +ngModule.vnComponent('vnShelvingDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + shelving: '<' + } +}); diff --git a/modules/shelving/front/descriptor/index.spec.js b/modules/shelving/front/descriptor/index.spec.js new file mode 100644 index 000000000..3ee33580b --- /dev/null +++ b/modules/shelving/front/descriptor/index.spec.js @@ -0,0 +1,29 @@ +import './index.js'; + +describe('component vnShelvingDescriptor', () => { + let $httpBackend; + let controller; + + const shelving = {id: 1, code: 'AA6'}; + + beforeEach(ngModule('shelving')); + + beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnShelvingDescriptor', {$element: null}, {shelving}); + jest.spyOn(controller.vnApp, 'showSuccess'); + })); + + describe('onDelete()', () => { + it('should delete entity and go to index', () => { + controller.$state.go = jest.fn(); + + $httpBackend.expectDELETE('Shelvings/1').respond(); + controller.onDelete(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('shelving.index'); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/shelving/front/index.js b/modules/shelving/front/index.js new file mode 100644 index 000000000..2ad9bc1b9 --- /dev/null +++ b/modules/shelving/front/index.js @@ -0,0 +1,11 @@ +export * from './module'; + +import './basic-data'; +import './card'; +import './create'; +import './descriptor'; +import './index/'; +import './main'; +import './search-panel'; +import './summary'; +import './log'; diff --git a/modules/shelving/front/index/index.html b/modules/shelving/front/index/index.html new file mode 100644 index 000000000..1532abd42 --- /dev/null +++ b/modules/shelving/front/index/index.html @@ -0,0 +1,47 @@ + + + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/modules/shelving/front/index/index.js b/modules/shelving/front/index/index.js new file mode 100644 index 000000000..04d8ea9cd --- /dev/null +++ b/modules/shelving/front/index/index.js @@ -0,0 +1,14 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + preview(shelving) { + this.selectedShelving = shelving; + this.$.summary.show(); + } +} + +ngModule.vnComponent('vnShelvingIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/shelving/front/index/index.spec.js b/modules/shelving/front/index/index.spec.js new file mode 100644 index 000000000..aad79fb0e --- /dev/null +++ b/modules/shelving/front/index/index.spec.js @@ -0,0 +1,39 @@ +import './index.js'; +describe('Component vnShelvingIndex', () => { + let controller; + let $window; + let shelvings = [{ + id: 1, + code: 'AAA' + }, { + id: 2, + code: 'AA1' + }, { + id: 3, + code: 'AA2' + }]; + + beforeEach(ngModule('shelving')); + + beforeEach(inject(($componentController, _$window_) => { + $window = _$window_; + const $element = angular.element(''); + controller = $componentController('vnShelvingIndex', {$element}); + })); + + describe('preview()', () => { + it('should show the dialog summary', () => { + controller.$.summary = {show: () => {}}; + jest.spyOn(controller.$.summary, 'show'); + + let event = new MouseEvent('click', { + view: $window, + bubbles: true, + cancelable: true + }); + controller.preview(event, shelvings[0]); + + expect(controller.$.summary.show).toHaveBeenCalledWith(); + }); + }); +}); diff --git a/modules/shelving/front/index/locale/es.yml b/modules/shelving/front/index/locale/es.yml new file mode 100644 index 000000000..4d30f4cee --- /dev/null +++ b/modules/shelving/front/index/locale/es.yml @@ -0,0 +1,2 @@ +Parking: Parking +Priority: Prioridad \ No newline at end of file diff --git a/modules/shelving/front/log/index.html b/modules/shelving/front/log/index.html new file mode 100644 index 000000000..8f0e6851c --- /dev/null +++ b/modules/shelving/front/log/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/shelving/front/log/index.js b/modules/shelving/front/log/index.js new file mode 100644 index 000000000..588e0995a --- /dev/null +++ b/modules/shelving/front/log/index.js @@ -0,0 +1,7 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +ngModule.vnComponent('vnShelvingLog', { + template: require('./index.html'), + controller: Section, +}); diff --git a/modules/shelving/front/log/locale/es.yml b/modules/shelving/front/log/locale/es.yml new file mode 100644 index 000000000..c572b78d1 --- /dev/null +++ b/modules/shelving/front/log/locale/es.yml @@ -0,0 +1 @@ +Changed by: Cambiado por \ No newline at end of file diff --git a/modules/shelving/front/main/index.html b/modules/shelving/front/main/index.html new file mode 100644 index 000000000..3f3cc718b --- /dev/null +++ b/modules/shelving/front/main/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/modules/shelving/front/main/index.js b/modules/shelving/front/main/index.js new file mode 100644 index 000000000..96689fbd9 --- /dev/null +++ b/modules/shelving/front/main/index.js @@ -0,0 +1,29 @@ +import ngModule from '../module'; +import ModuleMain from 'salix/components/module-main'; + +export default class Shelving extends ModuleMain { + constructor($element, $) { + super($element, $); + this.filter = { + include: [ + {relation: 'parking'} + ], + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return {code: {like: `%${value}%`}}; + case 'parkingFk': + case 'userFk': + case 'isRecyclable': + return {[param]: value}; + } + } +} + +ngModule.vnComponent('vnShelving', { + controller: Shelving, + template: require('./index.html') +}); diff --git a/modules/shelving/front/main/index.spec.js b/modules/shelving/front/main/index.spec.js new file mode 100644 index 000000000..dcfa912bc --- /dev/null +++ b/modules/shelving/front/main/index.spec.js @@ -0,0 +1,19 @@ +import './index'; + +describe('component vnShelving', () => { + let controller; + + beforeEach(ngModule('shelving')); + + beforeEach(inject($componentController => { + controller = $componentController('vnShelving', {$element: null}); + })); + + describe('exprBuilder()', () => { + it('should search by code', () => { + let expr = controller.exprBuilder('search', 'UXN'); + + expect(expr).toEqual({code: {like: '%UXN%'}},); + }); + }); +}); diff --git a/modules/shelving/front/main/locale/es.yml b/modules/shelving/front/main/locale/es.yml new file mode 100644 index 000000000..4c39469ce --- /dev/null +++ b/modules/shelving/front/main/locale/es.yml @@ -0,0 +1 @@ +Search shelving by code, parking or worker: Busca carros por código, parking o trabajador \ No newline at end of file diff --git a/modules/shelving/front/module.js b/modules/shelving/front/module.js new file mode 100644 index 000000000..8ba261ead --- /dev/null +++ b/modules/shelving/front/module.js @@ -0,0 +1,3 @@ +import {ng} from 'core/vendor'; + +export default ng.module('shelving', ['salix']); diff --git a/modules/shelving/front/routes.json b/modules/shelving/front/routes.json new file mode 100644 index 000000000..b99ca4cac --- /dev/null +++ b/modules/shelving/front/routes.json @@ -0,0 +1,70 @@ +{ + "module": "shelving", + "name": "Shelvings", + "icon" : "contact_support", + "dependencies": ["worker"], + "validations" : true, + "menus": { + "main": [ + {"state": "shelving.index", "icon": "contact_support"} + ], + "card": [ + {"state": "shelving.card.basicData", "icon": "settings"}, + {"state": "shelving.card.log", "icon": "history"} + ] + }, + "keybindings": [ + {"key": "s", "state": "shelving.index"} + ], + "routes": [ + { + "url": "/shelving", + "state": "shelving", + "abstract": true, + "component": "vn-shelving", + "description": "Shelvings" + }, + { + "url": "/index?q", + "state": "shelving.index", + "component": "vn-shelving-index", + "description": "Shelvings" + }, + { + "url": "/create", + "state": "shelving.create", + "component": "vn-shelving-create", + "description": "New shelving" + }, + { + "url": "/:id", + "state": "shelving.card", + "abstract": true, + "component": "vn-shelving-card" + }, + { + "url": "/summary", + "state": "shelving.card.summary", + "component": "vn-shelving-summary", + "description": "Summary", + "params": { + "shelving": "$ctrl.shelving" + } + }, + { + "url": "/basic-data", + "state": "shelving.card.basicData", + "component": "vn-shelving-basic-data", + "description": "Basic data", + "params": { + "shelving": "$ctrl.shelving" + } + }, + { + "url" : "/log", + "state": "shelving.card.log", + "component": "vn-shelving-log", + "description": "Log" + } + ] +} \ No newline at end of file diff --git a/modules/shelving/front/search-panel/index.html b/modules/shelving/front/search-panel/index.html new file mode 100644 index 000000000..b7ca068a5 --- /dev/null +++ b/modules/shelving/front/search-panel/index.html @@ -0,0 +1,43 @@ +
+
+ + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/modules/shelving/front/search-panel/index.js b/modules/shelving/front/search-panel/index.js new file mode 100644 index 000000000..51b81538b --- /dev/null +++ b/modules/shelving/front/search-panel/index.js @@ -0,0 +1,7 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +ngModule.vnComponent('vnShelvingSearchPanel', { + template: require('./index.html'), + controller: SearchPanel +}); diff --git a/modules/shelving/front/search-panel/locale/es.yml b/modules/shelving/front/search-panel/locale/es.yml new file mode 100644 index 000000000..bd19b1b33 --- /dev/null +++ b/modules/shelving/front/search-panel/locale/es.yml @@ -0,0 +1 @@ +Search shelvings by code: Busca carros por código \ No newline at end of file diff --git a/modules/shelving/front/summary/index.html b/modules/shelving/front/summary/index.html new file mode 100644 index 000000000..61e44d278 --- /dev/null +++ b/modules/shelving/front/summary/index.html @@ -0,0 +1,51 @@ + +
+ + + + {{::$ctrl.summary.code}} +
+ + +

+ + Basic data + +

+ + + + + + + + + + {{$ctrl.summary.worker.user.nickname}} + + + + + +
+
+
+ + \ No newline at end of file diff --git a/modules/shelving/front/summary/index.js b/modules/shelving/front/summary/index.js new file mode 100644 index 000000000..10a905f1d --- /dev/null +++ b/modules/shelving/front/summary/index.js @@ -0,0 +1,41 @@ +import ngModule from '../module'; +import Summary from 'salix/components/summary'; +import './style.scss'; + +class Controller extends Summary { + set shelving(value) { + this._shelving = value; + this.summary = null; + if (!value) return; + + const filter = { + include: [ + {relation: 'worker', + scope: { + fields: ['userFk'], + include: { + relation: 'user', + scope: { + fields: ['nickname'] + } + } + }}, + {relation: 'parking'} + ] + }; + this.$http.get(`Shelvings/${value.id}`, {filter}) + .then(res => this.summary = res.data); + } + + get shelving() { + return this._shelving; + } +} + +ngModule.vnComponent('vnShelvingSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + shelving: '<' + } +}); diff --git a/modules/shelving/front/summary/locale/es.yml b/modules/shelving/front/summary/locale/es.yml new file mode 100644 index 000000000..d5d14d52a --- /dev/null +++ b/modules/shelving/front/summary/locale/es.yml @@ -0,0 +1,5 @@ +Code: Código +Parking: Parking +Priority: Prioridad +Worker: Trabajador +Recyclable: Reciclable \ No newline at end of file diff --git a/modules/shelving/front/summary/style.scss b/modules/shelving/front/summary/style.scss new file mode 100644 index 000000000..1eb6b2323 --- /dev/null +++ b/modules/shelving/front/summary/style.scss @@ -0,0 +1,7 @@ +@import "variables"; + +vn-client-summary { + .alert span { + color: $color-alert + } +} \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/getClaimableFromTicket.js b/modules/ticket/back/methods/sale/getClaimableFromTicket.js index 4e0549a4d..ecbc52b94 100644 --- a/modules/ticket/back/methods/sale/getClaimableFromTicket.js +++ b/modules/ticket/back/methods/sale/getClaimableFromTicket.js @@ -24,6 +24,8 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const date = new Date(); + date.setHours(0, 0, 0, 0); const query = ` SELECT s.id AS saleFk, @@ -39,11 +41,11 @@ module.exports = Self => { INNER JOIN vn.sale s ON s.ticketFk = t.id LEFT JOIN vn.claimBeginning cb ON cb.saleFk = s.id - WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, CURDATE()) + WHERE (t.landed) >= TIMESTAMPADD(DAY, -7, ?) AND t.id = ? AND cb.id IS NULL ORDER BY t.landed DESC, t.id DESC`; - const claimableSales = await Self.rawSql(query, [ticketFk], myOptions); + const claimableSales = await Self.rawSql(query, [date, ticketFk], myOptions); return claimableSales; }; diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js index 703fb4ba7..c0c431636 100644 --- a/modules/ticket/back/methods/sale/refund.js +++ b/modules/ticket/back/methods/sale/refund.js @@ -1,23 +1,20 @@ -const UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { - Self.remoteMethodCtx('refund', { - description: 'Create ticket refund with lines and services changing the sign to the quantites', + Self.remoteMethod('refund', { + description: 'Create refund tickets with sales and services if provided', accessType: 'WRITE', - accepts: [{ - arg: 'sales', - description: 'The sales', - type: ['object'], - required: false - }, - { - arg: 'services', - type: ['object'], - required: false, - description: 'The services' - }], + accepts: [ + { + arg: 'salesIds', + type: ['number'], + required: true + }, + { + arg: 'servicesIds', + type: ['number'] + }, + ], returns: { - type: 'number', + type: ['number'], root: true }, http: { @@ -26,7 +23,8 @@ module.exports = Self => { } }); - Self.refund = async(ctx, sales, services, options) => { + Self.refund = async(salesIds, servicesIds, options) => { + const models = Self.app.models; const myOptions = {}; let tx; @@ -39,56 +37,103 @@ module.exports = Self => { } try { - const userId = ctx.req.accessToken.userId; + const refundAgencyMode = await models.AgencyMode.findOne({ + include: { + relation: 'zones', + scope: { + limit: 1, + field: ['id', 'name'] + } + }, + where: {code: 'refund'} + }, myOptions); - const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager'); - const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant'); - const hasValidRole = isClaimManager || isSalesAssistant; + const refoundZoneId = refundAgencyMode.zones()[0].id; - if (!hasValidRole) - throw new UserError(`You don't have privileges to create refund`); + const salesFilter = { + where: {id: {inq: salesIds}}, + include: { + relation: 'components', + scope: { + fields: ['saleFk', 'componentFk', 'value'] + } + } + }; + const sales = await models.Sale.find(salesFilter, myOptions); + const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))]; - const salesIds = []; - if (sales) { - for (let sale of sales) - salesIds.push(sale.id); - } else - salesIds.push(null); + const refundTickets = []; - const servicesIds = []; - if (services && services.length) { - for (let service of services) - servicesIds.push(service.id); - } else - servicesIds.push(null); + const now = new Date(); + const mappedTickets = new Map(); - const query = ` - DROP TEMPORARY TABLE IF EXISTS tmp.sale; - DROP TEMPORARY TABLE IF EXISTS tmp.ticketService; + for (let ticketId of ticketsIds) { + const filter = {include: {relation: 'address'}}; + const ticket = await models.Ticket.findById(ticketId, filter, myOptions); - CREATE TEMPORARY TABLE tmp.sale - SELECT s.id, s.itemFk, s.quantity, s.concept, s.price, s.discount, s.ticketFk - FROM sale s - WHERE s.id IN (?); + const refundTicket = await models.Ticket.create({ + clientFk: ticket.clientFk, + shipped: now, + addressFk: ticket.address().id, + agencyModeFk: refundAgencyMode.id, + nickname: ticket.address().nickname, + warehouseFk: ticket.warehouseFk, + companyFk: ticket.companyFk, + landed: now, + zoneFk: refoundZoneId + }, myOptions); - CREATE TEMPORARY TABLE tmp.ticketService - SELECT ts.description, ts.quantity, ts.price, ts.taxClassFk, ts.ticketServiceTypeFk, ts.ticketFk - FROM ticketService ts - WHERE ts.id IN (?); + refundTickets.push(refundTicket); - CALL vn.ticket_doRefund(@newTicket); + mappedTickets.set(ticketId, refundTicket.id); - DROP TEMPORARY TABLE tmp.sale; - DROP TEMPORARY TABLE tmp.ticketService;`; + await models.TicketRefund.create({ + refundTicketFk: refundTicket.id, + originalTicketFk: ticket.id, + }, myOptions); + } - await Self.rawSql(query, [salesIds, servicesIds], myOptions); + for (const sale of sales) { + const refundTicketId = mappedTickets.get(sale.ticketFk); + const createdSale = await models.Sale.create({ + ticketFk: refundTicketId, + itemFk: sale.itemFk, + quantity: - sale.quantity, + concept: sale.concept, + price: sale.price, + discount: sale.discount, + }, myOptions); - const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions); - const newTicketId = newTicket.id; + const components = sale.components(); + for (const component of components) + component.saleFk = createdSale.id; + + await models.SaleComponent.create(components, myOptions); + } + + if (servicesIds && servicesIds.length > 0) { + const servicesFilter = { + where: {id: {inq: servicesIds}} + }; + const services = await models.TicketService.find(servicesFilter, myOptions); + + for (const service of services) { + const refundTicketId = mappedTickets.get(service.ticketFk); + + await models.TicketService.create({ + description: service.description, + quantity: - service.quantity, + price: service.price, + taxClassFk: service.taxClassFk, + ticketFk: refundTicketId, + ticketServiceTypeFk: service.ticketServiceTypeFk, + }, myOptions); + } + } if (tx) await tx.commit(); - return newTicketId; + return refundTickets; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/back/methods/sale/specs/refund.spec.js b/modules/ticket/back/methods/sale/specs/refund.spec.js index 5cb353055..74077cf29 100644 --- a/modules/ticket/back/methods/sale/specs/refund.spec.js +++ b/modules/ticket/back/methods/sale/specs/refund.spec.js @@ -1,23 +1,30 @@ const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('sale refund()', () => { - const sales = [ - {id: 7, ticketFk: 11}, - {id: 8, ticketFk: 11} - ]; - const services = [{id: 1}]; + const userId = 5; + const activeCtx = { + accessToken: {userId: userId}, + }; - it('should create ticket with the selected lines changing the sign to the quantites', async() => { + const servicesIds = [3]; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should create ticket with the selected lines', async() => { const tx = await models.Sale.beginTransaction({}); - const ctx = {req: {accessToken: {userId: 9}}}; + const salesIds = [7, 8]; try { const options = {transaction: tx}; - const response = await models.Sale.refund(ctx, sales, services, options); - const [newTicketId] = await models.Sale.rawSql('SELECT MAX(t.id) id FROM vn.ticket t;', null, options); + const response = await models.Sale.refund(salesIds, servicesIds, options); - expect(response).toEqual(newTicketId.id); + expect(response.length).toBeGreaterThanOrEqual(1); await tx.rollback(); } catch (e) { @@ -26,24 +33,53 @@ describe('sale refund()', () => { } }); - it(`should throw an error if the user doesn't have privileges to create a refund`, async() => { + it('should create a ticket for each unique ticketFk in the sales', async() => { const tx = await models.Sale.beginTransaction({}); - const ctx = {req: {accessToken: {userId: 1}}}; - - let error; + const salesIds = [6, 7]; try { const options = {transaction: tx}; - await models.Sale.refund(ctx, sales, services, options); + const tickets = await models.Sale.refund(salesIds, servicesIds, options); + + const ticketsIds = tickets.map(ticket => ticket.id); + + const refundedTickets = await models.Ticket.find({ + where: { + id: { + inq: ticketsIds + } + }, + include: [ + { + relation: 'ticketSales', + scope: { + include: { + relation: 'components' + } + } + }, + { + relation: 'ticketServices', + } + ] + }, options); + + const firstRefoundedTicket = refundedTickets[0]; + const secondRefoundedTicket = refundedTickets[1]; + const salesLength = firstRefoundedTicket.ticketSales().length; + const componentsLength = firstRefoundedTicket.ticketSales()[0].components().length; + const servicesLength = secondRefoundedTicket.ticketServices().length; + + expect(refundedTickets.length).toEqual(2); + expect(salesLength).toEqual(1); + expect(componentsLength).toEqual(4); + expect(servicesLength).toBeGreaterThanOrEqual(1); await tx.rollback(); } catch (e) { await tx.rollback(); - error = e; + throw e; } - - expect(error).toBeDefined(); - expect(error.message).toEqual(`You don't have privileges to create refund`); }); }); diff --git a/modules/ticket/back/methods/ticket/canHaveStowaway.js b/modules/ticket/back/methods/ticket/canHaveStowaway.js deleted file mode 100644 index 9246d0308..000000000 --- a/modules/ticket/back/methods/ticket/canHaveStowaway.js +++ /dev/null @@ -1,48 +0,0 @@ - -module.exports = Self => { - Self.remoteMethod('canHaveStowaway', { - description: 'Returns if a ticket can have stowaway', - accessType: 'READ', - accepts: [{ - arg: 'id', - type: 'number', - required: true, - description: 'ticket id', - http: {source: 'path'} - }], - returns: { - root: true - }, - http: { - path: `/:id/canHaveStowaway`, - verb: 'GET' - } - }); - - Self.canHaveStowaway = async(id, options) => { - const models = Self.app.models; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const ticket = await models.Ticket.findById(id, { - include: { - relation: 'ship', - scope: { - fields: ['id'] - } - } - }, myOptions); - - const warehouse = await models.Warehouse.findById(ticket.warehouseFk, null, myOptions); - - const hasStowaway = ticket.ship() ? true : false; - const validStowaway = warehouse && warehouse.hasStowaway && !hasStowaway; - - if (!ticket.isDeleted && validStowaway) - return true; - - return false; - }; -}; diff --git a/modules/ticket/back/methods/ticket/deleteStowaway.js b/modules/ticket/back/methods/ticket/deleteStowaway.js deleted file mode 100644 index c3e5e0db1..000000000 --- a/modules/ticket/back/methods/ticket/deleteStowaway.js +++ /dev/null @@ -1,105 +0,0 @@ - -module.exports = Self => { - Self.remoteMethodCtx('deleteStowaway', { - description: 'Deletes an stowaway', - accessType: 'WRITE', - accepts: [{ - arg: 'id', - type: 'number', - required: true, - description: 'The ticket id', - http: {source: 'path'} - }], - returns: { - root: true - }, - http: { - path: `/:id/deleteStowaway`, - verb: 'POST' - } - }); - - Self.deleteStowaway = async(ctx, id, options) => { - const models = Self.app.models; - const $t = ctx.req.__; // $translate - const myOptions = {}; - let tx; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } - - try { - const ticket = await Self.findById(id, { - include: [{ - relation: 'ship' - }, { - relation: 'stowaway' - }, { - relation: 'client', - scope: { - include: { - relation: 'salesPersonUser', - scope: { - fields: ['id', 'name'] - } - } - } - }] - }, myOptions); - - let stowawayFk; - let shipFk; - if (ticket.stowaway()) { - shipFk = ticket.stowaway().shipFk; - stowawayFk = ticket.stowaway().id; - } else if (ticket.ship()) { - shipFk = ticket.ship().shipFk; - stowawayFk = ticket.ship().id; - } - - const stowaway = await models.Stowaway.findOne({ - where: { - id: stowawayFk, - shipFk: shipFk - } - }, myOptions); - const result = await stowaway.destroy(myOptions); - - const state = await models.State.findOne({ - where: { - code: 'BOARDING' - } - }, myOptions); - const ticketTracking = await models.TicketTracking.findOne({ - where: { - ticketFk: shipFk, - stateFk: state.id - } - }, myOptions); - - await ticketTracking.destroy(myOptions); - - const salesPerson = ticket.client().salesPersonUser(); - if (salesPerson) { - const origin = ctx.req.headers.origin; - const message = $t('This ticket is not an stowaway anymore', { - ticketId: stowawayFk, - ticketUrl: `${origin}/#!/ticket/${stowawayFk}/sale` - }); - await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); - } - - if (tx) await tx.commit(); - - return result; - } catch (e) { - if (tx) await tx.rollback(); - throw e; - } - }; -}; diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index 902831d99..da8f65be2 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -132,6 +132,8 @@ module.exports = Self => { Self.filter = async(ctx, filter, options) => { const userId = ctx.req.accessToken.userId; const conn = Self.dataSource.connector; + const date = new Date(); + date.setHours(0, 0, 0, 0); const models = Self.app.models; const args = ctx.args; @@ -198,22 +200,7 @@ module.exports = Self => { return {'t.routeFk': {neq: null}}; return {'t.routeFk': null}; case 'pending': - if (value) { - return {and: [ - {'st.alertLevel': 0}, - {'st.code': {nin: [ - 'OK', - 'BOARDING', - 'PRINTED', - 'PRINTED_AUTO', - 'PICKER_DESIGNED' - ]}} - ]}; - } else { - return {and: [ - {'st.alertLevel': {gt: 0}} - ]}; - } + return {'st.isNotValidated': value}; case 'id': case 'clientFk': case 'agencyModeFk': @@ -297,7 +284,8 @@ module.exports = Self => { stmts.push(stmt); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); - stmts.push(` + + stmt = new ParameterizedSQL(` CREATE TEMPORARY TABLE tmp.sale_getProblems (INDEX (ticketFk)) ENGINE = MEMORY @@ -305,7 +293,9 @@ module.exports = Self => { FROM tmp.filter f LEFT JOIN alertLevel al ON al.id = f.alertLevel WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) - AND f.shipped >= CURDATE()`); + AND f.shipped >= ?`, [date]); + stmts.push(stmt); + stmts.push('CALL ticket_getProblems(FALSE)'); stmt = new ParameterizedSQL(` diff --git a/modules/ticket/back/methods/ticket/getPossibleStowaways.js b/modules/ticket/back/methods/ticket/getPossibleStowaways.js deleted file mode 100644 index c97e3de89..000000000 --- a/modules/ticket/back/methods/ticket/getPossibleStowaways.js +++ /dev/null @@ -1,74 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethod('getPossibleStowaways', { - description: 'Returns a list of allowed tickets for a stowaway ticket', - accessType: 'READ', - accepts: [{ - arg: 'id', - type: 'number', - required: true, - description: 'ticket id', - http: {source: 'path'} - }], - returns: { - root: true - }, - http: { - path: `/:id/getPossibleStowaways`, - verb: 'GET' - } - }); - - Self.getPossibleStowaways = async(ticketFk, options) => { - const models = Self.app.models; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const canHaveStowaway = await models.Ticket.canHaveStowaway(ticketFk, myOptions); - - if (!canHaveStowaway) - throw new UserError(`Can't create stowaway for this ticket`); - - const ship = await models.Ticket.findById(ticketFk, null, myOptions); - - if (!ship || !ship.shipped) - return []; - - const lowestDate = new Date(ship.shipped.getTime()); - lowestDate.setHours(0, 0, -1, 0); - - const highestDate = new Date(ship.shipped.getTime()); - highestDate.setHours(23, 59, 59); - - const possibleStowaways = await models.Ticket.find({ - where: { - id: {neq: ticketFk}, - clientFk: ship.clientFk, - addressFk: ship.addressFk, - agencyModeFk: ship.agencyModeFk, - warehouseFk: {neq: ship.warehouseFk}, - shipped: { - between: [lowestDate.toJSON(), highestDate.toJSON()] - } - }, - include: [ - {relation: 'agencyMode'}, - {relation: 'warehouse'}, - {relation: 'ticketState', - scope: { - fields: ['stateFk'], - include: { - relation: 'state', - fields: ['id', 'name'], - } - }, - }, - ] - }, myOptions); - - return possibleStowaways; - }; -}; diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index befc75ffc..4e9c22a24 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -26,6 +26,8 @@ module.exports = function(Self) { Self.makeInvoice = async(ctx, ticketsIds, options) => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; + const date = new Date(); + date.setHours(0, 0, 0, 0); const myOptions = {}; let tx; @@ -81,7 +83,7 @@ module.exports = function(Self) { WHERE id IN(?) AND refFk IS NULL `, [ticketsIds], myOptions); - await Self.rawSql('CALL invoiceOut_new(?, CURDATE(), null, @invoiceId)', [serial], myOptions); + await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, date], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); diff --git a/modules/ticket/back/methods/ticket/refund.js b/modules/ticket/back/methods/ticket/refund.js new file mode 100644 index 000000000..620c8b0c7 --- /dev/null +++ b/modules/ticket/back/methods/ticket/refund.js @@ -0,0 +1,54 @@ +module.exports = Self => { + Self.remoteMethod('refund', { + description: 'Create refund tickets with all their sales and services', + accessType: 'WRITE', + accepts: [ + { + arg: 'ticketsIds', + type: ['number'], + required: true + }, + ], + returns: { + type: ['number'], + root: true + }, + http: { + path: `/refund`, + verb: 'post' + } + }); + + Self.refund = async(ticketsIds, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const filter = {where: {ticketFk: {inq: ticketsIds}}}; + + const sales = await models.Sale.find(filter, myOptions); + const salesIds = sales.map(sale => sale.id); + + const services = await models.TicketService.find(filter, myOptions); + const servicesIds = services.map(service => service.id); + + const refundedTickets = await models.Sale.refund(salesIds, servicesIds, myOptions); + + if (tx) await tx.commit(); + + return refundedTickets; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 38bd6e7b5..cec8096a6 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -107,28 +107,9 @@ module.exports = Self => { } } } - }, { - relation: 'ship' - }, { - relation: 'stowaway' }] }, myOptions); - // Change state to "fixing" if contains an stowaway and remove the link between them - let otherTicketId; - if (ticket.stowaway()) - otherTicketId = ticket.stowaway().shipFk; - else if (ticket.ship()) - otherTicketId = ticket.ship().id; - - if (otherTicketId) { - await models.Ticket.deleteStowaway(ctx, otherTicketId, myOptions); - await models.TicketTracking.changeState(ctx, { - ticketFk: otherTicketId, - code: 'FIXING' - }, myOptions); - } - // Send notification to salesPerson const salesPersonUser = ticket.client().salesPersonUser(); if (salesPersonUser) { @@ -142,6 +123,25 @@ module.exports = Self => { const updatedTicket = await ticket.updateAttribute('isDeleted', true, myOptions); + const [ticketCollection] = await models.TicketCollection.find({ + fields: ['id'], + where: { + ticketFk: ticket.id + } + }); + + if (ticketCollection) + await models.TicketCollection.destroyById(ticketCollection.id, myOptions); + + await Self.rawSql(` + DELETE sc + FROM vn.saleGroup sg + JOIN vn.sectorCollectionSaleGroup scsg ON scsg.saleGroupFk = sg.id + JOIN vn.sectorCollection sc ON sc.id = scsg.sectorCollectionFk + JOIN vn.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id + JOIN vn.sale s ON s.id = sgd.saleFk + WHERE s.ticketFk = ?;`, [ticket.id], myOptions); + if (tx) await tx.commit(); return updatedTicket; diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 43f5b93df..8b7e1685d 100644 --- a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -56,7 +56,6 @@ describe('ticket canBeInvoiced()', () => { it('should return falsy for a ticket shipping in future', async() => { const tx = await models.Ticket.beginTransaction({}); - try { const options = {transaction: tx}; diff --git a/modules/ticket/back/methods/ticket/specs/canHaveStowaway.spec.js b/modules/ticket/back/methods/ticket/specs/canHaveStowaway.spec.js deleted file mode 100644 index 2bd25c2d2..000000000 --- a/modules/ticket/back/methods/ticket/specs/canHaveStowaway.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -const models = require('vn-loopback/server/server').models; - -describe('ticket canHaveStowaway()', () => { - it('should return true if the ticket warehouse have hasStowaway equal 1', async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const ticketId = 16; - const canStowaway = await models.Ticket.canHaveStowaway(ticketId, options); - - expect(canStowaway).toBeTruthy(); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return false if the ticket warehouse dont have hasStowaway equal 0', async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const ticketId = 10; - const canStowaway = await models.Ticket.canHaveStowaway(ticketId, options); - - expect(canStowaway).toBeFalsy(); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/ticket/back/methods/ticket/specs/deleteStowaway.spec.js b/modules/ticket/back/methods/ticket/specs/deleteStowaway.spec.js deleted file mode 100644 index 62bfc71be..000000000 --- a/modules/ticket/back/methods/ticket/specs/deleteStowaway.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -const models = require('vn-loopback/server/server').models; - -describe('ticket deleteStowaway()', () => { - const shipId = 16; - const stowawayId = 17; - const ctx = { - req: { - accessToken: {userId: 18}, - headers: {origin: 'http://localhost'} - } - }; - ctx.req.__ = (value, params) => { - return params.nickname; - }; - - it(`should create an stowaway, delete it and see the states of both stowaway and ship go back to the last states`, async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - await models.Stowaway.rawSql(` - INSERT INTO stowaway (id, shipFk) VALUES (?, ?) - `, [stowawayId, shipId], options); - await models.Stowaway.rawSql( - `CALL ticketStateUpdate(?, ?)`, [shipId, 'BOARDING'], options); - await models.Stowaway.rawSql( - `CALL ticketStateUpdate(?, ?)`, [stowawayId, 'BOARDING'], options); - - let createdStowaways = await models.Stowaway.count({id: stowawayId, shipFk: shipId}, options); - - expect(createdStowaways).toEqual(1); - - let shipState = await models.TicketLastState.findOne({ - where: { - ticketFk: shipId - } - }, options); - let stowawayState = await models.TicketLastState.findOne({ - where: { - ticketFk: stowawayId - } - }, options); - - expect(shipState.name).toEqual('Embarcando'); - expect(stowawayState.name).toEqual('Embarcando'); - - await models.Ticket.deleteStowaway(ctx, shipId, options); - await models.Ticket.deleteStowaway(ctx, stowawayId, options); - - createdStowaways = await models.Stowaway.count({id: stowawayId, shipFk: shipId}, options); - - expect(createdStowaways).toEqual(0); - - shipState = await models.TicketLastState.findOne({ - where: { - ticketFk: shipId - } - }, options); - stowawayState = await models.TicketLastState.findOne({ - where: { - ticketFk: stowawayId - } - }, options); - - expect(shipState.name).toEqual('OK'); - expect(stowawayState.name).toEqual('Libre'); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/ticket/back/methods/ticket/specs/filter.spec.js b/modules/ticket/back/methods/ticket/specs/filter.spec.js index 4b583fc87..c3dc40092 100644 --- a/modules/ticket/back/methods/ticket/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket/specs/filter.spec.js @@ -153,7 +153,7 @@ describe('ticket filter()', () => { const secondRow = result[1]; const thirdRow = result[2]; - expect(result.length).toEqual(12); + expect(result.length).toEqual(17); expect(firstRow.state).toEqual('Entregado'); expect(secondRow.state).toEqual('Entregado'); expect(thirdRow.state).toEqual('Entregado'); @@ -213,7 +213,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(2); + expect(result.length).toEqual(3); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket/specs/getPossibleStowaways.spec.js b/modules/ticket/back/methods/ticket/specs/getPossibleStowaways.spec.js deleted file mode 100644 index ed1a327d4..000000000 --- a/modules/ticket/back/methods/ticket/specs/getPossibleStowaways.spec.js +++ /dev/null @@ -1,60 +0,0 @@ -const models = require('vn-loopback/server/server').models; -let UserError = require('vn-loopback/util/user-error'); - -describe('ticket getPossibleStowaways()', () => { - it(`should throw an error if Can't create stowaway for this ticket`, async() => { - const tx = await models.Ticket.beginTransaction({}); - - let error; - - try { - const options = {transaction: tx}; - - const ticketId = 10; - await models.Ticket.getPossibleStowaways(ticketId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).toEqual(new UserError(`Can't create stowaway for this ticket`)); - }); - - it('should return an empty list of tickets for a valid ticket', async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const ticketId = 12; - const possibleStowaways = await models.Ticket.getPossibleStowaways(ticketId, options); - - expect(possibleStowaways.length).toEqual(0); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should return allowed list of tickets for a valid ticket', async() => { - const tx = await models.Ticket.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const ticketId = 16; - const possibleStowaways = await models.Ticket.getPossibleStowaways(ticketId, options); - - expect(possibleStowaways.length).toEqual(1); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js index 9b629e634..132b1938d 100644 --- a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js +++ b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js @@ -8,6 +8,12 @@ describe('ticket setDeleted()', () => { accessToken: {userId: userId}, }; + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + it('should throw an error if the given ticket has a claim', async() => { const tx = await models.Ticket.beginTransaction({}); @@ -29,86 +35,62 @@ describe('ticket setDeleted()', () => { expect(error.message).toEqual('You must delete the claim id %d first'); }); - it('should delete ticket, remove stowaway and itemshelving then change stowaway state to "FIXING" ', async() => { - pending('test excluded by task #3693'); + it('should delete a sectorCollection row', async() => { const tx = await models.Ticket.beginTransaction({}); try { const options = {transaction: tx}; - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); const ctx = { req: { - accessToken: {userId: employeeUser}, - headers: { - origin: 'http://localhost:5000' - }, - __: () => {} + accessToken: {userId: 9}, + headers: {origin: 'http://localhost:5000'}, } }; + ctx.req.__ = value => { + return value; + }; + const ticketId = 23; - const sampleTicket = await models.Ticket.findById(12); - const sampleStowaway = await models.Ticket.findById(13); + await models.Ticket.setDeleted(ctx, ticketId, options); - sampleTicket.id = undefined; - const shipTicket = await models.Ticket.create(sampleTicket, options); + const [sectorCollection] = await models.Ticket.rawSql( + `SELECT COUNT(*) numberRows + FROM vn.sectorCollection`, [], options); - sampleStowaway.id = undefined; - const stowawayTicket = await models.Ticket.create(sampleStowaway, options); + expect(sectorCollection.numberRows).toEqual(0); - await models.Stowaway.rawSql(` - INSERT INTO vn.stowaway(id, shipFk) - VALUES (?, ?)`, [stowawayTicket.id, shipTicket.id], options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); - const boardingState = await models.State.findOne({ - where: { - code: 'BOARDING' + it('should delete a ticketCollection row', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = { + req: { + accessToken: {userId: 9}, + headers: {origin: 'http://localhost:5000'}, } - }, options); + }; + ctx.req.__ = value => { + return value; + }; + const ticketId = 23; - await models.TicketTracking.create({ - ticketFk: stowawayTicket.id, - stateFk: boardingState.id, - workerFk: ctx.req.accessToken.userId - }, options); + await models.Ticket.setDeleted(ctx, ticketId, options); - const okState = await models.State.findOne({ - where: { - code: 'OK' - } - }, options); + const [ticketCollection] = await models.Ticket.rawSql( + `SELECT COUNT(*) numberRows + FROM vn.ticketCollection`, [], options); - await models.TicketTracking.create({ - ticketFk: shipTicket.id, - stateFk: okState.id, - workerFk: ctx.req.accessToken.userId - }, options); - - let stowawayTicketState = await models.TicketState.findOne({ - where: { - ticketFk: stowawayTicket.id - } - }, options); - - let stowaway = await models.Stowaway.findById(shipTicket.id, null, options); - - expect(stowaway).toBeDefined(); - expect(stowawayTicketState.code).toEqual('BOARDING'); - - await models.Ticket.setDeleted(ctx, shipTicket.id, options); - - stowawayTicketState = await models.TicketState.findOne({ - where: { - ticketFk: stowawayTicket.id - } - }, options); - - stowaway = await models.Stowaway.findById(shipTicket.id, null, options); - - expect(stowaway).toBeNull(); - expect(stowawayTicketState.code).toEqual('FIXING'); + expect(ticketCollection.numberRows).toEqual(3); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 41885ee33..8a6ac0c00 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -38,10 +38,10 @@ "State":{ "dataSource": "vn" }, - "Stowaway": { + "Ticket": { "dataSource": "vn" }, - "Ticket": { + "TicketCollection": { "dataSource": "vn" }, "TicketDms": { @@ -56,6 +56,9 @@ "TicketPackaging": { "dataSource": "vn" }, + "TicketRefund": { + "dataSource": "vn" + }, "TicketRequest": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/stowaway.js b/modules/ticket/back/models/stowaway.js deleted file mode 100644 index 780b6b31c..000000000 --- a/modules/ticket/back/models/stowaway.js +++ /dev/null @@ -1,24 +0,0 @@ -const LoopBackContext = require('loopback-context'); -const UserError = require('vn-loopback/util/user-error'); - -module.exports = function(Self) { - Self.observe('before save', async function(ctx) { - const models = Self.app.models; - const canHaveStowaway = await models.Ticket.canHaveStowaway(ctx.instance.shipFk); - - if (!canHaveStowaway) - throw new UserError(`Can't create stowaway for this ticket`); - - if (ctx.isNewInstance) { - let where = { - code: 'BOARDING' - }; - let state = await models.State.findOne({where}); - let params = {ticketFk: ctx.instance.shipFk, stateFk: state.id}; - const loopBackContext = LoopBackContext.getCurrentContext(); - - let httpCtx = {req: loopBackContext.active}; - await models.TicketTracking.changeState(httpCtx, params); - } - }); -}; diff --git a/modules/ticket/back/models/stowaway.json b/modules/ticket/back/models/stowaway.json deleted file mode 100644 index ef3aeb40d..000000000 --- a/modules/ticket/back/models/stowaway.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "Stowaway", - "base": "VnModel", - "options": { - "mysql": { - "table": "stowaway" - } - }, - "properties": { - "id": { - "id": true, - "type": "number", - "forceId": false - }, - "shipFk": { - "type": "number", - "required": false - }, - "created":{ - "type": "date", - "required": false - } - }, - "relations": { - "ship": { - "type": "belongsTo", - "model": "Ticket", - "foreignKey": "shipFk" - }, - "ticket": { - "type": "belongsTo", - "model": "Ticket", - "foreignKey": "id" - } - } -} diff --git a/modules/ticket/back/models/ticket-collection.json b/modules/ticket/back/models/ticket-collection.json new file mode 100644 index 000000000..e941ac2ce --- /dev/null +++ b/modules/ticket/back/models/ticket-collection.json @@ -0,0 +1,22 @@ +{ + "name": "TicketCollection", + "base": "VnModel", + "options": { + "mysql": { + "table": "ticketCollection" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + } + }, + "relations": { + "ticket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "ticketFk" + } + } +} \ No newline at end of file diff --git a/modules/ticket/back/models/ticket-refund.json b/modules/ticket/back/models/ticket-refund.json new file mode 100644 index 000000000..8fd0e2306 --- /dev/null +++ b/modules/ticket/back/models/ticket-refund.json @@ -0,0 +1,40 @@ +{ + "name": "TicketRefund", + "base": "Loggable", + "options": { + "mysql": { + "table": "ticketRefund" + } + }, + "log": { + "model": "TicketLog", + "relation": "originalTicket" + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "refundTicketFk": { + "type": "number", + "required": true + }, + "originalTicketFk": { + "type": "number", + "required": true + } + }, + "relations": { + "refundTicket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "refundTicketFk" + }, + "originalTicket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "originalTicketFk" + } + } +} diff --git a/modules/ticket/back/models/ticket-service.js b/modules/ticket/back/models/ticket-service.js index aa94c42e3..209727ee4 100644 --- a/modules/ticket/back/models/ticket-service.js +++ b/modules/ticket/back/models/ticket-service.js @@ -3,17 +3,18 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.observe('before save', async ctx => { const models = Self.app.models; - let changes = ctx.currentInstance || ctx.instance; - if (changes) { - let ticketId = changes.ticketFk; - let isLocked = await models.Ticket.isLocked(ticketId); + const changes = ctx.currentInstance || ctx.instance; + + if (changes && !ctx.isNewInstance) { + const ticketId = changes.ticketFk; + const isLocked = await models.Ticket.isLocked(ticketId); if (isLocked) throw new UserError(`The current ticket can't be modified`); + } - if (changes.ticketServiceTypeFk) { - const ticketServiceType = await models.TicketServiceType.findById(changes.ticketServiceTypeFk); - changes.description = ticketServiceType.name; - } + if (changes && changes.ticketServiceTypeFk) { + const ticketServiceType = await models.TicketServiceType.findById(changes.ticketServiceTypeFk); + changes.description = ticketServiceType.name; } }); diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index b217eae4d..47d105824 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -14,7 +14,6 @@ module.exports = Self => { require('../methods/ticket/getSales')(Self); require('../methods/ticket/getSalesPersonMana')(Self); require('../methods/ticket/filter')(Self); - require('../methods/ticket/getPossibleStowaways')(Self); require('../methods/ticket/canBeInvoiced')(Self); require('../methods/ticket/makeInvoice')(Self); require('../methods/ticket/updateEditableTicket')(Self); @@ -23,13 +22,12 @@ module.exports = Self => { require('../methods/ticket/uploadFile')(Self); require('../methods/ticket/addSale')(Self); require('../methods/ticket/transferSales')(Self); - require('../methods/ticket/canHaveStowaway')(Self); require('../methods/ticket/recalculateComponents')(Self); - require('../methods/ticket/deleteStowaway')(Self); require('../methods/ticket/sendSms')(Self); require('../methods/ticket/isLocked')(Self); require('../methods/ticket/freightCost')(Self); require('../methods/ticket/getComponentsSum')(Self); + require('../methods/ticket/refund')(Self); Self.observe('before save', async function(ctx) { const loopBackContext = LoopBackContext.getCurrentContext(); diff --git a/modules/ticket/back/models/ticket.json b/modules/ticket/back/models/ticket.json index 65127a78c..09b01d213 100644 --- a/modules/ticket/back/models/ticket.json +++ b/modules/ticket/back/models/ticket.json @@ -69,16 +69,6 @@ "model": "Client", "foreignKey": "clientFk" }, - "ship": { - "type": "hasOne", - "model": "Stowaway", - "foreignKey": "shipFk" - }, - "stowaway": { - "type": "hasOne", - "model": "Stowaway", - "foreignKey": "id" - }, "warehouse": { "type": "belongsTo", "model": "Warehouse", @@ -115,6 +105,16 @@ "model": "TicketPackaging", "foreignKey": "ticketFk" }, + "ticketSales": { + "type": "hasMany", + "model": "Sale", + "foreignKey": "ticketFk" + }, + "ticketServices": { + "type": "hasMany", + "model": "TicketService", + "foreignKey": "ticketFk" + }, "tracking": { "type": "hasMany", "model": "TicketTracking", diff --git a/modules/ticket/front/card/index.js b/modules/ticket/front/card/index.js index 6e38039e2..e63fb9b15 100644 --- a/modules/ticket/front/card/index.js +++ b/modules/ticket/front/card/index.js @@ -6,18 +6,15 @@ class Controller extends ModuleCard { let filter = { include: [ { - relation: 'address'}, + relation: 'address' + }, { - relation: 'ship'}, - { - relation: 'stowaway'}, + relation: 'zone' + }, { relation: 'warehouse', scope: {fields: ['name']} }, - { - relation: 'zone', - }, { relation: 'invoiceOut', scope: {fields: ['id']} @@ -47,7 +44,8 @@ class Controller extends ModuleCard { } }, }, - }, { + }, + { relation: 'ticketState', scope: { fields: ['stateFk'], diff --git a/modules/ticket/front/create/card.html b/modules/ticket/front/create/card.html index 65c45d3dc..e48aea681 100644 --- a/modules/ticket/front/create/card.html +++ b/modules/ticket/front/create/card.html @@ -19,6 +19,7 @@ disabled="!$ctrl.clientId" url="{{ $ctrl.clientId ? 'Clients/'+ $ctrl.clientId +'/addresses' : null }}" fields="['nickname', 'street', 'city']" + where="{isActive: true}" ng-model="$ctrl.addressId" show-field="nickname" value-field="id" diff --git a/modules/ticket/front/descriptor-menu/index.html b/modules/ticket/front/descriptor-menu/index.html index 1dcfd669f..ea84743bc 100644 --- a/modules/ticket/front/descriptor-menu/index.html +++ b/modules/ticket/front/descriptor-menu/index.html @@ -103,20 +103,6 @@ translate> SMS Minimum import - - Add stowaway - - - Delete stowaway - Refund all @@ -260,21 +248,6 @@ sms="$ctrl.newSMS"> - - - - - - - - this.ticket = res.data) .then(() => { - this.canStowaway(); this.isTicketEditable(); this.hasDocuware(); }); @@ -228,27 +228,6 @@ class Controller extends Section { this.$.sms.open(); } - canStowaway() { - this.canShowStowaway = false; - if (!this.isTicketModule || !this.ticket) return; - - this.$http.get(`Tickets/${this.id}/canHaveStowaway`) - .then(res => this.canShowStowaway = !!res.data); - } - - get canDeleteStowaway() { - if (!this.ticket || !this.isTicketModule) - return false; - - return this.ticket.stowaway || this.ticket.ship; - } - - deleteStowaway() { - return this.$http.post(`Tickets/${this.id}/deleteStowaway`) - .then(() => this.reload()) - .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); - } - makeInvoice() { const params = {ticketsIds: [this.id]}; return this.$http.post(`Tickets/makeInvoice`, params) @@ -274,22 +253,14 @@ class Controller extends Section { } async refund() { - const filter = { - where: {ticketFk: this.id} - }; - const sales = await this.$http.get('Sales', {filter}); - this.sales = sales.data; - - const ticketServices = await this.$http.get('TicketServices', {filter}); - this.services = ticketServices.data; - - const params = { - sales: this.sales, - services: this.services - }; - const query = `Sales/refund`; + const params = {ticketsIds: [this.id]}; + const query = 'Tickets/refund'; return this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + const [refundTicket] = res.data; + this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { + ticketId: refundTicket.id + })); + this.$state.go('ticket.card.sale', {id: refundTicket.id}); }); } } diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index af377d8ea..2d08a7846 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -231,23 +231,6 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); - describe('canStowaway()', () => { - it('should make a query and return if the ticket can be stowawayed', () => { - $httpBackend.expect('GET', `Tickets/${ticket.id}/canHaveStowaway`).respond(true); - controller.canStowaway(); - $httpBackend.flush(); - - expect(controller.canShowStowaway).toBeTruthy(); - }); - - it('should not make a query if is not on the ticket module', () => { - $state.getCurrentPath = () => [null, {state: {name: 'client'}}]; - controller.canStowaway(); - - expect(controller.canShowStowaway).toBeFalsy(); - }); - }); - describe('recalculateComponents()', () => { it('should make a query and show a success message', () => { jest.spyOn(controller, 'reload').mockReturnThis(); @@ -262,27 +245,20 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { }); }); - // #4084 review with Juan - xdescribe('refund()', () => { + describe('refund()', () => { it('should make a query and go to ticket.card.sale', () => { controller.$state.go = jest.fn(); controller._id = ticket.id; - const sales = [{id: 1}]; - const services = [{id: 2}]; - $httpBackend.expectGET(`Sales`).respond(sales); - $httpBackend.expectGET(`TicketServices`).respond(services); - - const expectedParams = { - sales: sales, - services: services + const params = { + ticketsIds: [16] }; - $httpBackend.expectPOST(`Sales/refund`, expectedParams).respond(); + $httpBackend.expectPOST('Tickets/refund', params).respond([{id: 99}]); controller.refund(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: undefined}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99}); }); }); diff --git a/modules/ticket/front/descriptor-menu/locale/en.yml b/modules/ticket/front/descriptor-menu/locale/en.yml new file mode 100644 index 000000000..03f93db8d --- /dev/null +++ b/modules/ticket/front/descriptor-menu/locale/en.yml @@ -0,0 +1 @@ +The following refund ticket have been created: "The following refund ticket have been created: {{ticketId}}" \ No newline at end of file diff --git a/modules/ticket/front/descriptor-menu/locale/es.yml b/modules/ticket/front/descriptor-menu/locale/es.yml index 060d03154..b65159a3c 100644 --- a/modules/ticket/front/descriptor-menu/locale/es.yml +++ b/modules/ticket/front/descriptor-menu/locale/es.yml @@ -8,4 +8,5 @@ Send CSV: Enviar CSV Send CSV Delivery Note: Enviar albarán en CSV Send PDF Delivery Note: Enviar albarán en PDF Show Proforma: Ver proforma -Refund all: Abonar todo \ No newline at end of file +Refund all: Abonar todo +The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" \ No newline at end of file diff --git a/modules/ticket/front/descriptor/addStowaway.html b/modules/ticket/front/descriptor/addStowaway.html deleted file mode 100644 index 44d262d51..000000000 --- a/modules/ticket/front/descriptor/addStowaway.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - Ticket id - Shipped - Agency - Warehouse - State - - - - - {{ticket.id}} - {{ticket.landed | date: 'dd/MM/yyyy'}} - {{ticket.agencyMode.name}} - {{ticket.warehouse.name}} - {{ticket.ticketState.state.name}} - - - - - - - \ No newline at end of file diff --git a/modules/ticket/front/descriptor/addStowaway.js b/modules/ticket/front/descriptor/addStowaway.js deleted file mode 100644 index c88bda0af..000000000 --- a/modules/ticket/front/descriptor/addStowaway.js +++ /dev/null @@ -1,32 +0,0 @@ -import ngModule from '../module'; -import Component from 'core/lib/component'; -import './style.scss'; - -class Controller extends Component { - addStowaway(stowaway) { - let params = {id: stowaway.id, shipFk: this.ticket.id}; - this.$http.post(`Stowaways/`, params) - .then(() => this.cardReload()) - .then(() => { - this.vnApp.showSuccess(this.$t('Data saved!')); - this.$.dialog.hide(); - }); - } - - show() { - this.$.dialog.show(); - } - - hide() { - this.$.dialog.hide(); - } -} - -ngModule.vnComponent('vnAddStowaway', { - template: require('./addStowaway.html'), - controller: Controller, - bindings: { - ticket: '<', - cardReload: '&?' - } -}); diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html index 2c27b19cd..75bcd2801 100644 --- a/modules/ticket/front/descriptor/index.html +++ b/modules/ticket/front/descriptor/index.html @@ -93,36 +93,6 @@ icon="icon-basketadd">

-
- - -
-
- - -
-
-
- -
diff --git a/modules/ticket/front/descriptor/locale/es.yml b/modules/ticket/front/descriptor/locale/es.yml index 2a7bf360f..8ab88ce09 100644 --- a/modules/ticket/front/descriptor/locale/es.yml +++ b/modules/ticket/front/descriptor/locale/es.yml @@ -1,12 +1,4 @@ -Ship: Barco -Stowaway: Polizón Cliente: Client -Ship stowaways: Polizones del barco -Stowaways to add: Polizones a añadir -Stowaways of the ticket: Polizones del ticket -Add stowaway: Añadir polizón -Delete stowaway: Eliminar polizón -Are you sure you want to delete this stowaway?: ¿Seguro que quieres eliminar este polizón? Are you sure you want to send it?: ¿Seguro que quieres enviarlo? Show pallet report: Ver hoja de pallet Change shipped hour: Cambiar hora de envío diff --git a/modules/ticket/front/descriptor/style.scss b/modules/ticket/front/descriptor/style.scss index 1a69feeff..f083f41fc 100644 --- a/modules/ticket/front/descriptor/style.scss +++ b/modules/ticket/front/descriptor/style.scss @@ -1,11 +1,5 @@ @import "variables"; -.add-stowaway { - vn-data-viewer { - width: 640px - } -} - vn-dialog.modal-form { section.header { background-color: $color-main; diff --git a/modules/ticket/front/index.js b/modules/ticket/front/index.js index 01d1d7242..85a03ffb6 100644 --- a/modules/ticket/front/index.js +++ b/modules/ticket/front/index.js @@ -5,7 +5,6 @@ import './index/'; import './search-panel'; import './card'; import './descriptor'; -import './descriptor/addStowaway'; import './descriptor-popover'; import './descriptor-menu'; import './create/card'; diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index f4e5840f3..42eb10cb0 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -514,7 +514,7 @@ Refund diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 987333e28..2724bb97d 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -483,11 +483,18 @@ class Controller extends Section { const sales = this.selectedValidSales(); if (!sales) return; - const params = {sales: sales}; - const query = `Sales/refund`; - this.resetChanges(); + const salesIds = sales.map(sale => sale.id); + + const params = {salesIds: salesIds}; + const query = 'Sales/refund'; this.$http.post(query, params).then(res => { - this.$state.go('ticket.card.sale', {id: res.data}); + const [refundTicket] = res.data; + this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { + ticketId: refundTicket.id + })); + this.$state.go('ticket.card.sale', {id: refundTicket.id}); + + this.resetChanges(); }); } diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index a8ac2f3de..28d874932 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -704,18 +704,19 @@ describe('Ticket', () => { }); describe('createRefund()', () => { - it('should make an HTTP POST query and then call to the $state go() method', () => { + it('should make a query and then navigate to the created ticket sales section', () => { jest.spyOn(controller, 'selectedValidSales').mockReturnValue(controller.sales); - jest.spyOn(controller, 'resetChanges'); jest.spyOn(controller.$state, 'go'); - - const expectedId = 9999; - $httpBackend.expect('POST', `Sales/refund`).respond(200, expectedId); + const params = { + salesIds: [1, 4], + }; + const refundTicket = {id: 99}; + const createdTickets = [refundTicket]; + $httpBackend.expect('POST', 'Sales/refund', params).respond(200, createdTickets); controller.createRefund(); $httpBackend.flush(); - expect(controller.resetChanges).toHaveBeenCalledWith(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: expectedId}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', refundTicket); }); }); diff --git a/modules/travel/back/methods/thermograph/createThermograph.js b/modules/travel/back/methods/thermograph/createThermograph.js index 137f32ac4..23d9d6037 100644 --- a/modules/travel/back/methods/thermograph/createThermograph.js +++ b/modules/travel/back/methods/thermograph/createThermograph.js @@ -40,6 +40,7 @@ module.exports = Self => { const models = Self.app.models; let tx; const myOptions = {}; + const date = new Date(); if (typeof options == 'object') Object.assign(myOptions, options); @@ -57,8 +58,8 @@ module.exports = Self => { await Self.rawSql(` INSERT INTO travelThermograph(thermographFk, warehouseFk, temperatureFk, created) - VALUES (?, ?, ?, NOW()) - `, [thermograph.id, warehouseId, temperatureFk], myOptions); + VALUES (?, ?, ?, ?) + `, [thermograph.id, warehouseId, temperatureFk, date], myOptions); if (tx) await tx.commit(); diff --git a/modules/travel/back/methods/travel/cloneWithEntries.js b/modules/travel/back/methods/travel/cloneWithEntries.js index 393194f06..611f4e429 100644 --- a/modules/travel/back/methods/travel/cloneWithEntries.js +++ b/modules/travel/back/methods/travel/cloneWithEntries.js @@ -35,7 +35,7 @@ module.exports = Self => { 'landed', 'warehouseInFk', 'warehouseOutFk', - 'agencyFk', + 'agencyModeFk', 'ref' ] }); @@ -56,7 +56,7 @@ module.exports = Self => { travel.warehouseOutFk, travel.warehouseInFk, travel.ref, - travel.agencyFk + travel.agencyModeFk ] ); stmts.push(stmt); @@ -76,7 +76,7 @@ module.exports = Self => { 'landed', 'warehouseInFk', 'warehouseOutFk', - 'agencyFk', + 'agencyModeFk', 'ref' ] }); diff --git a/modules/travel/back/methods/travel/extraCommunityFilter.js b/modules/travel/back/methods/travel/extraCommunityFilter.js index af6e1ec0a..7769b7f21 100644 --- a/modules/travel/back/methods/travel/extraCommunityFilter.js +++ b/modules/travel/back/methods/travel/extraCommunityFilter.js @@ -34,7 +34,7 @@ module.exports = Self => { description: 'The landed to date filter' }, { - arg: 'agencyFk', + arg: 'agencyModeFk', type: 'number', description: 'The agencyModeFk id' }, @@ -96,7 +96,7 @@ module.exports = Self => { case 'continent': return {'cnt.code': value}; case 'id': - case 'agencyFk': + case 'agencyModeFk': case 'warehouseOutFk': case 'warehouseInFk': case 'totalEntries': @@ -143,7 +143,7 @@ module.exports = Self => { JOIN warehouse wo ON wo.id = t.warehouseOutFk JOIN country c ON c.id = wo.countryFk LEFT JOIN continent cnt ON cnt.id = c.continentFk - JOIN agencyMode am ON am.id = t.agencyFk` + JOIN agencyMode am ON am.id = t.agencyModeFk` ); stmt.merge(conn.makeWhere(filter.where)); diff --git a/modules/travel/back/methods/travel/filter.js b/modules/travel/back/methods/travel/filter.js index 586b4e5aa..3fa1d65f9 100644 --- a/modules/travel/back/methods/travel/filter.js +++ b/modules/travel/back/methods/travel/filter.js @@ -44,7 +44,7 @@ module.exports = Self => { description: 'The landed to date filter', http: {source: 'query'} }, { - arg: 'agencyFk', + arg: 'agencyModeFk', type: 'number', description: 'The agencyModeFk id', http: {source: 'query'} @@ -102,7 +102,7 @@ module.exports = Self => { case 'landedTo': return {'t.landed': {lte: value}}; case 'id': - case 'agencyFk': + case 'agencyModeFk': case 'warehouseOutFk': case 'warehouseInFk': case 'totalEntries': @@ -124,7 +124,7 @@ module.exports = Self => { t.landed, t.warehouseInFk, t.warehouseOutFk, - t.agencyFk, + t.agencyModeFk, t.ref, t.isDelivered, t.isReceived, @@ -137,7 +137,7 @@ module.exports = Self => { wout.name warehouseOutName, cnt.code continent FROM vn.travel t - JOIN vn.agencyMode am ON am.id = t.agencyFk + JOIN vn.agencyMode am ON am.id = t.agencyModeFk JOIN vn.warehouse win ON win.id = t.warehouseInFk JOIN vn.warehouse wout ON wout.id = t.warehouseOutFk JOIN warehouse wo ON wo.id = t.warehouseOutFk diff --git a/modules/travel/back/methods/travel/getAverageDays.js b/modules/travel/back/methods/travel/getAverageDays.js index 9a9649d84..d888d80c1 100644 --- a/modules/travel/back/methods/travel/getAverageDays.js +++ b/modules/travel/back/methods/travel/getAverageDays.js @@ -32,9 +32,9 @@ module.exports = Self => { t.warehouseOutFk, t.landed, t.shipped, - t.agencyFk + t.agencyModeFk FROM travel t - WHERE t.agencyFk = ? LIMIT 50)`, [agencyModeFk]); + WHERE t.agencyModeFk = ? LIMIT 50)`, [agencyModeFk]); stmts.push(stmt); stmt = new ParameterizedSQL(` @@ -44,10 +44,10 @@ module.exports = Self => { t.warehouseOutFk, (SELECT ROUND(AVG(DATEDIFF(t.landed, t.shipped ))) FROM tmp.travel t - WHERE t.agencyFk + WHERE t.agencyModeFk ORDER BY id DESC LIMIT 50) AS dayDuration FROM tmp.travel t - WHERE t.agencyFk + WHERE t.agencyModeFk ORDER BY t.id DESC LIMIT 1`); const avgDaysIndex = stmts.push(stmt) - 1; diff --git a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js index aa92fa308..2e79ff193 100644 --- a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js +++ b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js @@ -71,7 +71,7 @@ describe('Travel cloneWithEntries()', () => { expect(newTravel.ref).toEqual('fifth travel'); expect(newTravel.warehouseInFk).toEqual(warehouseThree); expect(newTravel.warehouseOutFk).toEqual(warehouseThree); - expect(newTravel.agencyFk).toEqual(agencyModeOne); + expect(newTravel.agencyModeFk).toEqual(agencyModeOne); expect(travelEntries.length).toBeGreaterThan(0); }); }); diff --git a/modules/travel/back/methods/travel/specs/extraCommunityFilter.spec.js b/modules/travel/back/methods/travel/specs/extraCommunityFilter.spec.js index b74160efe..3693aae82 100644 --- a/modules/travel/back/methods/travel/specs/extraCommunityFilter.spec.js +++ b/modules/travel/back/methods/travel/specs/extraCommunityFilter.spec.js @@ -70,10 +70,10 @@ describe('Travel extraCommunityFilter()', () => { expect(result.length).toEqual(3); }); - it('should return the travel matching "agencyFk"', async() => { + it('should return the travel matching "agencyModeFk"', async() => { const ctx = { args: { - agencyFk: 1 + agencyModeFk: 1 } }; diff --git a/modules/travel/back/models/travel.json b/modules/travel/back/models/travel.json index 95330e263..c20b7b0bf 100644 --- a/modules/travel/back/models/travel.json +++ b/modules/travel/back/models/travel.json @@ -44,17 +44,14 @@ "type": "number" }, "agencyModeFk": { - "type": "number", - "mysql": { - "columnName": "agencyFk" - } + "type": "number" } }, "relations": { "agency": { "type": "belongsTo", "model": "AgencyMode", - "foreignKey": "agencyFk" + "foreignKey": "agencyModeFk" }, "warehouseIn": { "type": "belongsTo", diff --git a/modules/travel/front/descriptor-menu/index.js b/modules/travel/front/descriptor-menu/index.js index 25922815e..0630bb618 100644 --- a/modules/travel/front/descriptor-menu/index.js +++ b/modules/travel/front/descriptor-menu/index.js @@ -25,7 +25,7 @@ class Controller extends Section { 'shipped', 'landed', 'totalEntries', - 'agencyFk', + 'agencyModeFk', 'warehouseInFk', 'warehouseOutFk', 'cargoSupplierFk' @@ -64,7 +64,7 @@ class Controller extends Section { onCloneAccept() { const params = JSON.stringify({ ref: this.travel.ref, - agencyModeFk: this.travel.agencyFk, + agencyModeFk: this.travel.agencyModeFk, shipped: this.travel.shipped, landed: this.travel.landed, warehouseInFk: this.travel.warehouseInFk, diff --git a/modules/travel/front/descriptor-menu/index.spec.js b/modules/travel/front/descriptor-menu/index.spec.js index 4ca93a6d6..40319e8e2 100644 --- a/modules/travel/front/descriptor-menu/index.spec.js +++ b/modules/travel/front/descriptor-menu/index.spec.js @@ -18,7 +18,7 @@ describe('Travel Component vnTravelDescriptorMenu', () => { controller.travel = { ref: 'the ref', - agencyFk: 'the agency', + agencyModeFk: 'the agency', shipped: 'the shipped date', landed: 'the landing date', warehouseInFk: 'the receiver warehouse', @@ -29,7 +29,7 @@ describe('Travel Component vnTravelDescriptorMenu', () => { const params = JSON.stringify({ ref: controller.travel.ref, - agencyModeFk: controller.travel.agencyFk, + agencyModeFk: controller.travel.agencyModeFk, shipped: controller.travel.shipped, landed: controller.travel.landed, warehouseInFk: controller.travel.warehouseInFk, diff --git a/modules/travel/front/descriptor/index.js b/modules/travel/front/descriptor/index.js index dc19f68af..b1f2f53be 100644 --- a/modules/travel/front/descriptor/index.js +++ b/modules/travel/front/descriptor/index.js @@ -14,9 +14,9 @@ class Controller extends Descriptor { let travelFilter; const travel = this.travel; - if (travel && travel.agencyFk) { + if (travel && travel.agencyModeFk) { travelFilter = this.travel && JSON.stringify({ - agencyFk: this.travel.agencyFk + agencyModeFk: this.travel.agencyModeFk }); } return travelFilter; diff --git a/modules/travel/front/extra-community-search-panel/index.html b/modules/travel/front/extra-community-search-panel/index.html index 8e51acc15..ab1e88891 100644 --- a/modules/travel/front/extra-community-search-panel/index.html +++ b/modules/travel/front/extra-community-search-panel/index.html @@ -29,7 +29,7 @@ diff --git a/modules/travel/front/index/index.html b/modules/travel/front/index/index.html index 8510cf328..27a700083 100644 --- a/modules/travel/front/index/index.html +++ b/modules/travel/front/index/index.html @@ -11,7 +11,7 @@ Id Reference - Agency + Agency Warehouse Out Shipped Delivered diff --git a/modules/travel/front/index/index.js b/modules/travel/front/index/index.js index 5cae6fa06..50036831f 100644 --- a/modules/travel/front/index/index.js +++ b/modules/travel/front/index/index.js @@ -10,7 +10,7 @@ export default class Controller extends Section { onCloneAccept(travel) { const params = JSON.stringify({ ref: travel.ref, - agencyModeFk: travel.agencyFk, + agencyModeFk: travel.agencyModeFk, shipped: travel.shipped, landed: travel.landed, warehouseInFk: travel.warehouseInFk, @@ -44,7 +44,7 @@ export default class Controller extends Section { case 'landed': return {'t.landed': {between: this.dateRange(value)}}; case 'id': - case 'agencyFk': + case 'agencyModeFk': case 'warehouseOutFk': case 'warehouseInFk': case 'totalEntries': diff --git a/modules/travel/front/index/index.spec.js b/modules/travel/front/index/index.spec.js index 1b2ab0a87..9abe46a64 100644 --- a/modules/travel/front/index/index.spec.js +++ b/modules/travel/front/index/index.spec.js @@ -35,11 +35,11 @@ describe('Travel Component vnTravelIndex', () => { const travel = { ref: 1, - agencyFk: 1 + agencyModeFk: 1 }; const travelParams = { ref: travel.ref, - agencyModeFk: travel.agencyFk + agencyModeFk: travel.agencyModeFk }; const queryParams = JSON.stringify(travelParams); controller.onCloneAccept(travel); diff --git a/modules/travel/front/search-panel/index.html b/modules/travel/front/search-panel/index.html index 8d4edec1a..2e9c796c3 100644 --- a/modules/travel/front/search-panel/index.html +++ b/modules/travel/front/search-panel/index.html @@ -29,7 +29,7 @@ diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js index 06356cdaf..fef3cf223 100644 --- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -46,30 +46,11 @@ module.exports = Self => { if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); - const minTime = new Date(args.timed); - minTime.setHours(0, 0, 0, 0); + query = `CALL vn.workerTimeControl_clockIn(?,?,?)`; + const [response] = await Self.rawSql(query, [workerId, args.timed, args.direction], myOptions); + if (response[0] && response[0].error) + throw new UserError(response[0].error); - query = `SELECT * FROM vn.workerLabour WHERE workerFk = ? AND (ended >= ? OR ended IS NULL);`; - const [workerLabour] = await Self.rawSql(query, [workerId, minTime]); - const absence = await models.Calendar.findOne({ - where: { - businessFk: workerLabour.businessFk, - dated: minTime - } - }); - if (absence) { - const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions); - const isNotHalfAbsence = absenceType.code != 'halfHoliday' - && absenceType.code != 'halfPaidLeave' - && absenceType.code != 'halfFurlough'; - if (isNotHalfAbsence) - throw new UserError(`The worker has a marked absence that day`); - } - return models.WorkerTimeControl.create({ - userFk: workerId, - direction: args.direction, - timed: args.timed, - manual: true - }, myOptions); + return response; }; }; diff --git a/modules/worker/back/methods/worker-time-control/specs/filter.spec.js b/modules/worker/back/methods/worker-time-control/specs/filter.spec.js index 7fe681ec1..927d83df3 100644 --- a/modules/worker/back/methods/worker-time-control/specs/filter.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/filter.spec.js @@ -1,8 +1,8 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; describe('workerTimeControl filter()', () => { it('should return 1 result filtering by id', async() => { - let ctx = {req: {accessToken: {userId: 1106}}, args: {workerFk: 1106}}; + const ctx = {req: {accessToken: {userId: 1106}}, args: {workerFk: 1106}}; const firstHour = new Date(); firstHour.setHours(7, 0, 0, 0); const lastHour = new Date(); @@ -14,13 +14,13 @@ describe('workerTimeControl filter()', () => { timed: {between: [firstHour, lastHour]} } }; - let result = await app.models.WorkerTimeControl.filter(ctx, filter); + const result = await models.WorkerTimeControl.filter(ctx, filter); expect(result.length).toEqual(4); }); it('should return a privilege error for a non subordinate worker', async() => { - let ctx = {req: {accessToken: {userId: 1107}}, args: {workerFk: 1106}}; + const ctx = {req: {accessToken: {userId: 1107}}, args: {workerFk: 1106}}; const firstHour = new Date(); firstHour.setHours(7, 0, 0, 0); const lastHour = new Date(); @@ -34,7 +34,7 @@ describe('workerTimeControl filter()', () => { }; let error; - await app.models.WorkerTimeControl.filter(ctx, filter).catch(e => { + await models.WorkerTimeControl.filter(ctx, filter).catch(e => { error = e; }).finally(() => { expect(error.message).toEqual(`You don't have enough privileges`); diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 0c2914934..e1e0feced 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -1,6 +1,6 @@ -const app = require('vn-loopback/server/server'); +/* eslint max-len: ["error", { "code": 150 }]*/ +const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); -const models = app.models; describe('workerTimeControl add/delete timeEntry()', () => { const HHRRId = 37; @@ -8,10 +8,18 @@ describe('workerTimeControl add/delete timeEntry()', () => { const employeeId = 1; const salesPersonId = 1106; const salesBossId = 19; - let activeCtx = { + const hankPymId = 1107; + const jessicaJonesId = 1110; + const monday = 1; + const tuesday = 2; + const thursday = 4; + const friday = 5; + const saturday = 6; + const sunday = 7; + const activeCtx = { accessToken: {userId: 50}, }; - let ctx = {req: activeCtx}; + const ctx = {req: activeCtx}; beforeAll(() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ @@ -19,190 +27,675 @@ describe('workerTimeControl add/delete timeEntry()', () => { }); }); - it('should fail to add a time entry if the target user is not a subordinate', async() => { - activeCtx.accessToken.userId = employeeId; - const workerId = 2; + describe('as Role errors', () => { + it('should fail to add a time entry if the target user is not a subordinate', async() => { + activeCtx.accessToken.userId = employeeId; + const workerId = 2; - let error; + let error; - try { - ctx.args = {timed: new Date(), direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId); - } catch (e) { - error = e; - } + try { + ctx.args = {timed: new Date(), direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId); + } catch (e) { + error = e; + } - expect(error).toBeDefined(); - expect(error.statusCode).toBe(400); - expect(error.message).toBe(`You don't have enough privileges`); + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toBe(`You don't have enough privileges`); + }); + + it('should fail to add if the current and the target user are the same and is not team boss', async() => { + activeCtx.accessToken.userId = employeeId; + const workerId = employeeId; + let error; + + try { + ctx.args = {timed: new Date(), direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toBe(`You don't have enough privileges`); + }); + + it('should add if the current user is team boss and the target user is himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = new Date(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should try but fail to delete his own time entry', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = salesBossId; + + let error; + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = new Date(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + activeCtx.accessToken.userId = salesPersonId; + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error).toBeDefined(); + expect(error.statusCode).toBe(400); + expect(error.message).toBe(`You don't have enough privileges`); + }); + + it('should delete the created time entry for the team boss as himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = new Date(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should delete the created time entry for the team boss as HHRR', async() => { + activeCtx.accessToken.userId = HHRRId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = new Date(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should edit the created time entry for the team boss as HHRR', async() => { + activeCtx.accessToken.userId = HHRRId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = new Date(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + ctx.args = {direction: 'out'}; + const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); + + expect(updatedTimeEntry.direction).toEqual('out'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); - it('should fail to add if the current and the target user are the same and is not team boss', async() => { - activeCtx.accessToken.userId = employeeId; - const workerId = employeeId; - let error; + describe('WorkerTimeControl_clockIn calls', () => { + it('should fail to add a time entry if the target user has an absence that day', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + const date = new Date(); + date.setDate(date.getDate() - 16); + date.setHours(8, 0, 0); + let error; - try { - ctx.args = {timed: new Date(), direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error.statusCode).toBe(400); - expect(error.message).toBe(`You don't have enough privileges`); - }); - - it('should add if the current user is team boss and the target user is a himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { + const tx = await models.WorkerTimeControl.beginTransaction({}); const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - const todayAtSix = new Date(); - todayAtSix.setHours(18, 30, 0, 0); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - ctx.args = {timed: todayAtSix, direction: 'in'}; - const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + expect(error.message).toBe(`No está permitido trabajar`); + }); - expect(createdTimeEntry.id).toBeDefined(); + it('should fail to add a time entry for a worker without an existing contract', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + const date = new Date(); + date.setFullYear(date.getFullYear() - 2); + let error; - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - it('should fail to add a time entry if the target user has absent that day', async() => { - activeCtx.accessToken.userId = salesBossId; - const workerId = salesPersonId; - let error; + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - let calendar = await app.models.Calendar.findById(3); + expect(error.message).toBe(`No hay un contrato en vigor`); + }); - try { - ctx.args = {timed: new Date(calendar.dated), direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId); - } catch (e) { - error = e; - } + describe('direction errors', () => { + it('should throw an error when trying "in" direction twice', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; - expect(error.message).toBe(`The worker has a marked absence that day`); - }); + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; - it('should try but fail to delete his own time entry', async() => { - activeCtx.accessToken.userId = salesBossId; - const workerId = salesBossId; + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - let error; - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - const todayAtSeven = new Date(); - todayAtSeven.setHours(19, 30, 0, 0); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - ctx.args = {timed: todayAtSeven, direction: 'in'}; - const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + expect(error.message).toBe(`Dirección incorrecta`); + }); - activeCtx.accessToken.userId = salesPersonId; - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; - expect(error).toBeDefined(); - expect(error.statusCode).toBe(400); - expect(error.message).toBe(`You don't have enough privileges`); - }); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; - it('should delete the created time entry for the team boss as himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - const todayAtFive = new Date(); - todayAtFive.setHours(17, 30, 0, 0); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - ctx.args = {timed: todayAtFive, direction: 'in'}; - const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + expect(error.message).toBe(`Dirección incorrecta`); + }); - expect(createdTimeEntry.id).toBeDefined(); + it('Should throw an error when trying "out" before closing a "middle" couple', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - it('should delete the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - const todayAtFive = new Date(); - todayAtFive.setHours(17, 30, 0, 0); + expect(error.message).toBe(`Dirección incorrecta`); + }); - ctx.args = {timed: todayAtFive, direction: 'in'}; - const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + it('should throw an error when trying "middle" after "out"', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; - expect(createdTimeEntry.id).toBeDefined(); + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - it('should edit the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; + expect(error.message).toBe(`Dirección incorrecta`); + }); - const todayAtFive = new Date(); - todayAtFive.setHours(17, 30, 0, 0); + it('should throw an error when trying "out" direction twice', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; - ctx.args = {timed: todayAtFive, direction: 'in'}; - const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; - expect(createdTimeEntry.id).toBeDefined(); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; - ctx.args = {direction: 'out'}; - const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - expect(updatedTimeEntry.direction).toEqual('out'); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + }); + + describe('12h rest', () => { + it('should throw an error when the 12h rest is not fulfilled yet', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario 12h.`); + }); + + it('should not fail as the 12h rest is fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 3500kg drivers with enforced 9h rest', () => { + it('should throw an error when the 9h enforced rest is not fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario 9h.`); + }); + + it('should not fail when the 9h enforced rest is fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + + let date = new Date(); + date.setDate(date.getDate() - 21); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 36h weekly rest', () => { + it('should throw an error when the 36h weekly rest is not fulfilled', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + date = weekDay(date, friday); + date.setHours(7, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(8, 1, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + + it('should throw an error when the 36h weekly rest is not fulfilled again', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, saturday); + date.setHours(3, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + }); + + describe('for 72h weekly rest', () => { + it('should throw when the 72h weekly rest is not fulfilled yet', async() => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + + let date = new Date(); + date.setMonth(date.getMonth() - 2); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, friday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, saturday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, saturday, ctx, workerId, options); + date = lastWeek(date); + + try { + date = weekDay(date, sunday); + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal 36h. / 72h.`); + }); + }); }); }); + +function weekDay(date, dayToSet) { + const currentDay = date.getDay(); + const distance = dayToSet - currentDay; + + date.setDate(date.getDate() + distance); + return date; +} + +function nextWeek(date) { + const sunday = 7; + const currentDay = date.getDay(); + let newDate = date; + if (currentDay != 0) + newDate = weekDay(date, sunday); + + newDate.setDate(newDate.getDate() + 1); + return newDate; +} + +function lastWeek(date) { + const monday = 1; + newDate = weekDay(date, monday); + + newDate.setDate(newDate.getDate() - 1); + return newDate; +} + +async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { + const dateStart = new Date(weekDay(date, dayStart)); + const dateEnd = new Date(dateStart); + dateEnd.setDate(dateStart.getDate() + dayEnd); + + for (let i = dayStart; i <= dayEnd; i++) { + dateStart.setHours(8, 0, 0); + ctx.args = {timed: dateStart, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setHours(16, 0, 0); + ctx.args = {timed: dateStart, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setDate(dateStart.getDate() + 1); + } +} diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index 098dc55fb..430ec7dfc 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -80,6 +80,9 @@ module.exports = Self => { if (hasHoursRecorded && isNotHalfAbsence) throw new UserError(`The worker has hours recorded that day`); + const date = new Date(); + const now = new Date(); + date.setHours(0, 0, 0, 0); const [result] = await Self.rawSql( `SELECT COUNT(*) halfHolidayCounter FROM vn.calendar c @@ -87,8 +90,8 @@ module.exports = Self => { JOIN postgresql.profile p ON p.profile_id = b.client_id WHERE c.dayOffTypeFk = 6 AND p.workerFk = ? - AND c.dated BETWEEN util.firstDayOfYear(CURDATE()) - AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [id]); + AND c.dated BETWEEN util.firstDayOfYear(?) + AND LAST_DAY(DATE_ADD(?, INTERVAL 12 - MONTH(?) MONTH))`, [id, date, now, now]); const hasHalfHoliday = result.halfHolidayCounter > 0; const isHalfHoliday = absenceType.code === 'halfHoliday'; diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js index 339b84b7b..7f093a330 100644 --- a/modules/worker/back/methods/worker/holidays.js +++ b/modules/worker/back/methods/worker/holidays.js @@ -57,25 +57,9 @@ module.exports = Self => { ended.setDate(0); ended.setHours(23, 59, 59, 59); - const filter = { - where: { - and: [ - {workerFk: id}, - { - or: [ - {started: {between: [started, ended]}}, - {ended: {between: [started, ended]}}, - {and: [{started: {lt: started}}, {ended: {gt: ended}}]}, - {and: [{started: {lt: started}}, {ended: null}]} - ] - } - ], - - } - }; - const contracts = await models.WorkerLabour.find(filter, myOptions); - let [firstContract] = contracts; - const payedHolidays = firstContract.payedHolidays; + const filter = {where: {businessFk: args.businessFk}}; + const contract = await models.WorkerLabour.findOne(filter, myOptions); + const payedHolidays = contract.payedHolidays; let queryIndex; const year = started.getFullYear(); diff --git a/modules/worker/back/methods/worker/specs/holidays.spec.js b/modules/worker/back/methods/worker/specs/holidays.spec.js index 317bc8f0f..d8310b738 100644 --- a/modules/worker/back/methods/worker/specs/holidays.spec.js +++ b/modules/worker/back/methods/worker/specs/holidays.spec.js @@ -27,4 +27,15 @@ describe('Worker holidays()', () => { expect(result.totalHolidays).toEqual(27.5); expect(result.holidaysEnjoyed).toEqual(5); }); + + it('should now get the payed holidays calendar for a worker', async() => { + const now = new Date(); + const year = now.getFullYear(); + + ctx.args = {businessFk: businessId, year: year}; + + const result = await app.models.Worker.holidays(ctx, workerId); + + expect(result.payedHolidays).toEqual(8); + }); }); diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index f7a344358..3d41707ce 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -49,6 +49,9 @@ }, "labelerFk": { "type" : "number" + }, + "mobileExtension": { + "type" : "number" } }, "relations": { diff --git a/modules/worker/front/basic-data/index.html b/modules/worker/front/basic-data/index.html index cbe683185..5a3acdde5 100644 --- a/modules/worker/front/basic-data/index.html +++ b/modules/worker/front/basic-data/index.html @@ -25,10 +25,18 @@ + + + + - - + + - - + + - + + + +

User data

diff --git a/modules/worker/front/summary/index.js b/modules/worker/front/summary/index.js index 3cdb2c36f..c2ad107d5 100644 --- a/modules/worker/front/summary/index.js +++ b/modules/worker/front/summary/index.js @@ -34,7 +34,7 @@ class Controller extends Summary { }, { relation: 'client', - scope: {fields: ['fi']} + scope: {fields: ['fi', 'phone']} }, { relation: 'boss', diff --git a/modules/worker/front/summary/locale/es.yml b/modules/worker/front/summary/locale/es.yml new file mode 100644 index 000000000..e9c8e5583 --- /dev/null +++ b/modules/worker/front/summary/locale/es.yml @@ -0,0 +1,3 @@ +Business phone: Teléfono de empresa +Personal phone: Teléfono personal +Mobile extension: Extensión móvil \ No newline at end of file diff --git a/modules/zone/back/methods/agency/specs/getLanded.spec.js b/modules/zone/back/methods/agency/specs/getLanded.spec.js index 3f239539f..c199b2ba6 100644 --- a/modules/zone/back/methods/agency/specs/getLanded.spec.js +++ b/modules/zone/back/methods/agency/specs/getLanded.spec.js @@ -1,4 +1,4 @@ -const app = require('vn-loopback/server/server'); +const models = require('vn-loopback/server/server').models; describe('agency getLanded()', () => { it('should return a landing date', async() => { @@ -9,12 +9,12 @@ describe('agency getLanded()', () => { const agencyModeFk = 7; const warehouseFk = 1; - const tx = await app.models.Zone.beginTransaction({}); + const tx = await models.Zone.beginTransaction({}); try { const options = {transaction: tx}; - const result = await app.models.Agency.getLanded(ctx, shipped, addressFk, agencyModeFk, warehouseFk, options); + const result = await models.Agency.getLanded(ctx, shipped, addressFk, agencyModeFk, warehouseFk, options); expect(result.landed).toBeDefined(); diff --git a/modules/zone/back/methods/zone/exclusionGeo.js b/modules/zone/back/methods/zone/exclusionGeo.js new file mode 100644 index 000000000..5026c58c5 --- /dev/null +++ b/modules/zone/back/methods/zone/exclusionGeo.js @@ -0,0 +1,64 @@ + +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('exclusionGeo', { + description: 'Exclude a geo from a zone', + accepts: [ + { + arg: 'zoneFk', + type: 'number', + description: 'The zone id' + }, + { + arg: 'date', + type: 'date', + description: 'The date to exclude' + }, + { + arg: 'geoIds', + type: ['number'], + description: 'The geos id' + } + + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/exclusionGeo`, + verb: 'POST' + } + }); + + Self.exclusionGeo = async(zoneFk, date, geoIds, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!geoIds[0]) throw new UserError(`You must select a location`); + + const newZoneExclusion = await models.ZoneExclusion.create({ + zoneFk: zoneFk, + dated: date + }, myOptions); + + const promises = []; + + for (const geoId of geoIds) { + const newZoneExclusionGeo = await models.ZoneExclusionGeo.create({ + zoneExclusionFk: newZoneExclusion.id, + geoFk: geoId + }, myOptions); + + promises.push(newZoneExclusionGeo); + } + + const newZoneExclusionGeos = await Promise.all(promises); + + return newZoneExclusionGeos; + }; +}; diff --git a/modules/zone/back/methods/zone/getEventsFiltered.js b/modules/zone/back/methods/zone/getEventsFiltered.js index 5e9cbae5a..b7875785d 100644 --- a/modules/zone/back/methods/zone/getEventsFiltered.js +++ b/modules/zone/back/methods/zone/getEventsFiltered.js @@ -44,20 +44,35 @@ module.exports = Self => { OR (type = 'range' AND ( (started BETWEEN ? AND ?) - OR (ended BETWEEN ? AND ?) + OR + (ended BETWEEN ? AND ?) + OR + (started <= ? AND ended >= ?) ) ) ) ORDER BY type='indefinitely' DESC, type='range' DESC, type='day' DESC;`; - const events = await Self.rawSql(query, [zoneFk, started, ended, started, ended, started, ended], myOptions); + const events = await Self.rawSql(query, + [zoneFk, started, ended, started, ended, started, ended, started, ended], myOptions); query = ` - SELECT * - FROM vn.zoneExclusion - WHERE zoneFk = ? - AND dated BETWEEN ? AND ?;`; + SELECT e.* + FROM vn.zoneExclusion e + LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE e.zoneFk = ? + AND e.dated BETWEEN ? AND ? + AND eg.zoneExclusionFk IS NULL;`; const exclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions); - return {events, exclusions}; + query = ` + SELECT eg.*, e.zoneFk, e.dated, e.created, e.userFk + FROM vn.zoneExclusion e + LEFT JOIN vn.zoneExclusionGeo eg ON eg.zoneExclusionFk = e.id + WHERE e.zoneFk = ? + AND e.dated BETWEEN ? AND ? + AND eg.zoneExclusionFk IS NOT NULL;`; + const geoExclusions = await Self.rawSql(query, [zoneFk, started, ended], myOptions); + + return {events, exclusions, geoExclusions}; }; }; diff --git a/modules/zone/back/methods/zone/specs/exclusionGeo.spec.js b/modules/zone/back/methods/zone/specs/exclusionGeo.spec.js new file mode 100644 index 000000000..3a345f2ce --- /dev/null +++ b/modules/zone/back/methods/zone/specs/exclusionGeo.spec.js @@ -0,0 +1,41 @@ +const models = require('vn-loopback/server/server').models; + +describe('zone exclusionGeo()', () => { + const zoneId = 1; + const today = new Date(); + + it(`should show an error when location isn't selected`, async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const geoIds = []; + + await models.Zone.exclusionGeo(zoneId, today, geoIds, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toContain(`You must select a location`); + }); + + it('should create two exclusion by geo', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const geoIds = [1, 2]; + const result = await models.Zone.exclusionGeo(zoneId, today, geoIds, options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js b/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js index ffa416282..8160ee05e 100644 --- a/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js +++ b/modules/zone/back/methods/zone/specs/getEventsFiltered.spec.js @@ -6,8 +6,9 @@ describe('zone getEventsFiltered()', () => { try { const options = {transaction: tx}; + const today = new Date(); - let result = await models.Zone.getEventsFiltered(10, '2021-10-01', '2021-10-02', options); + const result = await models.Zone.getEventsFiltered(10, today, today, options); expect(result.events.length).toEqual(1); expect(result.exclusions.length).toEqual(0); @@ -18,4 +19,46 @@ describe('zone getEventsFiltered()', () => { throw e; } }); + + describe('event range type', () => { + it('should return events and exclusions for the zone 9 in a range of dates', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const today = new Date(); + + const result = await models.Zone.getEventsFiltered(9, today, today, options); + + expect(result.events.length).toEqual(1); + expect(result.exclusions.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should not return events and exclusions for the zone 9 in a range of dates', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const date = new Date(); + date.setFullYear(date.getFullYear() - 2); + const dateTomorrow = new Date(date.setDate(date.getDate() + 1)); + + const result = await models.Zone.getEventsFiltered(9, date, dateTomorrow, options); + + expect(result.events.length).toEqual(0); + expect(result.exclusions.length).toEqual(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); }); diff --git a/modules/zone/back/methods/zone/specs/getZoneClosing.spec.js b/modules/zone/back/methods/zone/specs/getZoneClosing.spec.js index 0c479dda0..4fb4b4e5c 100644 --- a/modules/zone/back/methods/zone/specs/getZoneClosing.spec.js +++ b/modules/zone/back/methods/zone/specs/getZoneClosing.spec.js @@ -8,7 +8,6 @@ describe('zone getZoneClosing()', () => { const options = {transaction: tx}; const date = new Date(); const today = date.toISOString().split('T')[0]; - const result = await models.Zone.getZoneClosing([1, 2, 3], today, options); expect(result.length).toEqual(3); diff --git a/modules/zone/back/methods/zone/specs/updateExclusionGeo.spec.js b/modules/zone/back/methods/zone/specs/updateExclusionGeo.spec.js new file mode 100644 index 000000000..9db2e24be --- /dev/null +++ b/modules/zone/back/methods/zone/specs/updateExclusionGeo.spec.js @@ -0,0 +1,40 @@ +const models = require('vn-loopback/server/server').models; + +describe('zone updateExclusionGeo()', () => { + it(`should show an error when location isn't selected`, async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const zoneId = 1; + const geoIds = []; + + await models.Zone.updateExclusionGeo(zoneId, geoIds, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toContain(`You must select a location`); + }); + + it('should delete all exclusion and then create two exclusion by geo for a zone', async() => { + const tx = await models.Zone.beginTransaction({}); + + try { + const options = {transaction: tx}; + const zoneId = 2; + const geoIds = [1, 2]; + const result = await models.Zone.updateExclusionGeo(zoneId, geoIds, options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/zone/back/methods/zone/updateExclusionGeo.js b/modules/zone/back/methods/zone/updateExclusionGeo.js new file mode 100644 index 000000000..237e336e0 --- /dev/null +++ b/modules/zone/back/methods/zone/updateExclusionGeo.js @@ -0,0 +1,55 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('updateExclusionGeo', { + description: 'Update the geos excluded from a zone', + accepts: [ + { + arg: 'zoneExclusionFk', + type: 'number', + description: 'The zoneExclusion id' + }, + { + arg: 'geoIds', + type: ['number'], + description: 'The geos id' + } + + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/updateExclusionGeo`, + verb: 'POST' + } + }); + + Self.updateExclusionGeo = async(zoneExclusionFk, geoIds, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!geoIds[0]) throw new UserError(`You must select a location`); + + await models.ZoneExclusionGeo.destroyAll({ + zoneExclusionFk: zoneExclusionFk + }, myOptions); + + const promises = []; + + for (const geoId of geoIds) { + const params = { + zoneExclusionFk: zoneExclusionFk, + geoFk: geoId + }; + const deletedZoneExclusionGeos = models.ZoneExclusionGeo.create(params, myOptions); + promises.push(deletedZoneExclusionGeos); + } + + return Promise.all(promises); + }; +}; diff --git a/modules/zone/back/model-config.json b/modules/zone/back/model-config.json index ee555f3f4..261a89902 100644 --- a/modules/zone/back/model-config.json +++ b/modules/zone/back/model-config.json @@ -23,6 +23,9 @@ "ZoneExclusion": { "dataSource": "vn" }, + "ZoneExclusionGeo": { + "dataSource": "vn" + }, "ZoneGeo": { "dataSource": "vn" }, diff --git a/modules/zone/back/models/agency-mode.json b/modules/zone/back/models/agency-mode.json index 027cec190..99ed43b97 100644 --- a/modules/zone/back/models/agency-mode.json +++ b/modules/zone/back/models/agency-mode.json @@ -55,6 +55,11 @@ "type": "belongsTo", "model": "DeliveryMethod", "foreignKey": "deliveryMethodFk" + }, + "zones": { + "type": "hasMany", + "model": "Zone", + "foreignKey": "agencyModeFk" } }, "acls": [ diff --git a/modules/zone/back/models/zone-exclusion-geo.json b/modules/zone/back/models/zone-exclusion-geo.json new file mode 100644 index 000000000..816e4b650 --- /dev/null +++ b/modules/zone/back/models/zone-exclusion-geo.json @@ -0,0 +1,21 @@ +{ + "name": "ZoneExclusionGeo", + "base": "VnModel", + "options": { + "mysql": { + "table": "zoneExclusionGeo" + } + }, + "properties": { + "id": { + "id": true, + "type": "number" + }, + "zoneExclusionFk": { + "type": "number" + }, + "geoFk": { + "type": "number" + } + } +} \ No newline at end of file diff --git a/modules/zone/back/models/zone.js b/modules/zone/back/models/zone.js index ef1c8c5d9..6d5a6cdca 100644 --- a/modules/zone/back/models/zone.js +++ b/modules/zone/back/models/zone.js @@ -8,6 +8,8 @@ module.exports = Self => { require('../methods/zone/deleteZone')(Self); require('../methods/zone/includingExpired')(Self); require('../methods/zone/getZoneClosing')(Self); + require('../methods/zone/exclusionGeo')(Self); + require('../methods/zone/updateExclusionGeo')(Self); Self.validatesPresenceOf('agencyModeFk', { message: `Agency cannot be blank` diff --git a/modules/zone/front/calendar/index.js b/modules/zone/front/calendar/index.js index a24d10aef..3bc7158ef 100644 --- a/modules/zone/front/calendar/index.js +++ b/modules/zone/front/calendar/index.js @@ -75,6 +75,17 @@ class Controller extends Component { } } + this.geoExclusions = {}; + let geoExclusions = value.geoExclusions; + + if (geoExclusions) { + for (let geoExclusion of geoExclusions) { + let stamp = toStamp(geoExclusion.dated); + if (!this.geoExclusions[stamp]) this.geoExclusions[stamp] = []; + this.geoExclusions[stamp].push(geoExclusion); + } + } + let events = value.events; if (events) { @@ -135,11 +146,13 @@ class Controller extends Component { onSelection($event, $days, $type, $weekday) { let $events = []; let $exclusions = []; + let $geoExclusions = []; for (let day of $days) { let stamp = day.getTime(); $events = $events.concat(this.days[stamp] || []); $exclusions = $exclusions.concat(this.exclusions[stamp] || []); + $geoExclusions = $geoExclusions.concat(this.geoExclusions[stamp] || []); } this.emit('selection', { @@ -148,19 +161,23 @@ class Controller extends Component { $type, $weekday, $events, - $exclusions + $exclusions, + $geoExclusions }); } hasEvents(day) { let stamp = day.getTime(); - return this.days[stamp] || this.exclusions[stamp]; + return this.days[stamp] || this.exclusions[stamp] || this.geoExclusions[stamp]; } getClass(day) { let stamp = day.getTime(); - return this.exclusions[stamp] && !this.days[stamp] - ? 'excluded' : ''; + if (this.geoExclusions[stamp]) + return 'geoExcluded'; + else if (this.exclusions[stamp]) + return 'excluded'; + else return ''; } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; diff --git a/modules/zone/front/calendar/index.spec.js b/modules/zone/front/calendar/index.spec.js index be002925e..338f47917 100644 --- a/modules/zone/front/calendar/index.spec.js +++ b/modules/zone/front/calendar/index.spec.js @@ -15,6 +15,7 @@ describe('component vnZoneCalendar', () => { controller.zone = {id: 1}; controller.days = []; controller.exclusions = []; + controller.geoExclusions = []; })); describe('date() setter', () => { @@ -57,7 +58,7 @@ describe('component vnZoneCalendar', () => { }); describe('data() setter', () => { - it('should set the events and exclusions and then call the refreshEvents() method', () => { + it('should set the events, exclusions and geoExclusions and then call the refreshEvents() method', () => { jest.spyOn(controller, 'refreshEvents').mockReturnThis(); controller.data = { @@ -66,13 +67,17 @@ describe('component vnZoneCalendar', () => { }], events: [{ dated: new Date() - }] + }], + geoExclusions: [{ + dated: new Date() + }], }; expect(controller.refreshEvents).toHaveBeenCalledWith(); expect(controller.events).toBeDefined(); expect(controller.events.length).toEqual(1); expect(controller.exclusions).toBeDefined(); + expect(controller.geoExclusions).toBeDefined(); expect(Object.keys(controller.exclusions).length).toEqual(1); }); }); @@ -122,7 +127,8 @@ describe('component vnZoneCalendar', () => { $events: [], $exclusions: [], $type: 'day', - $weekday: 1 + $weekday: 1, + $geoExclusions: [], } ); }); @@ -151,5 +157,16 @@ describe('component vnZoneCalendar', () => { expect(result).toEqual('excluded'); }); + + it('should return the className "geoExcluded" for a date with geo excluded', () => { + const dated = new Date(); + + controller.geoExclusions = []; + controller.geoExclusions[dated.getTime()] = true; + + const result = controller.getClass(dated); + + expect(result).toEqual('geoExcluded'); + }); }); }); diff --git a/modules/zone/front/calendar/style.scss b/modules/zone/front/calendar/style.scss index 25b6a87d1..38491af58 100644 --- a/modules/zone/front/calendar/style.scss +++ b/modules/zone/front/calendar/style.scss @@ -33,6 +33,9 @@ vn-zone-calendar { &.excluded .day-number { background-color: $color-alert; } + &.geoExcluded .day-number { + background-color: $color-main; + } } } } diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index e71a1ae26..46ba87dea 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -2,7 +2,7 @@ id="calendar" vn-id="calendar" data="data" - on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions)" + on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions, $geoExclusions)" on-step="$ctrl.refresh()" class="vn-w-md"> @@ -98,7 +98,7 @@ fixed-bottom-right> @@ -198,3 +198,80 @@ message="This item will be deleted" question="Are you sure you want to continue?"> + + + + + + + + + + + + + +
+ + + + + +
+ + + + +
+
+
+
+ + + + + + + +
diff --git a/modules/zone/front/events/index.js b/modules/zone/front/events/index.js index 0df16a42a..b86330126 100644 --- a/modules/zone/front/events/index.js +++ b/modules/zone/front/events/index.js @@ -1,5 +1,6 @@ import ngModule from '../module'; import Section from 'salix/components/section'; +import './style.scss'; class Controller extends Section { constructor($element, $, vnWeekDays) { @@ -20,6 +21,16 @@ class Controller extends Section { return `Zones/${this.$params.id}/exclusions`; } + get checked() { + const geos = this.$.model.data || []; + const checkedLines = []; + for (let geo of geos) { + if (geo.checked) + checkedLines.push(geo); + } + return checkedLines; + } + refresh() { this.$.data = null; this.$.$applyAsync(() => { @@ -48,33 +59,56 @@ class Controller extends Section { : this.$t('Everyday'); } - onSelection(days, type, weekday, events, exclusions) { + onSelection(days, type, weekday, events, exclusions, exclusionGeos) { if (this.editMode == 'include') { if (events.length) - this.edit(events[0]); - else - this.create(type, days, weekday); - } else { - if (exclusions.length) - this.exclusionDelete(exclusions); - else - this.exclusionCreate(days); + return this.editInclusion(events[0]); + return this.createInclusion(type, days, weekday); + } else if (this.editMode == 'exclude') { + if (exclusions.length || exclusionGeos.length) + return this.editExclusion(exclusions[0] || {}, exclusionGeos); + return this.createExclusion(days); } } + editExclusion(exclusion, exclusionGeos) { + this.isNew = false; + this.excludeSelected = angular.copy(exclusion); + this.excludeSelected.type = exclusionGeos.length ? + 'specificLocations' : 'all'; + + this.exclusionGeos = new Set(); + if (exclusionGeos.length) { + this.excludeSelected.id = exclusionGeos[0].zoneExclusionFk; + exclusionGeos.forEach(x => this.exclusionGeos.add(x.geoFk)); + } + + this.$.excludeDialog.show(); + } + + createExclusion(days) { + this.isNew = true; + this.excludeSelected = { + type: 'all', + dated: days[0] + }; + this.exclusionGeos = new Set(); + this.$.excludeDialog.show(); + } + onEditClick(row, event) { if (event.defaultPrevented) return; - this.edit(row); + this.editInclusion(row); } - edit(row) { + editInclusion(row) { this.isNew = false; this.selected = angular.copy(row); this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays); - this.$.dialog.show(); + this.$.includeDialog.show(); } - create(type, days, weekday) { + createInclusion(type, days, weekday) { this.isNew = true; if (type == 'weekday') { @@ -92,7 +126,7 @@ class Controller extends Section { }; } - this.$.dialog.show(); + this.$.includeDialog.show(); } onIncludeResponse(response) { @@ -132,6 +166,19 @@ class Controller extends Section { } } + onExcludeResponse(response) { + const type = this.excludeSelected.type; + switch (response) { + case 'accept': { + if (type == 'all') + return this.exclusionCreate(); + return this.exclusionGeoCreate(); + } + case 'delete': + return this.exclusionDelete(this.excludeSelected); + } + } + onDeleteClick(id, event) { if (event.defaultPrevented) return; event.preventDefault(); @@ -149,31 +196,121 @@ class Controller extends Section { .then(() => this.refresh()); } - exclusionCreate(days) { - let exclusions = days.map(dated => { - return {dated}; - }); + exclusionCreate() { + const excludeSelected = this.excludeSelected; + const dated = excludeSelected.dated; + let req; - this.$http.post(this.exclusionsPath, exclusions) + if (this.isNew) + req = this.$http.post(this.exclusionsPath, [{dated}]); + if (!this.isNew) + req = this.$http.put(`${this.exclusionsPath}/${excludeSelected.id}`, {dated}); + + return req.then(() => { + this.refresh(); + }); + } + + exclusionGeoCreate() { + const excludeSelected = this.excludeSelected; + let req; + const geoIds = []; + this.exclusionGeos.forEach(id => geoIds.push(id)); + + if (this.isNew) { + const params = { + zoneFk: parseInt(this.$params.id), + date: excludeSelected.dated, + geoIds + }; + req = this.$http.post(`Zones/exclusionGeo`, params); + } else { + const params = { + zoneExclusionFk: this.excludeSelected.id, + geoIds + }; + req = this.$http.post(`Zones/updateExclusionGeo`, params); + } + return req.then(() => this.refresh()); + } + + exclusionDelete(exclusion) { + const path = `${this.exclusionsPath}/${exclusion.id}`; + return this.$http.delete(path) .then(() => this.refresh()); } - exclusionDelete(exclusions) { - let reqs = []; + set excludeSearch(value) { + this._excludeSearch = value; + if (!value) this.onSearch(); + } - for (let exclusion of exclusions) { - if (!exclusion.id) continue; - let path = `${this.exclusionsPath}/${exclusion.id}`; - reqs.push(this.$http.delete(path)); + get excludeSearch() { + return this._excludeSearch; + } + + onKeyDown(event) { + if (event.key == 'Enter') { + event.preventDefault(); + this.onSearch(); + } + } + + onSearch() { + const params = {search: this._excludeSearch}; + if (this.excludeSelected.type == 'specificLocations') { + this.$.model.applyFilter({}, params).then(() => { + const data = this.$.model.data; + this.getChecked(data); + this.$.treeview.data = data; + }); + } + } + + onFetch(item) { + const params = item ? {parentId: item.id} : null; + return this.$.model.applyFilter({}, params).then(() => { + const data = this.$.model.data; + this.getChecked(data); + return data; + }); + } + + onSort(a, b) { + if (b.selected !== a.selected) { + if (a.selected == null) + return 1; + if (b.selected == null) + return -1; + return b.selected - a.selected; } - this.$q.all(reqs) - .then(() => this.refresh()); + return a.name.localeCompare(b.name); + } + + getChecked(data) { + for (let geo of data) { + geo.checked = this.exclusionGeos.has(geo.id); + if (geo.childs) this.getChecked(geo.childs); + } + } + + onItemCheck(geoId, checked) { + if (checked) + this.exclusionGeos.add(geoId); + else + this.exclusionGeos.delete(geoId); } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; ngModule.vnComponent('vnZoneEvents', { template: require('./index.html'), - controller: Controller + controller: Controller, + bindings: { + zone: '<' + }, + require: { + card: '^vnZoneCard' + } }); diff --git a/modules/zone/front/events/index.spec.js b/modules/zone/front/events/index.spec.js index ed2c91c31..b4ff800d6 100644 --- a/modules/zone/front/events/index.spec.js +++ b/modules/zone/front/events/index.spec.js @@ -1,4 +1,5 @@ import './index'; +import crudModel from 'core/mocks/crud-model'; describe('component vnZoneEvents', () => { let $scope; @@ -34,7 +35,8 @@ describe('component vnZoneEvents', () => { const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`; const response = { events: 'myEvents', - exclusions: 'myExclusions' + exclusions: 'myExclusions', + geoExclusions: 'myGeoExclusions', }; $httpBackend.whenGET(query).respond(response); controller.refresh(); @@ -48,71 +50,129 @@ describe('component vnZoneEvents', () => { }); describe('onSelection()', () => { - it('should call the edit() method', () => { - jest.spyOn(controller, 'edit').mockReturnThis(); + it('should call the editInclusion() method', () => { + jest.spyOn(controller, 'editInclusion').mockReturnThis(); const weekday = {}; const days = []; const type = 'EventType'; const events = [{name: 'Event'}]; const exclusions = []; + const exclusionsGeo = []; controller.editMode = 'include'; - controller.onSelection(days, type, weekday, events, exclusions); + controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo); - expect(controller.edit).toHaveBeenCalledWith({name: 'Event'}); + expect(controller.editInclusion).toHaveBeenCalledWith({name: 'Event'}); }); - it('should call the create() method', () => { - jest.spyOn(controller, 'create').mockReturnThis(); + it('should call the createInclusion() method', () => { + jest.spyOn(controller, 'createInclusion').mockReturnThis(); const weekday = {dated: new Date()}; const days = [weekday]; const type = 'EventType'; const events = []; const exclusions = []; + const exclusionsGeo = []; controller.editMode = 'include'; - controller.onSelection(days, type, weekday, events, exclusions); + controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo); - expect(controller.create).toHaveBeenCalledWith(type, days, weekday); + expect(controller.createInclusion).toHaveBeenCalledWith(type, days, weekday); }); - it('should call the exclusionDelete() method', () => { - jest.spyOn(controller, 'exclusionDelete').mockReturnThis(); + it('should call the editExclusion() method with exclusions', () => { + jest.spyOn(controller, 'editExclusion').mockReturnThis(); const weekday = {}; const days = []; const type = 'EventType'; const events = []; - const exclusions = [{id: 1}]; - controller.editMode = 'delete'; - controller.onSelection(days, type, weekday, events, exclusions); + const exclusions = [{name: 'Exclusion'}]; + const exclusionsGeo = []; + controller.editMode = 'exclude'; + controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo); - expect(controller.exclusionDelete).toHaveBeenCalledWith(exclusions); + expect(controller.editExclusion).toHaveBeenCalled(); }); - it('should call the exclusionCreate() method', () => { - jest.spyOn(controller, 'exclusionCreate').mockReturnThis(); + it('should call the editExclusion() method with exclusionsGeo', () => { + jest.spyOn(controller, 'editExclusion').mockReturnThis(); + + const weekday = {}; + const days = []; + const type = 'EventType'; + const events = []; + const exclusions = []; + const exclusionsGeo = [{name: 'GeoExclusion'}]; + controller.editMode = 'exclude'; + controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo); + + expect(controller.editExclusion).toHaveBeenCalled(); + }); + + it('should call the createExclusion() method', () => { + jest.spyOn(controller, 'createExclusion').mockReturnThis(); const weekday = {}; const days = [{dated: new Date()}]; const type = 'EventType'; const events = []; const exclusions = []; - controller.editMode = 'delete'; - controller.onSelection(days, type, weekday, events, exclusions); + const exclusionsGeo = []; + controller.editMode = 'exclude'; + controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo); - expect(controller.exclusionCreate).toHaveBeenCalledWith(days); + expect(controller.createExclusion).toHaveBeenCalledWith(days); }); }); - describe('create()', () => { - it('shoud set the selected property and then call the dialog show() method', () => { - controller.$.dialog = {show: jest.fn()}; + describe('editExclusion()', () => { + it('shoud set the excludeSelected.type = "specificLocations" and then call the excludeDialog show() method', () => { + controller.$.excludeDialog = {show: jest.fn()}; + + const exclusionGeos = [{id: 1}]; + const exclusions = []; + + controller.editExclusion(exclusions, exclusionGeos); + + expect(controller.excludeSelected.type).toEqual('specificLocations'); + expect(controller.$.excludeDialog.show).toHaveBeenCalledWith(); + }); + + it('shoud set the excludeSelected.type = "all" and then call the excludeDialog show() method', () => { + controller.$.excludeDialog = {show: jest.fn()}; + + const exclusionGeos = []; + const exclusions = [{id: 1}]; + + controller.editExclusion(exclusions, exclusionGeos); + + expect(controller.excludeSelected.type).toEqual('all'); + expect(controller.$.excludeDialog.show).toHaveBeenCalledWith(); + }); + }); + + describe('createExclusion()', () => { + it('shoud set the excludeSelected property and then call the excludeDialog show() method', () => { + controller.$.excludeDialog = {show: jest.fn()}; + + const days = [new Date()]; + controller.createExclusion(days); + + expect(controller.excludeSelected).toBeDefined(); + expect(controller.isNew).toBeTruthy(); + expect(controller.$.excludeDialog.show).toHaveBeenCalledWith(); + }); + }); + + describe('createInclusion()', () => { + it('shoud set the selected property and then call the includeDialog show() method', () => { + controller.$.includeDialog = {show: jest.fn()}; const type = 'weekday'; const days = [new Date()]; const weekday = 1; - controller.create(type, days, weekday); + controller.createInclusion(type, days, weekday); const selection = controller.selected; const firstWeekday = selection.wdays[weekday]; @@ -120,23 +180,23 @@ describe('component vnZoneEvents', () => { expect(selection.type).toEqual('indefinitely'); expect(firstWeekday).toBeTruthy(); expect(controller.isNew).toBeTruthy(); - expect(controller.$.dialog.show).toHaveBeenCalledWith(); + expect(controller.$.includeDialog.show).toHaveBeenCalledWith(); }); - it('shoud set the selected property with the first day and then call the dialog show() method', () => { - controller.$.dialog = {show: jest.fn()}; + it('shoud set the selected property with the first day and then call the includeDialog show() method', () => { + controller.$.includeDialog = {show: jest.fn()}; const type = 'nonListedType'; const days = [new Date()]; const weekday = 1; - controller.create(type, days, weekday); + controller.createInclusion(type, days, weekday); const selection = controller.selected; expect(selection.type).toEqual('day'); expect(selection.dated).toEqual(days[0]); expect(controller.isNew).toBeTruthy(); - expect(controller.$.dialog.show).toHaveBeenCalledWith(); + expect(controller.$.includeDialog.show).toHaveBeenCalledWith(); }); }); @@ -180,6 +240,35 @@ describe('component vnZoneEvents', () => { }); }); + describe('onExcludeResponse()', () => { + it('should call the exclusionCreate() method', () => { + jest.spyOn(controller, 'exclusionCreate').mockReturnThis(); + + controller.excludeSelected = {type: 'all'}; + controller.onExcludeResponse('accept'); + + expect(controller.exclusionCreate).toHaveBeenCalledWith(); + }); + + it('should call the exclusionGeoCreate() method', () => { + jest.spyOn(controller, 'exclusionGeoCreate').mockReturnThis(); + + controller.excludeSelected = {type: 'specificLocations'}; + controller.onExcludeResponse('accept'); + + expect(controller.exclusionGeoCreate).toHaveBeenCalledWith(); + }); + + it('should call the exclusionDelete() method', () => { + jest.spyOn(controller, 'exclusionDelete').mockReturnThis(); + + controller.excludeSelected = {id: 1, type: 'all'}; + controller.onExcludeResponse('delete'); + + expect(controller.exclusionDelete).toHaveBeenCalledWith(controller.excludeSelected); + }); + }); + describe('onDeleteResponse()', () => { it('shoud make an HTTP DELETE query and then call the refresh() method', () => { jest.spyOn(controller, 'refresh').mockReturnThis(); @@ -197,9 +286,10 @@ describe('component vnZoneEvents', () => { it('shoud make an HTTP POST query and then call the refresh() method', () => { jest.spyOn(controller, 'refresh').mockReturnThis(); - const dates = [new Date()]; + controller.excludeSelected = {}; + controller.isNew = true; $httpBackend.expect('POST', `Zones/1/exclusions`).respond({id: 1}); - controller.exclusionCreate(dates); + controller.exclusionCreate(); $httpBackend.flush(); expect(controller.refresh).toHaveBeenCalledWith(); @@ -210,25 +300,41 @@ describe('component vnZoneEvents', () => { it('shoud make an HTTP DELETE query once and then call the refresh() method', () => { jest.spyOn(controller, 'refresh').mockReturnThis(); - const exclusions = [{id: 1}]; + const exclusions = {id: 1}; const firstExclusionId = 1; - $httpBackend.when('DELETE', `Zones/1/exclusions/${firstExclusionId}`).respond(200); + $httpBackend.expectDELETE(`Zones/1/exclusions/${firstExclusionId}`).respond(200); controller.exclusionDelete(exclusions); $httpBackend.flush(); expect(controller.refresh).toHaveBeenCalledWith(); }); + }); - it('shoud make an HTTP DELETE query for every event and then call the refresh() method', () => { - jest.spyOn(controller, 'refresh').mockReturnThis(); - jest.spyOn(controller.$http, 'delete').mockReturnValue(200); + describe('onSearch()', () => { + it('should call the applyFilter() method and then set the data', () => { + jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]); - const exclusions = [{id: 1}, {id: 2}, {id: 3}, {id: 4}]; - controller.exclusionDelete(exclusions); - $scope.$apply(); + controller.$.treeview = {}; + controller.$.model = crudModel; + controller.excludeSelected = {type: 'specificLocations'}; + controller._excludeSearch = 'es'; - expect(controller.$http.delete).toHaveBeenCalledTimes(4); - expect(controller.refresh).toHaveBeenCalledWith(); + controller.onSearch(); + const treeviewData = controller.$.treeview.data; + + expect(treeviewData).toBeDefined(); + expect(treeviewData.length).toEqual(3); + }); + }); + + describe('onFetch()', () => { + it('should call the applyFilter() method and then return the model data', () => { + jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]); + + controller.$.model = crudModel; + const result = controller.onFetch(); + + expect(result.length).toEqual(3); }); }); }); diff --git a/modules/zone/front/events/locale/es.yml b/modules/zone/front/events/locale/es.yml index eb581a719..1fb114720 100644 --- a/modules/zone/front/events/locale/es.yml +++ b/modules/zone/front/events/locale/es.yml @@ -4,3 +4,7 @@ Exclude: Excluir Events: Eventos Add event: Añadir evento Edit event: Editar evento +All: Todo +Specific locations: Localizaciones concretas +Locations where it is not distributed: Localizaciones en las que no se reparte +You must select a location: Debes seleccionar una localización diff --git a/modules/zone/front/events/style.scss b/modules/zone/front/events/style.scss new file mode 100644 index 000000000..49a6e87a6 --- /dev/null +++ b/modules/zone/front/events/style.scss @@ -0,0 +1,11 @@ +@import "variables"; + + .width{ + width: 600px + } + + .treeview{ + max-height: 300px; + overflow: auto; + } + diff --git a/modules/zone/front/location/style.scss b/modules/zone/front/location/style.scss index 2316a2622..24d685a51 100644 --- a/modules/zone/front/location/style.scss +++ b/modules/zone/front/location/style.scss @@ -1,19 +1,21 @@ @import "variables"; -vn-treeview-child { - .content > .vn-check:not(.indeterminate):not(.checked) { - color: $color-alert; +vn-zone-location { + vn-treeview-child { + .content > .vn-check:not(.indeterminate):not(.checked) { + color: $color-alert; - & > .btn { - border-color: $color-alert; + & > .btn { + border-color: $color-alert; + } + } + .content > .vn-check.checked { + color: $color-notice; + + & > .btn { + background-color: $color-notice; + border-color: $color-notice + } } } - .content > .vn-check.checked { - color: $color-notice; - - & > .btn { - background-color: $color-notice; - border-color: $color-notice - } - } -} \ No newline at end of file +} diff --git a/modules/zone/front/routes.json b/modules/zone/front/routes.json index e08f97314..7f67260da 100644 --- a/modules/zone/front/routes.json +++ b/modules/zone/front/routes.json @@ -85,10 +85,13 @@ "description": "Warehouses" }, { - "url": "/events", + "url": "/events?q", "state": "zone.card.events", "component": "vn-zone-events", - "description": "Calendar" + "description": "Calendar", + "params": { + "zone": "$ctrl.zone" + } }, { "url": "/location?q", diff --git a/package.json b/package.json index a10e445ed..13a7096d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "1.0.0", + "version": "8.6.0", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/methods/closure/closeAll.js b/print/methods/closure/closeAll.js index 7af3676f2..400ca2917 100644 --- a/print/methods/closure/closeAll.js +++ b/print/methods/closure/closeAll.js @@ -15,6 +15,7 @@ module.exports = async function(request, response, next) { SELECT t.id, t.clientFk, + t.companyFk, c.name clientName, c.email recipient, c.salesPersonFk, diff --git a/print/methods/closure/closeByAgency.js b/print/methods/closure/closeByAgency.js index 7807de23a..12e6b4c53 100644 --- a/print/methods/closure/closeByAgency.js +++ b/print/methods/closure/closeByAgency.js @@ -23,6 +23,7 @@ module.exports = async function(request, response, next) { SELECT t.id, t.clientFk, + t.companyFk, c.name clientName, c.email recipient, c.salesPersonFk, diff --git a/print/methods/closure/closeByRoute.js b/print/methods/closure/closeByRoute.js index 2c0bfd1eb..32a1688d3 100644 --- a/print/methods/closure/closeByRoute.js +++ b/print/methods/closure/closeByRoute.js @@ -17,6 +17,7 @@ module.exports = async function(request, response, next) { SELECT t.id, t.clientFk, + t.companyFk, c.name clientName, c.email recipient, c.salesPersonFk, diff --git a/print/methods/closure/closeByTicket.js b/print/methods/closure/closeByTicket.js index c71b3ecd0..f6d97cba3 100644 --- a/print/methods/closure/closeByTicket.js +++ b/print/methods/closure/closeByTicket.js @@ -16,6 +16,7 @@ module.exports = async function(request, response, next) { SELECT t.id, t.clientFk, + t.companyFk, c.name clientName, c.email recipient, c.salesPersonFk, diff --git a/print/methods/closure/closure.js b/print/methods/closure/closure.js index 57f21cf4c..67a2538e8 100644 --- a/print/methods/closure/closure.js +++ b/print/methods/closure/closure.js @@ -31,7 +31,7 @@ module.exports = { if (invoiceOut) { const args = Object.assign({ - invoiceId: invoiceOut.id, + refFk: invoiceOut.ref, recipientId: ticket.clientFk, recipient: ticket.recipient, replyTo: ticket.salesPersonEmail @@ -89,6 +89,37 @@ module.exports = { const email = new Email('delivery-note-link', args); await email.send(); } + + // Incoterms authorization + const {firstOrder} = await db.findOne(` + SELECT COUNT(*) as firstOrder + FROM ticket t + JOIN client c ON c.id = t.clientFk + WHERE t.clientFk = ? + AND NOT t.isDeleted + AND c.isVies + `, [ticket.clientFk]); + + if (firstOrder == 1) { + const args = Object.assign({ + ticketId: ticket.id, + recipientId: ticket.clientFk, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail + }, reqArgs); + + const email = new Email('incoterms-authorization', args); + await email.send(); + + const sample = await db.findOne( + `SELECT id + FROM sample + WHERE code = 'incoterms-authorization'`); + + await db.rawSql(` + INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?) + `, [ticket.clientFk, sample.id, ticket.companyFk]); + } } catch (error) { // Domain not found if (error.responseCode == 450) diff --git a/print/methods/csv/invoice/download.js b/print/methods/csv/invoice/download.js index 593d2d8d0..9cca99f2d 100644 --- a/print/methods/csv/invoice/download.js +++ b/print/methods/csv/invoice/download.js @@ -7,13 +7,13 @@ const sqlPath = path.join(__dirname, 'sql'); module.exports = async function(request, response, next) { try { const reqArgs = request.query; - if (!reqArgs.invoiceId) - throw new Error('The argument invoiceId is required'); + if (!reqArgs.refFk) + throw new Error('The argument refFk is required'); - const invoiceId = reqArgs.invoiceId; - const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const refFk = reqArgs.refFk; + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [refFk]); const content = toCSV(sales); - const fileName = `invoice_${invoiceId}.csv`; + const fileName = `invoice_${refFk}.csv`; response.setHeader('Content-type', 'text/csv'); response.setHeader('Content-Disposition', `inline; filename="${fileName}"`); diff --git a/print/methods/csv/invoice/send.js b/print/methods/csv/invoice/send.js index 919d7aeb1..2729e4a2b 100644 --- a/print/methods/csv/invoice/send.js +++ b/print/methods/csv/invoice/send.js @@ -8,22 +8,22 @@ const sqlPath = path.join(__dirname, 'sql'); module.exports = async function(request, response, next) { try { const reqArgs = request.query; - if (!reqArgs.invoiceId) - throw new Error('The argument invoiceId is required'); + if (!reqArgs.refFk) + throw new Error('The argument refFk is required'); - const invoiceId = reqArgs.invoiceId; - const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [invoiceId]); - const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [invoiceId]); + const refFk = reqArgs.refFk; + const invoice = await db.findOneFromDef(`${sqlPath}/invoice`, [refFk]); + const sales = await db.rawSqlFromDef(`${sqlPath}/sales`, [refFk]); const args = Object.assign({ - invoiceId: (String(invoice.id)), + refFk: invoice.refFk, recipientId: invoice.clientFk, recipient: invoice.recipient, replyTo: invoice.salesPersonEmail }, response.locals); const content = toCSV(sales); - const fileName = `invoice_${invoiceId}.csv`; + const fileName = `invoice_${refFk}.csv`; const email = new Email('invoice', args); await email.send({ overrideAttachments: true, diff --git a/print/methods/csv/invoice/sql/invoice.sql b/print/methods/csv/invoice/sql/invoice.sql index 853aaddc0..d484766a0 100644 --- a/print/methods/csv/invoice/sql/invoice.sql +++ b/print/methods/csv/invoice/sql/invoice.sql @@ -6,4 +6,5 @@ SELECT FROM invoiceOut io JOIN client c ON c.id = io.clientFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/methods/csv/invoice/sql/sales.sql b/print/methods/csv/invoice/sql/sales.sql index 34b5af1f7..33fe62476 100644 --- a/print/methods/csv/invoice/sql/sales.sql +++ b/print/methods/csv/invoice/sql/sales.sql @@ -31,5 +31,5 @@ FROM sale s AND itc.countryFk = s2.countryFk JOIN taxClass tc ON tc.id = itc.taxClassFk JOIN invoiceOut io ON io.ref = t.refFk -WHERE io.id = ? +WHERE t.refFk = ? ORDER BY s.ticketFk, s.created \ No newline at end of file diff --git a/print/methods/schedule/invoice.js b/print/methods/schedule/invoice.js index c76ca85b5..87c696075 100644 --- a/print/methods/schedule/invoice.js +++ b/print/methods/schedule/invoice.js @@ -38,7 +38,7 @@ module.exports = async function(request, response, next) { connection.query('START TRANSACTION'); const args = Object.assign({ - invoiceId: invoiceOut.id, + refFk: invoiceOut.ref, recipientId: invoiceOut.clientFk, recipient: invoiceOut.recipient, replyTo: invoiceOut.salesPersonEmail @@ -93,11 +93,12 @@ module.exports = async function(request, response, next) { await email.send(mailOptions); } // Update queue status + const date = new Date(); sql = `UPDATE invoiceOut_queue SET status = "printed", - printed = NOW() + printed = ? WHERE invoiceFk = ?`; - connection.query(sql, [invoiceOut.id]); + connection.query(sql, [date, invoiceOut.id]); connection.query('COMMIT'); } catch (error) { connection.query('ROLLBACK'); diff --git a/print/templates/email/incoterms-authorization/assets/css/import.js b/print/templates/email/incoterms-authorization/assets/css/import.js new file mode 100644 index 000000000..b44d6bd37 --- /dev/null +++ b/print/templates/email/incoterms-authorization/assets/css/import.js @@ -0,0 +1,8 @@ +const Stylesheet = require(`${appPath}/core/stylesheet`); + +module.exports = new Stylesheet([ + `${appPath}/common/css/spacing.css`, + `${appPath}/common/css/misc.css`, + `${appPath}/common/css/layout.css`, + `${appPath}/common/css/email.css`]) + .mergeStyles(); diff --git a/print/templates/email/incoterms-authorization/attachments.json b/print/templates/email/incoterms-authorization/attachments.json new file mode 100644 index 000000000..9dfd945db --- /dev/null +++ b/print/templates/email/incoterms-authorization/attachments.json @@ -0,0 +1,6 @@ +[ + { + "filename": "incoterms-authorization.pdf", + "component": "incoterms-authorization" + } +] \ No newline at end of file diff --git a/print/templates/email/incoterms-authorization/incoterms-authorization.html b/print/templates/email/incoterms-authorization/incoterms-authorization.html new file mode 100644 index 000000000..5dd0d7da6 --- /dev/null +++ b/print/templates/email/incoterms-authorization/incoterms-authorization.html @@ -0,0 +1,57 @@ + + + + + + {{ $t('subject') }} + + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+

{{ $t('title') }}

+

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

+

{{$t('description.instructions')}}

+

{{$t('description.conclusion')}}

+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/print/templates/email/incoterms-authorization/incoterms-authorization.js b/print/templates/email/incoterms-authorization/incoterms-authorization.js new file mode 100755 index 000000000..bac40d331 --- /dev/null +++ b/print/templates/email/incoterms-authorization/incoterms-authorization.js @@ -0,0 +1,27 @@ +const Component = require(`${appPath}/core/component`); +const emailHeader = new Component('email-header'); +const emailFooter = new Component('email-footer'); +const attachment = new Component('attachment'); +const attachments = require('./attachments.json'); + +module.exports = { + name: 'incoterms-authorization', + data() { + return {attachments}; + }, + components: { + 'email-header': emailHeader.build(), + 'email-footer': emailFooter.build(), + 'attachment': attachment.build() + }, + props: { + recipientId: { + type: [Number, String], + required: true + }, + companyId: { + type: [Number, String], + required: true + } + } +}; diff --git a/print/templates/email/incoterms-authorization/locale/es.yml b/print/templates/email/incoterms-authorization/locale/es.yml new file mode 100644 index 000000000..e8ce679fc --- /dev/null +++ b/print/templates/email/incoterms-authorization/locale/es.yml @@ -0,0 +1,6 @@ +subject: Autorización incoterms +title: Autorización incoterms +description: + dear: Estimado cliente + instructions: A continuación le adjuntamos la autorización incoterms que deberá entregar rellenada y firmada. + conclusion: ¡Gracias por su atención! \ No newline at end of file diff --git a/print/templates/email/incoterms-authorization/locale/fr.yml b/print/templates/email/incoterms-authorization/locale/fr.yml new file mode 100644 index 000000000..a2dc6fe3a --- /dev/null +++ b/print/templates/email/incoterms-authorization/locale/fr.yml @@ -0,0 +1,6 @@ +subject: Autorisation Incoterm +title: Autorisation Incoterm +description: + dear: Chers clients + instructions: Veuillez trouver ci-joint l'autorisation des INCOTERMS, une fois le document rempli doit être signé et renvoyé par e-mail. + conclusion: Dans l'attente de votre réponse, nous vous prions d'agréer, Madame, Monsieur, nos salutations distinguées. \ No newline at end of file diff --git a/print/templates/email/incoterms-authorization/locale/pt.yml b/print/templates/email/incoterms-authorization/locale/pt.yml new file mode 100644 index 000000000..bdf25ed0b --- /dev/null +++ b/print/templates/email/incoterms-authorization/locale/pt.yml @@ -0,0 +1,6 @@ +subject: Autorização do Incoterm +title: Autorização do Incoterm +description: + dear: Estimado cliente + instructions: Abaixo anexamos a autorização dos incoterms que deves preencher e reenviar-nos. + conclusion: Obrigado pela atenção. \ No newline at end of file diff --git a/print/templates/email/invoice/invoice.js b/print/templates/email/invoice/invoice.js index d92b65cb3..6f6ea8683 100755 --- a/print/templates/email/invoice/invoice.js +++ b/print/templates/email/invoice/invoice.js @@ -5,11 +5,11 @@ const emailFooter = new Component('email-footer'); module.exports = { name: 'invoice', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, }, components: { @@ -17,7 +17,7 @@ module.exports = { 'email-footer': emailFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/email/invoice/sql/invoice.sql b/print/templates/email/invoice/sql/invoice.sql index 195621a36..b6f845fb0 100644 --- a/print/templates/email/invoice/sql/invoice.sql +++ b/print/templates/email/invoice/sql/invoice.sql @@ -4,4 +4,4 @@ SELECT FROM invoiceOut io JOIN ticket t ON t.refFk = io.ref JOIN client c ON c.id = io.clientFk -WHERE io.id = ? \ No newline at end of file +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/email/letter-debtor-st/letter-debtor-st.html b/print/templates/email/letter-debtor-st/letter-debtor-st.html index c78657ec7..364e79d56 100644 --- a/print/templates/email/letter-debtor-st/letter-debtor-st.html +++ b/print/templates/email/letter-debtor-st/letter-debtor-st.html @@ -23,7 +23,7 @@
-

{{ $t('title') }} {{$i18n.locale}}

+

{{ $t('title') }}

{{ $t('sections.introduction.title') }},

{{ $t('sections.introduction.description') }}

diff --git a/print/templates/email/letter-debtor-st/locale/pt.yml b/print/templates/email/letter-debtor-st/locale/pt.yml new file mode 100644 index 000000000..6f6082345 --- /dev/null +++ b/print/templates/email/letter-debtor-st/locale/pt.yml @@ -0,0 +1,16 @@ +subject: Aviso inicial por saldo devedor +title: Aviso inicial por saldo devedor +sections: + introduction: + title: Estimado cliente + description: Através do presente escrito comunicamos-lhe que, segundo os nossos dados contáveis, + a sua conta tem um saldo pendente de liquidar. +checkExtract: Solicitamos-lhe que comprove que o extrato anexado corresponde com os dados que dispõe. + O nosso departamento de administração aclarará qualquer dúvida que possa ter, + e igualmente lhe facilitará qualquer documento que solicite. +checkValidData: Se ao comprovar os dados aportados, resultam corretos, rogamos que proceda com a regularizar a situação +payMethod: Se não deseja acudir pessoalmente às nossas oficinas, pode realizar o pagamento mediante + transferência bancaria à conta que figura ao pé do comunicado, a indicar o seu número de cliente, + ou bem pode realizar o pagamento através do nosso sítio web. +conclusion: De antemão agradecemos-lhe a sua amável colaboração. +transferAccount: Dados para transferência bancária diff --git a/print/templates/reports/cmr-authorization/sql/ticket.sql b/print/templates/reports/cmr-authorization/sql/ticket.sql deleted file mode 100644 index c6c994f7d..000000000 --- a/print/templates/reports/cmr-authorization/sql/ticket.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - t.id, - t.clientFk, - cty.country, - w.name AS warehouse -FROM ticket t - JOIN warehouse w ON w.id = t.warehouseFk - JOIN address a ON a.id = t.addressFk - JOIN province p ON p.id = a.provinceFk - JOIN autonomy au ON au.id = p.autonomyFk - JOIN country cty ON cty.id = au.countryFk -WHERE t.id = ? \ No newline at end of file diff --git a/print/templates/reports/driver-route/driver-route.html b/print/templates/reports/driver-route/driver-route.html index 5d840958d..600c49970 100644 --- a/print/templates/reports/driver-route/driver-route.html +++ b/print/templates/reports/driver-route/driver-route.html @@ -141,9 +141,8 @@ -
+

{{ticket.description}}

-

{{$t('stowaway')}}: {{ticket.shipFk}}

diff --git a/print/templates/reports/driver-route/locale/es.yml b/print/templates/reports/driver-route/locale/es.yml index 4c922ba64..3fb6b6885 100644 --- a/print/templates/reports/driver-route/locale/es.yml +++ b/print/templates/reports/driver-route/locale/es.yml @@ -20,7 +20,6 @@ phone: Teléfono warehouse: Almacén salesPerson: Comercial import: Importe -stowaway: Encajado dentro del ticket route: Ruta routeId: Ruta {0} ticket: Ticket \ No newline at end of file diff --git a/print/templates/reports/driver-route/sql/tickets.sql b/print/templates/reports/driver-route/sql/tickets.sql index 8806a0473..09e73819b 100644 --- a/print/templates/reports/driver-route/sql/tickets.sql +++ b/print/templates/reports/driver-route/sql/tickets.sql @@ -17,7 +17,6 @@ SELECT 0 AS import, am.name ticketAgency, tob.description, - s.shipFk, u.nickName salesPersonName, ipkg.itemPackingTypes FROM route r @@ -30,7 +29,6 @@ FROM route r LEFT JOIN province p ON a.provinceFk = p.id LEFT JOIN warehouse wh ON wh.id = t.warehouseFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk - LEFT JOIN stowaway s ON s.id = t.id LEFT JOIN ( SELECT t.id AS ticketFk, GROUP_CONCAT(DISTINCT(i.itemPackingTypeFk)) AS itemPackingTypes diff --git a/print/templates/reports/exportation/exportation.js b/print/templates/reports/exportation/exportation.js index fbf663249..a7e018c48 100755 --- a/print/templates/reports/exportation/exportation.js +++ b/print/templates/reports/exportation/exportation.js @@ -5,14 +5,14 @@ const reportFooter = new Component('report-footer'); module.exports = { name: 'exportation', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); if (!this.invoice) throw new Error('Something went wrong'); }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); } }, computed: { @@ -27,7 +27,7 @@ module.exports = { 'report-footer': reportFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/reports/exportation/sql/invoice.sql b/print/templates/reports/exportation/sql/invoice.sql index 8e92333dd..7ea55e481 100644 --- a/print/templates/reports/exportation/sql/invoice.sql +++ b/print/templates/reports/exportation/sql/invoice.sql @@ -3,4 +3,5 @@ SELECT io.ref, io.issued FROM invoiceOut io -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/extra-community/extra-community.js b/print/templates/reports/extra-community/extra-community.js index 9843d6bb7..4dad5dfff 100755 --- a/print/templates/reports/extra-community/extra-community.js +++ b/print/templates/reports/extra-community/extra-community.js @@ -12,7 +12,7 @@ module.exports = { shippedFrom: this.shippedStart, continent: this.continent, id: this.id, - agencyFk: this.agencyFk, + agencyModeFk: this.agencyModeFk, warehouseInFk: this.warehouseInFk, warehouseOutFk: this.warehouseOutFk, totalEntries: this.totalEntries, @@ -68,7 +68,7 @@ module.exports = { return {'t.ref': {like: `%${value}%`}}; case 'id': return `t.id = ${value}`; - case 'agencyFk': + case 'agencyModeFk': return `am.id = ${value}`; case 'warehouseOutFk': return `wo.id = ${value}`; @@ -101,7 +101,7 @@ module.exports = { 'continent', 'ref', 'id', - 'agencyFk', + 'agencyModeFk', 'warehouseOutFk', 'warehouseInFk', 'totalEntries', diff --git a/print/templates/reports/extra-community/sql/travels.sql b/print/templates/reports/extra-community/sql/travels.sql index 167c8b1f8..f8a4e0142 100644 --- a/print/templates/reports/extra-community/sql/travels.sql +++ b/print/templates/reports/extra-community/sql/travels.sql @@ -19,4 +19,4 @@ FROM travel t JOIN warehouse wo ON wo.id = t.warehouseOutFk JOIN country c ON c.id = wo.countryFk LEFT JOIN continent cnt ON cnt.id = c.continentFk - JOIN agencyMode am ON am.id = t.agencyFk \ No newline at end of file + JOIN agencyMode am ON am.id = t.agencyModeFk \ No newline at end of file diff --git a/print/templates/reports/cmr-authorization/assets/css/import.js b/print/templates/reports/incoterms-authorization/assets/css/import.js similarity index 100% rename from print/templates/reports/cmr-authorization/assets/css/import.js rename to print/templates/reports/incoterms-authorization/assets/css/import.js diff --git a/print/templates/reports/cmr-authorization/assets/css/style.css b/print/templates/reports/incoterms-authorization/assets/css/style.css similarity index 69% rename from print/templates/reports/cmr-authorization/assets/css/style.css rename to print/templates/reports/incoterms-authorization/assets/css/style.css index adfe4b4c9..34545c951 100644 --- a/print/templates/reports/cmr-authorization/assets/css/style.css +++ b/print/templates/reports/incoterms-authorization/assets/css/style.css @@ -2,13 +2,8 @@ font-size: 1.2em } -.signature .dummy-signature { - width: 400px; - height: 190px; - display: block; - content: ''; - overflow: hidden; - clear:both +.signature { + margin-top: 100px } .signature img { diff --git a/print/templates/reports/cmr-authorization/assets/images/signature.png b/print/templates/reports/incoterms-authorization/assets/images/signature.png similarity index 100% rename from print/templates/reports/cmr-authorization/assets/images/signature.png rename to print/templates/reports/incoterms-authorization/assets/images/signature.png diff --git a/print/templates/reports/cmr-authorization/cmr-authorization.html b/print/templates/reports/incoterms-authorization/incoterms-authorization.html similarity index 72% rename from print/templates/reports/cmr-authorization/cmr-authorization.html rename to print/templates/reports/incoterms-authorization/incoterms-authorization.html index 2d8342a47..27cad97b1 100644 --- a/print/templates/reports/cmr-authorization/cmr-authorization.html +++ b/print/templates/reports/incoterms-authorization/incoterms-authorization.html @@ -23,9 +23,10 @@

@@ -53,13 +54,13 @@

-

{{$t('signature')}}

+

{{ company.name }}

-

Juan Vicente Ferrer Roig
-
Director
+
{{company.manager}}
+
{{$t('manager')}}

{{$t('issued', [ - 'Algemesí', + company.city, issued.getDate(), $t('months')[issued.getMonth()], issued.getFullYear()]) @@ -68,22 +69,6 @@

- diff --git a/print/templates/reports/cmr-authorization/cmr-authorization.js b/print/templates/reports/incoterms-authorization/incoterms-authorization.js similarity index 69% rename from print/templates/reports/cmr-authorization/cmr-authorization.js rename to print/templates/reports/incoterms-authorization/incoterms-authorization.js index 1adc75fa6..656a9d7b0 100755 --- a/print/templates/reports/cmr-authorization/cmr-authorization.js +++ b/print/templates/reports/incoterms-authorization/incoterms-authorization.js @@ -3,13 +3,12 @@ const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); module.exports = { - name: 'cmr-authorization', + name: 'incoterms-authorization', async serverPrefetch() { - this.ticket = await this.findOneFromDef('ticket', [this.ticketId]); - if (!this.ticket) + this.client = await this.findOneFromDef('client', [this.recipientId]); + this.company = await this.findOneFromDef('company', [this.companyId]); + if (!this.client) throw new Error('Something went wrong'); - - this.client = await this.findOneFromDef('client', [this.ticket.clientFk]); }, computed: { issued: function() { @@ -21,7 +20,11 @@ module.exports = { 'report-footer': reportFooter.build() }, props: { - ticketId: { + recipientId: { + type: [Number, String], + required: true + }, + companyId: { type: [Number, String], required: true } diff --git a/print/templates/reports/cmr-authorization/locale/es.yml b/print/templates/reports/incoterms-authorization/locale/es.yml similarity index 58% rename from print/templates/reports/cmr-authorization/locale/es.yml rename to print/templates/reports/incoterms-authorization/locale/es.yml index 37e40202d..6936ebda9 100644 --- a/print/templates/reports/cmr-authorization/locale/es.yml +++ b/print/templates/reports/incoterms-authorization/locale/es.yml @@ -1,32 +1,32 @@ -reportName: autorizacion-cmr +reportName: autorizacion-incoterms description: '{socialName} una sociedad debidamente constituida con responsabilidad limitada y registrada conforme al derecho de sociedades de {country} y aquí representada por ___________________. {socialName}, con domicilio en {address}, CIF {fiscalID}. En adelante denominada {name}.' issued: 'En {0}, a {1} de {2} de {3}' -client: 'Client {0}' +client: 'Cliente {0}' declaration: '{socialName} declara por la presente que:' declarations: - - 'Todas las compras realizadas por {socialName} con Verdnatura Levante, S.L. se -entregan, Ex Works (Incoterms), en el almacén de Verdnatura Levante, S.L. situado en -{destinationWarehouse}.' - - '{socialName} reconoce que es importante para Verdnatura Levante, S.L. tener + - 'Todas las compras realizadas por {socialName} con {companyName} se +entregan, Ex Works (Incoterms), en el almacén de {companyName} situado en +{companyCity}.' + - '{socialName} reconoce que es importante para {companyName} tener comprobante de la entrega intracomunitaria de la mercancía a {destinationCountry} para poder facturar con 0% de IVA.' - 'Por tanto, al firmar este acuerdo, {socialName} declara que todos los bienes que -se compren a Verdnatura Levante, S.L. serán entregados a {destinationCountry}.' - - 'Además, {socialName} deberá, a primera solicitud de Verdnatura Levante, S.L., -proporcionar una prueba de que todos los productos comprados a Verdnatura Levante, S.L. han +se compren a {companyName} serán entregados a {destinationCountry}.' + - 'Además, {socialName} deberá, a primera solicitud de {companyName}, +proporcionar una prueba de que todos los productos comprados a {companyName} han sido entregados en {destinationCountry}.' - - 'Además de lo anterior, Verdnatura Levante, S.L. proporcionará a {socialName} + - 'Además de lo anterior, {companyName} proporcionará a {socialName} un resumen mensual en el que se incluyen todas las facturas (y las entregas correspondientes). -{socialName} firmará y devolverá el resumen mensual a Verdnatura Levante, +{socialName} firmará y devolverá el resumen mensual a {companyName}, S.L. dentro de los 5 días posteriores a la recepción del resumen.' -signature: Verdnatura Levante, S.L. signer: representative: Representante representativeRole: Cargo del representante signed: Fecha de firma +manager: Gerente months: - 'Enero' - 'Febrero' diff --git a/print/templates/reports/incoterms-authorization/locale/fr.yml b/print/templates/reports/incoterms-authorization/locale/fr.yml new file mode 100644 index 000000000..f1aeb0eff --- /dev/null +++ b/print/templates/reports/incoterms-authorization/locale/fr.yml @@ -0,0 +1,38 @@ +reportName: autorizacion-incoterms +description: '{socialName} une société dûment constituée à responsabilité limitée +et enregistrée en vertu du droit des sociétés de {country} et représentée aux présentes par +___________________. {socialName}, ayant son siège social {address}, +CIF {fiscalID}. Ci-après dénommé {name}.' +issued: 'A {0}, le {1} {2} {3}' +client: 'Client {0}' +declaration: '{socialName} déclare par la présente que:' +declarations: + - "Tous les achats réalisés par {socialName} avec {companyName} +sont livrés, Ex Works (Incoterms), sur l'entrepôt de {companyName} situé à {companyCity}." + - "{socialName} reconnaît qu'il est important pou {companyName} d'avoir +la preuve de la livraison intracommunautaire des biens à {destinationCountry} afin de pouvoir facturer à 0% de TVA." + - 'Par conséquent, en signant cet accord, {socialName} déclare que tous les biens achetés à {companyName} seront livrés à {destinationCountry}.' + - 'En outre, {socialName} doit, à la première demande de {companyName}, +fournir la preuve que tous les biens achetés à {companyName} ont été livrés à {destinationCountry}.' + - 'En plus de ce qui précède, {companyName} fournira à {socialName} +un résumé mensuel comprenant toutes les factures (et les livraisons correspondantes). +{socialName} signera et retournera le résumé mensuel à {companyName}, +dans les 5 jours suivant la réception du résumé.' +signer: + representative: Représentant + representativeRole: Position du représentant + signed: Date de la signature +manager: Gérente +months: + - 'Janvier' + - 'Fevrier' + - 'Mars' + - 'Avril' + - 'Mai' + - 'Juin' + - 'Juillet' + - 'Août' + - 'Septembre' + - 'Octobre' + - 'Novembre' + - 'Decembre' \ No newline at end of file diff --git a/print/templates/reports/incoterms-authorization/locale/pt.yml b/print/templates/reports/incoterms-authorization/locale/pt.yml new file mode 100644 index 000000000..2d33e6a1a --- /dev/null +++ b/print/templates/reports/incoterms-authorization/locale/pt.yml @@ -0,0 +1,42 @@ +reportName: autorizacion-incoterms +description: '{socialName} uma sociedade devidamente constituída com responsabilidade limitada e registada +conforme ao direito de sociedades da {country} e aqui representada por +___________________. {socialName}, com domicílio em {address}, +CIF {fiscalID}. Em adiante denominada {name}.' +issued: 'Em {0}, em {1} de {2} de {3}' +client: 'Cliente {0}' +declaration: '{socialName} declara através da presente que:' +declarations: + - 'Todas as compras realizadas por {socialName} a {companyName} se entregam, + Ex Works (Incoterms), no armazém da {companyName} situado em +{companyCity}.' + - '{socialName} reconhece ser importante para {companyName} + ter o comprovante da entrega intracomunitária da mercadoria a {destinationCountry} + para poder faturar com 0% de IVA.' + - 'Portanto, ao assinar este acordo, {socialName} declara que todos os bens + que se comprem na {companyName} serão entregues na {destinationCountry}.' + - 'Além disto, {socialName} deverá, na primeira solicitude da {companyName}, + proporcionar uma prova de que todos os produtos comprados na {companyName} + foram entregues na {destinationCountry}.' + - 'Além do anterio, {companyName} proporcionará a {socialName} +um resumo mensal onde se incluem todas as faturas (e as entregas correspondentes). +{socialName} assinará e devolverá o resumo mensal à {companyName}, +dentro dos 5 dias posteriores à receção do resumo.' +signer: + representative: Representante + representativeRole: Cargo de representante + signed: Data da assinatura +manager: Gerente +months: + - 'Janeiro' + - 'Fevereiro' + - 'Marchar' + - 'abril' + - 'Poderia' + - 'Junho' + - 'Julho' + - 'Agosto' + - 'Setembro' + - 'Outubro' + - 'Novembro' + - 'Dezembro' \ No newline at end of file diff --git a/print/templates/reports/cmr-authorization/sql/client.sql b/print/templates/reports/incoterms-authorization/sql/client.sql similarity index 100% rename from print/templates/reports/cmr-authorization/sql/client.sql rename to print/templates/reports/incoterms-authorization/sql/client.sql diff --git a/print/templates/reports/incoterms-authorization/sql/company.sql b/print/templates/reports/incoterms-authorization/sql/company.sql new file mode 100644 index 000000000..39c3290d1 --- /dev/null +++ b/print/templates/reports/incoterms-authorization/sql/company.sql @@ -0,0 +1,8 @@ +SELECT + s.name, + s.city, + cl.name AS manager +FROM company c + JOIN supplier s ON s.id = c.id + JOIN client cl ON cl.id = c.workerManagerFk +WHERE c.id = ? \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.js b/print/templates/reports/invoice-incoterms/invoice-incoterms.js index 99e23e15f..99a4e2e73 100755 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.js +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.js @@ -5,9 +5,9 @@ const reportFooter = new Component('report-footer'); module.exports = { name: 'invoice-incoterms', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); - this.client = await this.fetchClient(this.invoiceId); - this.incoterms = await this.fetchIncoterms(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); + this.client = await this.fetchClient(this.refFk); + this.incoterms = await this.fetchIncoterms(this.refFk); if (!this.invoice) throw new Error('Something went wrong'); @@ -16,14 +16,14 @@ module.exports = { }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, - fetchClient(invoiceId) { - return this.findOneFromDef('client', [invoiceId]); + fetchClient(refFk) { + return this.findOneFromDef('client', [refFk]); }, - fetchIncoterms(invoiceId) { - return this.findOneFromDef('incoterms', [invoiceId, invoiceId, invoiceId]); + fetchIncoterms(refFk) { + return this.findOneFromDef('incoterms', [refFk, refFk, refFk]); } }, components: { @@ -31,7 +31,7 @@ module.exports = { 'report-footer': reportFooter.build() }, props: { - invoiceId: { + refFk: { type: [Number, String], required: true } diff --git a/print/templates/reports/invoice-incoterms/sql/client.sql b/print/templates/reports/invoice-incoterms/sql/client.sql index dd6035222..3e66c15c9 100644 --- a/print/templates/reports/invoice-incoterms/sql/client.sql +++ b/print/templates/reports/invoice-incoterms/sql/client.sql @@ -9,4 +9,5 @@ FROM vn.invoiceOut io JOIN vn.country cty ON cty.id = c.countryFk LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial AND ios.taxAreaFk = 'CEE' -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/sql/incoterms.sql b/print/templates/reports/invoice-incoterms/sql/incoterms.sql index 5d894a4b2..6bb895129 100644 --- a/print/templates/reports/invoice-incoterms/sql/incoterms.sql +++ b/print/templates/reports/invoice-incoterms/sql/incoterms.sql @@ -51,7 +51,7 @@ SELECT io.issued, FROM vn.invoiceOut io JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.saleVolume sv ON sv.ticketFk = t.id - WHERE io.id = ? + WHERE t.refFk = ? ) sub2 ON TRUE JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk @@ -66,6 +66,7 @@ SELECT io.issued, JOIN vn.sale s ON t.id = s.ticketFk JOIN vn.item i ON i.id = s.itemFk JOIN vn.intrastat ir ON ir.id = i.intrastatFk - WHERE io.id = ? + WHERE t.refFk = ? )sub3 ON TRUE - WHERE io.id = ? + WHERE t.refFk = ? + diff --git a/print/templates/reports/invoice-incoterms/sql/invoice.sql b/print/templates/reports/invoice-incoterms/sql/invoice.sql index b9a929183..571ea2af3 100644 --- a/print/templates/reports/invoice-incoterms/sql/invoice.sql +++ b/print/templates/reports/invoice-incoterms/sql/invoice.sql @@ -14,4 +14,5 @@ FROM invoiceOut io JOIN company cny ON cny.id = io.companyFk JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index c5abfad7e..fd4acd4b5 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -7,15 +7,15 @@ const invoiceIncoterms = new Report('invoice-incoterms'); module.exports = { name: 'invoice', async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.invoiceId); - this.client = await this.fetchClient(this.invoiceId); - this.taxes = await this.fetchTaxes(this.invoiceId); - this.intrastat = await this.fetchIntrastat(this.invoiceId); - this.rectified = await this.fetchRectified(this.invoiceId); - this.hasIncoterms = await this.fetchHasIncoterms(this.invoiceId); + this.invoice = await this.fetchInvoice(this.refFk); + this.client = await this.fetchClient(this.refFk); + this.taxes = await this.fetchTaxes(this.refFk); + this.intrastat = await this.fetchIntrastat(this.refFk); + this.rectified = await this.fetchRectified(this.refFk); + this.hasIncoterms = await this.fetchHasIncoterms(this.refFk); - const tickets = await this.fetchTickets(this.invoiceId); - const sales = await this.fetchSales(this.invoiceId); + const tickets = await this.fetchTickets(this.refFk); + const sales = await this.fetchSales(this.refFk); const map = new Map(); @@ -65,29 +65,29 @@ module.exports = { } }, methods: { - fetchInvoice(invoiceId) { - return this.findOneFromDef('invoice', [invoiceId]); + fetchInvoice(refFk) { + return this.findOneFromDef('invoice', [refFk]); }, - fetchClient(invoiceId) { - return this.findOneFromDef('client', [invoiceId]); + fetchClient(refFk) { + return this.findOneFromDef('client', [refFk]); }, - fetchTickets(invoiceId) { - return this.rawSqlFromDef('tickets', [invoiceId]); + fetchTickets(refFk) { + return this.rawSqlFromDef('tickets', [refFk]); }, - fetchSales(invoiceId) { - return this.rawSqlFromDef('sales', [invoiceId, invoiceId]); + fetchSales(refFk) { + return this.rawSqlFromDef('sales', [refFk, refFk]); }, - fetchTaxes(invoiceId) { - return this.rawSqlFromDef(`taxes`, [invoiceId]); + fetchTaxes(refFk) { + return this.rawSqlFromDef(`taxes`, [refFk]); }, - fetchIntrastat(invoiceId) { - return this.rawSqlFromDef(`intrastat`, [invoiceId]); + fetchIntrastat(refFk) { + return this.rawSqlFromDef(`intrastat`, [refFk, refFk, refFk]); }, - fetchRectified(invoiceId) { - return this.rawSqlFromDef(`rectified`, [invoiceId]); + fetchRectified(refFk) { + return this.rawSqlFromDef(`rectified`, [refFk]); }, - fetchHasIncoterms(invoiceId) { - return this.findValueFromDef(`hasIncoterms`, [invoiceId]); + fetchHasIncoterms(refFk) { + return this.findValueFromDef(`hasIncoterms`, [refFk]); }, saleImport(sale) { const price = sale.quantity * sale.price; @@ -115,9 +115,8 @@ module.exports = { 'invoice-incoterms': invoiceIncoterms.build() }, props: { - invoiceId: { - type: [Number, String], - required: true + refFk: { + type: String } } }; diff --git a/print/templates/reports/invoice/sql/client.sql b/print/templates/reports/invoice/sql/client.sql index dd6035222..4c35838f2 100644 --- a/print/templates/reports/invoice/sql/client.sql +++ b/print/templates/reports/invoice/sql/client.sql @@ -9,4 +9,5 @@ FROM vn.invoiceOut io JOIN vn.country cty ON cty.id = c.countryFk LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial AND ios.taxAreaFk = 'CEE' -WHERE io.id = ? \ No newline at end of file + LEFT JOIN vn.ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/hasIncoterms.sql b/print/templates/reports/invoice/sql/hasIncoterms.sql index 27f61f57c..40a6db384 100644 --- a/print/templates/reports/invoice/sql/hasIncoterms.sql +++ b/print/templates/reports/invoice/sql/hasIncoterms.sql @@ -3,6 +3,6 @@ SELECT IF(incotermsFk IS NULL, FALSE, TRUE) AS hasIncoterms JOIN invoiceOut io ON io.ref = t.refFk JOIN client c ON c.id = t.clientFk JOIN address a ON a.id = t.addressFk - WHERE io.id = ? + WHERE t.refFk = ? AND IF(c.hasToinvoiceByAddress = FALSE, c.defaultAddressFk, TRUE) LIMIT 1 \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index 6bf72c158..e2ee47667 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,18 +1,22 @@ -SELECT - ir.id AS code, - ir.description AS description, - CAST(SUM(IFNULL(i.stems,1) * s.quantity) AS DECIMAL(10,2)) as stems, - CAST(SUM(IF(sv.physicalWeight, sv.physicalWeight, i.density * sub.cm3delivery/1000000)) AS DECIMAL(10,2)) netKg, - CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) AS subtotal - FROM vn.sale s - LEFT JOIN (SELECT ic.itemFk, ic.cm3, ic.cm3delivery - FROM vn.itemCost ic - WHERE ic.cm3 - GROUP BY ic.itemFk) sub ON s.itemFk = sub.itemFk - LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id - LEFT JOIN vn.ticket t ON t.id = s.ticketFk - LEFT JOIN vn.invoiceOut io ON io.ref = t.refFk - LEFT JOIN vn.item i ON i.id = s.itemFk - JOIN vn.intrastat ir ON ir.id = i.intrastatFk - WHERE io.id = ? - GROUP BY i.intrastatFk; \ No newline at end of file +SELECT + ir.id code, + ir.description description, + CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, + CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * + IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, + CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal + FROM vn.ticket t + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk + JOIN vn.intrastat ir ON ir.id = i.intrastatFk + LEFT JOIN ( + SELECT t2.weight + FROM vn.ticket t2 + WHERE refFk = ? AND weight + LIMIT 1 + ) sub ON TRUE + WHERE t.refFk = ? + AND i.intrastatFk + GROUP BY i.intrastatFk + ORDER BY i.intrastatFk; \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/invoice.sql b/print/templates/reports/invoice/sql/invoice.sql index aacbb0016..0f12e4f53 100644 --- a/print/templates/reports/invoice/sql/invoice.sql +++ b/print/templates/reports/invoice/sql/invoice.sql @@ -13,4 +13,5 @@ FROM invoiceOut io JOIN company cny ON cny.id = io.companyFk JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial -WHERE io.id = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/rectified.sql b/print/templates/reports/invoice/sql/rectified.sql index 1255b973c..ea814a05a 100644 --- a/print/templates/reports/invoice/sql/rectified.sql +++ b/print/templates/reports/invoice/sql/rectified.sql @@ -6,4 +6,5 @@ SELECT FROM vn.invoiceCorrection ic JOIN vn.invoiceOut io ON io.id = ic.correctedFk JOIN vn.invoiceCorrectionType ict ON ict.id = ic.invoiceCorrectionTypeFk -where ic.correctingFk = ? \ No newline at end of file + LEFT JOIN ticket t ON t.refFk = io.ref +WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql index cff8794db..f5721a594 100644 --- a/print/templates/reports/invoice/sql/sales.sql +++ b/print/templates/reports/invoice/sql/sales.sql @@ -37,7 +37,7 @@ SELECT JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk - WHERE io.id = ? + WHERE t.refFk = ? UNION ALL SELECT io.ref, @@ -69,4 +69,4 @@ SELECT JOIN vn.company co ON co.id = io.companyFk JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk JOIN vn.taxClass tc ON tc.id = ts.taxClassFk - WHERE io.id = ? \ No newline at end of file + WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/taxes.sql b/print/templates/reports/invoice/sql/taxes.sql index 19b1cc00e..2203d8b8a 100644 --- a/print/templates/reports/invoice/sql/taxes.sql +++ b/print/templates/reports/invoice/sql/taxes.sql @@ -6,6 +6,7 @@ SELECT FROM invoiceOutTax iot JOIN pgc ON pgc.code = iot.pgcFk LEFT JOIN pgcEqu pe ON pe.equFk = pgc.code - JOIN invoiceOut io ON io.id = iot.invoiceOutFk - WHERE invoiceOutFk = ? + JOIN invoiceOut io ON io.id = iot.invoiceOutFk + LEFT JOIN ticket t ON t.refFk = io.ref + WHERE t.refFk = ? ORDER BY iot.id \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/tickets.sql b/print/templates/reports/invoice/sql/tickets.sql index 7d135bd03..162f043e2 100644 --- a/print/templates/reports/invoice/sql/tickets.sql +++ b/print/templates/reports/invoice/sql/tickets.sql @@ -4,5 +4,5 @@ SELECT t.nickname FROM invoiceOut io JOIN ticket t ON t.refFk = io.ref -WHERE io.id = ? +WHERE t.refFk = ? ORDER BY t.shipped \ No newline at end of file From d0185017b70a175964b9da566a05f8db04a39e23 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 11 Aug 2022 08:29:24 +0200 Subject: [PATCH 114/150] Removed translate from grouping and packing --- modules/entry/front/summary/index.html | 4 ++-- modules/item/front/last-entries/index.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index 3dd9a4be5..ffd8aafab 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -123,12 +123,12 @@ {{::line.weight}} - {{::line.packing | dashIfEmpty}} + {{::line.packing | dashIfEmpty}} - {{::line.grouping | dashIfEmpty}} + {{::line.grouping | dashIfEmpty}} {{::line.buyingValue | currency: 'EUR':2}} diff --git a/modules/item/front/last-entries/index.html b/modules/item/front/last-entries/index.html index 29047c613..0348d4f66 100644 --- a/modules/item/front/last-entries/index.html +++ b/modules/item/front/last-entries/index.html @@ -70,12 +70,12 @@ {{entry.stickers | dashIfEmpty}} - {{::entry.packing | dashIfEmpty}} + {{::entry.packing | dashIfEmpty}} - {{::entry.grouping | dashIfEmpty}} + {{::entry.grouping | dashIfEmpty}} {{::entry.stems | dashIfEmpty}} From cceb456209907280f1841996701e662f914e7c46 Mon Sep 17 00:00:00 2001 From: joan Date: Fri, 12 Aug 2022 15:31:59 +0200 Subject: [PATCH 115/150] Filter by province --- modules/client/front/notification/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/client/front/notification/index.js b/modules/client/front/notification/index.js index 0506ea4ba..336005783 100644 --- a/modules/client/front/notification/index.js +++ b/modules/client/front/notification/index.js @@ -108,6 +108,7 @@ export default class Controller extends Section { case 'fi': case 'postcode': case 'salesPersonFk': + case 'provinceFk': return {[param]: value}; } } From 5e54e2771836ba2bea98776caa5915f90192e0a0 Mon Sep 17 00:00:00 2001 From: jgallego Date: Wed, 17 Aug 2022 12:19:54 +0200 Subject: [PATCH 116/150] quito left en taxes --- print/templates/reports/invoice/sql/taxes.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/print/templates/reports/invoice/sql/taxes.sql b/print/templates/reports/invoice/sql/taxes.sql index 2203d8b8a..5c04d2c74 100644 --- a/print/templates/reports/invoice/sql/taxes.sql +++ b/print/templates/reports/invoice/sql/taxes.sql @@ -7,6 +7,5 @@ SELECT JOIN pgc ON pgc.code = iot.pgcFk LEFT JOIN pgcEqu pe ON pe.equFk = pgc.code JOIN invoiceOut io ON io.id = iot.invoiceOutFk - LEFT JOIN ticket t ON t.refFk = io.ref - WHERE t.refFk = ? + WHERE io.ref = ? ORDER BY iot.id \ No newline at end of file From 9fe32788fc7c7ca739d0afe5eedae6b675ef5ca7 Mon Sep 17 00:00:00 2001 From: jgallego Date: Wed, 24 Aug 2022 14:38:05 +0200 Subject: [PATCH 117/150] regularizado --- .../10490-august/00-sale_afterUpdate.sql | 100 ++++++++++++++++++ db/changes/10490-august/delete.keep | 0 modules/claim/front/search-panel/index.html | 4 +- 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 db/changes/10490-august/00-sale_afterUpdate.sql delete mode 100644 db/changes/10490-august/delete.keep diff --git a/db/changes/10490-august/00-sale_afterUpdate.sql b/db/changes/10490-august/00-sale_afterUpdate.sql new file mode 100644 index 000000000..407fb52c9 --- /dev/null +++ b/db/changes/10490-august/00-sale_afterUpdate.sql @@ -0,0 +1,100 @@ +DROP TRIGGER IF EXISTS vn.sale_afterUpdate; +USE vn; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`sale_afterUpdate` + AFTER UPDATE ON `sale` + FOR EACH ROW +BEGIN + DECLARE vIsToSendMail BOOL; + DECLARE vPickedLines INT; + DECLARE vCollectionFk INT; + DECLARE vUserRole VARCHAR(255); + + IF !(NEW.id <=> OLD.id) + OR !(NEW.ticketFk <=> OLD.ticketFk) + OR !(NEW.itemFk <=> OLD.itemFk) + OR !(NEW.quantity <=> OLD.quantity) + OR !(NEW.created <=> OLD.created) + OR !(NEW.isPicked <=> OLD.isPicked) THEN + CALL stock.log_add('sale', NEW.id, OLD.id); + END IF; + + IF !(NEW.price <=> OLD.price) + OR !(NEW.ticketFk <=> OLD.ticketFk) + OR !(NEW.itemFk <=> OLD.itemFk) + OR !(NEW.quantity <=> OLD.quantity) + OR !(NEW.discount <=> OLD.discount) THEN + CALL ticket_requestRecalc(NEW.ticketFk); + CALL ticket_requestRecalc(OLD.ticketFk); + END IF; + + IF !(OLD.ticketFk <=> NEW.ticketFk) THEN + UPDATE ticketRequest SET ticketFk = NEW.ticketFk + WHERE saleFk = NEW.id; + END IF; + + SELECT account.myUser_getName() INTO vUserRole; + SELECT account.user_getMysqlRole(vUserRole) INTO vUserRole; + + IF !(OLD.quantity <=> NEW.quantity) THEN + SELECT COUNT(*) INTO vIsToSendMail + FROM vncontrol.inter i + JOIN vn.state s ON s.id = i.state_id + WHERE s.code='PACKED' + AND i.Id_Ticket = OLD.ticketFk + AND vUserRole IN ('salesPerson', 'salesTeamBoss') + LIMIT 1; + + IF vIsToSendMail THEN + CALL vn.mail_insert('jefesventas@verdnatura.es', + 'noreply@verdnatura.es', + CONCAT('Ticket ', OLD.ticketFk ,' modificada cantidad tras encajado'), + CONCAT('Ticket ', OLD.ticketFk ,'.
', + 'Modificada la catidad de ', OLD.quantity, ' a ' , NEW.quantity, + ' del artículo ', OLD.itemFk, ' tras estado encajado del ticket.
', + 'Este email se ha generado automáticamente' ) + ); + END IF; + IF (OLD.quantity > NEW.quantity) THEN + INSERT INTO saleComponent(saleFk, componentFk, value) + SELECT NEW.id, cm.id, sc.value + FROM saleComponent sc + JOIN component cd ON cd.id = sc.componentFk + JOIN component cm ON cm.code = 'mana' + WHERE saleFk = NEW.id AND cd.code = 'lastUnitsDiscount' + ON DUPLICATE KEY UPDATE value = sc.value + VALUES(value); + + DELETE sc.* + FROM vn.saleComponent sc + JOIN component c ON c.id = sc.componentFk + WHERE saleFk = NEW.id AND c.code = 'lastUnitsDiscount'; + END IF; + INSERT IGNORE INTO `vn`.`routeRecalc` (`routeFk`) + SELECT r.id + FROM vn.sale s + JOIN vn.ticket t ON t.id = s.ticketFk + JOIN vn.route r ON r.id = t.routeFk + WHERE r.isOk = FALSE + AND s.id = NEW.id + AND r.created >= CURDATE() + GROUP BY r.id; + END IF; + + IF !(ABS(NEW.isPicked) <=> ABS(OLD.isPicked)) AND NEW.quantity > 0 THEN + + UPDATE vn.collection c + JOIN vn.ticketCollection tc ON tc.collectionFk = c.id AND tc.ticketFk = NEW.ticketFk + SET c.salePickedCount = c.salePickedCount + IF(NEW.isPicked != 0, 1, -1); + + END IF; + + IF !(NEW.quantity <=> OLD.quantity) AND (NEW.quantity = 0 OR OLD.quantity = 0) THEN + + UPDATE vn.collection c + JOIN vn.ticketCollection tc ON tc.collectionFk = c.id AND tc.ticketFk = NEW.ticketFk + SET c.saleTotalCount = c.saleTotalCount + IF(OLD.quantity = 0, 1, -1); + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/10490-august/delete.keep b/db/changes/10490-august/delete.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/modules/claim/front/search-panel/index.html b/modules/claim/front/search-panel/index.html index d522763a1..eec8cd727 100644 --- a/modules/claim/front/search-panel/index.html +++ b/modules/claim/front/search-panel/index.html @@ -28,7 +28,7 @@ url="Workers/activeWithRole" search-function="{firstName: $search}" value-field="id" - where="{role: {inq: ['salesBoss', 'salesPerson', 'officeBoss']}}" + where="{role: {inq: ['salesTeamBoss', 'salesPerson', 'officeBoss']}}" label="Salesperson"> {{firstName}} {{name}} @@ -38,7 +38,7 @@ url="Workers/activeWithRole" search-function="{firstName: $search}" value-field="id" - where="{role: {inq: ['salesBoss', 'salesPerson']}}" + where="{role: {inq: ['salesTeamBoss', 'salesPerson']}}" label="Attended by"> {{firstName}} {{name}} From 6fcb1e54b48b198d14e5bf2feb174d46dff0dfe7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 6 Sep 2022 09:35:01 +0200 Subject: [PATCH 118/150] refactor(claim_action): set translations --- modules/claim/front/action/index.html | 62 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/modules/claim/front/action/index.html b/modules/claim/front/action/index.html index 059764ae2..35e788290 100644 --- a/modules/claim/front/action/index.html +++ b/modules/claim/front/action/index.html @@ -2,36 +2,36 @@ url="ClaimEnds/filter" link="{claimFk: $ctrl.$params.id}" data="$ctrl.salesClaimed" - auto-load="true" + auto-load="true" on-save="$ctrl.onSave()"> - - - -
@@ -47,7 +47,7 @@ @@ -66,12 +66,24 @@ Id Ticket - Destination - Landed - Quantity - Description - Price - Disc. + + Destination + + + Landed + + + Quantity + + + Description + + + Price + + + Disc. + Total @@ -80,13 +92,13 @@ ng-repeat="saleClaimed in $ctrl.salesClaimed" vn-repeat-last on-last="$ctrl.focusLastInput()"> - - {{::saleClaimed.itemFk | zeroFill:6}} @@ -94,7 +106,7 @@ {{::saleClaimed.ticketFk}} @@ -129,7 +141,7 @@ - @@ -140,17 +152,17 @@ vn-id="item-descriptor" warehouse-fk="$ctrl.vnConfig.warehouseFk"> - - - + @@ -173,4 +185,4 @@ - \ No newline at end of file + From 011e372558ec37f07625ddd4976f6d2eb4e88112 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 6 Sep 2022 12:21:38 +0200 Subject: [PATCH 119/150] Removed wrong commit code pick --- back/methods/edi/updateData.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 3dae2d47d..121e96778 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -200,31 +200,6 @@ module.exports = Self => { const toTable = table.toTable; const baseName = table.fileName; - const firstEntry = entries[0]; - const entryName = firstEntry.entryName; - const startIndex = (entryName.length - 10); - const endIndex = (entryName.length - 4); - const dateString = entryName.substring(startIndex, endIndex); - - const lastUpdated = new Date(); - - let updated = null; - if (file.updated) { - updated = new Date(file.updated); - updated.setHours(0, 0, 0, 0); - } - - // Format string date to a date object - lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`); - lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1); - lastUpdated.setDate(dateString.substring(0, 2)); - lastUpdated.setHours(0, 0, 0, 0); - - if (updated && lastUpdated <= updated) { - console.debug(`Table ${toTable} already updated, skipping...`); - return; - } - const tx = await Self.beginTransaction({}); try { From 6410a4db42d1b38f9f9b6603c7091eae1184c347 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 6 Sep 2022 12:54:33 +0200 Subject: [PATCH 120/150] Send response before ending task execution --- back/methods/edi/updateData.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 121e96778..ef0a84d19 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -16,7 +16,7 @@ module.exports = Self => { } }); - Self.updateData = async() => { + Self.updateData = async callback => { const models = Self.app.models; // Get files checksum @@ -35,6 +35,8 @@ module.exports = Self => { console.debug(`File already updated, skipping...`); } + callback(null, true); + if (updatableFiles.length === 0) return false; From 37f0b557f45e9a9af03d7a6c52f397cb60edd17f Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 6 Sep 2022 13:13:29 +0200 Subject: [PATCH 121/150] Removed callback --- back/methods/edi/updateData.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index ef0a84d19..b68c76368 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -35,8 +35,6 @@ module.exports = Self => { console.debug(`File already updated, skipping...`); } - callback(null, true); - if (updatableFiles.length === 0) return false; From 5c5440cdfc7a9a6a5a11ea014708914ebfca7319 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 6 Sep 2022 13:13:49 +0200 Subject: [PATCH 122/150] Removed callback --- back/methods/edi/updateData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index b68c76368..121e96778 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -16,7 +16,7 @@ module.exports = Self => { } }); - Self.updateData = async callback => { + Self.updateData = async() => { const models = Self.app.models; // Get files checksum From 7264595646c2e6d9622aa2eadfb771d3a15bec91 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 6 Sep 2022 14:23:25 +0200 Subject: [PATCH 123/150] Throw error for max-retries --- back/methods/edi/updateData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 121e96778..a143653c2 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -126,7 +126,7 @@ module.exports = Self => { const response = await new Promise((resolve, reject) => { ftpClient.exec((err, response) => { - if (response.error) { + if (err || response.error) { console.debug(`Error downloading checksum file... ${response.error}`); reject(err); } From 886abfd89723d3062bf8986cb134012fe1e123ad Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 7 Sep 2022 08:49:24 +0200 Subject: [PATCH 124/150] fix(monitor): fix left join salesFilter --- .../back/methods/sales-monitor/salesFilter.js | 12 ++++++------ .../methods/sales-monitor/specs/salesFilter.spec.js | 8 ++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index 9a7415055..0b97b11e1 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -207,7 +207,7 @@ module.exports = Self => { LEFT JOIN province p ON p.id = a.provinceFk LEFT JOIN warehouse w ON w.id = t.warehouseFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk - LEFT JOIN ticketState ts ON ts.ticketFk = t.id + STRAIGHT_JOIN ticketState ts ON ts.ticketFk = t.id LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN client c ON c.id = t.clientFk LEFT JOIN worker wk ON wk.id = c.salesPersonFk @@ -227,7 +227,7 @@ module.exports = Self => { // Get client debt balance stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.clientGetDebt'); stmts.push(` - CREATE TEMPORARY TABLE tmp.clientGetDebt + CREATE TEMPORARY TABLE tmp.clientGetDebt (PRIMARY KEY (clientFk)) ENGINE = MEMORY SELECT DISTINCT clientFk FROM tmp.filter`); @@ -238,7 +238,7 @@ module.exports = Self => { stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.tickets'); stmt = new ParameterizedSQL(` - CREATE TEMPORARY TABLE tmp.tickets + CREATE TEMPORARY TABLE tmp.tickets (PRIMARY KEY (id)) ENGINE = MEMORY SELECT f.*, r.risk AS debt @@ -268,10 +268,10 @@ module.exports = Self => { stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.sale_getProblems'); stmt = new ParameterizedSQL(` - CREATE TEMPORARY TABLE tmp.sale_getProblems + CREATE TEMPORARY TABLE tmp.sale_getProblems (INDEX (ticketFk)) ENGINE = MEMORY - SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped + SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped FROM tmp.filter f LEFT JOIN alertLevel al ON al.id = f.alertLevel WHERE (al.code = 'FREE' OR f.alertLevel IS NULL) @@ -377,7 +377,7 @@ module.exports = Self => { const ticketsIndex = stmts.push(stmt) - 1; stmts.push( - `DROP TEMPORARY TABLE + `DROP TEMPORARY TABLE tmp.filter, tmp.ticket_problems, tmp.sale_getProblems, diff --git a/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js index a4fb1b0af..0682cef09 100644 --- a/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js +++ b/modules/monitor/back/methods/sales-monitor/specs/salesFilter.spec.js @@ -147,16 +147,12 @@ describe('SalesMonitor salesFilter()', () => { const options = {transaction: tx}; const ctx = {req: {accessToken: {userId: 9}}, args: {pending: false}}; - const filter = {}; + const filter = {order: 'alertLevel ASC'}; const result = await models.SalesMonitor.salesFilter(ctx, filter, options); const firstRow = result[0]; - const secondRow = result[1]; - const thirdRow = result[2]; expect(result.length).toEqual(12); - expect(firstRow.state).toEqual('Entregado'); - expect(secondRow.state).toEqual('Entregado'); - expect(thirdRow.state).toEqual('Entregado'); + expect(firstRow.alertLevel).not.toEqual(0); await tx.rollback(); } catch (e) { From aba1696ad725e68e59de2c5eb12b149b4fc204fd Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 7 Sep 2022 10:26:32 +0200 Subject: [PATCH 125/150] Changes --- back/methods/edi/updateData.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index a143653c2..b081c4687 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -56,8 +56,6 @@ module.exports = Self => { for (const table of tables) { const fileName = table.file; - console.debug(`Downloading file ${fileName}...`); - remoteFile = `codes/${fileName}.ZIP`; tempDir = `${tempPath}/${fileName}`; tempFile = `${tempPath}/${fileName}.zip`; @@ -66,14 +64,14 @@ module.exports = Self => { await fs.readFile(tempFile); } catch (error) { if (error.code === 'ENOENT') { + console.debug(`Downloading file ${fileName}...`); const downloadOutput = await downloadFile(remoteFile, tempFile); if (downloadOutput.error) continue; } } - console.debug(`Extracting file ${fileName}...`); - await extractFile(tempFile, tempDir); + await extractFile(fileName, tempFile, tempDir); console.debug(`Updating table ${table.toTable}...`); await dumpData(tempDir, table); @@ -90,6 +88,7 @@ module.exports = Self => { // Clean files try { + console.debug(`Cleaning files...`); await fs.remove(tempPath); } catch (error) { if (error.code !== 'ENOENT') @@ -128,7 +127,7 @@ module.exports = Self => { ftpClient.exec((err, response) => { if (err || response.error) { console.debug(`Error downloading checksum file... ${response.error}`); - reject(err); + return reject(err); } resolve(response); @@ -159,9 +158,9 @@ module.exports = Self => { return new Promise((resolve, reject) => { ftpClient.exec((err, response) => { - if (response.error) { + if (err || response.error) { console.debug(`Error downloading file... ${response.error}`); - reject(err); + return reject(err); } resolve(response); @@ -169,11 +168,12 @@ module.exports = Self => { }); } - async function extractFile(tempFile, tempDir) { + async function extractFile(fileName, tempFile, tempDir) { const JSZip = require('jszip'); try { await fs.mkdir(tempDir); + console.debug(`Extracting file ${fileName}...`); } catch (error) { if (error.code !== 'EEXIST') throw e; From bbc5790945bce971f39a0e8e48f34b971865f3f4 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 7 Sep 2022 10:30:50 +0200 Subject: [PATCH 126/150] fix(smartTable): refresh button await query --- front/core/components/smart-table/index.html | 17 +++++++++-------- front/core/components/smart-table/index.js | 6 ++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/front/core/components/smart-table/index.html b/front/core/components/smart-table/index.html index a3295c47e..f26a6b4a2 100644 --- a/front/core/components/smart-table/index.html +++ b/front/core/components/smart-table/index.html @@ -10,9 +10,9 @@
-
- {{model.data.length}} - results +
+ {{model.data.length}} + results
@@ -64,7 +65,7 @@
Shown columns
-
@@ -101,4 +102,4 @@ - \ No newline at end of file + diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index 401541c2c..9e6e7009c 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -511,6 +511,12 @@ export default class SmartTable extends Component { return this.model.save() .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); } + + refresh() { + this.isRefreshing = true; + this.model.refresh() + .then(() => this.isRefreshing = false); + } } SmartTable.$inject = ['$element', '$scope', '$transclude']; From 8f86cacc960173daafc4e444458fc996daeb8790 Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 7 Sep 2022 10:48:24 +0200 Subject: [PATCH 127/150] Missing await --- back/methods/edi/updateData.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index b081c4687..7f4f0b5e7 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -79,6 +79,7 @@ module.exports = Self => { // Update files checksum for (const file of updatableFiles) { + console.log(`Updating file ${file.name} checksum...`); await Self.rawSql(` UPDATE edi.fileConfig SET checksum = ? @@ -226,9 +227,9 @@ module.exports = Self => { `, [new Date(), baseName], options); } - tx.commit(); + await tx.commit(); } catch (error) { - tx.rollback(); + await tx.rollback(); throw error; } console.log(`Updated table ${toTable}\n`); From 48916e2b07869e32bd69a54ede1128ab7adcfa29 Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 7 Sep 2022 11:26:58 +0200 Subject: [PATCH 128/150] Debug logs --- back/methods/edi/updateData.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 7f4f0b5e7..b6b06e19f 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -202,16 +202,20 @@ module.exports = Self => { const baseName = table.fileName; const tx = await Self.beginTransaction({}); - + console.log(`Started transaction`); try { const options = {transaction: tx}; + console.log(`Emptying table ${toTable}...`); const tableName = `edi.${toTable}`; await Self.rawSql(`DELETE FROM ??`, [tableName], options); + console.log(`Reading files...`); const dirFiles = await fs.readdir(tempDir); const files = dirFiles.filter(file => file.startsWith(baseName)); + console.log('Files to import: ' + files.join(', ')); + for (const file of files) { console.log(`Dumping data from file ${file}...`); @@ -228,6 +232,7 @@ module.exports = Self => { } await tx.commit(); + console.log(`Closed transaction`); } catch (error) { await tx.rollback(); throw error; From aec053868d3293ae164a800e53c7f68f26aa682a Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 7 Sep 2022 11:50:02 +0200 Subject: [PATCH 129/150] Debug logs --- back/methods/edi/updateData.js | 1 + 1 file changed, 1 insertion(+) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index b6b06e19f..58ea498ad 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -201,6 +201,7 @@ module.exports = Self => { const toTable = table.toTable; const baseName = table.fileName; + console.log(`Starting transaction...`); const tx = await Self.beginTransaction({}); console.log(`Started transaction`); try { From ab86ba0652e8cbff70f42fea754bedc043ba8d64 Mon Sep 17 00:00:00 2001 From: joan Date: Wed, 7 Sep 2022 12:28:53 +0200 Subject: [PATCH 130/150] Disabled transaction --- back/methods/edi/updateData.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 58ea498ad..f33466356 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -202,14 +202,14 @@ module.exports = Self => { const baseName = table.fileName; console.log(`Starting transaction...`); - const tx = await Self.beginTransaction({}); + // const tx = await Self.beginTransaction({}); console.log(`Started transaction`); try { - const options = {transaction: tx}; + // const options = {transaction: tx}; console.log(`Emptying table ${toTable}...`); const tableName = `edi.${toTable}`; - await Self.rawSql(`DELETE FROM ??`, [tableName], options); + await Self.rawSql(`DELETE FROM ??`, [tableName]); console.log(`Reading files...`); const dirFiles = await fs.readdir(tempDir); @@ -224,18 +224,19 @@ module.exports = Self => { const sqlTemplate = await fs.readFile(templatePath, 'utf8'); const filePath = path.join(tempDir, file); - await Self.rawSql(sqlTemplate, [filePath], options); + await Self.rawSql(sqlTemplate, [filePath]); await Self.rawSql(` UPDATE edi.tableConfig SET updated = ? WHERE fileName = ? - `, [new Date(), baseName], options); + `, [new Date(), baseName]); } - await tx.commit(); + // await tx.commit(); console.log(`Closed transaction`); } catch (error) { - await tx.rollback(); + // await tx.rollback(); + console.log(error); throw error; } console.log(`Updated table ${toTable}\n`); From a15c051098dd7ded735b9d15df683c269d9ee36c Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 8 Sep 2022 08:07:46 +0200 Subject: [PATCH 131/150] fix(monitor_salesFilter): mariadb not optimize query --- modules/monitor/back/methods/sales-monitor/salesFilter.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index 0b97b11e1..ff642b088 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -164,6 +164,10 @@ module.exports = Self => { let stmt; stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.filter'); + + stmts.push(`SET @_optimizer_search_depth = @@optimizer_search_depth`); + stmts.push(`SET SESSION optimizer_search_depth = 0`); + stmt = new ParameterizedSQL( `CREATE TEMPORARY TABLE tmp.filter (PRIMARY KEY (id)) @@ -224,6 +228,8 @@ module.exports = Self => { stmt.merge(conn.makeWhere(filter.where)); stmts.push(stmt); + stmts.push(`SET SESSION optimizer_search_depth = @_optimizer_search_depth`); + // Get client debt balance stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.clientGetDebt'); stmts.push(` From 151e1b52ed36eea7bff032a8883f97cba81b5bf2 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 8 Sep 2022 09:21:05 +0200 Subject: [PATCH 132/150] Updated transaction --- back/methods/edi/updateData.js | 191 +++++++++++++++++---------------- 1 file changed, 100 insertions(+), 91 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index f33466356..af0653672 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -20,80 +20,89 @@ module.exports = Self => { const models = Self.app.models; // Get files checksum - const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig'); + const tx = await Self.beginTransaction({}); - const updatableFiles = []; - for (const file of files) { - const fileChecksum = await getChecksum(file); + try { + const options = {transaction: tx}; + const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options); - if (file.checksum != fileChecksum) { - updatableFiles.push({ - name: file.name, - checksum: fileChecksum - }); - } else - console.debug(`File already updated, skipping...`); - } + const updatableFiles = []; + for (const file of files) { + const fileChecksum = await getChecksum(file); - if (updatableFiles.length === 0) - return false; - - // Download files - const container = await models.TempContainer.container('edi'); - const tempPath = path.join(container.client.root, container.name); - - let remoteFile; - let tempDir; - let tempFile; - - const fileNames = updatableFiles.map(file => file.name); - - const tables = await Self.rawSql(` - SELECT fileName, toTable, file - FROM edi.tableConfig - WHERE file IN (?)`, [fileNames]); - - for (const table of tables) { - const fileName = table.file; - - remoteFile = `codes/${fileName}.ZIP`; - tempDir = `${tempPath}/${fileName}`; - tempFile = `${tempPath}/${fileName}.zip`; - - try { - await fs.readFile(tempFile); - } catch (error) { - if (error.code === 'ENOENT') { - console.debug(`Downloading file ${fileName}...`); - const downloadOutput = await downloadFile(remoteFile, tempFile); - if (downloadOutput.error) - continue; - } + if (file.checksum != fileChecksum) { + updatableFiles.push({ + name: file.name, + checksum: fileChecksum + }); + } else + console.debug(`File already updated, skipping...`); } - await extractFile(fileName, tempFile, tempDir); + if (updatableFiles.length === 0) + return false; - console.debug(`Updating table ${table.toTable}...`); - await dumpData(tempDir, table); - } + // Download files + const container = await models.TempContainer.container('edi'); + const tempPath = path.join(container.client.root, container.name); - // Update files checksum - for (const file of updatableFiles) { - console.log(`Updating file ${file.name} checksum...`); - await Self.rawSql(` - UPDATE edi.fileConfig - SET checksum = ? - WHERE name = ?`, - [file.checksum, file.name]); - } + let remoteFile; + let tempDir; + let tempFile; - // Clean files - try { - console.debug(`Cleaning files...`); - await fs.remove(tempPath); + const fileNames = updatableFiles.map(file => file.name); + + const tables = await Self.rawSql(` + SELECT fileName, toTable, file + FROM edi.tableConfig + WHERE file IN (?)`, [fileNames], options); + + for (const table of tables) { + const fileName = table.file; + + remoteFile = `codes/${fileName}.ZIP`; + tempDir = `${tempPath}/${fileName}`; + tempFile = `${tempPath}/${fileName}.zip`; + + try { + await fs.readFile(tempFile); + } catch (error) { + if (error.code === 'ENOENT') { + console.debug(`Downloading file ${fileName}...`); + const downloadOutput = await downloadFile(remoteFile, tempFile); + if (downloadOutput.error) + continue; + } + } + + await extractFile(fileName, tempFile, tempDir); + + console.debug(`Updating table ${table.toTable}...`); + await dumpData(tempDir, table, options); + } + + // Update files checksum + for (const file of updatableFiles) { + console.log(`Updating file ${file.name} checksum...`); + await Self.rawSql(` + UPDATE edi.fileConfig + SET checksum = ? + WHERE name = ?`, + [file.checksum, file.name], options); + } + + // Clean files + try { + console.debug(`Cleaning files...`); + await fs.remove(tempPath); + } catch (error) { + if (error.code !== 'ENOENT') + throw e; + } } catch (error) { - if (error.code !== 'ENOENT') - throw e; + await tx.rollback(); + console.log(error); + throw error; } return true; @@ -197,48 +206,48 @@ module.exports = Self => { } } - async function dumpData(tempDir, table) { + async function dumpData(tempDir, table, options) { const toTable = table.toTable; const baseName = table.fileName; console.log(`Starting transaction...`); // const tx = await Self.beginTransaction({}); console.log(`Started transaction`); - try { - // const options = {transaction: tx}; + // try { + // const options = {transaction: tx}; - console.log(`Emptying table ${toTable}...`); - const tableName = `edi.${toTable}`; - await Self.rawSql(`DELETE FROM ??`, [tableName]); + console.log(`Emptying table ${toTable}...`); + const tableName = `edi.${toTable}`; + await Self.rawSql(`DELETE FROM ??`, [tableName]); - console.log(`Reading files...`); - const dirFiles = await fs.readdir(tempDir); - const files = dirFiles.filter(file => file.startsWith(baseName)); + console.log(`Reading files...`); + const dirFiles = await fs.readdir(tempDir); + const files = dirFiles.filter(file => file.startsWith(baseName)); - console.log('Files to import: ' + files.join(', ')); + console.log('Files to import: ' + files.join(', ')); - for (const file of files) { - console.log(`Dumping data from file ${file}...`); + for (const file of files) { + console.log(`Dumping data from file ${file}...`); - const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); - const sqlTemplate = await fs.readFile(templatePath, 'utf8'); - const filePath = path.join(tempDir, file); + const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); + const sqlTemplate = await fs.readFile(templatePath, 'utf8'); + const filePath = path.join(tempDir, file); - await Self.rawSql(sqlTemplate, [filePath]); - await Self.rawSql(` + await Self.rawSql(sqlTemplate, [filePath], options); + await Self.rawSql(` UPDATE edi.tableConfig SET updated = ? WHERE fileName = ? - `, [new Date(), baseName]); - } - - // await tx.commit(); - console.log(`Closed transaction`); - } catch (error) { - // await tx.rollback(); - console.log(error); - throw error; + `, [new Date(), baseName], options); } + + // await tx.commit(); + // console.log(`Closed transaction`); + // } catch (error) { + // await tx.rollback(); + // console.log(error); + // throw error; + // } console.log(`Updated table ${toTable}\n`); } }; From b499de7c08c0f0faca5621353a15b608fb2a7bda Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 8 Sep 2022 10:33:51 +0200 Subject: [PATCH 133/150] tx commit --- back/methods/edi/updateData.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index af0653672..747fff4b8 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -91,6 +91,8 @@ module.exports = Self => { [file.checksum, file.name], options); } + await tx.commit(); + // Clean files try { console.debug(`Cleaning files...`); @@ -101,7 +103,6 @@ module.exports = Self => { } } catch (error) { await tx.rollback(); - console.log(error); throw error; } From 4251b0baeb466657b7456bee360e02633f2d98c8 Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 8 Sep 2022 11:19:04 +0200 Subject: [PATCH 134/150] Removed debug logs --- back/methods/edi/updateData.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 747fff4b8..a7375676a 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -211,22 +211,13 @@ module.exports = Self => { const toTable = table.toTable; const baseName = table.fileName; - console.log(`Starting transaction...`); - // const tx = await Self.beginTransaction({}); - console.log(`Started transaction`); - // try { - // const options = {transaction: tx}; - console.log(`Emptying table ${toTable}...`); const tableName = `edi.${toTable}`; await Self.rawSql(`DELETE FROM ??`, [tableName]); - console.log(`Reading files...`); const dirFiles = await fs.readdir(tempDir); const files = dirFiles.filter(file => file.startsWith(baseName)); - console.log('Files to import: ' + files.join(', ')); - for (const file of files) { console.log(`Dumping data from file ${file}...`); @@ -242,13 +233,6 @@ module.exports = Self => { `, [new Date(), baseName], options); } - // await tx.commit(); - // console.log(`Closed transaction`); - // } catch (error) { - // await tx.rollback(); - // console.log(error); - // throw error; - // } console.log(`Updated table ${toTable}\n`); } }; From fce26342a9aa5ed26f7066884af1d615fcf7d9db Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 8 Sep 2022 11:42:16 +0200 Subject: [PATCH 135/150] Return inside try-catch --- back/methods/edi/updateData.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index a7375676a..86a9e1f31 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -101,12 +101,12 @@ module.exports = Self => { if (error.code !== 'ENOENT') throw e; } + + return true; } catch (error) { await tx.rollback(); throw error; } - - return true; }; let ftpClient; From de3e14e7396c64d29b4fa229d070d78087a04bb3 Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 12 Sep 2022 10:23:17 +0200 Subject: [PATCH 136/150] 4514 - Write permissions for rrhh --- modules/client/front/billing-data/index.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/client/front/billing-data/index.html b/modules/client/front/billing-data/index.html index 33a237ad7..410681930 100644 --- a/modules/client/front/billing-data/index.html +++ b/modules/client/front/billing-data/index.html @@ -16,7 +16,7 @@ @@ -39,7 +39,7 @@ ng-model="$ctrl.client.iban" rule on-change="$ctrl.autofillBic()" - vn-acl="salesAssistant"> + vn-acl="salesAssistant, hr"> {{bic}} {{name}} @@ -61,7 +61,7 @@ icon="add_circle" vn-click-stop="bankEntity.show({countryFk: $ctrl.client.countryFk})" vn-tooltip="New bank entity" - vn-acl="salesAssistant"> + vn-acl="salesAssistant, hr"> @@ -71,19 +71,19 @@ vn-one label="Received LCR" ng-model="$ctrl.client.hasLcr" - vn-acl="salesAssistant"> + vn-acl="salesAssistant, hr">
+ vn-acl="salesAssistant, hr"> + vn-acl="salesAssistant, hr"> From 27ecff685e5a1084dcb33a0e295461ec4d7f906c Mon Sep 17 00:00:00 2001 From: joan Date: Mon, 12 Sep 2022 12:45:52 +0200 Subject: [PATCH 137/150] fix(report): debtor letter with portuguese IBAN --- print/templates/email/letter-debtor-nd/sql/client.sql | 3 ++- print/templates/email/letter-debtor-st/sql/client.sql | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/print/templates/email/letter-debtor-nd/sql/client.sql b/print/templates/email/letter-debtor-nd/sql/client.sql index aad907a4b..479bfac5e 100644 --- a/print/templates/email/letter-debtor-nd/sql/client.sql +++ b/print/templates/email/letter-debtor-nd/sql/client.sql @@ -5,6 +5,7 @@ SELECT be.name AS bankName FROM client c JOIN company AS cny - JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk + JOIN supplierAccount AS sa ON + IF (ct.code = 'PT', sa.id = 907, sa.id = cny.supplierAccountFk) JOIN bankEntity be ON be.id = sa.bankEntityFk WHERE c.id = ? AND cny.id = ? \ No newline at end of file diff --git a/print/templates/email/letter-debtor-st/sql/client.sql b/print/templates/email/letter-debtor-st/sql/client.sql index aad907a4b..479bfac5e 100644 --- a/print/templates/email/letter-debtor-st/sql/client.sql +++ b/print/templates/email/letter-debtor-st/sql/client.sql @@ -5,6 +5,7 @@ SELECT be.name AS bankName FROM client c JOIN company AS cny - JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk + JOIN supplierAccount AS sa ON + IF (ct.code = 'PT', sa.id = 907, sa.id = cny.supplierAccountFk) JOIN bankEntity be ON be.id = sa.bankEntityFk WHERE c.id = ? AND cny.id = ? \ No newline at end of file From 8c6c7345cd58630ce291569c5df983eae5ca497a Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 13 Sep 2022 10:27:08 +0200 Subject: [PATCH 138/150] fix: change GET by POST, to solve "Request Header Fields Too Large" --- modules/entry/back/methods/entry/importBuysPreview.js | 2 +- modules/entry/front/buy/import/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/entry/back/methods/entry/importBuysPreview.js b/modules/entry/back/methods/entry/importBuysPreview.js index 5b88b587c..730ecab0a 100644 --- a/modules/entry/back/methods/entry/importBuysPreview.js +++ b/modules/entry/back/methods/entry/importBuysPreview.js @@ -20,7 +20,7 @@ module.exports = Self => { }, http: { path: `/:id/importBuysPreview`, - verb: 'GET' + verb: 'POST' } }); diff --git a/modules/entry/front/buy/import/index.js b/modules/entry/front/buy/import/index.js index a2a28ef69..ba0a98e62 100644 --- a/modules/entry/front/buy/import/index.js +++ b/modules/entry/front/buy/import/index.js @@ -58,7 +58,7 @@ class Controller extends Section { fetchBuys(buys) { const params = {buys}; const query = `Entries/${this.$params.id}/importBuysPreview`; - this.$http.get(query, {params}).then(res => { + this.$http.post(query, params).then(res => { this.import.buys = res.data; }); } From b02aa8466c86087429082a2605ac219e2fef8700 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 13 Sep 2022 10:27:17 +0200 Subject: [PATCH 139/150] fix: tfront --- modules/entry/front/buy/import/index.spec.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/entry/front/buy/import/index.spec.js b/modules/entry/front/buy/import/index.spec.js index bf100dc83..036f52074 100644 --- a/modules/entry/front/buy/import/index.spec.js +++ b/modules/entry/front/buy/import/index.spec.js @@ -111,9 +111,8 @@ describe('Entry', () => { 'volume': 1125} ]; - const serializedParams = $httpParamSerializer({buys}); - const query = `Entries/1/importBuysPreview?${serializedParams}`; - $httpBackend.expectGET(query).respond(200, buys); + const query = `Entries/1/importBuysPreview`; + $httpBackend.expectPOST(query).respond(200, buys); controller.fetchBuys(buys); $httpBackend.flush(); From cc6ba8712c2fa5bb40151e7fafdc5b3f845f553e Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 13 Sep 2022 12:15:28 +0200 Subject: [PATCH 140/150] feat: log when a day is excluded --- modules/zone/back/models/zone-exclusion.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/zone/back/models/zone-exclusion.json b/modules/zone/back/models/zone-exclusion.json index 415bce40c..e8088cd44 100644 --- a/modules/zone/back/models/zone-exclusion.json +++ b/modules/zone/back/models/zone-exclusion.json @@ -1,6 +1,10 @@ { "name": "ZoneExclusion", - "base": "VnModel", + "base": "Loggable", + "log": { + "model":"ZoneLog", + "relation": "zone" + }, "options": { "mysql": { "table": "zoneExclusion" From f3fe19360ac9932f4f10932d7145e00a199eb263 Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 13 Sep 2022 13:20:18 +0200 Subject: [PATCH 141/150] Closure refactor --- print/methods/closure/closeAll.js | 29 +++++++++++++++++++------- print/methods/closure/closeByAgency.js | 10 ++++----- print/methods/closure/closeByRoute.js | 10 ++++----- print/methods/closure/closeByTicket.js | 10 ++++----- print/methods/closure/index.js | 8 +++---- 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/print/methods/closure/closeAll.js b/print/methods/closure/closeAll.js index 400ca2917..dad8b4569 100644 --- a/print/methods/closure/closeAll.js +++ b/print/methods/closure/closeAll.js @@ -3,13 +3,22 @@ const closure = require('./closure'); module.exports = async function(request, response, next) { try { - const reqArgs = request.query; - if (!reqArgs.to) - throw new Error('The argument to is required'); + const reqArgs = request.body; - response.status(200).json({ - message: 'Success' - }); + let toDate = new Date(); + toDate.setDate(toDate.getDate() - 1); + + if (reqArgs.to) toDate = reqArgs.to; + + const todayMinDate = new Date(); + minDate.setHours(0, 0, 0, 0); + + const todayMaxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); + + // Prevent closure for current day + if (toDate >= todayMinDate && toDate <= todayMaxDate) + throw new Error('You cannot close tickets for today'); const tickets = await db.rawSql(` SELECT @@ -36,7 +45,7 @@ module.exports = async function(request, response, next) { AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) AND util.dayEnd(?) AND t.refFk IS NULL - GROUP BY t.id`, [reqArgs.to, reqArgs.to]); + GROUP BY t.id`, [toDate, toDate]); await closure.start(tickets, response.locals); @@ -52,7 +61,11 @@ module.exports = async function(request, response, next) { AND util.dayEnd(?) AND al.code NOT IN('DELIVERED','PACKED') AND t.routeFk - AND z.name LIKE '%MADRID%'`, [reqArgs.to, reqArgs.to]); + AND z.name LIKE '%MADRID%'`, [toDate, toDate]); + + response.status(200).json({ + message: 'Success' + }); } catch (error) { next(error); } diff --git a/print/methods/closure/closeByAgency.js b/print/methods/closure/closeByAgency.js index 12e6b4c53..bbf72f137 100644 --- a/print/methods/closure/closeByAgency.js +++ b/print/methods/closure/closeByAgency.js @@ -3,7 +3,7 @@ const closure = require('./closure'); module.exports = async function(request, response, next) { try { - const reqArgs = request.query; + const reqArgs = request.body; if (!reqArgs.agencyModeId) throw new Error('The argument agencyModeId is required'); @@ -14,10 +14,6 @@ module.exports = async function(request, response, next) { if (!reqArgs.to) throw new Error('The argument to is required'); - response.status(200).json({ - message: 'Success' - }); - const agencyIds = reqArgs.agencyModeId.split(','); const tickets = await db.rawSql(` SELECT @@ -53,6 +49,10 @@ module.exports = async function(request, response, next) { ]); await closure.start(tickets, response.locals); + + response.status(200).json({ + message: 'Success' + }); } catch (error) { next(error); } diff --git a/print/methods/closure/closeByRoute.js b/print/methods/closure/closeByRoute.js index 32a1688d3..1f5d571f6 100644 --- a/print/methods/closure/closeByRoute.js +++ b/print/methods/closure/closeByRoute.js @@ -4,15 +4,11 @@ const closure = require('./closure'); module.exports = async function(request, response, next) { try { - const reqArgs = request.query; + const reqArgs = request.body; if (!reqArgs.routeId) throw new Error('The argument routeId is required'); - response.status(200).json({ - message: 'Success' - }); - const tickets = await db.rawSql(` SELECT t.id, @@ -56,6 +52,10 @@ module.exports = async function(request, response, next) { const email = new Email('driver-route', args); await email.send(); } + + response.status(200).json({ + message: 'Success' + }); } catch (error) { next(error); } diff --git a/print/methods/closure/closeByTicket.js b/print/methods/closure/closeByTicket.js index f6d97cba3..71cadcecf 100644 --- a/print/methods/closure/closeByTicket.js +++ b/print/methods/closure/closeByTicket.js @@ -3,15 +3,11 @@ const closure = require('./closure'); module.exports = async function(request, response, next) { try { - const reqArgs = request.query; + const reqArgs = request.body; if (!reqArgs.ticketId) throw new Error('The argument ticketId is required'); - response.status(200).json({ - message: 'Success' - }); - const tickets = await db.rawSql(` SELECT t.id, @@ -38,6 +34,10 @@ module.exports = async function(request, response, next) { GROUP BY e.ticketFk`, [reqArgs.ticketId]); await closure.start(tickets, response.locals); + + response.status(200).json({ + message: 'Success' + }); } catch (error) { next(error); } diff --git a/print/methods/closure/index.js b/print/methods/closure/index.js index fcca76f71..2d5eaf4c5 100644 --- a/print/methods/closure/index.js +++ b/print/methods/closure/index.js @@ -1,9 +1,9 @@ const express = require('express'); const router = new express.Router(); -router.get('/all', require('./closeAll')); -router.get('/by-ticket', require('./closeByTicket')); -router.get('/by-agency', require('./closeByAgency')); -router.get('/by-route', require('./closeByRoute')); +router.post('/all', require('./closeAll')); +router.post('/by-ticket', require('./closeByTicket')); +router.post('/by-agency', require('./closeByAgency')); +router.post('/by-route', require('./closeByRoute')); module.exports = router; From 5a46444875b94f2fb2c5d885b88a9448ea6905ae Mon Sep 17 00:00:00 2001 From: joan Date: Tue, 13 Sep 2022 13:53:44 +0200 Subject: [PATCH 142/150] Use POST request method instead of GET --- back/methods/chat/notifyIssues.js | 2 +- back/methods/chat/sendQueued.js | 2 +- back/methods/dms/deleteTrashFiles.js | 2 +- back/methods/edi/updateData.js | 2 +- .../item/back/methods/item-image-queue/downloadImages.js | 2 +- .../back/methods/worker-time-control-mail/checkInbox.js | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/back/methods/chat/notifyIssues.js b/back/methods/chat/notifyIssues.js index b0a42b7be..902ee59cd 100644 --- a/back/methods/chat/notifyIssues.js +++ b/back/methods/chat/notifyIssues.js @@ -8,7 +8,7 @@ module.exports = Self => { }, http: { path: `/notifyIssues`, - verb: 'GET' + verb: 'POST' } }); diff --git a/back/methods/chat/sendQueued.js b/back/methods/chat/sendQueued.js index ab94a1746..66fbfcdc5 100644 --- a/back/methods/chat/sendQueued.js +++ b/back/methods/chat/sendQueued.js @@ -10,7 +10,7 @@ module.exports = Self => { }, http: { path: `/sendQueued`, - verb: 'GET' + verb: 'POST' } }); diff --git a/back/methods/dms/deleteTrashFiles.js b/back/methods/dms/deleteTrashFiles.js index 7cfb9f8d1..9da508285 100644 --- a/back/methods/dms/deleteTrashFiles.js +++ b/back/methods/dms/deleteTrashFiles.js @@ -12,7 +12,7 @@ module.exports = Self => { }, http: { path: `/deleteTrashFiles`, - verb: 'GET' + verb: 'POST' } }); diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 86a9e1f31..c5705513f 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -12,7 +12,7 @@ module.exports = Self => { }, http: { path: `/updateData`, - verb: 'GET' + verb: 'POST' } }); diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js index ec8244f49..05b223598 100644 --- a/modules/item/back/methods/item-image-queue/downloadImages.js +++ b/modules/item/back/methods/item-image-queue/downloadImages.js @@ -12,7 +12,7 @@ module.exports = Self => { }, http: { path: `/downloadImages`, - verb: 'GET' + verb: 'POST' } }); diff --git a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js index bfecb1605..3e64a985a 100644 --- a/modules/worker/back/methods/worker-time-control-mail/checkInbox.js +++ b/modules/worker/back/methods/worker-time-control-mail/checkInbox.js @@ -11,7 +11,7 @@ module.exports = Self => { }, http: { path: `/checkInbox`, - verb: 'GET' + verb: 'POST' } }); @@ -58,8 +58,8 @@ module.exports = Self => { emailBody = bufferCopy.toUpperCase().trim(); const bodyPositionOK = emailBody.match(/\bOK\b/i); - - if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122)) + const bodyPositionIndex = (bodyPositionOK.index == 0 || bodyPositionOK.index == 122); + if (bodyPositionOK != null && bodyPositionIndex) isEmailOk = true; else isEmailOk = false; From a6a5a84add45171960387c75f30a0762f7c8af70 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 13 Sep 2022 14:42:15 +0200 Subject: [PATCH 143/150] fix: incorrect name of function --- modules/zone/front/events/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/zone/front/events/index.html b/modules/zone/front/events/index.html index 46ba87dea..9b79f3317 100644 --- a/modules/zone/front/events/index.html +++ b/modules/zone/front/events/index.html @@ -91,7 +91,7 @@ Date: Tue, 13 Sep 2022 15:11:14 +0200 Subject: [PATCH 144/150] refactor(travel_extra-community): use smart-table, fix scroll --- e2e/helpers/selectors.js | 6 +- .../10-travel/04_extra_community.spec.js | 16 +- .../methods/travel/extraCommunityFilter.js | 5 +- .../extra-community-search-panel/index.html | 10 +- .../travel/front/extra-community/index.html | 230 +++++++++++------- modules/travel/front/extra-community/index.js | 56 ++++- .../front/extra-community/index.spec.js | 16 +- .../travel/front/extra-community/style.scss | 53 ++-- 8 files changed, 253 insertions(+), 139 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 37f7308a5..0ad9ad7f4 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1014,9 +1014,9 @@ export default { save: 'vn-travel-create vn-submit > button' }, travelExtraCommunity: { - anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr', - firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]', - firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]', + anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]', + firstTravelReference: 'vn-travel-extra-community tbody:nth-child(2) vn-textfield[ng-model="travel.ref"]', + firstTravelLockedKg: 'vn-travel-extra-community tbody:nth-child(2) vn-input-number[ng-model="travel.kg"]', removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i' }, travelBasicData: { diff --git a/e2e/paths/10-travel/04_extra_community.spec.js b/e2e/paths/10-travel/04_extra_community.spec.js index e4a859192..a1cad6a7d 100644 --- a/e2e/paths/10-travel/04_extra_community.spec.js +++ b/e2e/paths/10-travel/04_extra_community.spec.js @@ -19,18 +19,22 @@ describe('Travel extra community path', () => { it('should edit the travel reference and the locked kilograms', async() => { await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter); await page.waitForSpinnerLoad(); - await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); - await page.waitForSpinnerLoad(); - await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500'); + await page.clearInput(selectors.travelExtraCommunity.firstTravelReference); + await page.write(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); + await page.clearInput(selectors.travelExtraCommunity.firstTravelLockedKg); + await page.write(selectors.travelExtraCommunity.firstTravelLockedKg, '1500'); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); }); it('should reload the index and confirm the reference and locked kg were edited', async() => { await page.accessToSection('travel.index'); await page.accessToSection('travel.extraCommunity'); await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter); - await page.waitForTextInElement(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); - const reference = await page.getProperty(selectors.travelExtraCommunity.firstTravelReference, 'innerText'); - const lockedKg = await page.getProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'innerText'); + + const reference = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelReference, 'value'); + const lockedKg = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'value'); expect(reference).toContain('edited reference'); expect(lockedKg).toContain(1500); diff --git a/modules/travel/back/methods/travel/extraCommunityFilter.js b/modules/travel/back/methods/travel/extraCommunityFilter.js index 7769b7f21..feb16d052 100644 --- a/modules/travel/back/methods/travel/extraCommunityFilter.js +++ b/modules/travel/back/methods/travel/extraCommunityFilter.js @@ -128,7 +128,7 @@ module.exports = Self => { w.name AS warehouseInFk, w.name AS warehouseInName, SUM(b.stickers) AS stickers, - s.id AS supplierFk, + s.id AS cargoSupplierFk, s.nickname AS cargoSupplierNickname, CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedKg, CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg @@ -160,13 +160,14 @@ module.exports = Self => { e.travelFk, e.ref, e.loadPriority, + s.id AS supplierFk, s.name AS supplierName, SUM(b.stickers) AS stickers, e.evaNotes, e.notes, CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedkg, CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg - FROM tmp.travel tr + FROM tmp.travel tr JOIN entry e ON e.travelFk = tr.id JOIN buy b ON b.entryFk = e.id JOIN packaging pkg ON pkg.id = b.packageFk diff --git a/modules/travel/front/extra-community-search-panel/index.html b/modules/travel/front/extra-community-search-panel/index.html index ab1e88891..09fbb8949 100644 --- a/modules/travel/front/extra-community-search-panel/index.html +++ b/modules/travel/front/extra-community-search-panel/index.html @@ -50,15 +50,15 @@ @@ -84,4 +84,4 @@ - \ No newline at end of file + diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index 0b94c6c1e..e68523750 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -1,6 +1,7 @@ - - -
- - - - - - - - Id - Supplier - Freighter - Reference - Packages - Bl. KG - Phy. KG - Vol. KG - - Wh. Out - - W. Shipped - - Wh. In - - W. Landed - - - - - - + + +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + +
+ Id {{$ctrl.dragClientY}} + + Supplier + + Freighter + + Reference + + Packages + + Bl. KG + + Phy. KG + + Vol. KG + + Wh. Out + + W. Shipped + + Wh. In + + W. Landed +
+ {{::travel.id}} - - {{::travel.agencyModeName}} - {{::travel.cargoSupplierNickname}} - - {{travel.ref}} - - - - - - {{::travel.stickers}} - - {{travel.kg}} - - - - - - {{::travel.loadedKg}} - {{::travel.volumeKg}} - {{::travel.warehouseOutName}} - {{::travel.shipped | date: 'dd/MM/yyyy'}} - {{::travel.warehouseInName}} - {{::travel.landed | date: 'dd/MM/yyyy'}} - - + + + {{::travel.cargoSupplierNickname}} + + {{::travel.agencyModeName}} + + + + {{::travel.stickers}} + + + {{::travel.loadedKg}}{{::travel.volumeKg}}{{::travel.warehouseOutName}}{{::travel.shipped | date: 'dd/MM/yyyy'}}{{::travel.warehouseInName}}{{::travel.landed | date: 'dd/MM/yyyy'}}
{{::entry.id}} - - {{::entry.supplierName}} - - {{::entry.ref}} - {{::entry.stickers}} - - {{::entry.loadedkg}} - {{::entry.volumeKg}} - + + + {{::entry.supplierName}} + + {{::entry.ref}}{{::entry.stickers}}{{::entry.loadedkg}}{{::entry.volumeKg}} {{::entry.notes}} - - + {{::entry.evaNotes}} - - - - - - - - - +
+
+
+ - + + diff --git a/modules/travel/front/extra-community/index.js b/modules/travel/front/extra-community/index.js index a380c1ed8..461712e9c 100644 --- a/modules/travel/front/extra-community/index.js +++ b/modules/travel/front/extra-community/index.js @@ -14,8 +14,15 @@ class Controller extends Section { draggable.addEventListener('dragend', event => this.dragEnd(event)); - this.draggableElement = 'a[draggable]'; - this.droppableElement = 'vn-tbody[vn-droppable]'; + draggable.addEventListener('dragover', + event => this.dragOver(event)); + draggable.addEventListener('dragenter', + event => this.dragEnter(event)); + draggable.addEventListener('dragleave', + event => this.dragLeave(event)); + + this.draggableElement = 'tr[draggable]'; + this.droppableElement = 'tbody[vn-droppable]'; const twoDays = 2; const shippedFrom = new Date(); @@ -32,6 +39,8 @@ class Controller extends Section { landedTo: landedTo, continent: 'AM' }; + + this.smartTableOptions = {}; } get hasDateRange() { @@ -44,6 +53,15 @@ class Controller extends Section { return hasLanded || hasShipped || hasContinent || hasWarehouseOut; } + onDragInterval() { + if (this.dragClientY > 0 && this.dragClientY < 75) + this.$window.scrollTo(0, this.$window.scrollY - 10); + + const maxHeight = window.screen.availHeight - (window.outerHeight - window.innerHeight); + if (this.dragClientY > maxHeight - 75 && this.dragClientY < maxHeight) + this.$window.scrollTo(0, this.$window.scrollY + 10); + } + findDraggable($event) { const target = $event.target; const draggable = target.closest(this.draggableElement); @@ -65,6 +83,7 @@ class Controller extends Section { const id = parseInt(draggable.id); this.entryId = id; this.entry = draggable; + this.interval = setInterval(() => this.onDragInterval(), 50); } dragEnd($event) { @@ -72,6 +91,8 @@ class Controller extends Section { draggable.classList.remove('dragging'); this.entryId = null; this.entry = null; + + clearInterval(this.interval); } onDrop($event) { @@ -91,6 +112,37 @@ class Controller extends Section { } } + undrop() { + if (!this.dropping) return; + this.dropping.classList.remove('dropping'); + this.dropping = null; + } + + dragOver($event) { + this.dragClientY = $event.clientY; + $event.preventDefault(); + } + + dragEnter($event) { + let element = this.findDroppable($event); + if (element) this.dropCount++; + + if (element != this.dropping) { + this.undrop(); + if (element) element.classList.add('dropping'); + this.dropping = element; + } + } + + dragLeave($event) { + let element = this.findDroppable($event); + + if (element) { + this.dropCount--; + if (this.dropCount == 0) this.undrop(); + } + } + save(id, data) { const endpoint = `Travels/${id}`; this.$http.patch(endpoint, data) diff --git a/modules/travel/front/extra-community/index.spec.js b/modules/travel/front/extra-community/index.spec.js index 59688a46c..ae48b9ca1 100644 --- a/modules/travel/front/extra-community/index.spec.js +++ b/modules/travel/front/extra-community/index.spec.js @@ -27,7 +27,7 @@ describe('Travel Component vnTravelExtraCommunity', () => { describe('findDraggable()', () => { it('should find the draggable element', () => { - const draggable = document.createElement('a'); + const draggable = document.createElement('tr'); draggable.setAttribute('draggable', true); const $event = new Event('dragstart'); @@ -43,7 +43,7 @@ describe('Travel Component vnTravelExtraCommunity', () => { describe('findDroppable()', () => { it('should find the droppable element', () => { - const droppable = document.createElement('vn-tbody'); + const droppable = document.createElement('tbody'); droppable.setAttribute('vn-droppable', true); const $event = new Event('drop'); @@ -58,9 +58,9 @@ describe('Travel Component vnTravelExtraCommunity', () => { }); describe('dragStart()', () => { - it(`should add the class "dragging" to the draggable element + it(`should add the class "dragging" to the draggable element and then set the entryId controller property`, () => { - const draggable = document.createElement('a'); + const draggable = document.createElement('tr'); draggable.setAttribute('draggable', true); draggable.setAttribute('id', 3); @@ -78,9 +78,9 @@ describe('Travel Component vnTravelExtraCommunity', () => { }); describe('dragEnd()', () => { - it(`should remove the class "dragging" from the draggable element + it(`should remove the class "dragging" from the draggable element and then set the entryId controller property to null`, () => { - const draggable = document.createElement('a'); + const draggable = document.createElement('tr'); draggable.setAttribute('draggable', true); draggable.setAttribute('id', 3); draggable.classList.add('dragging'); @@ -100,13 +100,13 @@ describe('Travel Component vnTravelExtraCommunity', () => { describe('onDrop()', () => { it('should make an HTTP patch query', () => { - const droppable = document.createElement('vn-tbody'); + const droppable = document.createElement('tbody'); droppable.setAttribute('vn-droppable', true); droppable.setAttribute('id', 1); jest.spyOn(controller, 'findDroppable').mockReturnValue(droppable); - const oldDroppable = document.createElement('vn-tbody'); + const oldDroppable = document.createElement('tbody'); oldDroppable.setAttribute('vn-droppable', true); const entry = document.createElement('div'); oldDroppable.appendChild(entry); diff --git a/modules/travel/front/extra-community/style.scss b/modules/travel/front/extra-community/style.scss index f903f94ea..532a3056a 100644 --- a/modules/travel/front/extra-community/style.scss +++ b/modules/travel/front/extra-community/style.scss @@ -15,41 +15,44 @@ vn-travel-extra-community { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + cursor: pointer; } - vn-td-editable text { - background-color: transparent; - padding: 0; - border: 0; - border-bottom: 1px dashed $color-active; - border-radius: 0; - color: $color-active - } - - vn-td-editable:hover text:after { - font-family: 'Material Icons'; - content: 'edit'; - position: absolute; - right: -15px; - color: $color-spacer - } - - vn-table[vn-droppable] { + table[vn-droppable] { border-radius: 0; } - a[draggable] { + tr[draggable] { transition: all .5s; cursor: move; + overflow: auto; outline: 0; + height: 65px; + pointer-events: fill; + user-select:all; } - a[draggable]:hover { - background-color: $color-hover-cd + tr[draggable] *::selection{ + background-color: transparent; } - a[draggable].dragging { - background-color: $color-success-light; - font-weight:bold + tr[draggable]:hover { + background-color: $color-hover-cd; } -} \ No newline at end of file + + tr[draggable].dragging { + background-color: $color-primary-light; + color: $color-font-light; + font-weight:bold; + } + + .td-editable{ + input{ + font-size: 1.25rem!important; + } + } + + .number *{ + text-align: right; + } +} From 49a7a053109fbd546ac69ed276387f291f1a59c4 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 14 Sep 2022 08:08:07 +0200 Subject: [PATCH 145/150] feat(travel_extra-community): add vn-anchor --- .../travel/front/extra-community/index.html | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index e68523750..dbfc69082 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -27,7 +27,7 @@
@@ -40,7 +40,7 @@ - Id {{$ctrl.dragClientY}} + Id Supplier @@ -88,7 +88,11 @@ ng-attr-id="{{::travel.id}}" vn-stop-click> + class="header" + vn-anchor="::{ + state: 'travel.card.basicData', + params: {id: travel.id} + }"> {{::travel.agencyModeName}} - + {{::travel.stickers}} - + + min="0"> {{::travel.loadedKg}} From 6b252f7e331eab03daca11591b195a5c13e6a965 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 14 Sep 2022 10:13:30 +0200 Subject: [PATCH 146/150] refactor(item_waste): refactor table and add totals in table_head --- .../back/methods/item/getWasteByWorker.js | 23 +++++- .../item/specs/getWasteByWorker.spec.js | 1 + modules/item/front/waste/index/index.html | 79 ++++++++++--------- modules/item/front/waste/index/style.scss | 20 ++--- 4 files changed, 71 insertions(+), 52 deletions(-) diff --git a/modules/item/back/methods/item/getWasteByWorker.js b/modules/item/back/methods/item/getWasteByWorker.js index 2b0b78974..4c3c64a91 100644 --- a/modules/item/back/methods/item/getWasteByWorker.js +++ b/modules/item/back/methods/item/getWasteByWorker.js @@ -30,11 +30,24 @@ module.exports = Self => { sum(ws.saleWaste) AS dwindle FROM bs.waste ws WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?)) - AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) + AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) GROUP BY buyer, family ) sub ORDER BY percentage DESC`, [date, date], myOptions); + const wastesTotal = await Self.rawSql(` + SELECT *, 100 * dwindle / total AS percentage + FROM ( + SELECT buyer, + sum(ws.saleTotal) AS total, + sum(ws.saleWaste) AS dwindle + FROM bs.waste ws + WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?)) + AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) + GROUP BY buyer + ) sub + ORDER BY percentage DESC`, [date, date], myOptions); + const details = []; for (let waste of wastes) { @@ -55,6 +68,14 @@ module.exports = Self => { buyerDetail.lines.push(waste); } + for (let waste of details) { + let buyerTotal = wastesTotal.find(totals => { + return waste.buyer == totals.buyer; + }); + + Object.assign(waste, buyerTotal); + } + return details; }; }; diff --git a/modules/item/back/methods/item/specs/getWasteByWorker.spec.js b/modules/item/back/methods/item/specs/getWasteByWorker.spec.js index c8b9d5e15..52f806bd3 100644 --- a/modules/item/back/methods/item/specs/getWasteByWorker.spec.js +++ b/modules/item/back/methods/item/specs/getWasteByWorker.spec.js @@ -12,6 +12,7 @@ describe('Item getWasteByWorker()', () => { const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; expect(anyResult.buyer).toMatch(/(CharlesXavier|HankPym|DavidCharlesHaller)/); + expect(anyResult.total).toBeGreaterThanOrEqual(1000); expect(anyResult.lines.length).toBeGreaterThanOrEqual(3); await tx.rollback(); diff --git a/modules/item/front/waste/index/index.html b/modules/item/front/waste/index/index.html index c80733e9e..5da5acbf1 100644 --- a/modules/item/front/waste/index/index.html +++ b/modules/item/front/waste/index/index.html @@ -4,39 +4,46 @@ data="details"> -
- -
{{detail.buyer}}
- - - - -
- - - - - Family - Percentage - Dwindle - Total - - - - - {{::waste.family}} - {{::(waste.percentage / 100) | percentage: 2}} - {{::waste.dwindle | currency: 'EUR'}} - {{::waste.total | currency: 'EUR'}} - - - - -
-
\ No newline at end of file + + + + + Buyer + Family + Percentage + Dwindle + Total + + + + + + {{::detail.buyer}} + {{::detail.family}} + {{::(detail.percentage / 100) | percentage: 2}} + {{::detail.dwindle | currency: 'EUR'}} + {{::detail.total | currency: 'EUR'}} + + + + + + + + {{::waste.family}} + {{::(waste.percentage / 100) | percentage: 2}} + {{::waste.dwindle | currency: 'EUR'}} + {{::waste.total | currency: 'EUR'}} + + + + + + diff --git a/modules/item/front/waste/index/style.scss b/modules/item/front/waste/index/style.scss index 8b44cb6f1..36fac3311 100644 --- a/modules/item/front/waste/index/style.scss +++ b/modules/item/front/waste/index/style.scss @@ -5,20 +5,9 @@ vn-item-waste-index, vn-item-waste-detail { .header { padding: 12px 0 5px 0; - color: gray; + background-color: $color-bg; font-size: 1.2rem; - border-bottom: $border; margin-bottom: 10px; - - & > vn-none > vn-icon { - @extend %clickable-light; - color: $color-button; - font-size: 1.4rem; - } - - vn-none > .arrow { - transition: transform 200ms; - } } vn-table vn-th.waste-family, @@ -26,12 +15,13 @@ vn-item-waste-detail { max-width: 64px; width: 64px } + .hidden { display: none; - } - .header > vn-none > .arrow.hidden { + + .arrow.hidden { display: block; transform: rotate(180deg); } -} \ No newline at end of file +} From 06eab58b3d6613effa5aa970391ae178553a7af7 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 14 Sep 2022 12:45:40 +0200 Subject: [PATCH 147/150] is agency --- modules/travel/front/extra-community/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index dbfc69082..f19ab592e 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -46,7 +46,7 @@ Supplier - Freighter + Agency Reference From 9d5351c84c9cd0eb8ea9919fc1937bbcdcf491fe Mon Sep 17 00:00:00 2001 From: joan Date: Thu, 15 Sep 2022 07:31:49 +0000 Subject: [PATCH 148/150] Updated db image --- db/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/db/Dockerfile b/db/Dockerfile index cb3aa741c..152b953dc 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -1,6 +1,6 @@ -FROM mariadb:10.7.3 +FROM mariadb:10.7.5 -ENV MYSQL_ROOT_PASSWORD root +ENV MARIADB_ROOT_PASSWORD root ENV TZ Europe/Madrid ARG DEBIAN_FRONTEND=noninteractive @@ -22,6 +22,7 @@ COPY \ docker/docker-start.sh \ /usr/local/bin/ +RUN chmod 775 /etc/mysql/conf.d/docker.cnf RUN mkdir /mysql-data \ && chown -R mysql:mysql /mysql-data @@ -35,6 +36,8 @@ COPY \ dump/structure.sql \ dump/dumpedFixtures.sql \ ./ + +RUN chmod 775 config.ini RUN gosu mysql docker-init.sh \ && docker-dump.sh mysqlPlugins \ && docker-dump.sh mockDate \ From f5bbe3a9ac1dc0ac2285bcece4d262145068d8c1 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 15 Sep 2022 11:55:07 +0200 Subject: [PATCH 149/150] fix: scope updated --- modules/route/back/models/vehicle.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/route/back/models/vehicle.json b/modules/route/back/models/vehicle.json index a35926bef..34a376b89 100644 --- a/modules/route/back/models/vehicle.json +++ b/modules/route/back/models/vehicle.json @@ -50,8 +50,10 @@ }, "scope": { "where": { - "isActive": true - } + "isActive": { + "neq": false + } + } }, "acls": [ { From 503e78d4f0bf15bcf3223e35dea6aa866621d9bb Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 16 Sep 2022 09:07:43 +0200 Subject: [PATCH 150/150] fix: rollback mariadb version --- db/Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/db/Dockerfile b/db/Dockerfile index 152b953dc..cb3aa741c 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -1,6 +1,6 @@ -FROM mariadb:10.7.5 +FROM mariadb:10.7.3 -ENV MARIADB_ROOT_PASSWORD root +ENV MYSQL_ROOT_PASSWORD root ENV TZ Europe/Madrid ARG DEBIAN_FRONTEND=noninteractive @@ -22,7 +22,6 @@ COPY \ docker/docker-start.sh \ /usr/local/bin/ -RUN chmod 775 /etc/mysql/conf.d/docker.cnf RUN mkdir /mysql-data \ && chown -R mysql:mysql /mysql-data @@ -36,8 +35,6 @@ COPY \ dump/structure.sql \ dump/dumpedFixtures.sql \ ./ - -RUN chmod 775 config.ini RUN gosu mysql docker-init.sh \ && docker-dump.sh mysqlPlugins \ && docker-dump.sh mockDate \