diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql
index af250d461..590fe34b6 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
@@ -426,50 +426,50 @@ INSERT INTO `vn`.`clientConfig`(`id`, `riskTolerance`, `maxCreditRows`, `maxPric
(1, 200, 10, 0.25, 2, 4, 5, 300.00, 1, 1, 2);
-INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
+INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`, `customsAgentFk`)
VALUES
- (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, -74.1111111, 10.1111111, 0, 1),
- (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, -74.2222222, 10.2222222, 0, 1),
- (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, -74.3333333, 10.3333333, 0, 1),
- (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1 , 1104, 2, -74.4444444, 10.4444444, 0, 1),
- (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1),
- (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1),
- (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1),
- (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
- (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1),
- (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1),
- (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1),
- (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1),
- (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
- (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
- (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0),
- (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
- (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
- (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
- (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0),
- (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0),
- (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0),
- (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0),
- (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, -74.555555, 10.555555, 0, 0),
- (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0),
- (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0),
- (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0),
- (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0),
- (128, 'Cerebro', 'address 28', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
- (129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0),
- (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0);
+ (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, -74.1111111, 10.1111111, 0, 1, 1),
+ (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, -74.2222222, 10.2222222, 0, 1, NULL),
+ (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, -74.3333333, 10.3333333, 0, 1, NULL),
+ (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1 , 1104, 2, -74.4444444, 10.4444444, 0, 1, NULL),
+ (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1, NULL),
+ (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1, NULL),
+ (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1, NULL),
+ (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1, NULL),
+ (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1, NULL),
+ (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1, NULL),
+ (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1, NULL),
+ (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1, NULL),
+ (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0, NULL),
+ (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0, NULL),
+ (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0, NULL),
+ (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, -74.555555, 10.555555, 0, 0, NULL),
+ (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0, NULL),
+ (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0, NULL),
+ (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0, NULL),
+ (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0, NULL),
+ (128, 'Cerebro', 'address 28', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0, NULL),
+ (129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0, NULL),
+ (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0, NULL);
INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`)
SELECT name, CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, 1, id, 2, 1
@@ -648,13 +648,13 @@ INSERT INTO `vn`.`invoiceOutSerial`
('X', 'Exportación global', 0, 'WORLD', 0, 'global'),
('N', 'Múltiple Intracomunitaria', 0, 'CEE', 1, 'multiple');
-INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
+INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`, `customsAgentFk`, `incotermsFk`)
VALUES
- (1, 'T', 1026.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
- (2, 'T', 121.36, util.VN_CURDATE(), 1102, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
- (3, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
- (4, 'T', 8.88, util.VN_CURDATE(), 1104, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
- (5, 'A', 8.88, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 0);
+ (1, 'E', 1026.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0, 1, 'FAS'),
+ (2, 'T', 121.36, util.VN_CURDATE(), 1102, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0, NULL, NULL),
+ (3, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0, NULL, NULL),
+ (4, 'T', 8.88, util.VN_CURDATE(), 1104, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0, NULL, NULL),
+ (5, 'A', 8.88, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 0, NULL, NULL);
UPDATE `vn`.`invoiceOut` SET ref = 'T1111111' WHERE id = 1;
UPDATE `vn`.`invoiceOut` SET ref = 'T2222222' WHERE id = 2;
@@ -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/invoiceOut_new.sql b/db/routines/vn/procedures/invoiceOut_new.sql
index 723f33df5..1f20fb5fc 100644
--- a/db/routines/vn/procedures/invoiceOut_new.sql
+++ b/db/routines/vn/procedures/invoiceOut_new.sql
@@ -34,6 +34,7 @@ BEGIN
DECLARE vMaxShipped DATE;
DECLARE vDone BOOL;
DECLARE vTicketFk INT;
+ DECLARE vAddressFk INT;
DECLARE vCursor CURSOR FOR
SELECT id
FROM tmp.ticketToInvoice;
@@ -48,11 +49,13 @@ BEGIN
DATE(vInvoiceDate) >= invoiceOut_getMaxIssued(
vSerial,
t.companyFk,
- YEAR(vInvoiceDate))
+ YEAR(vInvoiceDate)),
+ t.addressFk
INTO vClientFk,
vCompanyFk,
vMaxShipped,
- vIsCorrectInvoiceDate
+ vIsCorrectInvoiceDate,
+ vAddressFk
FROM tmp.ticketToInvoice tt
JOIN ticket t ON t.id = tt.id;
@@ -105,7 +108,9 @@ BEGIN
clientFk,
dued,
companyFk,
- siiTypeInvoiceOutFk
+ siiTypeInvoiceOutFk,
+ customsAgentFk,
+ incotermsFk
)
SELECT
1,
@@ -118,9 +123,12 @@ BEGIN
vCplusCorrectingInvoiceTypeFk,
IF(vSerial = vSimplifiedSerial,
vCplusSimplifiedInvoiceTypeFk,
- vCplusStandardInvoiceTypeFk))
- FROM client
- WHERE id = vClientFk;
+ vCplusStandardInvoiceTypeFk)),
+ a.customsAgentFk,
+ a.incotermsFk
+ FROM client c
+ JOIN address a ON a.id = vAddressFk
+ WHERE c.id = vClientFk;
SET vNewInvoiceId = LAST_INSERT_ID();
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/routines/vn/triggers/workerManaExcluded_beforeInsert.sql b/db/routines/vn/triggers/workerManaExcluded_beforeInsert.sql
new file mode 100644
index 000000000..824f0982b
--- /dev/null
+++ b/db/routines/vn/triggers/workerManaExcluded_beforeInsert.sql
@@ -0,0 +1,9 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`workerManaExcluded_beforeInsert`
+ BEFORE INSERT ON `workerManaExcluded`
+ FOR EACH ROW
+BEGIN
+ DELETE FROM workerMana
+ WHERE workerFk = NEW.workerFk;
+END$$
+DELIMITER ;
diff --git a/db/routines/vn/triggers/workerManaExcluded_beforeUpdate.sql b/db/routines/vn/triggers/workerManaExcluded_beforeUpdate.sql
new file mode 100644
index 000000000..83d73e131
--- /dev/null
+++ b/db/routines/vn/triggers/workerManaExcluded_beforeUpdate.sql
@@ -0,0 +1,9 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`workerManaExcluded_beforeUpdate`
+ BEFORE UPDATE ON `workerManaExcluded`
+ FOR EACH ROW
+BEGIN
+ DELETE FROM workerMana
+ WHERE workerFk = NEW.workerFk;
+END$$
+DELIMITER ;
diff --git a/db/routines/vn/triggers/workerMana_beforeInsert.sql b/db/routines/vn/triggers/workerMana_beforeInsert.sql
new file mode 100644
index 000000000..2d27004e3
--- /dev/null
+++ b/db/routines/vn/triggers/workerMana_beforeInsert.sql
@@ -0,0 +1,10 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`workerMana_beforeInsert`
+ BEFORE INSERT ON `workerMana`
+ FOR EACH ROW
+BEGIN
+ IF (SELECT EXISTS(SELECT TRUE FROM workerManaExcluded WHERE workerFk = NEW.workerFk)) THEN
+ CALL util.throw('Worker is excluded from mana');
+ END IF;
+END$$
+DELIMITER ;
diff --git a/db/routines/vn/triggers/workerMana_beforeUpdate.sql b/db/routines/vn/triggers/workerMana_beforeUpdate.sql
new file mode 100644
index 000000000..6916733cb
--- /dev/null
+++ b/db/routines/vn/triggers/workerMana_beforeUpdate.sql
@@ -0,0 +1,10 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`workerMana_beforeUpdate`
+ BEFORE UPDATE ON `workerMana`
+ FOR EACH ROW
+BEGIN
+ IF (SELECT EXISTS(SELECT TRUE FROM workerManaExcluded WHERE workerFk = NEW.workerFk)) THEN
+ CALL util.throw('Worker is excluded from mana');
+ END IF;
+END$$
+DELIMITER ;
diff --git a/db/routines/vn/triggers/workerTimeControl_afterDelete.sql b/db/routines/vn/triggers/workerTimeControl_afterDelete.sql
index 27432fccb..96db381b5 100644
--- a/db/routines/vn/triggers/workerTimeControl_afterDelete.sql
+++ b/db/routines/vn/triggers/workerTimeControl_afterDelete.sql
@@ -3,10 +3,12 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`workerTimeControl_after
AFTER DELETE ON `workerTimeControl`
FOR EACH ROW
BEGIN
- INSERT INTO workerLog
- SET `action` = 'delete',
- `changedModel` = 'WorkerTimeControl',
- `changedModelId` = OLD.id,
- `userFk` = account.myUser_getId();
+ IF account.myUser_getId() IS NOT NULL THEN
+ INSERT INTO workerLog
+ SET `action` = 'delete',
+ `changedModel` = 'WorkerTimeControl',
+ `changedModelId` = OLD.id,
+ `userFk` = account.myUser_getId();
+ END IF;
END$$
DELIMITER ;
diff --git a/db/versions/11269-wheatBirch/00-firstScript.sql b/db/versions/11269-wheatBirch/00-firstScript.sql
new file mode 100644
index 000000000..9432d131b
--- /dev/null
+++ b/db/versions/11269-wheatBirch/00-firstScript.sql
@@ -0,0 +1,15 @@
+ALTER TABLE vn.invoiceOut ADD COLUMN IF NOT EXISTS customsAgentFk INT(11) DEFAULT NULL AFTER siiTrascendencyInvoiceOutFk;
+ALTER TABLE vn.invoiceOut ADD COLUMN IF NOT EXISTS incotermsFk varchar(3) DEFAULT NULL AFTER customsAgentFk;
+
+ALTER TABLE vn.invoiceOut ADD CONSTRAINT invoiceOut_customsAgentFk FOREIGN KEY (customsAgentFk)
+ REFERENCES vn.customsAgent (id) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+ALTER TABLE vn.invoiceOut ADD CONSTRAINT invoiceOut_incotermsFk FOREIGN KEY (incotermsFk)
+ REFERENCES vn.incoterms (`code`) ON DELETE RESTRICT ON UPDATE CASCADE;
+
+UPDATE vn.invoiceOut io
+ JOIN vn.client c ON c.id = io.clientFk
+ JOIN vn.ticket t ON t.clientFk = c.id
+ JOIN vn.address a ON a.id = t.addressFk
+ SET io.customsAgentFk = a.customsAgentFk,
+ io.incotermsFk = a.incotermsFk;
\ No newline at end of file
diff --git a/db/versions/11387-whiteDendro/00-firstScript.sql b/db/versions/11387-whiteDendro/00-firstScript.sql
new file mode 100644
index 000000000..4e9f1d217
--- /dev/null
+++ b/db/versions/11387-whiteDendro/00-firstScript.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS vn.productionCountry(
+ countryFk MEDIUMINT(8) UNSIGNED NOT NULL,
+ volumeGrowthEstimatePercent DECIMAL(6, 2) COMMENT 'Porcentaje estimado de crecimiento del volumen',
+ PRIMARY KEY (countryFk),
+ CONSTRAINT productionCountryVolume_countryFK
+ FOREIGN KEY (countryFk) REFERENCES vn.country (id)
+ ON DELETE RESTRICT ON UPDATE CASCADE
+) COMMENT = 'Datos de producción por país'
\ No newline at end of file
diff --git a/db/versions/11396-blueErica/00-firstScript.sql b/db/versions/11396-blueErica/00-firstScript.sql
new file mode 100644
index 000000000..b21965fe8
--- /dev/null
+++ b/db/versions/11396-blueErica/00-firstScript.sql
@@ -0,0 +1,5 @@
+DELETE FROM vn.workerMana
+ WHERE workerFk IN (
+ SELECT workerFk
+ FROM vn.workerManaExcluded
+ );
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/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 8d5eab4bc..06428475f 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 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/client/specs/updateAddress.spec.js b/modules/client/back/methods/client/specs/updateAddress.spec.js
index 68981f8b7..0453332d7 100644
--- a/modules/client/back/methods/client/specs/updateAddress.spec.js
+++ b/modules/client/back/methods/client/specs/updateAddress.spec.js
@@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models;
describe('Address updateAddress', () => {
- const clientId = 1101;
- const addressId = 1;
+ const clientId = 1102;
+ const addressId = 2;
const provinceId = 5;
const incotermsId = 'FAS';
const customAgentOneId = 1;
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/filter.js b/modules/invoiceOut/back/methods/invoiceOut/filter.js
index 764fdbb78..ab6782140 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/filter.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/filter.js
@@ -79,6 +79,12 @@ module.exports = Self => {
type: 'date',
description: 'The due date filter',
http: {source: 'query'}
+ },
+ {
+ arg: 'customsAgentFk',
+ type: 'integer',
+ description: 'The customsAgent id',
+ http: {source: 'query'}
}
],
returns: {
@@ -120,6 +126,7 @@ module.exports = Self => {
case 'companyFk':
case 'issued':
case 'dued':
+ case 'customsAgentFk':
param = `i.${param}`;
return {[param]: value};
}
@@ -139,11 +146,14 @@ module.exports = Self => {
i.dued,
i.clientFk,
i.hasPdf,
+ i.customsAgentFk,
c.socialName AS clientSocialName,
- co.code AS companyCode
+ co.code AS companyCode,
+ ca.fiscalName AS customsAgentName
FROM invoiceOut i
LEFT JOIN client c ON c.id = i.clientFk
- LEFT JOIN company co ON co.id = i.companyFk`
+ LEFT JOIN company co ON co.id = i.companyFk
+ LEFT JOIN customsAgent ca ON ca.id = i.customsAgentFk`
);
stmt.merge(conn.makeSuffix(filter));
diff --git a/modules/invoiceOut/back/models/invoice-out.json b/modules/invoiceOut/back/models/invoice-out.json
index 1ee36accb..2ad13fe3f 100644
--- a/modules/invoiceOut/back/models/invoice-out.json
+++ b/modules/invoiceOut/back/models/invoice-out.json
@@ -66,6 +66,11 @@
"model": "Ticket",
"foreignKey": "refFk",
"primaryKey": "ref"
+ },
+ "customsAgentFk": {
+ "type": "belongsTo",
+ "model": "CustomsAgent",
+ "foreignKey": "customsAgentFk"
}
}
}
diff --git a/modules/route/back/methods/route/driverRouteEmail.js b/modules/route/back/methods/route/driverRouteEmail.js
index bbac2b0e8..78069683d 100644
--- a/modules/route/back/methods/route/driverRouteEmail.js
+++ b/modules/route/back/methods/route/driverRouteEmail.js
@@ -8,7 +8,7 @@ module.exports = Self => {
arg: 'id',
type: 'number',
required: true,
- description: 'The client id',
+ description: 'The route id',
http: {source: 'path'}
}, {
arg: 'replyTo',
@@ -31,26 +31,13 @@ module.exports = Self => {
});
Self.driverRouteEmail = async(ctx, id) => {
- const models = Self.app.models;
- const {workerFk, agencyMode} = await Self.findById(id, {
- fields: ['workerFk', 'agencyModeFk'],
+ const {agencyMode} = await Self.findById(id, {
+ fields: ['agencyModeFk'],
include: {relation: 'agencyMode'}
});
const {reportMail} = agencyMode();
- let user;
- let account;
-
- if (workerFk) {
- user = await models.VnUser.findById(workerFk, {
- fields: ['active', 'id'],
- include: {relation: 'emailUser'}
- });
- account = await models.Account.findById(workerFk);
- }
-
- if (user?.active && account) ctx.args.recipient = user.emailUser().email;
- else ctx.args.recipient = reportMail;
+ ctx.args.recipient = reportMail;
if (!ctx.args.recipient) throw new UserError('An email is necessary');
return Self.sendTemplate(ctx, 'driver-route');
};
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-request/getItemTypeWorker.js b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js
index f160cfaac..2f2a85abb 100644
--- a/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js
+++ b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js
@@ -30,7 +30,7 @@ module.exports = Self => {
Object.assign(myOptions, options);
const query =
- `SELECT DISTINCT u.id, u.nickname
+ `SELECT DISTINCT u.id, u.nickname, w.firstName, w.lastName
FROM itemType it
JOIN worker w ON w.id = it.workerFk
JOIN account.user u ON u.id = w.id`;
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/ticket/back/models/specs/ticket-service.spec.js b/modules/ticket/back/models/specs/ticket-service.spec.js
new file mode 100644
index 000000000..2691123eb
--- /dev/null
+++ b/modules/ticket/back/models/specs/ticket-service.spec.js
@@ -0,0 +1,64 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('ticketService model ', () => {
+ const originalTicketFk = 1;
+ const refundTicketFk = 11;
+
+ let tx;
+ let opts;
+ let ticketService;
+
+ beforeEach(async() => {
+ tx = await models.Sale.beginTransaction({});
+ opts = {transaction: tx};
+
+ ticketService = await models.TicketService.create({
+ ticketFk: refundTicketFk,
+ description: 'test',
+ quantity: 1,
+ price: 100,
+ taxClassFk: 1,
+ ticketServiceTypeFk: 1
+ }, opts);
+ });
+
+ afterEach(async() => {
+ await tx.rollback();
+ });
+
+ describe('TicketService', () => {
+ it('should allow updating description and quantity for non-refund tickets', async() => {
+ await ticketService.updateAttributes({
+ ticketServiceTypeFk: 2,
+ quantity: 5
+ }, opts);
+
+ const updated = await models.TicketService.findById(ticketService.id, null, opts);
+
+ expect(updated.description).not.toBe('test');
+ expect(updated.quantity).toBe(5);
+ });
+
+ it('should only allow updating description for refund tickets', async() => {
+ await models.TicketRefund.create({
+ refundTicketFk,
+ originalTicketFk
+ }, opts);
+
+ await ticketService.updateAttributes({
+ ticketServiceTypeFk: 2
+ }, opts);
+
+ try {
+ await ticketService.updateAttributes({
+ ticketServiceTypeFk: 3,
+ quantity: 5
+ }, opts);
+
+ fail('Should have thrown error');
+ } catch (e) {
+ expect(e.message).toBe('Only description can be modified in refund tickets');
+ }
+ });
+ });
+});
diff --git a/modules/ticket/back/models/ticket-service.js b/modules/ticket/back/models/ticket-service.js
index 209727ee4..77479498a 100644
--- a/modules/ticket/back/models/ticket-service.js
+++ b/modules/ticket/back/models/ticket-service.js
@@ -10,9 +10,18 @@ module.exports = Self => {
const isLocked = await models.Ticket.isLocked(ticketId);
if (isLocked)
throw new UserError(`The current ticket can't be modified`);
+
+ const isRefund = await models.TicketRefund.findOne({
+ where: {refundTicketFk: ticketId}
+ }, {
+ transaction: ctx.options.transaction
+ });
+
+ if (isRefund && ctx.data && Object.keys(ctx.data).some(field => field !== 'ticketServiceTypeFk'))
+ throw new UserError('Only description can be modified in refund tickets');
}
- if (changes && changes.ticketServiceTypeFk) {
+ if (changes?.ticketServiceTypeFk) {
const ticketServiceType = await models.TicketServiceType.findById(changes.ticketServiceTypeFk);
changes.description = ticketServiceType.name;
}
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 a843ac9c5..e4cbf1406 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "salix-back",
- "version": "25.02.0",
+ "version": "25.04.0",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",
diff --git a/print/templates/reports/invoice-incoterms/sql/incoterms.sql b/print/templates/reports/invoice-incoterms/sql/incoterms.sql
index 016a8342e..535451674 100644
--- a/print/templates/reports/invoice-incoterms/sql/incoterms.sql
+++ b/print/templates/reports/invoice-incoterms/sql/incoterms.sql
@@ -1,9 +1,9 @@
WITH tickets AS(
-SELECT id, packages, addressFk, weight
- FROM ticket
- WHERE refFk= ?
+SELECT id, addressFk, packages, refFk
+ FROM vn.ticket
+ WHERE refFk = ?
), volume AS(
-SELECT SUM(volume) volume
+SELECT SUM(volume) volume, MAX(weight)weight
FROM tickets t
JOIN vn.saleVolume sv ON sv.ticketFk = t.id
), intrastat AS(
@@ -12,10 +12,14 @@ SELECT GROUP_CONCAT(DISTINCT ir.description ORDER BY ir.description SEPARATOR '
JOIN vn.sale s ON t.id = s.ticketFk
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.intrastat ir ON ir.id = i.intrastatFk
-)SELECT SUM(t.packages) packages,
- a.incotermsFk,
+), totalPackages AS(
+SELECT SUM(packages)packages
+ FROM tickets s
+)
+SELECT tp.packages,
+ io.incotermsFk,
ic.name incotermsName,
- MAX(t.weight) weight,
+ v.weight weight,
ca.fiscalName customsAgentName,
ca.street customsAgentStreet,
ca.nif customsAgentNif,
@@ -23,9 +27,10 @@ SELECT GROUP_CONCAT(DISTINCT ir.description ORDER BY ir.description SEPARATOR '
ca.email customsAgentEmail,
CAST(v.volume AS DECIMAL (10,2)) volume,
i.intrastat
- FROM tickets t
- JOIN vn.address a ON a.id = t.addressFk
- JOIN vn.incoterms ic ON ic.code = a.incotermsFk
- LEFT JOIN vn.customsAgent ca ON ca.id = a.customsAgentFk
+ FROM vn.invoiceOut io
+ JOIN vn.incoterms ic ON ic.code = io.incotermsFk
+ LEFT JOIN vn.customsAgent ca ON ca.id = io.customsAgentFk
JOIN volume v
JOIN intrastat i
+ JOIN totalPackages tp
+ WHERE `ref` = (SELECT DISTINCT refFk FROM tickets)
\ No newline at end of file