diff --git a/db/routines/vn/procedures/ticket_canbePostponed.sql b/db/routines/vn/procedures/ticket_canbePostponed.sql index 1f3c43057b..a21e171cfa 100644 --- a/db/routines/vn/procedures/ticket_canbePostponed.sql +++ b/db/routines/vn/procedures/ticket_canbePostponed.sql @@ -19,6 +19,7 @@ BEGIN sub2.iptd futureIpt, sub2.state futureState, t.clientFk, + cl.salespersonFk, t.warehouseFk, ts.alertLevel, sub2.alertLevel futureAlertLevel, @@ -38,6 +39,7 @@ BEGIN JOIN vn.province p ON p.id = a.provinceFk JOIN vn.country c ON c.id = p.countryFk JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.client cl ON cl.id = t.clientFk JOIN vn.state st ON st.id = ts.stateFk JOIN vn.alertLevel al ON al.id = ts.alertLevel LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id diff --git a/db/routines/vn/triggers/itemTaxCountry_beforeDelete.sql b/db/routines/vn/triggers/itemTaxCountry_beforeDelete.sql new file mode 100644 index 0000000000..461b861f28 --- /dev/null +++ b/db/routines/vn/triggers/itemTaxCountry_beforeDelete.sql @@ -0,0 +1,8 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemTaxCountry_beforeDelete` + BEFORE DELETE ON `itemTaxCountry` + FOR EACH ROW +BEGIN + CALL util.throw('Records in this table cannot be deleted'); +END$$ +DELIMITER ; diff --git a/db/routines/vn/triggers/itemTaxCountry_beforeUpdate.sql b/db/routines/vn/triggers/itemTaxCountry_beforeUpdate.sql index ad7d6327bf..5220028e8b 100644 --- a/db/routines/vn/triggers/itemTaxCountry_beforeUpdate.sql +++ b/db/routines/vn/triggers/itemTaxCountry_beforeUpdate.sql @@ -4,5 +4,9 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemTaxCountry_beforeUp FOR EACH ROW BEGIN SET NEW.editorFk = account.myUser_getId(); + + IF NOT(NEW.`countryFk` <=> OLD.`countryFk`) OR NOT(NEW.`itemFk` <=> OLD.`itemFk`) THEN + CALL util.throw('Only the VAT can be modified'); + END IF; END$$ DELIMITER ; diff --git a/db/routines/vn2008/views/Split_lines.sql b/db/routines/vn2008/views/Split_lines.sql deleted file mode 100644 index 0b7897be73..0000000000 --- a/db/routines/vn2008/views/Split_lines.sql +++ /dev/null @@ -1,8 +0,0 @@ -CREATE OR REPLACE DEFINER=`root`@`localhost` - SQL SECURITY DEFINER - VIEW `vn2008`.`Split_lines` -AS SELECT `sl`.`id` AS `Id_Split_lines`, - `sl`.`splitFk` AS `Id_Split`, - `sl`.`itemFk` AS `Id_Article`, - `sl`.`buyFk` AS `Id_Compra` -FROM `vn`.`splitLine` `sl` \ No newline at end of file diff --git a/db/versions/11383-maroonChico/00-town.sql b/db/versions/11383-maroonChico/00-town.sql new file mode 100644 index 0000000000..8fdd8c7b09 --- /dev/null +++ b/db/versions/11383-maroonChico/00-town.sql @@ -0,0 +1,10 @@ +UPDATE vn.town t + LEFT JOIN vn.zoneGeo zg ON zg.id = t.geoFk + SET t.geoFk = NULL + WHERE zg.id IS NULL; + +ALTER TABLE vn.town + ADD CONSTRAINT town_zoneGeo_FK FOREIGN KEY (geoFk) + REFERENCES vn.zoneGeo(id) + ON DELETE RESTRICT + ON UPDATE CASCADE; diff --git a/db/versions/11383-maroonChico/01-postCode.sql b/db/versions/11383-maroonChico/01-postCode.sql new file mode 100644 index 0000000000..668cf69cbf --- /dev/null +++ b/db/versions/11383-maroonChico/01-postCode.sql @@ -0,0 +1,10 @@ +UPDATE vn.postCode pc + LEFT JOIN vn.zoneGeo zg ON zg.id = pc.geoFk + SET pc.geoFk = NULL + WHERE zg.id IS NULL; + +ALTER TABLE vn.postCode + ADD CONSTRAINT postCode_zoneGeo_FK FOREIGN KEY (geoFk) + REFERENCES vn.zoneGeo(id) + ON DELETE RESTRICT + ON UPDATE CASCADE; diff --git a/db/versions/11383-maroonChico/02-province.sql b/db/versions/11383-maroonChico/02-province.sql new file mode 100644 index 0000000000..c16d33cd8d --- /dev/null +++ b/db/versions/11383-maroonChico/02-province.sql @@ -0,0 +1,10 @@ +UPDATE vn.province p + LEFT JOIN vn.zoneGeo zg ON zg.id = p.geoFk + SET p.geoFk = NULL + WHERE zg.id IS NULL; + +ALTER TABLE vn.province + ADD CONSTRAINT province_zoneGeo_FK FOREIGN KEY (geoFk) + REFERENCES vn.zoneGeo(id) + ON DELETE RESTRICT + ON UPDATE CASCADE; diff --git a/db/versions/11418-goldenRuscus/00-firstScript.sql b/db/versions/11418-goldenRuscus/00-firstScript.sql new file mode 100644 index 0000000000..6c623d7b77 --- /dev/null +++ b/db/versions/11418-goldenRuscus/00-firstScript.sql @@ -0,0 +1,130 @@ +-- Place your SQL code here +CREATE TABLE IF NOT EXISTS `vn`.`itemSoldOutTag` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name_UNIQUE` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT + CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Ultimas unidades'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Temporalmente'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Descatalogado'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta mayo'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta febrero'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta diciembre'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta enero'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta marzo'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta nueva temporada'); +INSERT IGNORE INTO `vn`.`itemSoldOutTag` (`name`) VALUES ('Hasta septiembre'); + +UPDATE vn.tag + SET isFree=FALSE, + sourceTable='itemSoldOutTag' + WHERE name= 'Agotado'; + + +CREATE TABLE IF NOT EXISTS `vn`.`itemDurationTag` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name_UNIQUE` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT + CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('11 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('12 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('13 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('17 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('7 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('16-20 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('17-21 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('19-23 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('3-4 semanas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('13-17 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14-16 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15-19 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('18-25 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('20 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10-13 días'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 meses'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('5 años'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('10 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('20 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('35 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('6 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('11 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('12 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('14 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('15 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('18 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('19 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('24 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('25 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('30 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('32 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('4 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('40 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('45 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('50 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('55 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('70 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('8 horas'); +INSERT IGNORE INTO `vn`.`itemDurationTag` (`name`) VALUES ('9 horas'); + +UPDATE vn.tag + SET isFree=FALSE, + sourceTable='itemDurationTag' + WHERE name= 'Duracion'; + + +CREATE TABLE IF NOT EXISTS `vn`.`itemGrowingTag` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name_UNIQUE` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT + CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-06'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('02-06'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-05'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-07'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-08'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('03-11'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-06'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-09'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('04-11'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-07'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-08'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-10'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('05-11'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-09'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-10'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('06-11'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-09'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-10'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-11'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('07-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('09-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-04 / 10-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-04 / 9-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05 / 10-12'); +INSERT IGNORE INTO `vn`.`itemGrowingTag` (`name`) VALUES ('01-05 / 11-12'); + +UPDATE vn.tag + SET isFree=FALSE, + sourceTable='itemGrowingTag' + WHERE name= 'Recolecta'; + +GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemSoldOutTag TO logisticAssist; +GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemDurationTag TO logisticAssist; +GRANT SELECT, UPDATE, INSERT, DELETE ON TABLE vn.itemGrowingTag TO logisticAssist; diff --git a/db/versions/11422-blackAsparagus/00-firstScript.sql b/db/versions/11422-blackAsparagus/00-firstScript.sql new file mode 100644 index 0000000000..bb0e7c3e9f --- /dev/null +++ b/db/versions/11422-blackAsparagus/00-firstScript.sql @@ -0,0 +1,5 @@ +RENAME TABLE vn.sorter TO vn.sorter__; +ALTER TABLE vn.sorter__ COMMENT='@deprecated 2025-01-22'; + +RENAME TABLE vn.splitLine TO vn.splitLine__; +ALTER TABLE vn.splitLine__ COMMENT='@deprecated 2025-01-22'; \ No newline at end of file diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index d9689e31ad..af1dc56bcd 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -238,25 +238,11 @@ describe('Ticket Edit sale path', () => { await page.waitToClick(selectors.globalItems.cancelButton); }); - it('should select the third sale and create a claim of it', async() => { - await page.accessToSearchResult('16'); - await page.accessToSection('ticket.card.sale'); - await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); - await page.waitToClick(selectors.ticketSales.moreMenu); - await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); - await page.waitToClick(selectors.globalItems.acceptButton); - await page.waitForNavigation(); - }); - - it('should search for a ticket then access to the sales section', async() => { - await page.goBack(); - await page.goBack(); + it('should select the third sale and delete it', async() => { await page.loginAndModule('salesPerson', 'ticket'); await page.accessToSearchResult('16'); await page.accessToSection('ticket.card.sale'); - }); - it('should select the third sale and delete it', async() => { await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.deleteSaleButton); await page.waitToClick(selectors.globalItems.acceptButton); diff --git a/e2e/paths/05-ticket/06_basic_data_steps.spec.js b/e2e/paths/05-ticket/06_basic_data_steps.spec.js index 77f0e0459c..0a3ae4edce 100644 --- a/e2e/paths/05-ticket/06_basic_data_steps.spec.js +++ b/e2e/paths/05-ticket/06_basic_data_steps.spec.js @@ -75,7 +75,7 @@ describe('Ticket Edit basic data path', () => { const result = await page .waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText'); - expect(result).toContain('-€228.25'); + expect(result).toContain('-€111.75'); }); it(`should select a new reason for the changes made then click on finalize`, async() => { diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 8d5eab4bc5..06428475fc 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -211,7 +211,7 @@ "Name should be uppercase": "Name should be uppercase", "You cannot update these fields": "You cannot update these fields", "CountryFK cannot be empty": "Country cannot be empty", - "No tickets to invoice": "There are no tickets to invoice that meet the invoicing requirements", + "No tickets to invoice": "There are no tickets to invoice that meet the invoicing requirements", "You are not allowed to modify the alias": "You are not allowed to modify the alias", "You already have the mailAlias": "You already have the mailAlias", "This machine is already in use.": "This machine is already in use.", @@ -247,9 +247,11 @@ "ticketLostExpedition": "The ticket [{{ticketId}}]({{{ticketUrl}}}) has the following lost expedition:{{ expeditionId }}", "The raid information is not correct": "The raid information is not correct", "Payment method is required": "Payment method is required", - "Sales already moved": "Sales already moved", - "Holidays to past days not available": "Holidays to past days not available", "Price cannot be blank": "Price cannot be blank", "There are tickets to be invoiced": "There are tickets to be invoiced", - "The address of the customer must have information about Incoterms and Customs Agent": "The address of the customer must have information about Incoterms and Customs Agent" -} + "The address of the customer must have information about Incoterms and Customs Agent": "The address of the customer must have information about Incoterms and Customs Agent", + "Sales already moved": "Sales already moved", + "Holidays to past days not available": "Holidays to past days not available", + "Incorrect delivery order alert on route": "Incorrect delivery order alert on route: {{ route }} zone: {{ zone }}", + "Ticket has been delivered out of order": "The ticket {{ticket}} {{{fullUrl}}} has been delivered out of order." +} \ No newline at end of file diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 3183a96972..abd2f79a0d 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -390,14 +390,11 @@ "The web user's email already exists": "El correo del usuario web ya existe", "Sales already moved": "Ya han sido transferidas", "The raid information is not correct": "La información de la redada no es correcta", - "No trips found because input coordinates are not connected": "No se encontraron rutas porque las coordenadas de entrada no están conectadas", - "This request is not supported": "Esta solicitud no es compatible", - "Invalid options or too many coordinates": "Opciones invalidas o demasiadas coordenadas", - "No address has coordinates": "Ninguna dirección tiene coordenadas", "An item type with the same code already exists": "Un tipo con el mismo código ya existe", "Holidays to past days not available": "Las vacaciones a días pasados no están disponibles", "All tickets have a route order": "Todos los tickets tienen orden de ruta", - "Price cannot be blank": "Price cannot be blank", "There are tickets to be invoiced": "La zona tiene tickets por facturar", - "Social name should be uppercase": "La razón social debe ir en mayúscula" + "Incorrect delivery order alert on route": "Alerta de orden de entrega incorrecta en ruta: {{ route }} zona: {{ zone }}", + "Ticket has been delivered out of order": "El ticket {{ticket}} {{{fullUrl}}} no ha sigo entregado en su orden.", + "Price cannot be blank": "El precio no puede estar en blanco" } \ No newline at end of file diff --git a/loopback/locale/fr.json b/loopback/locale/fr.json index f49196a8fd..d7d5b7710e 100644 --- a/loopback/locale/fr.json +++ b/loopback/locale/fr.json @@ -362,9 +362,11 @@ "The invoices have been created but the PDFs could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré", "It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré", "Cannot send mail": "Impossible d'envoyer le mail", - "Original invoice not found": "Facture originale introuvable", - "The quantity claimed cannot be greater than the quantity of the line": "Le montant réclamé ne peut pas être supérieur au montant de la ligne", - "You do not have permission to modify the booked field": "Vous n'avez pas la permission de modifier le champ comptabilisé", + "Original invoice not found": "Facture originale introuvable", + "The quantity claimed cannot be greater than the quantity of the line": "Le montant réclamé ne peut pas être supérieur au montant de la ligne", + "You do not have permission to modify the booked field": "Vous n'avez pas la permission de modifier le champ comptabilisé", "ticketLostExpedition": "Le ticket [{{ticketId}}]({{{ticketUrl}}}) a l'expédition perdue suivante : {{expeditionId}}", - "The web user's email already exists": "L'email de l'internaute existe déjà" -} + "The web user's email already exists": "L'email de l'internaute existe déjà", + "Incorrect delivery order alert on route": "Alerte de bon de livraison incorrect sur l'itinéraire: {{ route }} zone : {{ zone }}", + "Ticket has been delivered out of order": "Le ticket {{ticket}} {{{fullUrl}}} a été livré hors ordre." +} \ No newline at end of file diff --git a/loopback/locale/pt.json b/loopback/locale/pt.json index e2374d35f3..d1ac2ef236 100644 --- a/loopback/locale/pt.json +++ b/loopback/locale/pt.json @@ -365,5 +365,7 @@ "Cannot send mail": "Não é possível enviar o email", "The quantity claimed cannot be greater than the quantity of the line": "O valor reclamado não pode ser superior ao valor da linha", "ticketLostExpedition": "O ticket [{{ticketId}}]({{{ticketUrl}}}) tem a seguinte expedição perdida: {{expeditionId}}", - "The web user's email already exists": "O e-mail do utilizador da web já existe." -} + "The web user's email already exists": "O e-mail do utilizador da web já existe.", + "Incorrect delivery order alert on route": "Alerta de ordem de entrega incorreta na rota: {{ route }} zona: {{ zone }}", + "Ticket has been delivered out of order": "O ticket {{ticket}} {{{fullUrl}}} foi entregue fora de ordem." +} \ No newline at end of file diff --git a/modules/claim/back/methods/claim/filter.js b/modules/claim/back/methods/claim/filter.js index bacdd40216..bf9cd94412 100644 --- a/modules/claim/back/methods/claim/filter.js +++ b/modules/claim/back/methods/claim/filter.js @@ -109,6 +109,7 @@ module.exports = Self => { const args = ctx.args; const myOptions = {}; let to; + let myTeamIds = []; if (typeof options == 'object') Object.assign(myOptions, options); @@ -133,21 +134,8 @@ module.exports = Self => { claimIdsByClaimResponsibleFk = claims.map(claim => claim.claimFk); } - // Apply filter by team - const teamMembersId = []; - if (args.myTeam != null) { - const worker = await models.Worker.findById(userId, { - include: { - relation: 'collegues' - } - }, myOptions); - const collegues = worker.collegues() || []; - for (let collegue of collegues) - teamMembersId.push(collegue.collegueFk); - - if (teamMembersId.length == 0) - teamMembersId.push(userId); - } + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); const where = buildFilter(ctx.args, (param, value) => { switch (param) { @@ -184,9 +172,9 @@ module.exports = Self => { return {'t.zoneFk': value}; case 'myTeam': if (value) - return {'cl.workerFk': {inq: teamMembersId}}; + return {'cl.workerFk': {inq: myTeamIds}}; else - return {'cl.workerFk': {nin: teamMembersId}}; + return {'cl.workerFk': {nin: myTeamIds}}; } }); diff --git a/modules/client/back/methods/client/specs/updateAddress.spec.js b/modules/client/back/methods/client/specs/updateAddress.spec.js index 0453332d71..233ab9ccb8 100644 --- a/modules/client/back/methods/client/specs/updateAddress.spec.js +++ b/modules/client/back/methods/client/specs/updateAddress.spec.js @@ -157,4 +157,52 @@ describe('Address updateAddress', () => { throw e; } }); + + it('should update ticket observations when updateObservations is true', async() => { + const tx = await models.Client.beginTransaction({}); + const client = 1103; + const address = 123; + const ticket = 31; + const observationType = 3; + + const salesAssistantId = 21; + const addressObservation = 'nuevo texto'; + const ticketObservation = 'texto a modificar'; + + try { + const options = {transaction: tx}; + ctx.req.accessToken.userId = salesAssistantId; + ctx.args = { + updateObservations: true, + incotermsFk: incotermsId, + provinceFk: provinceId, + customsAgentFk: customAgentOneId + }; + + await models.AddressObservation.create({ + addressFk: address, + observationTypeFk: observationType, + description: addressObservation + }, options); + + await models.TicketObservation.create({ + ticketFk: ticket, + observationTypeFk: observationType, + description: ticketObservation + }, options); + + await models.Client.updateAddress(ctx, client, address, options); + + const updatedObservation = await models.TicketObservation.findOne({ + where: {ticketFk: ticket, observationTypeFk: observationType} + }, options); + + expect(updatedObservation.description).toEqual(addressObservation); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); diff --git a/modules/client/back/methods/client/updateAddress.js b/modules/client/back/methods/client/updateAddress.js index efef83d6b3..e6e5adb451 100644 --- a/modules/client/back/methods/client/updateAddress.js +++ b/modules/client/back/methods/client/updateAddress.js @@ -80,6 +80,10 @@ module.exports = function(Self) { { arg: 'latitude', type: 'any', + }, + { + arg: 'updateObservations', + type: 'boolean' } ], returns: { @@ -135,6 +139,17 @@ module.exports = function(Self) { delete args.ctx; // Remove unwanted properties const updatedAddress = await address.updateAttributes(ctx.args, myOptions); + if (args.updateObservations) { + const ticket = await Self.rawSql(` + UPDATE ticketObservation to2 + JOIN ticket t ON t.id = to2.ticketFk + JOIN address a ON a.id = t.addressFk + JOIN addressObservation ao ON ao.addressFk = a.id + SET to2.description = ao.description + WHERE ao.observationTypeFk = to2.observationTypeFk + AND a.id = ? + AND t.shipped >= util.VN_CURDATE()`, [addressId], myOptions); + } return updatedAddress; }; diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js index cf8bd855ab..e00048cf54 100644 --- a/modules/client/back/methods/defaulter/filter.js +++ b/modules/client/back/methods/defaulter/filter.js @@ -21,7 +21,7 @@ module.exports = Self => { } ], returns: { - type: ['object'], + type: 'object', root: true }, http: { @@ -41,23 +41,28 @@ module.exports = Self => { switch (param) { case 'search': return {or: [ - {'d.clientFk': value}, - {'d.clientName': {like: `%${value}%`}} + {'c.id': value}, + {'c.name': {like: `%${value}%`}} ]}; } }); - filter = mergeFilters(ctx.args.filter, {where}); + const date = Date.vnNew(); + date.setHours(0, 0, 0, 0); + + filter = mergeFilters({where: {'d.created': date, 'd.amount': {gt: 0}}}, ctx.args.filter); + filter = mergeFilters(filter, {where}); const stmts = []; - const date = Date.vnNew(); - date.setHours(0, 0, 0, 0); - const stmt = new ParameterizedSQL( - `SELECT * - FROM ( - SELECT - DISTINCT c.id clientFk, + let stmt = new ParameterizedSQL( + `CREATE OR REPLACE TEMPORARY TABLE tmp.defaulters + WITH clientObservations AS + (SELECT clientFk,text, created, workerFk + FROM vn.clientObservation + GROUP BY clientFk + ORDER BY created DESC + )SELECT c.id clientFk, c.name clientName, c.salesPersonFk, c.businessTypeFk = 'worker' isWorker, @@ -80,36 +85,43 @@ module.exports = Self => { JOIN client c ON c.id = d.clientFk JOIN country cn ON cn.id = c.countryFk JOIN payMethod pm ON pm.id = c.payMethodFk - LEFT JOIN clientObservation co ON co.clientFk = c.id + LEFT JOIN clientObservations co ON co.clientFk = c.id LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN ( - SELECT r1.started, r1.clientFk, r1.finished + SELECT r1.started, r1.clientFk, r1.finished FROM recovery r1 JOIN ( - SELECT MAX(started) AS maxStarted, clientFk + SELECT MAX(started) maxStarted, clientFk FROM recovery GROUP BY clientFk ) r2 ON r1.clientFk = r2.clientFk AND r1.started = r2.maxStarted + WHERE r1.finished + GROUP BY r1.clientFk ) r ON r.clientFk = c.id LEFT JOIN workerDepartment wd ON wd.workerFk = u.id - LEFT JOIN department dp ON dp.id = wd.departmentFk - WHERE - d.created = ? - AND d.amount > 0 - ORDER BY co.created DESC) d` - , [date]); + LEFT JOIN department dp ON dp.id = wd.departmentFk`); stmt.merge(conn.makeWhere(filter.where)); - stmt.merge(`GROUP BY d.clientFk`); + stmts.push(stmt); + + stmt = new ParameterizedSQL(` + SELECT SUM(amount) amount + FROM tmp.defaulters + `); + stmts.push(stmt); + + stmt = new ParameterizedSQL(` + SELECT * + FROM tmp.defaulters + `); stmt.merge(conn.makeOrderBy(filter.order)); - stmt.merge(conn.makeLimit(filter)); const itemsIndex = stmts.push(stmt) - 1; const sql = ParameterizedSQL.join(stmts, ';'); const result = await conn.executeStmt(sql, myOptions); - return itemsIndex === 0 ? result : result[itemsIndex]; + return {defaulters: result[itemsIndex], amount: result[itemsIndex - 1][0].amount}; }; }; diff --git a/modules/client/back/methods/defaulter/specs/filter.spec.js b/modules/client/back/methods/defaulter/specs/filter.spec.js index 0a970823e5..ca7a6b4ffe 100644 --- a/modules/client/back/methods/defaulter/specs/filter.spec.js +++ b/modules/client/back/methods/defaulter/specs/filter.spec.js @@ -11,10 +11,10 @@ describe('defaulter filter()', () => { const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}}; const result = await models.Defaulter.filter(ctx, null, options); - const firstRow = result[0]; + const firstRow = result.defaulters[0]; expect(firstRow.clientFk).toEqual(1101); - expect(result.length).toEqual(5); + expect(result.defaulters.length).toEqual(5); await tx.rollback(); } catch (e) { @@ -31,7 +31,7 @@ describe('defaulter filter()', () => { const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; const result = await models.Defaulter.filter(ctx, null, options); - const firstRow = result[0]; + const firstRow = result.defaulters[0]; expect(firstRow.clientFk).toEqual(1101); @@ -50,7 +50,7 @@ describe('defaulter filter()', () => { const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'Petter Parker'}}; const result = await models.Defaulter.filter(ctx, null, options); - const firstRow = result[0]; + const firstRow = result.defaulters[0]; expect(firstRow.clientName).toEqual('Petter Parker'); @@ -60,4 +60,23 @@ describe('defaulter filter()', () => { throw e; } }); + + it('should return the defaulter the sum of every defaulters', async() => { + const tx = await models.Defaulter.beginTransaction({}); + + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}}; + const {defaulters, amount} = await models.Defaulter.filter(ctx, null, options); + + const total = defaulters.reduce((total, row) => total + row.amount, 0); + + expect(total).toEqual(amount); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); }); diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index ac8a722bd2..4947edeafb 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -123,25 +123,13 @@ module.exports = Self => { date.setHours(0, 0, 0, 0); const args = ctx.args; const myOptions = {}; + let myTeamIds = []; if (typeof options == 'object') Object.assign(myOptions, options); - // Apply filter by team - const teamMembersId = []; - if (args.myTeam != null) { - const worker = await models.Worker.findById(userId, { - include: { - relation: 'collegues' - } - }, myOptions); - const collegues = worker.collegues() || []; - for (let collegue of collegues) - teamMembersId.push(collegue.collegueFk); - - if (teamMembersId.length == 0) - teamMembersId.push(userId); - } + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); if (ctx.args && args.to) { const dateTo = args.to; @@ -163,9 +151,9 @@ module.exports = Self => { case 'mine': case 'myTeam': if (value) - return {'c.salesPersonFk': {inq: teamMembersId}}; + return {'c.salesPersonFk': {inq: myTeamIds}}; else - return {'c.salesPersonFk': {nin: teamMembersId}}; + return {'c.salesPersonFk': {nin: myTeamIds}}; case 'id': case 'clientFk': param = `t.${param}`; diff --git a/modules/order/back/methods/order/filter.js b/modules/order/back/methods/order/filter.js index 2aeb1aac56..f24d9b2258 100644 --- a/modules/order/back/methods/order/filter.js +++ b/modules/order/back/methods/order/filter.js @@ -80,29 +80,15 @@ module.exports = Self => { const conn = Self.dataSource.connector; const myOptions = {}; const userId = ctx.req.accessToken.userId; + let myTeamIds = []; if (typeof options == 'object') Object.assign(myOptions, options); const args = ctx.args; - // Apply filter by team - const teamMembersId = []; - if (args.myTeam != null) { - const worker = await models.Worker.findById(userId, { - include: { - relation: 'collegues' - } - }, myOptions); - const collegues = worker.collegues() || []; - for (let collegue of collegues) - teamMembersId.push(collegue.collegueFk); - if (teamMembersId.length == 0) - teamMembersId.push(userId); - } - - if (args?.myTeam) - args.teamIds = teamIds; + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); if (args?.to) args.to.setHours(23, 59, 0, 0); @@ -133,9 +119,9 @@ module.exports = Self => { return {'o.confirmed': value ? 1 : 0}; case 'myTeam': if (value) - return {'c.salesPersonFk': {inq: teamMembersId}}; + return {'c.salesPersonFk': {inq: myTeamIds}}; else - return {'c.salesPersonFk': {nin: teamMembersId}}; + return {'c.salesPersonFk': {nin: myTeamIds}}; case 'showEmpty': return {'o.total': {neq: value}}; case 'id': diff --git a/modules/ticket/back/methods/ticket-request/filter.js b/modules/ticket/back/methods/ticket-request/filter.js index c2edcae81a..1318c1ab35 100644 --- a/modules/ticket/back/methods/ticket-request/filter.js +++ b/modules/ticket/back/methods/ticket-request/filter.js @@ -87,6 +87,7 @@ module.exports = Self => { const myOptions = {}; const models = Self.app.models; const args = ctx.args; + let myTeamIds = []; if (typeof options == 'object') Object.assign(myOptions, options); @@ -94,20 +95,8 @@ module.exports = Self => { if (ctx.args.mine) ctx.args.attenderFk = userId; - const teamMembersId = []; - if (args.myTeam != null) { - const worker = await models.Worker.findById(userId, { - include: { - relation: 'collegues' - } - }, myOptions); - const collegues = worker.collegues() || []; - for (let collegue of collegues) - teamMembersId.push(collegue.collegueFk); - - if (teamMembersId.length == 0) - teamMembersId.push(userId); - } + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); const today = Date.vnNew(); const future = Date.vnNew(); @@ -145,9 +134,9 @@ module.exports = Self => { return {'c.salesPersonFk': value}; case 'myTeam': if (value) - return {'tr.requesterFk': {inq: teamMembersId}}; + return {'tr.requesterFk': {inq: myTeamIds}}; else - return {'tr.requesterFk': {nin: teamMembersId}}; + return {'tr.requesterFk': {nin: myTeamIds}}; case 'daysOnward': today.setHours(0, 0, 0, 0); future.setDate(today.getDate() + value); diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index c612234701..f125ac586f 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -142,28 +142,14 @@ module.exports = Self => { date.setHours(0, 0, 0, 0); const models = Self.app.models; const args = ctx.args; - + let myTeamIds = []; const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); - // Apply filter by team - const teamMembersId = []; - if (args.myTeam != null) { - const worker = await models.Worker.findById(userId, { - include: { - relation: 'collegues' - } - }, myOptions); - - const collegues = worker.collegues() || []; - for (let collegue of collegues) - teamMembersId.push(collegue.collegueFk); - - if (teamMembersId.length == 0) - teamMembersId.push(userId); - } + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); if (ctx.args && args.to) { const dateTo = args.to; @@ -195,9 +181,9 @@ module.exports = Self => { case 'mine': case 'myTeam': if (value) - return {'c.salesPersonFk': {inq: teamMembersId}}; + return {'c.salesPersonFk': {inq: myTeamIds}}; else - return {'c.salesPersonFk': {nin: teamMembersId}}; + return {'c.salesPersonFk': {nin: myTeamIds}}; case 'alertLevel': return {'ts.alertLevel': value}; diff --git a/modules/ticket/back/methods/ticket/merge.js b/modules/ticket/back/methods/ticket/merge.js index 1106cef06f..6922ad1701 100644 --- a/modules/ticket/back/methods/ticket/merge.js +++ b/modules/ticket/back/methods/ticket/merge.js @@ -40,21 +40,23 @@ module.exports = Self => { try { for (let ticket of tickets) { - const originFullPath = `${url}ticket/${ticket.originId}/summary`; - const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`; - const message = $t('Ticket merged', { - originDated: dateUtil.toString(new Date(ticket.originShipped)), - destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)), - originId: ticket.originId, - destinationId: ticket.destinationId, - originFullPath, - destinationFullPath - }); if (!ticket.originId || !ticket.destinationId) continue; await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions); - if (await models.Ticket.setDeleted(ctx, ticket.originId, myOptions)) - await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message); + if (await models.Ticket.setDeleted(ctx, ticket.originId, myOptions)) { + if (!ticket.salesPersonFk) continue; + const originFullPath = `${url}ticket/${ticket.originId}/summary`; + const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`; + const message = $t('Ticket merged', { + originDated: dateUtil.toString(new Date(ticket.originShipped)), + destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)), + originId: ticket.originId, + destinationId: ticket.destinationId, + originFullPath, + destinationFullPath + }); + await models.Chat.sendCheckingPresence(ctx, ticket.salesPersonFk, message); + } } if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/priceDifference.js b/modules/ticket/back/methods/ticket/priceDifference.js index 7db03e2685..7e4fe82835 100644 --- a/modules/ticket/back/methods/ticket/priceDifference.js +++ b/modules/ticket/back/methods/ticket/priceDifference.js @@ -108,7 +108,10 @@ module.exports = Self => { // Get items movable const ticketOrigin = await models.Ticket.findById(args.id, null, myOptions); - const differenceShipped = ticketOrigin.shipped.getTime() > args.shipped.getTime(); + let shipped = ticketOrigin.shipped ?? ticketOrigin.updated; + + const differenceShipped = shipped.getTime() > args.shipped.getTime(); + const differenceWarehouse = ticketOrigin.warehouseFk != args.warehouseId; salesObj.haveDifferences = differenceShipped || differenceWarehouse; diff --git a/modules/ticket/back/methods/ticket/saveSign.js b/modules/ticket/back/methods/ticket/saveSign.js index ac2a7bc669..f99311c392 100644 --- a/modules/ticket/back/methods/ticket/saveSign.js +++ b/modules/ticket/back/methods/ticket/saveSign.js @@ -28,7 +28,6 @@ module.exports = Self => { verb: 'POST' } }); - Self.saveSign = async(ctx, tickets, location, signedTime, options) => { const models = Self.app.models; const myOptions = {userId: ctx.req.accessToken.userId}; @@ -111,6 +110,12 @@ module.exports = Self => { scope: { fields: ['id'] } + }, + { + relation: 'zone', + scope: { + fields: ['id', 'zoneFk,', 'name'] + } }] }, myOptions); @@ -151,6 +156,28 @@ module.exports = Self => { await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticketId, stateCode], myOptions); + if (stateCode == 'DELIVERED' && ticket.priority) { + const orderState = await models.State.findOne({ + where: {code: 'DELIVERED'}, + fields: ['id'] + }, myOptions); + + const ticketIncorrect = await Self.rawSql(` + SELECT t.id + FROM ticket t + JOIN ticketState ts ON ts.ticketFk = t.id + JOIN state s ON s.code = ts.code + WHERE t.routeFk = ? + AND s.\`order\` < ? + AND priority <(SELECT t.priority + FROM ticket t + WHERE t.id = ?)` + , [ticket.routeFk, orderState.id, ticket.id], myOptions); + + if (ticketIncorrect?.length > 0) + await sendMail(ctx, ticket.routeFk, ticket.id, ticket.zone().name); + } + if (ticket?.address()?.province()?.country()?.code != 'ES' && ticket.$cmrFk) { await models.Ticket.saveCmr(ctx, [ticketId], myOptions); externalTickets.push(ticketId); @@ -163,4 +190,25 @@ module.exports = Self => { } await models.Ticket.sendCmrEmail(ctx, externalTickets); }; + + async function sendMail(ctx, route, ticket, zoneName) { + const $t = ctx.req.__; + const url = await Self.app.models.Url.getUrl(); + const sendTo = 'repartos@verdnatura.es'; + const fullUrl = `${url}route/${route}/summary`; + const emailSubject = $t('Incorrect delivery order alert on route', { + route, + zone: zoneName + }); + const emailBody = $t('Ticket has been delivered out of order', { + ticket, + fullUrl + }); + + await Self.app.models.Mail.create({ + receiver: sendTo, + subject: emailSubject, + body: emailBody + }); + } }; diff --git a/modules/ticket/back/methods/ticket/specs/merge.spec.js b/modules/ticket/back/methods/ticket/specs/merge.spec.js index 68f9d33936..99e0c6e819 100644 --- a/modules/ticket/back/methods/ticket/specs/merge.spec.js +++ b/modules/ticket/back/methods/ticket/specs/merge.spec.js @@ -7,7 +7,7 @@ describe('ticket merge()', () => { destinationId: 12, originShipped: Date.vnNew(), destinationShipped: Date.vnNew(), - workerFk: 1 + salesPersonFk: 1 }; it('should merge two tickets', async() => { diff --git a/modules/ticket/back/methods/ticket/specs/saveSign.spec.js b/modules/ticket/back/methods/ticket/specs/saveSign.spec.js index e93408973a..3b426c2cf9 100644 --- a/modules/ticket/back/methods/ticket/specs/saveSign.spec.js +++ b/modules/ticket/back/methods/ticket/specs/saveSign.spec.js @@ -1,12 +1,20 @@ const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('Ticket saveSign()', () => { let ctx = {req: { getLocale: () => { return 'en'; }, + __: () => {}, accessToken: {userId: 9} - }}; + } + }; + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: ctx + }); + }); it(`should throw error if the ticket's alert level is lower than 2`, async() => { const tx = await models.TicketDms.beginTransaction({}); @@ -51,4 +59,46 @@ describe('Ticket saveSign()', () => { expect(ticketTrackingAfter.name).toBe('Entregado en parte'); }); + + it('should send an email to notify that the delivery order is not correct', async() => { + const tx = await models.Ticket.beginTransaction({}); + const ticketFk = 8; + const priority = 5; + const stateFk = 10; + const stateTicketFk = 2; + const expeditionFk = 11; + const expeditionStateFK = 2; + + let mailCountBefore; + let mailCountAfter; + spyOn(models.Dms, 'uploadFile').and.returnValue([{id: 1}]); + + const options = {transaction: tx}; + const tickets = [ticketFk]; + + const expedition = await models.Expedition.findById(expeditionFk, null, options); + expedition.updateAttribute('stateTypeFk', expeditionStateFK, options); + + const ticket = await models.Ticket.findById(ticketFk, null, options); + ticket.updateAttribute('priority', priority, options); + + const filter = {where: { + ticketFk: ticketFk, + stateFk: stateTicketFk} + }; + try { + const ticketTracking = await models.TicketTracking.findOne(filter, options); + ticketTracking.updateAttribute('stateFk', stateFk, options); + mailCountBefore = await models.Mail.count(options); + await models.Ticket.saveSign(ctx, tickets, null, null, options); + mailCountAfter = await models.Mail.count(options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + + expect(mailCountAfter).toBeGreaterThan(mailCountBefore); + }); }); diff --git a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js index d3ac3c6aa6..063433680b 100644 --- a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js @@ -22,14 +22,16 @@ describe('Ticket transferClient()', () => { it('should throw an error as the ticket is not editable', async() => { try { const ticketId = 4; - await models.Ticket.transferClient(ctx, ticketId, clientId, options); + const addressFk = null; + await models.Ticket.transferClient(ctx, ticketId, clientId, addressFk, options); } catch (e) { expect(e.message).toEqual('This ticket is locked'); } }); it('should be assigned a different clientFk and nickname in the original ticket', async() => { - await models.Ticket.transferClient(ctx, 2, clientId, options); + const addressFk = null; + await models.Ticket.transferClient(ctx, 2, clientId, addressFk, options); const afterTransfer = await models.Ticket.findById(2, null, options); const client = await models.Client.findById(clientId, {fields: ['defaultAddressFk']}, options); const address = await models.Address.findById(client.defaultAddressFk, {fields: ['nickname']}, options); @@ -39,7 +41,8 @@ describe('Ticket transferClient()', () => { }); it('should be assigned a different clientFk and nickname in the original and refund ticket and claim', async() => { - await models.Ticket.transferClient(ctx, originalTicketId, clientId, options); + const addressFk = null; + await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options); const [originalTicket, refundTicket] = await models.Ticket.find({ where: {id: {inq: [originalTicketId, refundTicketId]}} @@ -59,4 +62,39 @@ describe('Ticket transferClient()', () => { expect(originalTicket.nickname).toEqual(address.nickname); expect(refundTicket.nickname).toEqual(address.nickname); }); + + it('should be assigned a different addressFk and nickname in the original and refund ticket', async() => { + const addressFk = 131; + await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options); + + const [originalTicket, refundTicket] = await models.Ticket.find({ + where: {id: {inq: [originalTicketId, refundTicketId]}} + }, options); + + const claim = await models.Claim.findOne({ + where: {ticketFk: originalTicketId} + }, options); + + const address = await models.Address.findById(addressFk, {fields: ['id', 'nickname', 'clientFk']}, options); + + expect(originalTicket.clientFk).toEqual(clientId); + expect(originalTicket.clientFk).toEqual(address.clientFk); + expect(refundTicket.clientFk).toEqual(clientId); + expect(refundTicket.clientFk).toEqual(address.clientFk); + expect(claim.clientFk).toEqual(clientId); + expect(claim.clientFk).toEqual(address.clientFk); + + expect(originalTicket.nickname).toEqual(address.nickname); + expect(refundTicket.nickname).toEqual(address.nickname); + }); + + it('should be thrown an error if the new address is not belong to the client', async() => { + const addressFk = 1; + try { + await models.Ticket.transferClient(ctx, originalTicketId, clientId, addressFk, options); + fail('Expected an error to be thrown, but none was thrown.'); + } catch (e) { + expect(e.message).toEqual('The address does not belong to the client'); + } + }); }); diff --git a/modules/ticket/back/methods/ticket/transferClient.js b/modules/ticket/back/methods/ticket/transferClient.js index 95bee008da..2e845144ed 100644 --- a/modules/ticket/back/methods/ticket/transferClient.js +++ b/modules/ticket/back/methods/ticket/transferClient.js @@ -1,3 +1,4 @@ +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('transferClient', { description: 'Transferring ticket to another client', @@ -12,6 +13,10 @@ module.exports = Self => { arg: 'clientFk', type: 'number', required: true, + }, { + arg: 'addressFk', + type: 'number', + required: false, }], http: { path: `/:id/transferClient`, @@ -19,7 +24,7 @@ module.exports = Self => { } }); - Self.transferClient = async(ctx, id, clientFk, options) => { + Self.transferClient = async(ctx, id, clientFk, addressFk, options) => { const models = Self.app.models; const myOptions = {}; let tx; @@ -43,10 +48,12 @@ module.exports = Self => { const client = await models.Client.findById(clientFk, {fields: ['id', 'defaultAddressFk']}, myOptions); - const address = await models.Address.findById(client.defaultAddressFk, - {fields: ['id', 'nickname']}, myOptions); + const address = await models.Address.findById(addressFk ? addressFk : client.defaultAddressFk, + {fields: ['id', 'nickname', 'clientFk']}, myOptions); - const attributes = {clientFk, addressFk: client.defaultAddressFk, nickname: address.nickname}; + if (address.clientFk !== clientFk) throw new UserError('The address does not belong to the client'); + + const attributes = {clientFk, addressFk: address.id, nickname: address.nickname}; const tickets = []; const ticketIds = []; diff --git a/modules/worker/back/methods/worker/filter.js b/modules/worker/back/methods/worker/filter.js index 52c60572a7..087f080bd7 100644 --- a/modules/worker/back/methods/worker/filter.js +++ b/modules/worker/back/methods/worker/filter.js @@ -73,6 +73,11 @@ module.exports = Self => { type: 'String', description: 'The user email', http: {source: 'query'} + }, + { + arg: 'myTeam', + type: 'boolean', + description: 'Whether to show only tickets for the current logged user team (currently user tickets)' } ], returns: { @@ -85,10 +90,21 @@ module.exports = Self => { } }); - Self.filter = async(ctx, filter) => { - let conn = Self.dataSource.connector; + Self.filter = async(ctx, filter, options) => { + const userId = ctx.req.accessToken.userId; + const conn = Self.dataSource.connector; + const models = Self.app.models; + const args = ctx.args; + let myTeamIds = []; + const myOptions = {}; - let where = buildFilter(ctx.args, (param, value) => { + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (args.myTeam != null) + myTeamIds = await models.Worker.myTeam(userId); + + const where = buildFilter(ctx.args, (param, value) => { switch (param) { case 'search': return /^\d+$/.test(value) @@ -117,12 +133,17 @@ module.exports = Self => { return {'u.name': {like: `%${value}%`}}; case 'email': return {'eu.email': {like: `%${value}%`}}; + case 'myTeam': + if (value) + return {'c.salesPersonFk': {inq: myTeamIds}}; + else + return {'c.salesPersonFk': {nin: myTeamIds}}; } }); - filter = mergeFilters(ctx.args.filter, {where}); + filter = mergeFilters(filter, {where}); - let stmts = []; + const stmts = []; let stmt; stmt = new ParameterizedSQL( @@ -145,11 +166,12 @@ module.exports = Self => { LEFT JOIN account.emailUser eu ON eu.userFk = u.id` ); - stmt.merge(conn.makeSuffix(filter)); - let itemsIndex = stmts.push(stmt) - 1; + stmt.merge(conn.makeWhere(filter.where)); + stmts.push(stmt); - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await conn.executeStmt(sql); - return itemsIndex === 0 ? result : result[itemsIndex]; + const itemsIndex = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + return result[itemsIndex]; }; }; diff --git a/modules/worker/back/methods/worker/myTeam.js b/modules/worker/back/methods/worker/myTeam.js new file mode 100644 index 0000000000..25c5916fb6 --- /dev/null +++ b/modules/worker/back/methods/worker/myTeam.js @@ -0,0 +1,43 @@ +module.exports = Self => { + Self.remoteMethod('myTeam', { + description: 'Return the members of the user team', + accessType: 'READ', + accepts: [{ + arg: 'userId', + type: 'string', + required: true + }], + returns: { + type: 'string', + root: true + }, + http: { + path: `/myTeam`, + verb: 'GET' + } + }); + + Self.myTeam = async(userId, options) => { + const models = Self.app.models; + const myOptions = {}; + const teamMembersId = []; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const worker = await models.Worker.findById(userId, { + include: { + relation: 'collegues' + } + }, myOptions); + const collegues = worker.collegues() || []; + for (let collegue of collegues) + teamMembersId.push(collegue.collegueFk); + + if (teamMembersId.length == 0) + teamMembersId.push(userId); + + return teamMembersId; + }; +}; + diff --git a/modules/worker/back/methods/worker/specs/filter.spec.js b/modules/worker/back/methods/worker/specs/filter.spec.js index 2eb3535763..27eb79cdb1 100644 --- a/modules/worker/back/methods/worker/specs/filter.spec.js +++ b/modules/worker/back/methods/worker/specs/filter.spec.js @@ -1,25 +1,69 @@ +const models = require('vn-loopback/server/server').models; const app = require('vn-loopback/server/server'); describe('worker filter()', () => { - it('should return 1 result filtering by id', async() => { - let result = await app.models.Worker.filter({args: {filter: {}, search: 1}}); + const ctx = beforeAll.getCtx(); - expect(result.length).toEqual(1); - expect(result[0].id).toEqual(1); + it('should return 1 result filtering by id', async() => { + const tx = await models.Worker.beginTransaction({}); + + try { + const options = {transaction: tx}; + const filter = {}; + const args = {search: 1}; + ctx.args = args; + + let result = await app.models.Worker.filter(ctx, filter, options); + + expect(result.length).toEqual(1); + expect(result[0].id).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should return 1 result filtering by string', async() => { - let result = await app.models.Worker.filter({args: {filter: {}, search: 'administrativeNick'}}); + const tx = await models.Worker.beginTransaction({}); - expect(result.length).toEqual(1); - expect(result[0].id).toEqual(5); + try { + const options = {transaction: tx}; + const filter = {}; + const args = {search: 'administrativeNick'}; + ctx.args = args; + + let result = await app.models.Worker.filter(ctx, filter, options); + + expect(result.length).toEqual(1); + expect(result[0].id).toEqual(5); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); - it('should return 2 results filtering by name', async() => { - let result = await app.models.Worker.filter({args: {filter: {}, firstName: 'agency'}}); + it('should return 2 result filtering by name', async() => { + const tx = await models.Worker.beginTransaction({}); - expect(result.length).toEqual(2); - expect(result[0].nickname).toEqual('agencyNick'); - expect(result[1].nickname).toEqual('agencyBossNick'); + try { + const options = {transaction: tx}; + const filter = {}; + const args = {firstName: 'agency'}; + ctx.args = args; + + let result = await app.models.Worker.filter(ctx, filter, options); + + expect(result[0].nickname).toEqual('agencyNick'); + expect(result[1].nickname).toEqual('agencyBossNick'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 2e45b78da7..97e6a283c3 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -21,6 +21,7 @@ module.exports = Self => { require('../methods/worker/isAuthorized')(Self); require('../methods/worker/setPassword')(Self); require('../methods/worker/getAvailablePda')(Self); + require('../methods/worker/myTeam')(Self); Self.validateAsync('fi', tinIsValid, { message: 'Invalid TIN' diff --git a/package.json b/package.json index e4cbf1406b..72f8e2d1ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "25.04.0", + "version": "25.06.0", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0",