diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql
index d74d85d7c..2a655ba7e 100644
--- a/db/dump/fixtures.before.sql
+++ b/db/dump/fixtures.before.sql
@@ -158,13 +158,13 @@ INSERT INTO `account`.`mailForward`(`account`, `forwardTo`)
-INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
+INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`, `hasToDownloadRate`)
VALUES
- (1, 'EUR', 'Euro', 1),
- (2, 'USD', 'Dollar USA', 1.4),
- (3, 'GBP', 'Libra', 1),
- (4, 'JPY', 'Yen Japones', 1),
- (5, 'CNY', 'Yuan Chino', 1.2);
+ (1, 'EUR', 'Euro', 1, FALSE),
+ (2, 'USD', 'Dollar USA', 1.4, TRUE),
+ (3, 'GBP', 'Libra', 1, TRUE),
+ (4, 'JPY', 'Yen Japones', 1, FALSE),
+ (5, 'CNY', 'Yuan Chino', 1.2, TRUE);
INSERT INTO `vn`.`country`(`id`, `name`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
VALUES
@@ -694,22 +694,22 @@ INSERT INTO `vn`.`invoiceOutExpense`(`id`, `invoiceOutFk`, `amount`, `expenseFk`
(6, 4, 8.07, 2000000000, util.VN_CURDATE()),
(7, 5, 8.07, 2000000000, util.VN_CURDATE());
-INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `agencyModeFk`, `travelingDays`, `price`, `bonus`, `itemMaxSize`)
- VALUES
- (1, 'Zone pickup A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100),
- (2, 'Zone pickup B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100),
- (3, 'Zone 247 A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100),
- (4, 'Zone 247 B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100),
- (5, 'Zone expensive A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100),
- (6, 'Zone expensive B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100),
- (7, 'Zone refund', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 23, 0, 1, 0, 100),
- (8, 'Zone others', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 10, 0, 1, 0, 100),
- (9, 'Zone superMan', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 2, 0, 1, 0, 100),
- (10, 'Zone teleportation', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 3, 0, 1, 0, 100),
- (11, 'Zone pickup C', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100),
- (12, 'Zone entanglement', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 4, 0, 1, 0, 100),
- (13, 'Zone quantum break', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 5, 0, 1, 0, 100);
-
+INSERT INTO `vn`.`zone`
+ (`id`, `name`, `hour`, `agencyModeFk`, `travelingDays`, `price`, `bonus`, `itemMaxSize`, `priceOptimum`)
+VALUES
+ (1, 'Zone pickup A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100, 1),
+ (2, 'Zone pickup B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100, 1),
+ (3, 'Zone 247 A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100, 1),
+ (4, 'Zone 247 B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100, 1),
+ (5, 'Zone expensive A', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100, 500),
+ (6, 'Zone expensive B', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100, 500),
+ (7, 'Zone refund', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 23, 0, 1, 0, 100, 0.5),
+ (8, 'Zone others', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 10, 0, 1, 0, 100, 0.5),
+ (9, 'Zone superMan', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 2, 0, 1, 0, 100, 0.5),
+ (10, 'Zone teleportation', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 3, 0, 1, 0, 100, 0.5),
+ (11, 'Zone pickup C', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 1, 0, 1, 0, 100, 0.5),
+ (12, 'Zone entanglement', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 4, 0, 1, 0, 100, 0.5),
+ (13, 'Zone quantum break', CONCAT(util.VN_CURDATE(), ' ', TIME('23:59')), 5, 0, 1, 0, 100, 0.5);
INSERT INTO `vn`.`zoneWarehouse` (`id`, `zoneFk`, `warehouseFk`)
VALUES
diff --git a/db/routines/vn/events/client_setPackagesDiscountFactor.sql b/db/routines/vn/events/client_setPackagesDiscountFactor.sql
new file mode 100644
index 000000000..a0dc33cac
--- /dev/null
+++ b/db/routines/vn/events/client_setPackagesDiscountFactor.sql
@@ -0,0 +1,8 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `vn`.`client_setPackagesDiscountFactor`
+ ON SCHEDULE EVERY 1 DAY
+ STARTS '2024-10-18 03:00:00.000'
+ ON COMPLETION PRESERVE
+ ENABLE
+DO CALL client_setPackagesDiscountFactor()$$
+DELIMITER ;
diff --git a/db/routines/vn/procedures/catalog_componentCalculate.sql b/db/routines/vn/procedures/catalog_componentCalculate.sql
index e29e13a8c..aaf2db408 100644
--- a/db/routines/vn/procedures/catalog_componentCalculate.sql
+++ b/db/routines/vn/procedures/catalog_componentCalculate.sql
@@ -231,7 +231,19 @@ BEGIN
SELECT tcc.warehouseFK,
tcc.itemFk,
c2.id,
- z.inflation * ROUND(ic.cm3delivery * (IFNULL(zo.price,5000) - IFNULL(zo.bonus,0)) / (1000 * vc.standardFlowerBox) , 4) cost
+ z.inflation
+ * ROUND(
+ ic.cm3delivery
+ * (
+ (
+ zo.priceOptimum + (( zo.price - zo.priceOptimum) * 2 * ( 1 - c.packagesDiscountFactor))
+ )
+ - IFNULL(zo.bonus, 0)
+ )
+ / (1000 * vc.standardFlowerBox),
+ 4
+ ) cost
+
FROM tmp.ticketComponentCalculate tcc
JOIN item i ON i.id = tcc.itemFk
JOIN tmp.zoneOption zo ON zo.zoneFk = vZoneFk
@@ -239,6 +251,7 @@ BEGIN
JOIN agencyMode am ON am.id = z.agencyModeFk
JOIN vn.volumeConfig vc
JOIN vn.component c2 ON c2.code = 'delivery'
+ JOIN `client` c on c.id = vClientFk
LEFT JOIN itemCost ic ON ic.warehouseFk = tcc.warehouseFk
AND ic.itemFk = tcc.itemFk
HAVING cost <> 0;
diff --git a/db/routines/vn/procedures/client_setPackagesDiscountFactor.sql b/db/routines/vn/procedures/client_setPackagesDiscountFactor.sql
new file mode 100644
index 000000000..f6068ca37
--- /dev/null
+++ b/db/routines/vn/procedures/client_setPackagesDiscountFactor.sql
@@ -0,0 +1,25 @@
+DELIMITER $$
+
+CREATE OR REPLACE DEFINER=`vn`@`localhost`
+PROCEDURE `vn`.`client_setPackagesDiscountFactor`()
+BEGIN
+ /**
+ * Set the discount factor for the packages of the clients.
+ */
+ UPDATE client c
+ JOIN (
+ SELECT t.clientFk,
+ LEAST((
+ SUM(t.packages) / COUNT(DISTINCT DATE(t.shipped))
+ ) / cc.packagesOptimum, 1) discountFactor
+ FROM ticket t
+ JOIN clientConfig cc ON TRUE
+ WHERE t.shipped > util.VN_CURDATE() - INTERVAL cc.monthsToCalcOptimumPrice MONTH
+ AND t.packages
+ GROUP BY t.clientFk
+ ) ca ON c.id = ca.clientFk
+ SET c.packagesDiscountFactor = ca.discountFactor;
+
+END$$
+
+DELIMITER ;
diff --git a/db/routines/vn/procedures/zone_getAddresses.sql b/db/routines/vn/procedures/zone_getAddresses.sql
index 2e5982c82..9946b0b73 100644
--- a/db/routines/vn/procedures/zone_getAddresses.sql
+++ b/db/routines/vn/procedures/zone_getAddresses.sql
@@ -1,26 +1,27 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`zone_getAddresses`(
vSelf INT,
- vShipped DATE,
+ vLanded DATE,
vDepartmentFk INT
)
BEGIN
/**
* Devuelve un listado de todos los clientes activos
* con consignatarios a los que se les puede
- * vender producto para esa zona.
+ * entregar producto para esa zona.
*
* @param vSelf Id de zona
- * @param vShipped Fecha de envio
- * @param vDepartmentFk Id de departamento
+ * @param vLanded Fecha de entrega
+ * @param vDepartmentFk Id de departamento | NULL para mostrar todos
* @return Un select
*/
CALL zone_getPostalCode(vSelf);
WITH clientWithTicket AS (
- SELECT clientFk
+ SELECT DISTINCT clientFk
FROM vn.ticket
- WHERE shipped BETWEEN vShipped AND util.dayEnd(vShipped)
+ WHERE landed BETWEEN vLanded AND util.dayEnd(vLanded)
+ AND NOT isDeleted
)
SELECT c.id,
c.name,
@@ -30,7 +31,7 @@ BEGIN
u.name username,
aai.invoiced,
cnb.lastShipped,
- cwt.clientFk
+ IF(cwt.clientFk, TRUE, FALSE) hasTicket
FROM vn.client c
JOIN vn.worker w ON w.id = c.salesPersonFk
JOIN vn.workerDepartment wd ON wd.workerFk = w.id
@@ -50,7 +51,7 @@ BEGIN
AND c.isActive
AND ct.code = 'normal'
AND bt.code <> 'worker'
- AND (d.id = vDepartmentFk OR NOT vDepartmentFk)
+ AND (d.id = vDepartmentFk OR vDepartmentFk IS NULL)
GROUP BY c.id;
DROP TEMPORARY TABLE tmp.zoneNodes;
diff --git a/db/routines/vn/procedures/zone_getOptionsForShipment.sql b/db/routines/vn/procedures/zone_getOptionsForShipment.sql
index fa48b0b0f..17d1b3d11 100644
--- a/db/routines/vn/procedures/zone_getOptionsForShipment.sql
+++ b/db/routines/vn/procedures/zone_getOptionsForShipment.sql
@@ -9,7 +9,7 @@ BEGIN
* @return tmp.zoneOption(zoneFk, hour, travelingDays, price, bonus, specificity) The computed options
*/
DECLARE vHour TIME DEFAULT TIME(util.VN_NOW());
-
+
DROP TEMPORARY TABLE IF EXISTS tLandings;
CREATE TEMPORARY TABLE tLandings
(INDEX (eventFk))
@@ -30,6 +30,7 @@ BEGIN
TIME(IFNULL(e.`hour`, z.`hour`)) `hour`,
l.travelingDays,
IFNULL(e.price, z.price) price,
+ IFNULL(e.priceOptimum, z.priceOptimum) priceOptimum,
IFNULL(e.bonus, z.bonus) bonus,
l.landed,
vShipped shipped
diff --git a/db/versions/11398-orangeRose/00-zoneEventPriceOptimum.sql b/db/versions/11398-orangeRose/00-zoneEventPriceOptimum.sql
new file mode 100644
index 000000000..0440714e4
--- /dev/null
+++ b/db/versions/11398-orangeRose/00-zoneEventPriceOptimum.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `vn`.`zoneEvent`
+ ADD COLUMN `priceOptimum` DECIMAL(10,2) NULL COMMENT 'Precio mínimo que puede pagar un bulto'
+ AFTER `price`,
+ ADD CONSTRAINT `ck_zoneEvent_priceOptimum`
+ CHECK (priceOptimum <= price)
diff --git a/db/versions/11398-orangeRose/00-zonePriceOptimum.sql b/db/versions/11398-orangeRose/00-zonePriceOptimum.sql
new file mode 100644
index 000000000..82b2001cd
--- /dev/null
+++ b/db/versions/11398-orangeRose/00-zonePriceOptimum.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `vn`.`zone`
+ ADD COLUMN `priceOptimum` DECIMAL(10,2) NOT NULL COMMENT 'Precio mínimo que puede pagar un bulto'
+ AFTER `price`,
+ ADD CONSTRAINT `ck_zone_priceOptimum`
+ CHECK (priceOptimum <= price)
diff --git a/db/versions/11398-orangeRose/01-zoneUpdate.sql b/db/versions/11398-orangeRose/01-zoneUpdate.sql
new file mode 100644
index 000000000..042f2a92b
--- /dev/null
+++ b/db/versions/11398-orangeRose/01-zoneUpdate.sql
@@ -0,0 +1,2 @@
+UPDATE `vn`.`zone`
+ SET `priceOptimum` = `price`;
diff --git a/db/versions/11398-orangeRose/02-clientAlter.sql b/db/versions/11398-orangeRose/02-clientAlter.sql
new file mode 100644
index 000000000..b5275a301
--- /dev/null
+++ b/db/versions/11398-orangeRose/02-clientAlter.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `vn`.`client`
+ ADD COLUMN `packagesDiscountFactor` DECIMAL(4,3) NOT NULL DEFAULT 1.000
+ COMMENT 'Porcentaje de ajuste entre el numero de bultos medio del cliente, y el número medio óptimo para las zonas en las que compra';
diff --git a/db/versions/11398-orangeRose/03-clientConfig.sql b/db/versions/11398-orangeRose/03-clientConfig.sql
new file mode 100644
index 000000000..2869f26a2
--- /dev/null
+++ b/db/versions/11398-orangeRose/03-clientConfig.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `vn`.`clientConfig`
+ ADD COLUMN `packagesOptimum` INT UNSIGNED NOT NULL DEFAULT 20 COMMENT 'Numero de bultos por cliente/dia para conseguir el precio optimo',
+ ADD COLUMN `monthsToCalcOptimumPrice` TINYINT UNSIGNED NOT NULL DEFAULT 3 COMMENT 'Número de meses a usar para el cálculo de client.packagesDiscountFactor';
diff --git a/db/versions/11405-blackMoss/00-entryAlter.sql b/db/versions/11405-blackMoss/00-entryAlter.sql
new file mode 100644
index 000000000..3320b9dd3
--- /dev/null
+++ b/db/versions/11405-blackMoss/00-entryAlter.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `vn`.`entry`
+ ADD COLUMN `initialTemperature` decimal(10,2) DEFAULT NULL COMMENT 'Temperatura de como lo recibimos del proveedor ej. en colombia',
+ ADD COLUMN `finalTemperature` decimal(10,2) DEFAULT NULL COMMENT 'Temperatura final de como llega a nuestras instalaciones';
diff --git a/db/versions/11406-bronzeMoss/00-currrencyAlter.sql b/db/versions/11406-bronzeMoss/00-currrencyAlter.sql
new file mode 100644
index 000000000..86465545e
--- /dev/null
+++ b/db/versions/11406-bronzeMoss/00-currrencyAlter.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `vn`.`currency`
+ADD COLUMN `hasToDownloadRate` TINYINT(1) NOT NULL DEFAULT 0 comment 'Si se guarda el tipo de cambio diariamente en referenceRate';
diff --git a/db/versions/11406-bronzeMoss/01-currrencyUpdate.sql b/db/versions/11406-bronzeMoss/01-currrencyUpdate.sql
new file mode 100644
index 000000000..5e0882de2
--- /dev/null
+++ b/db/versions/11406-bronzeMoss/01-currrencyUpdate.sql
@@ -0,0 +1,3 @@
+UPDATE `vn`.`currency`
+ SET `hasToDownloadRate` = TRUE
+ WHERE `code` IN ('USD', 'CNY', 'GBP');
diff --git a/db/versions/11407-turquoiseTulip/00-firstScript.sql b/db/versions/11407-turquoiseTulip/00-firstScript.sql
new file mode 100644
index 000000000..72d29061d
--- /dev/null
+++ b/db/versions/11407-turquoiseTulip/00-firstScript.sql
@@ -0,0 +1,2 @@
+INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
+ VALUES ('VnUser','adminUser','WRITE','ALLOW','ROLE','sysadmin');
\ No newline at end of file
diff --git a/db/versions/11410-blackTulip/00-firstScript.sql b/db/versions/11410-blackTulip/00-firstScript.sql
new file mode 100644
index 000000000..e300c4b7c
--- /dev/null
+++ b/db/versions/11410-blackTulip/00-firstScript.sql
@@ -0,0 +1,2 @@
+RENAME TABLE bi.f_tvc TO bi.f_tvc__;
+ALTER TABLE bi.f_tvc__ COMMENT='@deprecated 2025-01-15';
\ No newline at end of file
diff --git a/db/versions/11415-chocolateTulip/00-firstScript.sql b/db/versions/11415-chocolateTulip/00-firstScript.sql
new file mode 100644
index 000000000..2ed7ec9b5
--- /dev/null
+++ b/db/versions/11415-chocolateTulip/00-firstScript.sql
@@ -0,0 +1 @@
+CREATE INDEX ticket_landed_IDX USING BTREE ON vn.ticket (landed);
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 d9689e31a..af1dc56bc 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 77f0e0459..0a3ae4edc 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 5f95ec683..6aad8a4c1 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.",
@@ -248,9 +248,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 cde81e0cb..abd2f79a0 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -390,13 +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"
-}
+ "There are tickets to be invoiced": "La zona tiene tickets por facturar",
+ "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 f49196a8f..d7d5b7710 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 e2374d35f..d1ac2ef23 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/client/back/methods/client/createAddress.js b/modules/client/back/methods/client/createAddress.js
index 2709632cb..6bd4a26c1 100644
--- a/modules/client/back/methods/client/createAddress.js
+++ b/modules/client/back/methods/client/createAddress.js
@@ -52,6 +52,14 @@ module.exports = function(Self) {
arg: 'customsAgentFk',
type: 'number'
},
+ {
+ arg: 'longitude',
+ type: 'number'
+ },
+ {
+ arg: 'latitude',
+ type: 'number'
+ },
{
arg: 'isActive',
type: 'boolean'
diff --git a/modules/client/back/methods/defaulter/filter.js b/modules/client/back/methods/defaulter/filter.js
index 5359ce4a7..cf8bd855a 100644
--- a/modules/client/back/methods/defaulter/filter.js
+++ b/modules/client/back/methods/defaulter/filter.js
@@ -94,7 +94,7 @@ module.exports = Self => {
AND r1.started = r2.maxStarted
) r ON r.clientFk = c.id
LEFT JOIN workerDepartment wd ON wd.workerFk = u.id
- JOIN department dp ON dp.id = wd.departmentFk
+ LEFT JOIN department dp ON dp.id = wd.departmentFk
WHERE
d.created = ?
AND d.amount > 0
diff --git a/modules/client/back/models/client-credit.json b/modules/client/back/models/client-credit.json
index b92639b80..b57374dc3 100644
--- a/modules/client/back/models/client-credit.json
+++ b/modules/client/back/models/client-credit.json
@@ -19,6 +19,9 @@
},
"created": {
"type": "date"
+ },
+ "workerFk": {
+ "type": "number"
}
},
"relations": {
@@ -33,4 +36,4 @@
"foreignKey": "workerFk"
}
}
-}
\ No newline at end of file
+}
diff --git a/modules/entry/back/methods/entry/filter.js b/modules/entry/back/methods/entry/filter.js
index d7740dd4e..e5eae85fd 100644
--- a/modules/entry/back/methods/entry/filter.js
+++ b/modules/entry/back/methods/entry/filter.js
@@ -119,6 +119,16 @@ module.exports = Self => {
arg: 'invoiceAmount',
type: 'number',
description: `The invoice amount`
+ },
+ {
+ arg: 'initialTemperature',
+ type: 'number',
+ description: 'Initial temperature value'
+ },
+ {
+ arg: 'finalTemperature',
+ type: 'number',
+ description: 'Final temperature value'
}
],
returns: {
@@ -170,6 +180,10 @@ module.exports = Self => {
case 'invoiceInFk':
param = `e.${param}`;
return {[param]: value};
+ case 'initialTemperature':
+ return {'e.initialTemperature': {lte: value}};
+ case 'finalTemperature':
+ return {'e.finalTemperature': {gte: value}};
}
});
filter = mergeFilters(ctx.args.filter, {where});
@@ -204,6 +218,8 @@ module.exports = Self => {
e.gestDocFk,
e.invoiceInFk,
e.invoiceAmount,
+ e.initialTemperature,
+ e.finalTemperature,
t.landed,
s.name supplierName,
s.nickname supplierAlias,
diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json
index 4a09c7d6a..1ff062119 100644
--- a/modules/entry/back/models/entry.json
+++ b/modules/entry/back/models/entry.json
@@ -68,6 +68,12 @@
},
"invoiceAmount": {
"type": "number"
+ },
+ "initialTemperature": {
+ "type": "number"
+ },
+ "finalTemperature": {
+ "type": "number"
}
},
"relations": {
diff --git a/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js b/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js
index 989b1d4a2..99ff4cd79 100644
--- a/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js
+++ b/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js
@@ -13,66 +13,114 @@ module.exports = Self => {
}
});
- Self.exchangeRateUpdate = async() => {
- const response = await axios.get('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml');
- const xmlData = response.data;
-
- const doc = new DOMParser({errorHandler: {warning: () => {}}})?.parseFromString(xmlData, 'text/xml');
- const cubes = doc?.getElementsByTagName('Cube');
- if (!cubes || cubes.length === 0)
- throw new UserError('No cubes found. Exiting the method.');
-
+ Self.exchangeRateUpdate = async(options = {}) => {
const models = Self.app.models;
+ const myOptions = {};
+ let tx;
- const maxDateRecord = await models.ReferenceRate.findOne({order: 'dated DESC'});
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
- const maxDate = maxDateRecord?.dated ? new Date(maxDateRecord.dated) : null;
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ const response = await axios.get('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml');
+ const xmlData = response.data;
+
+ const doc = new DOMParser({errorHandler: {warning: () => {}}})
+ .parseFromString(xmlData, 'text/xml');
+ const cubes = doc?.getElementsByTagName('Cube');
+ if (!cubes || cubes.length === 0)
+ throw new UserError('No cubes found. Exiting the method.');
+
+ const currencies = await models.Currency.find({where: {hasToDownloadRate: true}}, myOptions);
+ const maxDateRecord = await models.ReferenceRate.findOne({order: 'dated DESC'}, myOptions);
+ const maxDate = maxDateRecord?.dated ? new Date(maxDateRecord.dated) : null;
+ let lastProcessedDate = maxDate;
+
+ for (const cube of Array.from(cubes)) {
+ if (cube.nodeType === doc.ELEMENT_NODE && cube.attributes.getNamedItem('time')) {
+ const xmlDate = new Date(cube.getAttribute('time'));
+ const xmlDateWithoutTime = new Date(
+ xmlDate.getFullYear(),
+ xmlDate.getMonth(),
+ xmlDate.getDate()
+ );
+
+ if (!maxDate || xmlDateWithoutTime > maxDate) {
+ if (lastProcessedDate && xmlDateWithoutTime > lastProcessedDate) {
+ for (const currency of currencies) {
+ await fillMissingDates(
+ models, currency, lastProcessedDate, xmlDateWithoutTime, myOptions
+ );
+ }
+ }
+ }
- for (const cube of Array.from(cubes)) {
- if (cube.nodeType === doc.ELEMENT_NODE && cube.attributes.getNamedItem('time')) {
- const xmlDate = new Date(cube.getAttribute('time'));
- const xmlDateWithoutTime = new Date(xmlDate.getFullYear(), xmlDate.getMonth(), xmlDate.getDate());
- if (!maxDate || maxDate < xmlDateWithoutTime) {
for (const rateCube of Array.from(cube.childNodes)) {
if (rateCube.nodeType === doc.ELEMENT_NODE) {
const currencyCode = rateCube.getAttribute('currency');
const rate = rateCube.getAttribute('rate');
- if (['USD', 'CNY', 'GBP'].includes(currencyCode)) {
- const currency = await models.Currency.findOne({where: {code: currencyCode}});
- if (!currency) throw new UserError(`Currency not found for code: ${currencyCode}`);
+ const currency = currencies.find(c => c.code === currencyCode);
+ if (currency) {
const existingRate = await models.ReferenceRate.findOne({
- where: {currencyFk: currency.id, dated: xmlDate}
- });
+ where: {currencyFk: currency.id, dated: xmlDateWithoutTime}
+ }, myOptions);
if (existingRate) {
if (existingRate.value !== rate)
- await existingRate.updateAttributes({value: rate});
+ await existingRate.updateAttributes({value: rate}, myOptions);
} else {
await models.ReferenceRate.create({
currencyFk: currency.id,
- dated: xmlDate,
+ dated: xmlDateWithoutTime,
value: rate
- });
- }
- const monday = 1;
- if (xmlDateWithoutTime.getDay() === monday) {
- const saturday = new Date(xmlDateWithoutTime);
- saturday.setDate(xmlDateWithoutTime.getDate() - 2);
- const sunday = new Date(xmlDateWithoutTime);
- sunday.setDate(xmlDateWithoutTime.getDate() - 1);
-
- for (const date of [saturday, sunday]) {
- await models.ReferenceRate.upsertWithWhere(
- {currencyFk: currency.id, dated: date},
- {currencyFk: currency.id, dated: date, value: rate}
- );
- }
+ }, myOptions);
}
}
}
}
+
+ lastProcessedDate = xmlDateWithoutTime;
}
}
+
+ if (tx) await tx.commit();
+ } catch (error) {
+ if (tx) await tx.rollback();
+ throw error;
}
};
+
+ async function getLastValidRate(models, currencyId, date, myOptions) {
+ return models.ReferenceRate.findOne({
+ where: {currencyFk: currencyId, dated: {lt: date}},
+ order: 'dated DESC'
+ }, myOptions);
+ }
+
+ async function fillMissingDates(models, currency, startDate, endDate, myOptions) {
+ const cursor = new Date(startDate);
+ cursor.setDate(cursor.getDate() + 1);
+ while (cursor < endDate) {
+ const existingRate = await models.ReferenceRate.findOne({
+ where: {currencyFk: currency.id, dated: cursor}
+ }, myOptions);
+
+ if (!existingRate) {
+ const lastValid = await getLastValidRate(models, currency.id, cursor, myOptions);
+ if (lastValid) {
+ await models.ReferenceRate.create({
+ currencyFk: currency.id,
+ dated: new Date(cursor),
+ value: lastValid.value
+ }, myOptions);
+ }
+ }
+ cursor.setDate(cursor.getDate() + 1);
+ }
+ }
};
diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js
index 0fd7ea165..c3dcca5ae 100644
--- a/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js
+++ b/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js
@@ -1,52 +1,190 @@
describe('exchangeRateUpdate functionality', function() {
const axios = require('axios');
const models = require('vn-loopback/server/server').models;
+ let tx; let options;
- beforeEach(function() {
- spyOn(axios, 'get').and.returnValue(Promise.resolve({
- data: `
-
-
-
-
- `
- }));
+ function formatYmd(d) {
+ const mm = (d.getMonth() + 1).toString().padStart(2, '0');
+ const dd = d.getDate().toString().padStart(2, '0');
+ return `${d.getFullYear()}-${mm}-${dd}`;
+ }
+
+ afterEach(async() => {
+ await tx.rollback();
});
- it('should process XML data and update or create rates in the database', async function() {
+ beforeEach(async() => {
+ tx = await models.Sale.beginTransaction({});
+ options = {transaction: tx};
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({data: ''}));
+ });
+
+ it('should process XML data and create rates', async function() {
+ const d1 = Date.vnNew();
+ const d4 = Date.vnNew();
+ d4.setDate(d4.getDate() + 1);
+ const xml = `
+
+
+
+
+
+
+
+ `;
+ axios.get.and.returnValue(Promise.resolve({data: xml}));
spyOn(models.ReferenceRate, 'findOne').and.returnValue(Promise.resolve(null));
spyOn(models.ReferenceRate, 'create').and.returnValue(Promise.resolve());
+ await models.InvoiceIn.exchangeRateUpdate(options);
- await models.InvoiceIn.exchangeRateUpdate();
-
- expect(models.ReferenceRate.create).toHaveBeenCalledTimes(2);
+ expect(models.ReferenceRate.create).toHaveBeenCalledTimes(3);
});
- it('should not create or update rates when no XML data is available', async function() {
+ it('should handle no data', async function() {
axios.get.and.returnValue(Promise.resolve({}));
spyOn(models.ReferenceRate, 'create');
-
- let thrownError = null;
+ let e;
try {
- await models.InvoiceIn.exchangeRateUpdate();
- } catch (error) {
- thrownError = error;
+ await models.InvoiceIn.exchangeRateUpdate(options);
+ } catch (err) {
+ e = err;
}
- expect(thrownError.message).toBe('No cubes found. Exiting the method.');
+ expect(e.message).toBe('No cubes found. Exiting the method.');
+ expect(models.ReferenceRate.create).not.toHaveBeenCalled();
});
- it('should handle errors gracefully', async function() {
+ it('should handle errors', async function() {
axios.get.and.returnValue(Promise.reject(new Error('Network error')));
- let error;
-
+ let e;
try {
- await models.InvoiceIn.exchangeRateUpdate();
- } catch (e) {
- error = e;
+ await models.InvoiceIn.exchangeRateUpdate(options);
+ } catch (err) {
+ e = err;
}
- expect(error).toBeDefined();
- expect(error.message).toBe('Network error');
+ expect(e).toBeDefined();
+ expect(e.message).toBe('Network error');
+ });
+
+ it('should update existing rate', async function() {
+ const existingRate = await models.ReferenceRate.findOne({
+ order: 'id DESC'
+ }, options);
+
+ if (!existingRate) return fail('No ReferenceRate records in DB');
+
+ const currency = await models.Currency.findById(existingRate.currencyFk, null, options);
+
+ const xml = `
+
+
+
+ `;
+
+ axios.get.and.returnValue(Promise.resolve({data: xml}));
+
+ await models.InvoiceIn.exchangeRateUpdate(options);
+
+ const updatedRate = await models.ReferenceRate.findById(existingRate.id, null, options);
+
+ expect(updatedRate.value).toBeCloseTo('2.22');
+ });
+
+ it('should not update if same rate', async function() {
+ const existingRate = await models.ReferenceRate.findOne({order: 'id DESC'}, options);
+ if (!existingRate) return fail('No existing ReferenceRate in DB');
+
+ const currency = await models.Currency.findById(existingRate.currencyFk, null, options);
+
+ const oldValue = existingRate.value;
+ const xml = `
+
+
+
+ `;
+
+ axios.get.and.returnValue(Promise.resolve({data: xml}));
+
+ await models.InvoiceIn.exchangeRateUpdate(options);
+
+ const updatedRate = await models.ReferenceRate.findById(existingRate.id, null, options);
+
+ expect(updatedRate.value).toBe(oldValue);
+ });
+
+ it('should backfill missing dates', async function() {
+ const lastRate = await models.ReferenceRate.findOne({order: 'dated DESC'}, options);
+ if (!lastRate) return fail('No existing ReferenceRate data in DB');
+
+ const currency = await models.Currency.findById(lastRate.currencyFk, null, options);
+
+ const d1 = new Date(lastRate.dated);
+ d1.setDate(d1.getDate() + 1);
+ const d4 = new Date(lastRate.dated);
+ d4.setDate(d4.getDate() + 4);
+
+ const xml = `
+
+
+
+
+
+
+ `;
+
+ axios.get.and.returnValue(Promise.resolve({data: xml}));
+
+ const beforeCount = await models.ReferenceRate.count({}, options);
+ await models.InvoiceIn.exchangeRateUpdate(options);
+ const afterCount = await models.ReferenceRate.count({}, options);
+
+ expect(afterCount - beforeCount).toBe(4);
+ });
+
+ it('should create entries for day1 and day2 from the feed, and not backfill day3', async function() {
+ const lastRate = await models.ReferenceRate.findOne({order: 'dated DESC'}, options);
+ if (!lastRate) return fail('No existing ReferenceRate data in DB');
+
+ const currency = await models.Currency.findById(lastRate.currencyFk, null, options);
+ if (!currency) return fail(`No currency for ID ${lastRate.currencyFk}`);
+
+ const day1 = new Date(lastRate.dated);
+ day1.setDate(day1.getDate() + 1);
+
+ const day2 = new Date(lastRate.dated);
+ day2.setDate(day2.getDate() + 2);
+
+ const day3 = new Date(lastRate.dated);
+ day3.setDate(day3.getDate() + 3);
+
+ const xml = `
+
+
+
+
+
+
+ `;
+
+ axios.get.and.returnValue(Promise.resolve({data: xml}));
+
+ await models.InvoiceIn.exchangeRateUpdate(options);
+
+ const day3Record = await models.ReferenceRate.findOne({
+ where: {currencyFk: currency.id, dated: day3}
+ }, options);
+
+ expect(day3Record).toBeNull();
+
+ const day1Record = await models.ReferenceRate.findOne({
+ where: {currencyFk: currency.id, dated: day1}
+ }, options);
+ const day2Record = await models.ReferenceRate.findOne({
+ where: {currencyFk: currency.id, dated: day2}
+ }, options);
+
+ expect(day1Record.value).toBeCloseTo('1.1');
+ expect(day2Record.value).toBeCloseTo('2.2');
});
});
diff --git a/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js
index f66221409..78fce348e 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js
@@ -74,7 +74,8 @@ module.exports = Self => {
AND t.companyFk = ?
AND NOT t.isDeleted
GROUP BY IF(c.hasToInvoiceByAddress, a.id, c.id)
- HAVING SUM(t.totalWithVat) > 0;`;
+ HAVING SUM(t.totalWithVat) > 0
+ ORDER BY c.id`;
const addresses = await Self.rawSql(query, [
minShipped,
diff --git a/modules/supplier/back/methods/supplier/newSupplier.js b/modules/supplier/back/methods/supplier/newSupplier.js
index 3cca4195f..eb941ed69 100644
--- a/modules/supplier/back/methods/supplier/newSupplier.js
+++ b/modules/supplier/back/methods/supplier/newSupplier.js
@@ -28,6 +28,7 @@ module.exports = Self => {
delete args.ctx;
if (!args.name) throw new UserError('The social name cannot be empty');
+ if (args.name !== args.name.toUpperCase()) throw new UserError('Social name should be uppercase');
const data = {...args, ...{nickname: args.name}};
const supplier = await models.Supplier.create(data, myOptions);
diff --git a/modules/ticket/back/methods/ticket/saveSign.js b/modules/ticket/back/methods/ticket/saveSign.js
index ac2a7bc66..f99311c39 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/saveSign.spec.js b/modules/ticket/back/methods/ticket/specs/saveSign.spec.js
index e93408973..3b426c2cf 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/travel/back/methods/travel/filter.js b/modules/travel/back/methods/travel/filter.js
index 30c1a45fa..837e30b30 100644
--- a/modules/travel/back/methods/travel/filter.js
+++ b/modules/travel/back/methods/travel/filter.js
@@ -1,4 +1,3 @@
-
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
@@ -91,6 +90,11 @@ module.exports = Self => {
arg: 'landed',
type: 'date',
description: 'The landed date'
+ },
+ {
+ arg: 'awbFk',
+ type: 'number',
+ description: 'The awbFk id'
}
],
returns: {
@@ -168,14 +172,17 @@ module.exports = Self => {
t.totalEntries,
t.isRaid,
t.daysInForward,
+ t.awbFk,
am.name agencyModeName,
+ a.code awbCode,
win.name warehouseInName,
wout.name warehouseOutName,
cnt.code continent
- FROM vn.travel t
- 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
+ FROM travel t
+ JOIN agencyMode am ON am.id = t.agencyModeFk
+ JOIN warehouse win ON win.id = t.warehouseInFk
+ JOIN warehouse wout ON wout.id = t.warehouseOutFk
+ LEFT JOIN awb a ON a.id = t.awbFk
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) AS t`
diff --git a/modules/travel/back/methods/travel/getEntries.js b/modules/travel/back/methods/travel/getEntries.js
index 50088ccfa..2399f8bc4 100644
--- a/modules/travel/back/methods/travel/getEntries.js
+++ b/modules/travel/back/methods/travel/getEntries.js
@@ -41,7 +41,9 @@ module.exports = Self => {
* b.stickers)/1000000) AS DECIMAL(10,2)) m3,
TRUNCATE(SUM(b.stickers)/(COUNT( b.id) / COUNT( DISTINCT b.id)),0) hb,
CAST(SUM(b.freightValue*b.quantity) AS DECIMAL(10,2)) freightValue,
- CAST(SUM(b.packageValue*b.quantity) AS DECIMAL(10,2)) packageValue
+ CAST(SUM(b.packageValue*b.quantity) AS DECIMAL(10,2)) packageValue,
+ e.initialTemperature,
+ e.finalTemperature
FROM vn.travel t
LEFT JOIN vn.entry e ON t.id = e.travelFk
LEFT JOIN vn.buy b ON b.entryFk = e.id
diff --git a/modules/travel/back/models/currency.json b/modules/travel/back/models/currency.json
index f3241fad1..427a18e31 100644
--- a/modules/travel/back/models/currency.json
+++ b/modules/travel/back/models/currency.json
@@ -20,6 +20,9 @@
},
"ratio": {
"type": "number"
+ },
+ "hasToDownloadRate": {
+ "type": "boolean"
}
},
"acls": [
diff --git a/modules/zone/back/models/zone-event.json b/modules/zone/back/models/zone-event.json
index 366bdec9d..cf5045a8c 100644
--- a/modules/zone/back/models/zone-event.json
+++ b/modules/zone/back/models/zone-event.json
@@ -42,6 +42,9 @@
"price": {
"type": "number"
},
+ "priceOptimum": {
+ "type": "number"
+ },
"bonus": {
"type": "number"
},
diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json
index 141b28750..4f963568f 100644
--- a/modules/zone/back/models/zone.json
+++ b/modules/zone/back/models/zone.json
@@ -28,6 +28,9 @@
"price": {
"type": "number"
},
+ "priceOptimum": {
+ "type": "number"
+ },
"bonus": {
"type": "number"
},
diff --git a/package.json b/package.json
index 72de69dbf..12727dc34 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",