diff --git a/.eslintrc.yml b/.eslintrc.yml
index edbc47195..0d74348f2 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -36,3 +36,7 @@ rules:
jasmine/no-focused-tests: 0
jasmine/prefer-toHaveBeenCalledWith: 0
arrow-spacing: ["error", { "before": true, "after": true }]
+ no-restricted-syntax:
+ - "error"
+ - selector: "NewExpression[callee.name='Date']"
+ message: "Use Date.vnNew() instead of new Date()."
diff --git a/back/methods/collection/getSales.js b/back/methods/collection/getSales.js
index a9e5f2e60..f3575672f 100644
--- a/back/methods/collection/getSales.js
+++ b/back/methods/collection/getSales.js
@@ -29,10 +29,8 @@ module.exports = Self => {
});
Self.getSales = async(ctx, collectionOrTicketFk, print, source, options) => {
- const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
- const $t = ctx.req.__;
if (typeof options == 'object')
Object.assign(myOptions, options);
@@ -59,22 +57,6 @@ module.exports = Self => {
if (print) await Self.rawSql('CALL vn.collection_printSticker(?,NULL)', [id], myOptions);
- for (let ticket of tickets) {
- if (ticket.observaciones) {
- let observations = ticket.observaciones.split(' ');
-
- for (let observation of observations) {
- const salesPerson = ticket.salesPersonFk;
- if (observation.startsWith('#') || observation.startsWith('@')) {
- await models.Chat.send(ctx,
- observation,
- $t('ticketCommercial', {ticket: ticket.ticketFk, salesPerson})
- );
- }
- }
- }
- }
-
return getCollection(id, tickets, sales, placements, myOptions);
};
diff --git a/back/methods/collection/spec/assign.spec.js b/back/methods/collection/spec/assign.spec.js
index 745343819..b00631d22 100644
--- a/back/methods/collection/spec/assign.spec.js
+++ b/back/methods/collection/spec/assign.spec.js
@@ -28,9 +28,10 @@ describe('ticket assign()', () => {
await tx.rollback();
});
- it('should throw an error when there is not picking tickets', async() => {
+ it('should throw an error when there are no picking tickets', async() => {
try {
await models.Collection.assign(ctx, options);
+ fail('Expected an error to be thrown, but none was thrown.');
} catch (e) {
expect(e.message).toEqual('There are not picking tickets');
}
diff --git a/back/methods/workerActivity/add.js b/back/methods/workerActivity/add.js
index 4592a0797..89131491d 100644
--- a/back/methods/workerActivity/add.js
+++ b/back/methods/workerActivity/add.js
@@ -31,7 +31,7 @@ module.exports = Self => {
return await Self.rawSql(`
INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model)
SELECT ?, ?, ?
- FROM workerTimeControlParams wtcp
+ FROM workerTimeControlConfig wtcc
LEFT JOIN (
SELECT wa.workerFk,
wa.created,
@@ -44,7 +44,7 @@ module.exports = Self => {
) sub ON TRUE
WHERE sub.workerFk IS NULL
OR sub.code <> ?
- OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcp.dayBreak;`
+ OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcc.dayBreak;`
, [userId, code, model, userId, code], myOptions);
};
};
diff --git a/back/models/expedition_PrintOut.json b/back/models/expedition_PrintOut.json
index dd49b0234..23a2fdbc4 100644
--- a/back/models/expedition_PrintOut.json
+++ b/back/models/expedition_PrintOut.json
@@ -14,9 +14,6 @@
},
"itemFk": {
"type": "number"
- },
- "isChecked": {
- "type": "boolean"
}
}
}
\ No newline at end of file
diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql
index eb376b6c6..e5d5f53dc 100644
--- a/db/dump/fixtures.before.sql
+++ b/db/dump/fixtures.before.sql
@@ -1505,32 +1505,32 @@ INSERT INTO `vn`.`awb` (id, code, package, weight, created, amount, transitoryFk
(9, '99610289193', 302, 2972, util.VN_CURDATE(), 3871, 442, 1),
(10, '07546500856', 185, 2364, util.VN_CURDATE(), 5321, 442, 1);
-INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyModeFk`, `m3`, `kg`,`ref`, `totalEntries`, `cargoSupplierFk`, `awbFK`, `daysInForward`)
- VALUES (1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000, 'first travel', 1, 1, 1, NULL),
- (2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150.00, 2000, 'second travel', 2, 2, 2, NULL),
- (3, util.VN_CURDATE(), util.VN_CURDATE(), 1, 2, 1, 0.00, 0.00, 'third travel', 1, 1, 3, NULL),
- (4, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 3, 1, 50.00, 500, 'fourth travel', 0, 2, 4, NULL),
- (5, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 3, 1, 50.00, 500, 'fifth travel', 1, 1, 5, NULL),
- (6, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 50.00, 500, 'sixth travel', 1, 2, 6, NULL),
- (7, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 4, 1, 50.00, 500, 'seventh travel', 2, 1, 7, 2),
- (8, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 1, 1, 50.00, 500, 'eight travel', 1, 2, 10, NULL),
- (10, DATE_ADD(util.VN_CURDATE(), INTERVAL +5 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL +5 DAY), 5, 1, 1, 50.00, 500, 'nineth travel', 1, 2, 10, 2),
- (11, util.VN_CURDATE() - INTERVAL 1 DAY , util.VN_CURDATE(), 6, 3, 0, 50.00, 500, 'eleventh travel', 1, 2, 4, NULL),
- (12, util.VN_CURDATE() , util.VN_CURDATE() + INTERVAL 1 DAY, 6, 3, 0, 50.00, 500, 'eleventh travel', 1, 2, 4, NULL);
+INSERT INTO `vn`.`travel`(`id`,`shipped`, `landed`, `warehouseInFk`, `warehouseOutFk`, `agencyModeFk`, `m3`, `kg`,`ref`, `totalEntries`, `cargoSupplierFk`, `awbFK`, `isRaid`, `daysInForward`)
+ VALUES (1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), 1, 2, 1, 100.00, 1000, 'first travel', 1, 1, 1, FALSE, NULL),
+ (2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 2, 1, 150.00, 2000, 'second travel', 2, 2, 2, FALSE, NULL),
+ (3, util.VN_CURDATE(), util.VN_CURDATE(), 1, 2, 1, 0.00, 0.00, 'third travel', 1, 1, 3, FALSE, NULL),
+ (4, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 3, 1, 50.00, 500, 'fourth travel', 0, 2, 4, FALSE, NULL),
+ (5, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 3, 1, 50.00, 500, 'fifth travel', 1, 1, 5, FALSE, NULL),
+ (6, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 4, 4, 1, 50.00, 500, 'sixth travel', 1, 2, 6, FALSE,NULL),
+ (7, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 4, 1, 50.00, 500, 'seventh travel', 2, 1, 7, TRUE, 2),
+ (8, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 1, 1, 50.00, 500, 'eight travel', 1, 2, 10, FALSE, NULL),
+ (10, DATE_ADD(util.VN_CURDATE(), INTERVAL +5 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL +5 DAY), 5, 1, 1, 50.00, 500, 'nineth travel', 1, 2, 10, TRUE, 2),
+ (11, util.VN_CURDATE() - INTERVAL 1 DAY , util.VN_CURDATE(), 6, 3, 0, 50.00, 500, 'eleventh travel', 1, 2, 4, FALSE, NULL),
+ (12, util.VN_CURDATE() , util.VN_CURDATE() + INTERVAL 1 DAY, 6, 3, 0, 50.00, 500, 'eleventh travel', 1, 2, 4, FALSE, NULL);
-INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed`, `companyFk`, `invoiceNumber`, `reference`, `isExcludedFromAvailable`, `evaNotes`)
- VALUES
- (1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 1, 442, 'IN2001', 'Movement 1', 0, ''),
- (2, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 442, 'IN2002', 'Movement 2', 0, 'observation two'),
- (3, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 0, 442, 'IN2003', 'Movement 3', 0, 'observation three'),
- (4, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 69, 'IN2004', 'Movement 4', 0, 'observation four'),
- (5, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 0, 442, 'IN2005', 'Movement 5', 0, 'observation five'),
- (6, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 6, 0, 442, 'IN2006', 'Movement 6', 0, 'observation six'),
- (7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2007', 'Movement 7', 0, 'observation seven'),
- (8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2008', 'Movement 8', 1,''),
- (9, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL +2 DAY), 10, 0, 442, 'IN2009', 'Movement 9', 1, ''),
- (10, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL +2 DAY), 10, 0, 442, 'IN2009', 'Movement 10', 1, ''),
- (99, 69, '2000-12-01 00:00:00.000', 11, 0, 442, 'IN2009', 'Movement 99', 0, '');
+INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed`, `companyFk`, `invoiceNumber`, `reference`, `isExcludedFromAvailable`, `evaNotes`, `typeFk`)
+ VALUES
+ (1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 1, 442, 'IN2001', 'Movement 1', 0, '', 'packaging'),
+ (2, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 442, 'IN2002', 'Movement 2', 0, 'observation two', 'product'),
+ (3, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 3, 0, 442, 'IN2003', 'Movement 3', 0, 'observation three', 'product'),
+ (4, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2, 0, 69, 'IN2004', 'Movement 4', 0, 'observation four', 'product'),
+ (5, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 5, 0, 442, 'IN2005', 'Movement 5', 0, 'observation five', 'product'),
+ (6, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 6, 0, 442, 'IN2006', 'Movement 6', 0, 'observation six', 'product'),
+ (7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2007', 'Movement 7', 0, 'observation seven', 'product'),
+ (8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'IN2008', 'Movement 8', 1, '', 'product'),
+ (9, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL +2 DAY), 10, 0, 442, 'IN2009', 'Movement 9', 1, '', 'product'),
+ (10, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL +2 DAY), 10, 0, 442, 'IN2009', 'Movement 10', 1, '', 'product'),
+ (99, 69, util.VN_CURDATE() - INTERVAL 1 MONTH, 11, 0, 442, 'IN2009', 'Movement 99', 0, '', 'product');
INSERT INTO `vn`.`entryConfig` (`defaultEntry`, `inventorySupplierFk`, `defaultSupplierFk`)
VALUES (2, 4, 1);
@@ -2521,10 +2521,6 @@ INSERT INTO `vn`.`queuePriority`(`id`, `priority`, `code`)
(2, 'Normal', 'normal'),
(3, 'Baja', 'low');
-INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`)
- VALUES
- (1, 43200, 129600, 734400, 43200, 50400, 259200, 1296000, 36000);
-
INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1', '11');
INSERT INTO `vn`.`thermograph`(`id`, `model`)
@@ -4008,10 +4004,17 @@ INSERT IGNORE INTO pbx.queueMember
UPDATE vn.department SET pbxQueue = '1000' WHERE name = "CAMARA";
UPDATE vn.department SET pbxQueue = '2000' WHERE name = "VENTAS";
-INSERT INTO srt.buffer (id, x, y, `size`, `length`, stateFk, typeFk, isActive, code, stratus, hasWorkerWaiting, reserve, routeFk, dayMinute, lastUnloaded, hasStrapper, typeDefaultFk, motors, editorFk)
+INSERT INTO vn.workerRelatives (workerFk, isDescendant, disabilityGradeFk, birthed, adoptionYear, isDependend, isJointCustody, updated)
+ VALUES
+ (1106, 1, NULL, 0, NULL, 0, 0, '2024-10-29 08:42:44.000');
+INSERT INTO vn.routeAction (id, name, price, isMainlineDelivered) VALUES(1, 'Pintar traje', 50.00, 0);
+INSERT INTO vn.routeComplement (id, dated, workerFk, price, routeActionFk) VALUES(1, util.VN_CURDATE(), 9, 50.00, 1);
+
+
+INSERT INTO srt.buffer (id, x, y, `size`, `length`, stateFk, typeFk, isActive, code, stratus, hasWorkerWaiting, reserve, routeFk, dayMinute, lastUnloaded, hasStrapper, typeDefaultFk, motors, editorFk)
VALUES (0, 0, 0, 0, NULL, 3, 1, 0, 'ENT', 0, 0, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
(1, 0, 9900, 0, NULL, 1, 0, 0, 'NOK', 0, 0, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
- (2, 0, 0, 450, 13000, 1, 0, 1, '01A', 1, 1, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
+ (2, 0, 0, 450, 13000, 1, 0, 1, '01A', 1, 1, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
(3, 1400, 0, 450, 13000, 1, 0, 1, '01B', 1, 0, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
(4, 0, 500, 500, 13000, 1, 4, 1, '02A', 2, 1, NULL, NULL, NULL, NULL, 1, 4, 13, NULL),
(5, 1400, 500, 500, 13000, 1, 4, 1, '02B', 2, 1, NULL, NULL, NULL, NULL, 1, 4, 13, NULL),
@@ -4021,10 +4024,12 @@ INSERT INTO srt.buffer (id, x, y, `size`, `length`, stateFk, typeFk, isActive, c
(9, 1400, 1500, 500, 13000, 1, 1, 1, '04B', 4, 0, NULL, NULL, NULL, NULL, 0, 1, 1, NULL),
(10, 0, 2000, 500, 13000, 1, 1, 1, '05A', 5, 0, NULL, NULL, NULL, NULL, 0, 1, 1, NULL);
-
-INSERT IGNORE INTO vn.saySimpleCountry (countryFk, channel)
+INSERT IGNORE INTO vn.saySimpleCountry (countryFk, channel)
VALUES (19, '1169'),
(8, '1183');
-INSERT IGNORE INTO vn.saySimpleConfig (url, defaultChannel)
- VALUES ('saysimle-url-mock', 1320);
\ No newline at end of file
+INSERT IGNORE INTO vn.saySimpleConfig (url, defaultChannel)
+ VALUES ('saysimle-url-mock', 1320);
+
+INSERT INTO vn.workerIrpf (workerFk,spouseNif, geographicMobilityDate)
+ VALUES (1106,'26493101E','2019-09-20');
diff --git a/db/routines/cache/procedures/availableNoRaids_refresh.sql b/db/routines/cache/procedures/availableNoRaids_refresh.sql
index efbbf6a13..447300305 100644
--- a/db/routines/cache/procedures/availableNoRaids_refresh.sql
+++ b/db/routines/cache/procedures/availableNoRaids_refresh.sql
@@ -53,7 +53,7 @@ proc: BEGIN
WHERE t.landed BETWEEN vInventoryDate AND vStartDate
AND t.warehouseInFk = vWarehouse
AND s.name != 'INVENTARIO'
- AND NOT t.daysInForward
+ AND NOT t.isRaid
GROUP BY b.itemFk
) c
JOIN vn.item i ON i.id = c.itemFk
diff --git a/db/routines/edi/triggers/supplyResponse_afterUpdate.sql b/db/routines/edi/triggers/supplyResponse_afterUpdate.sql
index 28a8c9466..f3e5aaefd 100644
--- a/db/routines/edi/triggers/supplyResponse_afterUpdate.sql
+++ b/db/routines/edi/triggers/supplyResponse_afterUpdate.sql
@@ -14,7 +14,7 @@ BEGIN
b.stickers = NEW.NumberOfUnits
WHERE i.supplyResponseFk = NEW.ID
AND am.name = 'LOGIFLORA'
- AND tr.daysInForward
+ AND tr.isRaid
AND tr.landed >= util.VN_CURDATE();
END$$
diff --git a/db/routines/hedera/procedures/item_getVisible.sql b/db/routines/hedera/procedures/item_getVisible.sql
index 365161bdf..d5bbe9d76 100644
--- a/db/routines/hedera/procedures/item_getVisible.sql
+++ b/db/routines/hedera/procedures/item_getVisible.sql
@@ -59,7 +59,7 @@ BEGIN
JOIN vn.travel t ON t.id = e.travelFk
WHERE t.landed BETWEEN vDateInv AND vDate
AND t.warehouseInFk = vWarehouse
- AND NOT t.daysInForward
+ AND NOT t.isRaid
UNION ALL
SELECT b.itemFk, -b.quantity
FROM vn.buy b
@@ -67,7 +67,7 @@ BEGIN
JOIN vn.travel t ON t.id = e.travelFk
WHERE t.shipped BETWEEN vDateInv AND util.VN_CURDATE()
AND t.warehouseOutFk = vWarehouse
- AND NOT t.daysInForward
+ AND NOT t.isRaid
AND t.isDelivered
UNION ALL
SELECT m.itemFk, -m.quantity
diff --git a/db/routines/stock/procedures/log_refreshBuy.sql b/db/routines/stock/procedures/log_refreshBuy.sql
index d8e727f17..874e14072 100644
--- a/db/routines/stock/procedures/log_refreshBuy.sql
+++ b/db/routines/stock/procedures/log_refreshBuy.sql
@@ -6,12 +6,11 @@ BEGIN
DROP TEMPORARY TABLE IF EXISTS tValues;
CREATE TEMPORARY TABLE tValues
ENGINE = MEMORY
- SELECT
- b.id buyFk,
+ SELECT b.id buyFk,
e.id entryFk,
t.id travelFk,
b.itemFk,
- t.daysInForward,
+ t.isRaid,
ADDTIME(t.shipped,
IFNULL(t.shipmentHour, '00:00:00')) shipped,
t.warehouseOutFk,
@@ -50,7 +49,7 @@ BEGIN
itemFk,
TIMESTAMPADD(DAY, life, @dated),
quantity,
- IF(isIn, isReceived, isDelivered) AND NOT daysInForward
+ IF(isIn, isReceived, isDelivered) AND NOT isRaid
FROM tValues
WHERE isIn OR !lessThanInventory;
@@ -65,7 +64,7 @@ BEGIN
itemFk,
created,
quantity,
- IF(isIn, isDelivered, isReceived) AND NOT daysInForward
+ IF(isIn, isDelivered, isReceived) AND NOT isRaid
FROM tValues
WHERE !isIn OR !lessThanInventory;
diff --git a/db/routines/vn/functions/timeWorkerControl_getDirection.sql b/db/routines/vn/functions/timeWorkerControl_getDirection.sql
deleted file mode 100644
index c0f1e67ea..000000000
--- a/db/routines/vn/functions/timeWorkerControl_getDirection.sql
+++ /dev/null
@@ -1,65 +0,0 @@
-DELIMITER $$
-CREATE OR REPLACE DEFINER=`vn`@`localhost` FUNCTION `vn`.`timeWorkerControl_getDirection`(vUserFk INT, vTimed DATETIME)
- RETURNS varchar(6) CHARSET utf8mb3 COLLATE utf8mb3_unicode_ci
- NOT DETERMINISTIC
- READS SQL DATA
-BEGIN
-/**
- * Verifica la dirección de la fichada
- * @param vUserFk Identificador del trabajador
- * @param vTimed Hora de la fichada
- * @return Retorna sentido de la fichada 'in, out, middle'
- */
-
- DECLARE vPrevious DATETIME ;
- DECLARE vNext DATETIME ;
- DECLARE vPreviousDirection VARCHAR(3) ;
- DECLARE vNextDirection VARCHAR(3) ;
- DECLARE vDayStayMax INT;
- DECLARE vTimedSeconds INT;
- DECLARE vLastTimeIn INT;
-
- SELECT UNIX_TIMESTAMP(vTimed) INTO vTimedSeconds;
-
- SELECT dayStayMax INTO vDayStayMax
- FROM vn.workerTimeControlParams;
-
- SELECT timed, direction INTO vNext,vNextDirection
- FROM vn.workerTimeControl
- WHERE userFk = vUserFk
- AND direction IN ('in','out')
- AND timed > vTimed
- ORDER BY timed ASC
- LIMIT 1;
-
- SELECT timed, direction INTO vPrevious, vPreviousDirection
- FROM vn.workerTimeControl
- WHERE userFk = vUserFk
- AND direction IN ('in','out')
- AND timed < vTimed
- ORDER BY timed DESC
- LIMIT 1;
-
- IF (vTimedSeconds - UNIX_TIMESTAMP(vPrevious) + UNIX_TIMESTAMP(vNext) - vTimedSeconds)<= vDayStayMax AND vPreviousDirection = 'in' AND vNextDirection = 'out' THEN
- RETURN 'middle';
- END IF;
-
-
- IF (vTimedSeconds> UNIX_TIMESTAMP(vPrevious)) THEN
- IF vPreviousDirection = 'in' THEN
- RETURN 'out';
- ELSE
- SELECT UNIX_TIMESTAMP(MAX(timed)) INTO vLastTimeIn
- FROM vn.workerTimeControl
- WHERE userFk = vUserFk
- AND direction ='in'
- AND timed < vPrevious;
- IF vTimedSeconds - vLastTimeIn <= vDayStayMax THEN
- RETURN 'out';
- END IF;
- END IF;
- END IF;
-
- RETURN 'in';
-END$$
-DELIMITER ;
diff --git a/db/routines/vn/procedures/absoluteInventoryHistory.sql b/db/routines/vn/procedures/absoluteInventoryHistory.sql
index 3ea8cf4de..d0d9ffac2 100644
--- a/db/routines/vn/procedures/absoluteInventoryHistory.sql
+++ b/db/routines/vn/procedures/absoluteInventoryHistory.sql
@@ -39,7 +39,7 @@ BEGIN
AND vWarehouseFk IN (tr.warehouseInFk, 0)
AND b.itemFk = vItemFk
AND NOT e.isExcludedFromAvailable
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
UNION ALL
SELECT tr.shipped,
NULL,
@@ -58,7 +58,7 @@ BEGIN
AND s.id <> (SELECT supplierFk FROM inventoryConfig)
AND b.itemFk = vItemFk
AND NOT e.isExcludedFromAvailable
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
UNION ALL
SELECT t.shipped,
NULL,
diff --git a/db/routines/vn/procedures/available_traslate.sql b/db/routines/vn/procedures/available_traslate.sql
index 513f58e36..bfd5b34c8 100644
--- a/db/routines/vn/procedures/available_traslate.sql
+++ b/db/routines/vn/procedures/available_traslate.sql
@@ -42,7 +42,7 @@ proc: BEGIN
WHERE t.landed BETWEEN vDatedInventory AND vDatedFrom
AND t.warehouseInFk = vWarehouseLanding
AND NOT e.isExcludedFromAvailable
- AND NOT t.daysInForward
+ AND NOT t.isRaid
GROUP BY c.itemFk;
-- Tabla con el ultimo dia de last_buy para cada producto
@@ -57,7 +57,7 @@ proc: BEGIN
JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN tItemRange i ON t.itemFk = i.itemFk
WHERE t.warehouseFk = vWarehouseShipment
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
ON DUPLICATE KEY UPDATE tItemRange.dated = GREATEST(tItemRange.dated,
tr.landed);
@@ -94,7 +94,7 @@ proc: BEGIN
JOIN tItemRangeLive ir ON ir.itemFk = b.itemFk
WHERE NOT e.isExcludedFromAvailable
AND b.quantity <> 0
- AND NOT t.daysInForward
+ AND NOT t.isRaid
AND t.warehouseInFk = vWarehouseLanding
AND t.landed >= vDatedFrom
AND (ir.dated IS NULL OR t.landed <= ir.dated)
diff --git a/db/routines/vn/procedures/entry_getTransfer.sql b/db/routines/vn/procedures/entry_getTransfer.sql
index 9527e0bf2..873827db2 100644
--- a/db/routines/vn/procedures/entry_getTransfer.sql
+++ b/db/routines/vn/procedures/entry_getTransfer.sql
@@ -166,7 +166,7 @@ BEGIN
LEFT JOIN tmp.buyUltimateFromInterval bufi ON bufi.itemFk = i.id
LEFT JOIN buy b3 ON b3.id = bufi.buyFk
WHERE ic.display
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
AND (ti.visible OR ti.available)
ORDER BY i.typeFk, i.name, i.id, i.size, i.category, o.name;
diff --git a/db/routines/vn/procedures/entry_isEditable.sql b/db/routines/vn/procedures/entry_isEditable.sql
index 12b6d0ef6..c417f6789 100644
--- a/db/routines/vn/procedures/entry_isEditable.sql
+++ b/db/routines/vn/procedures/entry_isEditable.sql
@@ -9,16 +9,18 @@ BEGIN
*
* @param vSelf Id de entrada
*/
- DECLARE vIsEditable BOOL;
+ DECLARE vIsNotEditable BOOL DEFAULT FALSE;
- SELECT e.isBooked INTO vIsEditable
+ SELECT TRUE INTO vIsNotEditable
FROM `entry` e
- JOIN entryType et ON et.code = e.typeFk
- WHERE NOT et.isInformal
- AND e.id = vSelf;
+ LEFT JOIN entryType et ON et.code = e.typeFk
+ WHERE e.id = vSelf
+ AND e.isBooked
+ AND (e.typeFk IS NULL OR NOT et.isInformal);
- IF vIsEditable AND NOT IFNULL(@isModeInventory, FALSE) THEN
+ IF vIsNotEditable AND NOT IFNULL(@isModeInventory, FALSE) THEN
CALL util.throw(CONCAT('Entry ', vSelf, ' is not editable'));
END IF;
+
END$$
DELIMITER ;
diff --git a/db/routines/vn/procedures/expeditionScan_Put.sql b/db/routines/vn/procedures/expeditionScan_Put.sql
index fc7d4da23..999b9af10 100644
--- a/db/routines/vn/procedures/expeditionScan_Put.sql
+++ b/db/routines/vn/procedures/expeditionScan_Put.sql
@@ -4,12 +4,12 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`expeditionScan_Put`(
vExpeditionFk INT
)
BEGIN
- IF NOT (SELECT TRUE FROM expedition WHERE id = vExpeditionFk LIMIT 1) THEN
- CALL util.throw('Expedition not exists');
+ IF NOT EXISTS (SELECT id FROM expeditionPallet WHERE id = vPalletFk) THEN
+ CALL util.throw('Pallet not exists');
END IF;
- IF NOT (SELECT TRUE FROM expeditionPallet WHERE id = vPalletFk LIMIT 1) THEN
- CALL util.throw('Pallet not exists');
+ IF NOT EXISTS (SELECT id FROM expedition WHERE id = vExpeditionFk) THEN
+ CALL util.throw('Expedition not exists');
END IF;
REPLACE expeditionScan(expeditionFk, palletFk)
diff --git a/db/routines/vn/procedures/inventoryMake.sql b/db/routines/vn/procedures/inventoryMake.sql
index 65dceef3d..316083db0 100644
--- a/db/routines/vn/procedures/inventoryMake.sql
+++ b/db/routines/vn/procedures/inventoryMake.sql
@@ -137,7 +137,7 @@ BEGIN
JOIN travel tr ON tr.id = e.travelFk
WHERE tr.warehouseInFk = vWarehouseFk
AND tr.landed BETWEEN vDateLastInventory AND vDateYesterday
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
GROUP BY b.itemFk;
-- Transfers
@@ -150,7 +150,7 @@ BEGIN
JOIN travel tr ON tr.id = e.travelFk
WHERE tr.warehouseOutFk = vWarehouseFk
AND tr.shipped BETWEEN vDateLastInventory AND vDateYesterday
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
GROUP BY b.itemFk
) sub
ON DUPLICATE KEY UPDATE quantity = IFNULL(quantity, 0) + sub.quantityOut;
diff --git a/db/routines/vn/procedures/itemShelvingSale_setQuantity.sql b/db/routines/vn/procedures/itemShelvingSale_setQuantity.sql
index f141762b5..1975180d1 100644
--- a/db/routines/vn/procedures/itemShelvingSale_setQuantity.sql
+++ b/db/routines/vn/procedures/itemShelvingSale_setQuantity.sql
@@ -48,7 +48,7 @@ BEGIN
iss.itemShelvingFk,
SUM(IFNULL(iss.quantity,0)),
IF(sgd.id, 'PREVIOUS_PREPARATION', 'PREPARED'),
- ish.shelvingFk
+ sh.code
INTO vItemFk,
vSaleFk,
vItemShelvingFk,
@@ -58,7 +58,8 @@ BEGIN
FROM itemShelvingSale iss
JOIN sale s ON s.id = iss.saleFk
JOIN itemShelving ish ON ish.id = iss.itemShelvingFk
- LEFT JOIN vn.saleGroupDetail sgd ON sgd.saleFk = iss.saleFk
+ LEFT JOIN saleGroupDetail sgd ON sgd.saleFk = iss.saleFk
+ LEFT JOIN shelving sh ON sh.id = ish.shelvingFk
WHERE iss.id = vItemShelvingSaleFk
AND NOT iss.isPicked;
diff --git a/db/routines/vn/procedures/itemShelving_get.sql b/db/routines/vn/procedures/itemShelving_get.sql
index 93022c2af..0038257c2 100644
--- a/db/routines/vn/procedures/itemShelving_get.sql
+++ b/db/routines/vn/procedures/itemShelving_get.sql
@@ -21,7 +21,8 @@ BEGIN
ish.isChecked,
ic.url,
ish.available,
- ish.buyFk
+ ish.buyFk,
+ ish.shelvingFk
FROM itemShelving ish
JOIN item i ON i.id = ish.itemFk
JOIN shelving s ON s.id = ish.shelvingFk
diff --git a/db/routines/vn/procedures/item_getBalance.sql b/db/routines/vn/procedures/item_getBalance.sql
index 260f0a527..c4974491c 100644
--- a/db/routines/vn/procedures/item_getBalance.sql
+++ b/db/routines/vn/procedures/item_getBalance.sql
@@ -59,7 +59,7 @@ BEGIN
AND (s.id <> vSupplierInventoryFk OR vDated IS NULL)
AND b.itemFk = vItemFk
AND NOT e.isExcludedFromAvailable
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
),
entriesOut AS (
SELECT 'entry',
@@ -95,7 +95,7 @@ BEGIN
AND b.itemFk = vItemFk
AND NOT e.isExcludedFromAvailable
AND NOT w.isFeedStock
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
),
sales AS (
WITH itemSales AS (
diff --git a/db/routines/vn/procedures/item_getMinacum.sql b/db/routines/vn/procedures/item_getMinacum.sql
index 8a42bd737..e3c915820 100644
--- a/db/routines/vn/procedures/item_getMinacum.sql
+++ b/db/routines/vn/procedures/item_getMinacum.sql
@@ -63,7 +63,7 @@ BEGIN
AND NOT e.isExcludedFromAvailable
AND b.quantity <> 0
AND (vItemFk IS NULL OR b.itemFk = vItemFk)
- AND NOT t.daysInForward
+ AND NOT t.isRaid
UNION ALL
SELECT r.itemFk,
r.shipment,
diff --git a/db/routines/vn/procedures/item_multipleBuyByDate.sql b/db/routines/vn/procedures/item_multipleBuyByDate.sql
index 7bd809312..04d3f1855 100644
--- a/db/routines/vn/procedures/item_multipleBuyByDate.sql
+++ b/db/routines/vn/procedures/item_multipleBuyByDate.sql
@@ -30,7 +30,7 @@ BEGIN
AND NOT s.name = 'INVENTARIO'
AND (vWarehouseFk IS NULL OR t.warehouseInFk = vWarehouseFk)
AND w.isComparative
- AND NOT t.daysInForward
+ AND NOT t.isRaid
GROUP BY i.id;
UPDATE tmp.itemInventory y
diff --git a/db/routines/vn/procedures/item_valuateInventory.sql b/db/routines/vn/procedures/item_valuateInventory.sql
index b6d687960..7e420be27 100644
--- a/db/routines/vn/procedures/item_valuateInventory.sql
+++ b/db/routines/vn/procedures/item_valuateInventory.sql
@@ -109,7 +109,7 @@ BEGIN
JOIN warehouse w ON w.id = tr.warehouseInFk
WHERE tr.landed BETWEEN vInventoried AND vDateDayEnd
AND IF(tr.landed = util.VN_CURDATE(), tr.isReceived, TRUE)
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
AND w.valuatedInventory
AND t.isInventory
AND e.supplierFk <> vInventorySupplierFk
@@ -131,7 +131,7 @@ BEGIN
JOIN itemCategory ic ON ic.id = t.categoryFk
JOIN warehouse w ON w.id = tr.warehouseOutFk
WHERE tr.shipped BETWEEN vInventoried AND vDateDayEnd
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
AND w.valuatedInventory
AND t.isInventory
AND (t.id = vItemTypeFk OR vItemTypeFk IS NULL)
@@ -196,7 +196,7 @@ BEGIN
JOIN warehouse wIn ON wIn.id = tr.warehouseInFk
JOIN warehouse wOut ON wOut.id = tr.warehouseOutFk
WHERE vDated >= tr.shipped AND vDated < tr.landed
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
AND wIn.valuatedInventory
AND t.isInventory
AND e.isConfirmed
diff --git a/db/routines/vn/procedures/multipleInventory.sql b/db/routines/vn/procedures/multipleInventory.sql
index 6b26e456f..ee18bbe96 100644
--- a/db/routines/vn/procedures/multipleInventory.sql
+++ b/db/routines/vn/procedures/multipleInventory.sql
@@ -60,7 +60,7 @@ proc: BEGIN
AND IFNULL(vWarehouseFk, t.warehouseInFk) = t.warehouseInFk
AND w.isComparative
AND NOT e.isExcludedFromAvailable
- AND NOT t.daysInForward
+ AND NOT t.isRaid
UNION ALL
SELECT b.itemFk, - b.quantity
FROM buy b
@@ -71,7 +71,7 @@ proc: BEGIN
AND IFNULL(vWarehouseFk, t.warehouseOutFk) = t.warehouseOutFk
AND w.isComparative
AND NOT e.isExcludedFromAvailable
- AND NOT t.daysInForward
+ AND NOT t.isRaid
) sub
GROUP BY itemFk;
@@ -121,7 +121,7 @@ proc: BEGIN
AND IFNULL(vWarehouseFk, t.warehouseInFk) = t.warehouseInFk
AND w.isComparative
AND NOT e.isExcludedFromAvailable
- AND NOT t.daysInForward
+ AND NOT t.isRaid
UNION ALL
SELECT b.itemFk, t.shipped, - b.quantity
FROM buy b
@@ -132,7 +132,7 @@ proc: BEGIN
AND IFNULL(vWarehouseFk, t.warehouseOutFk) = t.warehouseOutFk
AND w.isComparative
AND NOT e.isExcludedFromAvailable
- AND NOT t.daysInForward
+ AND NOT t.isRaid
) sub
GROUP BY sub.itemFk, sub.dated;
diff --git a/db/routines/vn/procedures/ticket_splitItemPackingType.sql b/db/routines/vn/procedures/ticket_splitItemPackingType.sql
index 0ee865af5..31e0c24e7 100644
--- a/db/routines/vn/procedures/ticket_splitItemPackingType.sql
+++ b/db/routines/vn/procedures/ticket_splitItemPackingType.sql
@@ -5,122 +5,122 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_splitItemPacki
)
BEGIN
/**
- * Clona y reparte las ventas de un ticket en funcion del tipo de empaquetado.
- * Respeta el id inicial para el tipo propuesto.
+ * Separa en diferentes tickets según el tipo de empaquetado
+ * El ticket original conserva las líneas del tipo de empaquetado especificado
+ * Las líneas sin tipo de empaquetado se asignan al ticket del tipo por defecto.
*
- * @param vSelf Id ticket
- * @param vOriginalItemPackingTypeFk Tipo para el que se reserva el número de ticket original
+ * @param vSelf Id del ticket original
+ * @param vOriginalItemPackingTypeFk Tipo de empaquetado a mantener en el ticket original
* @return table tmp.ticketIPT(ticketFk, itemPackingTypeFk)
*/
- DECLARE vItemPackingTypeFk VARCHAR(1) DEFAULT 'H';
+ DECLARE vIsDone BOOLEAN DEFAULT FALSE;
+ DECLARE vCurrentPackingType VARCHAR(1);
+ DECLARE vDefaultPackingType VARCHAR(1);
+ DECLARE vHasOriginalPackingType BOOLEAN;
DECLARE vNewTicketFk INT;
- DECLARE vPackingTypesToSplit INT;
- DECLARE vDone INT DEFAULT FALSE;
+ DECLARE vTicketFk INT;
- DECLARE vSaleGroup CURSOR FOR
- SELECT itemPackingTypeFk
- FROM tSaleGroup
- WHERE itemPackingTypeFk IS NOT NULL
- ORDER BY (itemPackingTypeFk = vOriginalItemPackingTypeFk) DESC;
+ DECLARE vItemPackingTypes CURSOR FOR
+ SELECT DISTINCT itemPackingTypeFk FROM tSalesToMove;
- DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vIsDone = TRUE;
+
+ DECLARE EXIT HANDLER FOR SQLEXCEPTION
+ BEGIN
+ ROLLBACK;
+ RESIGNAL;
+ END;
+
+ CREATE OR REPLACE TEMPORARY TABLE tSalesToMove (
+ ticketFk INT,
+ saleFk INT,
+ itemPackingTypeFk VARCHAR(1)
+ ) ENGINE=MEMORY;
+
+ SELECT COALESCE(MAX(ic.defaultPackingTypeFk), MAX(i.itemPackingTypeFk)) INTO vDefaultPackingType
+ FROM vn.sale s
+ JOIN item i ON i.id = s.itemFk
+ LEFT JOIN itemConfig ic ON ic.defaultPackingTypeFk = i.itemPackingTypeFk
+ WHERE s.ticketFk = vSelf
+ GROUP BY s.ticketFk;
+
+ SELECT EXISTS (
+ SELECT TRUE
+ FROM sale s
+ JOIN item i ON i.id = s.itemFk
+ WHERE s.ticketFk = vSelf
+ AND i.itemPackingTypeFk = vOriginalItemPackingTypeFk
+ ) INTO vHasOriginalPackingType;
+
+ IF vOriginalItemPackingTypeFk IS NULL OR NOT vHasOriginalPackingType THEN
+ SET vOriginalItemPackingTypeFk = vDefaultPackingType;
+ END IF;
START TRANSACTION;
- SELECT id
- FROM sale
- WHERE ticketFk = vSelf
- AND NOT quantity
+ SELECT t.id INTO vTicketFk
+ FROM ticket t
+ JOIN sale s ON s.id = t.id
+ WHERE t.id = vSelf
FOR UPDATE;
- DELETE FROM sale
- WHERE NOT quantity
- AND ticketFk = vSelf;
-
- CREATE OR REPLACE TEMPORARY TABLE tSale
- (PRIMARY KEY (id))
- ENGINE = MEMORY
- SELECT s.id, i.itemPackingTypeFk, IFNULL(sv.litros, 0) litros
+ INSERT INTO tSalesToMove (saleFk, itemPackingTypeFk)
+ SELECT s.id, i.itemPackingTypeFk
FROM sale s
JOIN item i ON i.id = s.itemFk
- LEFT JOIN saleVolume sv ON sv.saleFk = s.id
- WHERE s.ticketFk = vSelf;
+ WHERE s.ticketFk = vSelf
+ AND i.itemPackingTypeFk <> vOriginalItemPackingTypeFk;
- CREATE OR REPLACE TEMPORARY TABLE tSaleGroup
- ENGINE = MEMORY
- SELECT itemPackingTypeFk, SUM(litros) totalLitros
- FROM tSale
- GROUP BY itemPackingTypeFk;
+ OPEN vItemPackingTypes;
+ l: LOOP
+ SET vIsDone = FALSE;
+ FETCH vItemPackingTypes INTO vCurrentPackingType;
- SELECT COUNT(*) INTO vPackingTypesToSplit
- FROM tSaleGroup
- WHERE itemPackingTypeFk IS NOT NULL;
+ IF vIsDone THEN
+ LEAVE l;
+ END IF;
- CREATE OR REPLACE TEMPORARY TABLE tmp.ticketIPT(
- ticketFk INT,
- itemPackingTypeFk VARCHAR(1)
- ) ENGINE = MEMORY;
+ CALL ticket_Clone(vSelf, vNewTicketFk);
- CASE vPackingTypesToSplit
- WHEN 0 THEN
- INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
- VALUES(vSelf, vItemPackingTypeFk);
- WHEN 1 THEN
- INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
- SELECT vSelf, itemPackingTypeFk
- FROM tSaleGroup
- WHERE itemPackingTypeFk IS NOT NULL;
- ELSE
- OPEN vSaleGroup;
- FETCH vSaleGroup INTO vItemPackingTypeFk;
+ SELECT id INTO vTicketFk
+ FROM ticket t
+ WHERE t.id = vNewTicketFk
+ FOR UPDATE;
- INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
- VALUES(vSelf, vItemPackingTypeFk);
+ UPDATE tSalesToMove
+ SET ticketFk = vNewTicketFk
+ WHERE itemPackingTypeFk = vCurrentPackingType;
- l: LOOP
- SET vDone = FALSE;
- FETCH vSaleGroup INTO vItemPackingTypeFk;
+ IF vCurrentPackingType = vDefaultPackingType THEN
+ INSERT INTO tSalesToMove (ticketFk, saleFk, itemPackingTypeFk)
+ SELECT vNewTicketFk, s.id, i.itemPackingTypeFk
+ FROM sale s
+ JOIN item i ON i.id = s.itemFk
+ WHERE s.ticketFk = vSelf
+ AND i.itemPackingTypeFk IS NULL;
+ END IF;
- IF vDone THEN
- LEAVE l;
- END IF;
+ END LOOP;
+ CLOSE vItemPackingTypes;
- CALL ticket_Clone(vSelf, vNewTicketFk);
+ UPDATE sale s
+ JOIN tSalesToMove t ON t.saleFk = s.id
+ SET s.ticketFk = t.ticketFk;
- INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
- VALUES(vNewTicketFk, vItemPackingTypeFk);
- END LOOP;
-
- CLOSE vSaleGroup;
-
- SELECT s.id
- FROM sale s
- JOIN tSale ts ON ts.id = s.id
- JOIN tmp.ticketIPT t ON t.itemPackingTypeFk = ts.itemPackingTypeFk
- FOR UPDATE;
-
- UPDATE sale s
- JOIN tSale ts ON ts.id = s.id
- JOIN tmp.ticketIPT t ON t.itemPackingTypeFk = ts.itemPackingTypeFk
- SET s.ticketFk = t.ticketFk;
-
- SELECT itemPackingTypeFk INTO vItemPackingTypeFk
- FROM tSaleGroup sg
- WHERE sg.itemPackingTypeFk IS NOT NULL
- ORDER BY sg.itemPackingTypeFk
- LIMIT 1;
-
- UPDATE sale s
- JOIN tSale ts ON ts.id = s.id
- JOIN tmp.ticketIPT t ON t.itemPackingTypeFk = vItemPackingTypeFk
- SET s.ticketFk = t.ticketFk
- WHERE ts.itemPackingTypeFk IS NULL;
- END CASE;
+ CREATE OR REPLACE TEMPORARY TABLE tmp.ticketIPT
+ ENGINE=MEMORY
+ SELECT s.ticketFk, MAX(i.itemPackingTypeFk) itemPackingTypeFk
+ FROM sale s
+ JOIN item i ON i.id = s.itemFk
+ WHERE s.ticketFk = vSelf
+ GROUP BY s.ticketFk
+ UNION
+ SELECT ticketFk, MAX(itemPackingTypeFk)
+ FROM tSalesToMove
+ GROUP BY ticketFk;
COMMIT;
- DROP TEMPORARY TABLE
- tSale,
- tSaleGroup;
+ DROP TEMPORARY TABLE tSalesToMove;
END$$
DELIMITER ;
diff --git a/db/routines/vn/procedures/travelVolume_get.sql b/db/routines/vn/procedures/travelVolume_get.sql
index bb98cacdf..ab24a6395 100644
--- a/db/routines/vn/procedures/travelVolume_get.sql
+++ b/db/routines/vn/procedures/travelVolume_get.sql
@@ -10,7 +10,7 @@ BEGIN
JOIN vn.entry e ON e.travelFk = tr.id
JOIN vn.buy b ON b.entryFk = e.id
WHERE tr.landed BETWEEN vFromDated AND vToDated
- AND NOT tr.daysInForward
+ AND NOT tr.isRaid
AND tr.warehouseInFk = vWarehouseFk
GROUP BY tr.landed , a.name ;
END$$
diff --git a/db/routines/vn/procedures/travel_checkRaid.sql b/db/routines/vn/procedures/travel_checkRaid.sql
new file mode 100644
index 000000000..64f3355e2
--- /dev/null
+++ b/db/routines/vn/procedures/travel_checkRaid.sql
@@ -0,0 +1,17 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`travel_checkRaid`(
+ vIsRaid BOOL,
+ vDaysInForward INT
+)
+BEGIN
+/**
+ * Check if the values of isRaid and daysInforward are correct
+ *
+ * @param vIsRaid idRaid value
+ * @param vDaysInForward daysInForward value
+ */
+ IF (NOT vIsRaid AND vDaysInForward IS NOT NULL) OR (vIsRaid AND vDaysInForward IS NULL) THEN
+ CALL util.throw('The raid information is not correct');
+ END IF;
+END$$
+DELIMITER ;
diff --git a/db/routines/vn/procedures/travel_moveRaids.sql b/db/routines/vn/procedures/travel_moveRaids.sql
index aa554a1a0..cf0fce23c 100644
--- a/db/routines/vn/procedures/travel_moveRaids.sql
+++ b/db/routines/vn/procedures/travel_moveRaids.sql
@@ -3,7 +3,7 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`travel_moveRaids`()
BEGIN
/**
* Desplaza los travels en el futuro y avisa a los compradores
- *
+ *
*/
DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vBuyerEmail VARCHAR(40);
@@ -11,21 +11,21 @@ BEGIN
DECLARE vMailBody TEXT DEFAULT '';
DECLARE vCur CURSOR FOR
- SELECT GROUP_CONCAT(DISTINCT
- CONCAT('https://salix.verdnatura.es/#!/travel/',
- ttm.travelFk,
+ SELECT GROUP_CONCAT(DISTINCT
+ CONCAT('https://salix.verdnatura.es/#!/travel/',
+ ttm.travelFk,
'/summary ')
ORDER BY ttm.travelFk SEPARATOR '\n\r') travelLink,
CONCAT(u.name, '@verdnatura.es') buyerEmail
FROM tTravelToMove ttm
- JOIN entry e ON e.travelFk = ttm.travelFk
+ JOIN entry e ON e.travelFk = ttm.travelFk
JOIN buy b ON b.entryFk = e.id
JOIN item i ON i.id = b.itemFk
JOIN itemType it ON it.id = i.typeFk
JOIN account.user u ON u.id = it.workerFk
GROUP BY u.name;
- DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
@@ -34,10 +34,11 @@ BEGIN
END;
CREATE OR REPLACE TEMPORARY TABLE tTravelToMove
- SELECT id travelFk,
+ SELECT id travelFk,
util.VN_CURDATE() + INTERVAL daysInForward DAY newLanded
FROM travel
- WHERE daysInForward;
+ WHERE isRaid
+ AND daysInForward;
START TRANSACTION;
diff --git a/db/routines/vn/procedures/workerTimeControl_check.sql b/db/routines/vn/procedures/workerTimeControl_check.sql
deleted file mode 100644
index 30cf5c639..000000000
--- a/db/routines/vn/procedures/workerTimeControl_check.sql
+++ /dev/null
@@ -1,168 +0,0 @@
-DELIMITER $$
-CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`workerTimeControl_check`(vUserFk INT, vDated DATE,vTabletFk VARCHAR(100))
-proc: BEGIN
-/**
- * Verifica si el empleado puede fichar en el momento actual, si puede fichar llama a workerTimeControlAdd
- *
- * @param vUserFk Identificador del trabajador
- * @return Retorna si encuentra un problema 'odd','maxTimeWork','breakDay','breakWeek' ;
- * En caso de tener algun problema retorna el primero que encuentra
- */
- DECLARE vLastIn DATETIME ;
- DECLARE vLastOut DATETIME ;
- DECLARE vDayWorkMax INT;
- DECLARE vDayBreak INT;
- DECLARE vWeekBreak INT ;
- DECLARE vWeekScope INT;
- DECLARE vDayStayMax INT;
- DECLARE vProblem VARCHAR(20) DEFAULT NULL;
- DECLARE vTimedWorked INT;
- DECLARE vCalendarStateType VARCHAR(20) DEFAULT NULL;
- DECLARE vDepartmentFk INT;
- DECLARE vTo VARCHAR(50) DEFAULT NULL;
- DECLARE vUserName VARCHAR(50) DEFAULT NULL;
- DECLARE vBody VARCHAR(255) DEFAULT NULL;
-
- SELECT dayBreak, weekBreak, weekScope, dayWorkMax, dayStayMax
- INTO vDayBreak, vWeekBreak, vWeekScope, vDayWorkMax, vDayStayMax
- FROM workerTimeControlParams;
-
- SELECT MAX(timed) INTO vLastIn
- FROM workerTimeControl
- WHERE userFk = vUserFk
- AND direction = 'in';
-
- SELECT MAX(timed) INTO vLastOut
- FROM workerTimeControl
- WHERE userFk = vUserFk
- AND direction = 'out';
-
- SELECT CONCAT(u.name,'@verdnatura.es') INTO vTo
- FROM account.user u
- WHERE u.id = (SELECT bossFk FROM worker WHERE id = vUserFk);
-
- SELECT CONCAT(firstName,' ',lastName) INTO vUserName
- FROM worker w
- WHERE w.id = vUserFk;
-
-
- IF UNIX_TIMESTAMP(util.VN_NOW()) - UNIX_TIMESTAMP(vLastIn) > vDayStayMax THEN -- NUEVA JORNADA
-
- -- VERIFICAR DESCANSO DIARIO
- IF UNIX_TIMESTAMP(util.VN_NOW()) - UNIX_TIMESTAMP(vLastOut) < vDayBreak THEN
- SELECT "Descansos 12 h" AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Descansos 12 h") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
- END IF;
-
- -- VERIFICAR FICHADAS IMPARES DEL ÚLTIMO DÍA QUE SE FICHÓ
- IF (SELECT MOD(COUNT(*),2) -- <>0
- FROM workerTimeControl
- WHERE userFk = vUserFk
- AND timed >= vLastIn
- ) THEN
- SELECT "Dias con fichadas impares" AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Dias con fichadas impares") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
- END IF;
-
- -- VERIFICAR VACACIONES
- SELECT at2.name INTO vCalendarStateType
- FROM calendar c
- JOIN business b ON b.id = c.businessFk
- JOIN absenceType at2 ON at2.id = c.dayOffTypeFk
- WHERE c.dated = util.VN_CURDATE()
- AND at2.isAllowedToWork = FALSE
- AND b.workerFk = vUserFk
- LIMIT 1;
-
- IF(LENGTH(vCalendarStateType)) THEN
- SELECT vCalendarStateType AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Vacaciones") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
-
- END IF;
-
- -- VERIFICAR CONTRATO EN VIGOR
- IF (SELECT COUNT(*)
- FROM business b
- WHERE b.workerFk = vUserFk
- AND b.started <= vDated
- AND IFNULL(b.ended, vDated) >= vDated
- ) = 0 THEN
- SELECT "No hay un contrato en vigor" AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"No hay un contrato en vigor") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
-
- END IF;
-
- -- VERIFICAR DESCANSO SEMANAL
- SET @vHasBreakWeek:= FALSE;
- SET @vLastTimed:= UNIX_TIMESTAMP((util.VN_NOW() - INTERVAL vWeekScope SECOND));
-
- DROP TEMPORARY TABLE IF EXISTS tmp.trash;
- CREATE TEMPORARY TABLE tmp.trash
- SELECT IF(vWeekBreak-(UNIX_TIMESTAMP(timed)-@vLastTimed) <= 0, @vHasBreakWeek:=TRUE, TRUE) alias,
- @vLastTimed:= UNIX_TIMESTAMP(timed)
- FROM workerTimeControl
- WHERE timed>= (util.VN_NOW() - INTERVAL vWeekScope SECOND)
- AND userFk= vUserFk
- AND direction IN ('in','out')
- ORDER BY timed ASC;
-
- IF UNIX_TIMESTAMP(util.VN_NOW()) - UNIX_TIMESTAMP(vLastOut) < vWeekBreak AND @vHasBreakWeek = FALSE THEN -- REVISA SI EL DESCANSO SE HA REALIZADO DESPUÉS DE LA ÚLTIMA FICHADA
- SELECT "Descansos 36 h" AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Descansos 36 h") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
- END IF;
-
- DROP TEMPORARY TABLE tmp.trash;
-
- ELSE -- DIA ACTUAL
-
- -- VERIFICA QUE EL TIEMPO EFECTIVO NO SUPERE EL MÁXIMO
- SELECT IFNULL(SUM(if( mod(wtc.order,2)=1, -UNIX_TIMESTAMP(timed), UNIX_TIMESTAMP(timed))),0) - IF( MOD(COUNT(*),2), UNIX_TIMESTAMP(util.VN_NOW()), 0) INTO vTimedWorked
- FROM workerTimeControl wtc
- WHERE userFk = vUserFk
- AND timed >= vLastIn
- ORDER BY timed;
-
- IF vTimedWorked > vDayWorkMax THEN
- SELECT "Jornadas" AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No ha podido fichar por el siguiente problema: ',"Jornadas") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
- END IF;
-
- END IF;
-
- -- VERIFICAR DEPARTAMENTO
- /* IF vTabletFk IS NOT NULL THEN
- SELECT wtcu.departmentFk INTO vDepartmentFk
- FROM workerTimeControlUserInfo wtcu
- WHERE wtcu.userFk = vUserFk;
- IF (SELECT COUNT(td.tabletFk)
- FROM tabletDepartment td
- WHERE td.tabletFk = vTabletFk AND td.departmentFk = vDepartmentFk
- ) = 0 THEN
- SELECT "No perteneces a este departamento." AS problem;
- -- ENVIAMOS CORREO AL BOSSFK
- SELECT CONCAT(vUserName,' No a podido fichar por el siguiente problema: ',"No perteneces a este departamento.") INTO vBody;
- CALL mail_insert(vTo,vTo,'error al fichar',vBody);
- LEAVE proc;
- END IF;
- END IF;*/
-
-END$$
-DELIMITER ;
diff --git a/db/routines/vn/triggers/entry_beforeUpdate.sql b/db/routines/vn/triggers/entry_beforeUpdate.sql
index 3b999012f..4f2c5ed4a 100644
--- a/db/routines/vn/triggers/entry_beforeUpdate.sql
+++ b/db/routines/vn/triggers/entry_beforeUpdate.sql
@@ -3,7 +3,7 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`entry_beforeUpdate`
BEFORE UPDATE ON `entry`
FOR EACH ROW
BEGIN
- DECLARE vDaysInForward INT;
+ DECLARE vIsRaid BOOL;
DECLARE vPrintedCount INT;
DECLARE vHasDistinctWarehouses BOOL;
DECLARE vTotalBuy INT;
@@ -38,10 +38,10 @@ BEGIN
CALL travel_throwAwb(NEW.travelFk);
END IF;
- SELECT daysInForward INTO vDaysInForward
+ SELECT t.isRaid INTO vIsRaid
FROM travel t
JOIN entry e ON e.travelFk = t.id
- WHERE entryFk = NEW.id;
+ WHERE e.id = NEW.id;
SELECT NOT (o.warehouseInFk <=> n.warehouseInFk)
OR NOT (o.warehouseOutFk <=> n.warehouseOutFk)
@@ -50,7 +50,7 @@ BEGIN
WHERE o.id = OLD.travelFk
AND n.id = NEW.travelFk;
- IF vDaysInForward AND vHasDistinctWarehouses THEN
+ IF vIsRaid AND vHasDistinctWarehouses THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'A cloned entry cannot be moved to a travel with different warehouses';
END IF;
diff --git a/db/routines/vn/triggers/route_afterUpdate.sql b/db/routines/vn/triggers/route_afterUpdate.sql
index ec205090e..447608acc 100644
--- a/db/routines/vn/triggers/route_afterUpdate.sql
+++ b/db/routines/vn/triggers/route_afterUpdate.sql
@@ -2,18 +2,18 @@ DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`route_afterUpdate`
AFTER UPDATE ON `route`
FOR EACH ROW
-BEGIN
+BEGIN
IF IFNULL(NEW.gestdocFk,0) <> IFNULL(OLD.gestdocFk,0) AND NEW.gestdocFk > 0 THEN
-- JGF 09/09/14 cuando se añade un gestdoc a una ruta, se le asigna automagicamente a todos sus Tickets
-
+
-- Inserta el gestdoc en todos los tickets de la ruta
INSERT INTO ticketDms(ticketFk,dmsFk)
SELECT id, NEW.gestdocFk FROM ticket WHERE routeFk = NEW.id
ON DUPLICATE KEY UPDATE dmsFk = NEW.gestdocFk;
- -- Update del firmado
- UPDATE ticket t
- JOIN ticketDms tg ON t.id = tg.ticketFk
+ -- Update del firmado
+ UPDATE ticket t
+ JOIN ticketDms tg ON t.id = tg.ticketFk
SET isSigned = 1 WHERE t.routeFk = NEW.id;
END IF;
@@ -21,7 +21,8 @@ BEGIN
OR !(NEW.kmEnd <=> OLD.kmEnd)
OR !(NEW.workerFk <=> OLD.workerFk)
OR !(NEW.m3 <=> OLD.m3)
- OR !(NEW.agencyModeFk <=> OLD.agencyModeFk)THEN
+ OR !(NEW.agencyModeFk <=> OLD.agencyModeFk)
+ OR !(NEW.vehicleFk <=> OLD.vehicleFk)THEN
CALL route_calcCommission(NEW.id);
END IF;
diff --git a/db/routines/vn/triggers/town_beforeUpdate.sql b/db/routines/vn/triggers/town_beforeUpdate.sql
deleted file mode 100644
index fc1410d5c..000000000
--- a/db/routines/vn/triggers/town_beforeUpdate.sql
+++ /dev/null
@@ -1,10 +0,0 @@
-DELIMITER $$
-CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`town_beforeUpdate`
- BEFORE UPDATE ON `town`
- FOR EACH ROW
-BEGIN
- -- IF !(OLD.geoFk <=> NEW.geoFk) THEN
- -- CALL zoneGeo_throwNotEditable;
- -- END IF;
-END$$
-DELIMITER ;
diff --git a/db/routines/vn/triggers/travel_beforeInsert.sql b/db/routines/vn/triggers/travel_beforeInsert.sql
index 4563c9a81..5356ed537 100644
--- a/db/routines/vn/triggers/travel_beforeInsert.sql
+++ b/db/routines/vn/triggers/travel_beforeInsert.sql
@@ -8,7 +8,11 @@ BEGIN
CALL travel_checkDates(NEW.shipped, NEW.landed);
CALL travel_checkWarehouseIsFeedStock(NEW.warehouseInFk);
-
+
+ IF NEW.isRaid IS NOT NULL OR NEW.daysInForward IS NOT NULL THEN
+ CALL travel_checkRaid(NEW.isRaid, NEW.daysInForward);
+ END IF;
+
IF NEW.awbFk IS NOT NULL THEN
CALL travel_throwAwb(NEW.id);
END IF;
diff --git a/db/routines/vn/triggers/travel_beforeUpdate.sql b/db/routines/vn/triggers/travel_beforeUpdate.sql
index 33578fea1..5a27b43b4 100644
--- a/db/routines/vn/triggers/travel_beforeUpdate.sql
+++ b/db/routines/vn/triggers/travel_beforeUpdate.sql
@@ -7,7 +7,7 @@ BEGIN
SET NEW.editorFk = account.myUser_getId();
- IF NOT (NEW.landed <=> OLD.landed)
+ IF NOT (NEW.landed <=> OLD.landed)
OR NOT (NEW.shipped <=> OLD.shipped) THEN
CALL travel_checkDates(NEW.shipped, NEW.landed);
END IF;
@@ -20,19 +20,23 @@ BEGIN
CALL travel_checkWarehouseIsFeedStock(NEW.warehouseInFk);
END IF;
+ IF NOT (NEW.isRaid <=> OLD.isRaid) OR NOT (NEW.daysInForward <=> OLD.daysInForward) THEN
+ CALL travel_checkRaid(NEW.isRaid, NEW.daysInForward);
+ END IF;
+
IF NOT (NEW.awbFk <=> OLD.awbFk)THEN
- SELECT COUNT(*) INTO vHasAnyInvoiceBooked
+ SELECT COUNT(*) INTO vHasAnyInvoiceBooked
FROM travel t
- JOIN entry e ON e.travelFk = t.id
- JOIN invoiceIn ii ON ii.id = e.invoiceInFk
+ JOIN entry e ON e.travelFk = t.id
+ JOIN invoiceIn ii ON ii.id = e.invoiceInFk
WHERE t.id = NEW.id
- AND ii.isBooked;
-
+ AND ii.isBooked;
+
IF vHasAnyInvoiceBooked THEN
CALL util.throw('The travel has entries with booked invoices');
END IF;
- END IF;
-
+ END IF;
+
IF (NOT(NEW.awbFk <=> OLD.awbFk)) AND NEW.awbFk IS NOT NULL THEN
CALL travel_throwAwb(NEW.id);
END IF;
diff --git a/db/routines/vn/views/itemEntryIn.sql b/db/routines/vn/views/itemEntryIn.sql
index 6196e9396..60af585f2 100644
--- a/db/routines/vn/views/itemEntryIn.sql
+++ b/db/routines/vn/views/itemEntryIn.sql
@@ -6,7 +6,7 @@ AS SELECT `t`.`warehouseInFk` AS `warehouseInFk`,
`b`.`itemFk` AS `itemFk`,
`b`.`quantity` AS `quantity`,
`t`.`isReceived` AS `isReceived`,
- `t`.`daysInForward` AS `isVirtualStock`,
+ `t`.`isRaid` AS `isVirtualStock`,
`e`.`id` AS `entryFk`
FROM (
(
diff --git a/db/routines/vn/views/itemEntryOut.sql b/db/routines/vn/views/itemEntryOut.sql
index f18116e61..4793ddbb8 100644
--- a/db/routines/vn/views/itemEntryOut.sql
+++ b/db/routines/vn/views/itemEntryOut.sql
@@ -15,5 +15,5 @@ FROM (
JOIN `vn`.`travel` `t` ON(`e`.`travelFk` = `t`.`id`)
)
WHERE `e`.`isExcludedFromAvailable` = 0
- AND NOT `t`.`daysInForward`
+ AND NOT `t`.`isRaid`
AND `b`.`quantity` <> 0
diff --git a/db/routines/vn/views/lastPurchases.sql b/db/routines/vn/views/lastPurchases.sql
index 9dc5ec898..3dcaf8c1d 100644
--- a/db/routines/vn/views/lastPurchases.sql
+++ b/db/routines/vn/views/lastPurchases.sql
@@ -31,5 +31,5 @@ FROM (
LEFT JOIN `edi`.`ekt` `ek` ON(`ek`.`id` = `b`.`ektFk`)
)
WHERE `tr`.`landed` BETWEEN `util`.`yesterday`() AND `util`.`tomorrow`()
- AND NOT `tr`.`daysInForward`
+ AND NOT `tr`.`isRaid`
AND `b`.`stickers` > 0
diff --git a/db/routines/vn2008/views/entrySource.sql b/db/routines/vn2008/views/entrySource.sql
index 9c0c0f6ed..732603617 100644
--- a/db/routines/vn2008/views/entrySource.sql
+++ b/db/routines/vn2008/views/entrySource.sql
@@ -8,6 +8,7 @@ AS SELECT `e`.`gestDocFk` AS `gestdoc_id`,
`e`.`isExcludedFromAvailable` AS `Inventario`,
`e`.`isConfirmed` AS `Confirmada`,
`e`.`isOrdered` AS `Pedida`,
+ `tr`.`isRaid` AS `isRaid`,
`tr`.`daysInForward` AS `daysInForward`,
`e`.`evaNotes` AS `notas`,
`e`.`supplierFk` AS `Id_Proveedor`,
diff --git a/db/routines/vn2008/views/travel.sql b/db/routines/vn2008/views/travel.sql
index 0e1f5acb2..38dfd40ea 100644
--- a/db/routines/vn2008/views/travel.sql
+++ b/db/routines/vn2008/views/travel.sql
@@ -18,5 +18,6 @@ AS SELECT `t`.`id` AS `id`,
`t`.`totalEntries` AS `totalEntries`,
`t`.`appointment` AS `appointment`,
`t`.`awbFk` AS `awbFk`,
+ `t`.`isRaid` AS `isRaid`,
`t`.`daysInForward` AS `daysInForward`
FROM `vn`.`travel` `t`
diff --git a/db/routines/vn2008/views/v_compres.sql b/db/routines/vn2008/views/v_compres.sql
index 324e459f6..633feb471 100644
--- a/db/routines/vn2008/views/v_compres.sql
+++ b/db/routines/vn2008/views/v_compres.sql
@@ -86,4 +86,4 @@ FROM (
)
WHERE NOT `W_IN`.`isFeedStock`
AND NOT `E`.`Inventario`
- AND NOT `TR`.`daysInForward`
+ AND NOT `TR`.`isRaid`
diff --git a/db/versions/11312-navyAralia/00-firstScript.sql b/db/versions/11312-navyAralia/00-firstScript.sql
new file mode 100644
index 000000000..452b1feed
--- /dev/null
+++ b/db/versions/11312-navyAralia/00-firstScript.sql
@@ -0,0 +1,23 @@
+ALTER TABLE vn.workerRelatives DROP FOREIGN KEY workerRelatives_disabilityGradeFk;
+ALTER TABLE vn.workerRelatives DROP FOREIGN KEY workerRelatives_workerFk;
+
+INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
+ VALUES
+ ('WorkerRelative','updateAttributes','*','ALLOW','ROLE','hr'),
+ ('WorkerRelative','crud','WRITE','ALLOW','ROLE','hr'),
+ ('WorkerRelative','findById','*','ALLOW','ROLE','hr'),
+ ('WorkerRelative','find','*','ALLOW','ROLE','hr'),
+ ('WorkerRelative','upsert','*','ALLOW','ROLE','hr'),
+ ('WorkerRelative','filter','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','updateAttributes','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','crud','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','findById','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','find','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','upsert','*','ALLOW','ROLE','hr'),
+ ('WorkerIrpf','filter','*','ALLOW','ROLE','hr'),
+ ('DisabilityGrade','updateAttributes','*','ALLOW','ROLE','hr'),
+ ('DisabilityGrade','crud','*','ALLOW','ROLE','hr'),
+ ('DisabilityGrade','findById','*','ALLOW','ROLE','hr'),
+ ('DisabilityGrade','find','*','ALLOW','ROLE','hr'),
+ ('DisabilityGrade','upsert','*','ALLOW','ROLE','hr');
+
diff --git a/db/versions/11322-azureAspidistra/00-entryAcl.sql b/db/versions/11322-azureAspidistra/00-entryAcl.sql
new file mode 100644
index 000000000..836737d4b
--- /dev/null
+++ b/db/versions/11322-azureAspidistra/00-entryAcl.sql
@@ -0,0 +1,40 @@
+-- Eliminar registros existentes donde property = '*'
+DELETE FROM `salix`.ACL WHERE model = 'entry' AND property = '*';
+
+-- Insertar permisos para los métodos solicitados en el modelo Entry
+INSERT INTO `salix`.ACL (model, property, accessType, permission, principalType, principalId)
+VALUES
+ -- Permisos para administrative
+ ('Entry', 'upsert', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'isBooked', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'findById', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'find', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'filter', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'count', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'getEntry', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'getBuys', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'findOne', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'deleteBuys', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'editLatestBuys', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'importBuys', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'importBuysPreview', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'lastItemBuys', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('Entry', 'latestBuysFilter', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+
+ -- Permisos para buyer (excluyendo isBooked)
+ ('Entry', 'upsert', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'findById', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'find', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'filter', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'count', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'getEntry', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'getBuys', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'findOne', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'deleteBuys', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'editLatestBuys', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'importBuys', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'importBuysPreview', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'lastItemBuys', 'READ', 'ALLOW', 'ROLE', 'buyer'),
+ ('Entry', 'latestBuysFilter', 'READ', 'ALLOW', 'ROLE', 'buyer');
diff --git a/db/versions/11332-tealErica/00-firstScript.sql b/db/versions/11332-tealErica/00-firstScript.sql
new file mode 100644
index 000000000..6358695b2
--- /dev/null
+++ b/db/versions/11332-tealErica/00-firstScript.sql
@@ -0,0 +1,4 @@
+ALTER TABLE vn.travel ADD isRaid tinyint(1) DEFAULT 0 NOT NULL COMMENT 'Redada';
+
+ALTER TABLE vn.travel MODIFY COLUMN daysInForward int(10) unsigned DEFAULT NULL
+ COMMENT 'Cuando es una redada, indica el número de días que se añadirán a la fecha de hoy para establecer el landed. NULL si no es una redada';
diff --git a/db/versions/11341-pinkSalal/00-firstScript.sql b/db/versions/11341-pinkSalal/00-firstScript.sql
new file mode 100644
index 000000000..2c9e912d9
--- /dev/null
+++ b/db/versions/11341-pinkSalal/00-firstScript.sql
@@ -0,0 +1,5 @@
+
+ALTER TABLE vn.travel ADD IF NOT EXISTS isRaid tinyint(1) DEFAULT 0 NOT NULL COMMENT 'Redada';
+
+ALTER TABLE vn.travel MODIFY COLUMN daysInForward int(10) unsigned DEFAULT NULL
+ COMMENT 'Cuando es una redada, indica el número de días que se añadirán a la fecha de hoy para establecer el landed. NULL si no es una redada';
diff --git a/db/versions/11351-bronzeMedeola/00-firstScript.sql b/db/versions/11351-bronzeMedeola/00-firstScript.sql
new file mode 100644
index 000000000..c94447a89
--- /dev/null
+++ b/db/versions/11351-bronzeMedeola/00-firstScript.sql
@@ -0,0 +1,2 @@
+
+ALTER TABLE vn.itemConfig ADD defaultPackingTypeFk VARCHAR(1) DEFAULT 'H' NULL;
diff --git a/db/versions/11353-wheatCymbidium/00-firstScript.sql b/db/versions/11353-wheatCymbidium/00-firstScript.sql
new file mode 100644
index 000000000..557ffab9e
--- /dev/null
+++ b/db/versions/11353-wheatCymbidium/00-firstScript.sql
@@ -0,0 +1,15 @@
+
+
+USE vn;
+
+INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
+VALUES('RouteAction', 'find', 'READ', 'ALLOW', 'ROLE', 'delivery');
+
+INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
+VALUES('RouteComplement', 'find', 'READ', 'ALLOW', 'ROLE', 'delivery');
+
+INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
+VALUES('RouteComplement', 'create', 'WRITE', 'ALLOW', 'ROLE', 'delivery');
+
+INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
+VALUES('RouteComplement', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'delivery');
\ No newline at end of file
diff --git a/db/versions/11354-aquaMastic/00-firstScript.sql b/db/versions/11354-aquaMastic/00-firstScript.sql
new file mode 100644
index 000000000..dda3f4752
--- /dev/null
+++ b/db/versions/11354-aquaMastic/00-firstScript.sql
@@ -0,0 +1,2 @@
+RENAME TABLE vn.workerTimeControlParams TO vn.workerTimeControlParams__;
+ALTER TABLE vn.workerTimeControlParams__ COMMENT='@deprecated 2024-11-19';
\ No newline at end of file
diff --git a/db/versions/11355-pinkMedeola/00-firstScript.sql b/db/versions/11355-pinkMedeola/00-firstScript.sql
new file mode 100644
index 000000000..fc89b386c
--- /dev/null
+++ b/db/versions/11355-pinkMedeola/00-firstScript.sql
@@ -0,0 +1,3 @@
+-- Place your SQL code here
+INSERT IGNORE INTO vn.saySimpleConfig (url, defaultChannel)
+ VALUES ('https://verdnatura.saysimple.io/start-conversation', 1320);
\ No newline at end of file
diff --git a/db/versions/11357-whiteGerbera/00-firstScript.sql b/db/versions/11357-whiteGerbera/00-firstScript.sql
new file mode 100644
index 000000000..c10ec4d26
--- /dev/null
+++ b/db/versions/11357-whiteGerbera/00-firstScript.sql
@@ -0,0 +1,4 @@
+ALTER TABLE vn.travel ADD IF NOT EXISTS isRaid tinyint(1) DEFAULT 0 NOT NULL COMMENT 'Redada';
+
+ALTER TABLE vn.travel MODIFY COLUMN daysInForward int(10) unsigned DEFAULT NULL
+ COMMENT 'Cuando es una redada, indica el número de días que se añadirán a la fecha de hoy para establecer el landed. NULL si no es una redada';
diff --git a/e2e/paths/04-item/01_summary.spec.js b/e2e/paths/04-item/01_summary.spec.js
new file mode 100644
index 000000000..51195be48
--- /dev/null
+++ b/e2e/paths/04-item/01_summary.spec.js
@@ -0,0 +1,133 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item summary path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'item');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should search for an item', async() => {
+ await page.doSearch('Ranged weapon');
+ const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
+
+ await page.waitForTextInElement(selectors.itemsIndex.firstSearchResult, 'Ranged weapon');
+ await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
+ const isVisible = await page.isVisible(selectors.itemSummary.basicData);
+
+ expect(resultsCount).toBe(4);
+ expect(isVisible).toBeTruthy();
+ });
+
+ it(`should check the item summary preview shows fields from basic data`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.basicData, 'Ranged weapon longbow 200cm');
+ const result = await page.waitToGetProperty(selectors.itemSummary.basicData, 'innerText');
+
+ expect(result).toContain('Ranged weapon longbow 200cm');
+ });
+
+ it(`should check the item summary preview shows fields from tags`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.tags, 'Brown');
+ const result = await page.waitToGetProperty(selectors.itemSummary.tags, 'innerText');
+
+ expect(result).toContain('Brown');
+ });
+
+ it(`should check the item summary preview shows fields from botanical`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.botanical, 'Abelia');
+ const result = await page.waitToGetProperty(selectors.itemSummary.botanical, 'innerText');
+
+ expect(result).toContain('Abelia');
+ });
+
+ it(`should check the item summary preview shows fields from barcode`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.barcode, '1');
+ const result = await page.waitToGetProperty(selectors.itemSummary.barcode, 'innerText');
+
+ expect(result).toContain('1');
+ });
+
+ it(`should close the summary popup`, async() => {
+ await page.closePopup();
+ await page.waitForSelector(selectors.itemSummary.basicData, {hidden: true});
+ });
+
+ it('should search for other item', async() => {
+ await page.doSearch('Melee Reinforced');
+ const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
+
+ await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
+ await page.waitForSelector(selectors.itemSummary.basicData, {visible: true});
+
+ expect(resultsCount).toBe(3);
+ });
+
+ it(`should now check the item summary preview shows fields from basic data`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.basicData, 'Melee Reinforced weapon combat fist 15cm');
+ const result = await page.waitToGetProperty(selectors.itemSummary.basicData, 'innerText');
+
+ expect(result).toContain('Melee Reinforced weapon combat fist 15cm');
+ });
+
+ it(`should now check the item summary preview shows fields from tags`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.tags, 'Silver');
+ const result = await page.waitToGetProperty(selectors.itemSummary.tags, 'innerText');
+
+ expect(result).toContain('Silver');
+ });
+
+ it(`should now check the item summary preview shows fields from botanical`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.botanical, '-');
+ const result = await page.waitToGetProperty(selectors.itemSummary.botanical, 'innerText');
+
+ expect(result).toContain('-');
+ });
+
+ it(`should now close the summary popup`, async() => {
+ await page.closePopup();
+ await page.waitForSelector(selectors.itemSummary.basicData, {hidden: true});
+ });
+
+ it(`should navigate to one of the items detailed section`, async() => {
+ await page.accessToSearchResult('Melee weapon combat fist 15cm');
+ await page.waitForState('item.card.summary');
+ });
+
+ it(`should check the descritor edit button is not visible for employee`, async() => {
+ const visibleButton = await page.isVisible(selectors.itemDescriptor.editButton);
+
+ expect(visibleButton).toBeFalsy();
+ });
+
+ it(`should check the item summary shows fields from basic data section`, async() => {
+ await page.waitForTextInElement(selectors.itemSummary.basicData, 'Melee weapon combat fist 15cm');
+ const result = await page.waitToGetProperty(selectors.itemSummary.basicData, 'innerText');
+
+ expect(result).toContain('Melee weapon combat fist 15cm');
+ });
+
+ it(`should check the item summary shows fields from tags section`, async() => {
+ const result = await page.waitToGetProperty(selectors.itemSummary.tags, 'innerText');
+
+ expect(result).toContain('Silver');
+ });
+
+ it(`should check the item summary shows fields from botanical section`, async() => {
+ const result = await page.waitToGetProperty(selectors.itemSummary.botanical, 'innerText');
+
+ expect(result).toContain('procera');
+ });
+
+ it(`should check the item summary shows fields from barcodes section`, async() => {
+ const result = await page.waitToGetProperty(selectors.itemSummary.barcode, 'innerText');
+
+ expect(result).toContain('4');
+ });
+});
diff --git a/e2e/paths/04-item/02_basic_data.spec.js b/e2e/paths/04-item/02_basic_data.spec.js
new file mode 100644
index 000000000..3bad18303
--- /dev/null
+++ b/e2e/paths/04-item/02_basic_data.spec.js
@@ -0,0 +1,64 @@
+import getBrowser from '../../helpers/puppeteer';
+
+const $ = {
+ form: 'vn-item-basic-data form',
+ intrastatForm: '.vn-dialog.shown form',
+ newIntrastatButton: 'vn-item-basic-data vn-icon-button[vn-tooltip="New intrastat"] > button'
+};
+
+describe('Item Edit basic data path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('Melee weapon combat fist 15cm');
+ });
+
+ beforeEach(async() => {
+ await page.accessToSection('item.card.basicData');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should edit the item basic data and confirm the item data was edited`, async() => {
+ const values = {
+ type: 'Anthurium',
+ intrastat: 'Coral y materiales similares',
+ relevancy: 1,
+ generic: 'Pallet',
+ isActive: false,
+ priceInKg: true,
+ isFragile: true,
+ packingOut: 5
+ };
+
+ const message = await page.sendForm($.form, values);
+ await page.reloadSection('item.card.basicData');
+ const formValues = await page.fetchForm($.form, Object.keys(values));
+
+ expect(message.isSuccess).toBeTrue();
+ expect(formValues).toEqual(values);
+ });
+
+ it(`should create a new intrastat and save it`, async() => {
+ await page.click($.newIntrastatButton);
+ await page.waitForSelector($.intrastatForm);
+ await page.fillForm($.intrastatForm, {
+ id: '588420239',
+ description: 'Tropical Flowers'
+ });
+ await page.respondToDialog('accept');
+
+ const message = await page.sendForm($.form);
+ await page.reloadSection('item.card.basicData');
+ const formValues = await page.fetchForm($.form, ['intrastat']);
+
+ expect(message.isSuccess).toBeTrue();
+ expect(formValues).toEqual({intrastat: 'Tropical Flowers'});
+ });
+});
diff --git a/e2e/paths/04-item/03_tax.spec.js b/e2e/paths/04-item/03_tax.spec.js
new file mode 100644
index 000000000..6013094e9
--- /dev/null
+++ b/e2e/paths/04-item/03_tax.spec.js
@@ -0,0 +1,48 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item edit tax path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('Ranged weapon longbow 200cm');
+ await page.accessToSection('item.card.tax');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should add the item tax to all countries`, async() => {
+ await page.autocompleteSearch(selectors.itemTax.firstClass, 'General VAT');
+ await page.autocompleteSearch(selectors.itemTax.secondClass, 'General VAT');
+ await page.waitToClick(selectors.itemTax.submitTaxButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the first item tax class was edited`, async() => {
+ await page.reloadSection('item.card.tax');
+ const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value');
+
+ expect(firstVatType).toEqual('General VAT');
+ });
+
+ it(`should confirm the second item tax class was edited`, async() => {
+ const secondVatType = await page
+ .waitToGetProperty(selectors.itemTax.secondClass, 'value');
+
+ expect(secondVatType).toEqual('General VAT');
+ });
+
+ it(`should edit the first class without saving the form`, async() => {
+ await page.autocompleteSearch(selectors.itemTax.firstClass, 'Reduced VAT');
+ const firstVatType = await page.waitToGetProperty(selectors.itemTax.firstClass, 'value');
+
+ expect(firstVatType).toEqual('Reduced VAT');
+ });
+});
diff --git a/e2e/paths/04-item/04_tags.spec.js b/e2e/paths/04-item/04_tags.spec.js
new file mode 100644
index 000000000..f13cf9aa4
--- /dev/null
+++ b/e2e/paths/04-item/04_tags.spec.js
@@ -0,0 +1,79 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item create tags path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('Ranged weapon longbow 200cm');
+ await page.accessToSection('item.card.tags');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should create a new tag and delete a former one', async() => {
+ await page.waitToClick(selectors.itemTags.fourthRemoveTagButton);
+ await page.waitToClick(selectors.itemTags.addItemTagButton);
+ await page.autocompleteSearch(selectors.itemTags.seventhTag, 'Ancho de la base');
+ await page.write(selectors.itemTags.seventhValue, '50');
+ await page.clearInput(selectors.itemTags.seventhRelevancy);
+ await page.write(selectors.itemTags.seventhRelevancy, '4');
+ await page.waitToClick(selectors.itemTags.submitItemTagsButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the fourth row data is the expected one', async() => {
+ await page.reloadSection('item.card.tags');
+ await page.waitForSelector('vn-item-tags');
+ let result = await page.waitToGetProperty(selectors.itemTags.fourthTag, 'value');
+
+ expect(result).toEqual('Ancho de la base');
+
+ result = await page
+ .waitToGetProperty(selectors.itemTags.fourthValue, 'value');
+
+ expect(result).toEqual('50');
+
+ result = await page
+ .waitToGetProperty(selectors.itemTags.fourthRelevancy, 'value');
+
+ expect(result).toEqual('4');
+ });
+
+ it('should confirm the fifth row data is the expected one', async() => {
+ let tag = await page
+ .waitToGetProperty(selectors.itemTags.fifthTag, 'value');
+
+ let value = await page
+ .waitToGetProperty(selectors.itemTags.fifthValue, 'value');
+
+ let relevancy = await page
+ .waitToGetProperty(selectors.itemTags.fifthRelevancy, 'value');
+
+ expect(tag).toEqual('Color');
+ expect(value).toEqual('Brown');
+ expect(relevancy).toEqual('5');
+ });
+
+ it('should confirm the sixth row data is the expected one', async() => {
+ let tag = await page
+ .waitToGetProperty(selectors.itemTags.sixthTag, 'value');
+
+ let value = await page
+ .waitToGetProperty(selectors.itemTags.sixthValue, 'value');
+
+ let relevancy = await page
+ .waitToGetProperty(selectors.itemTags.sixthRelevancy, 'value');
+
+ expect(tag).toEqual('Categoria');
+ expect(value).toEqual('+1 precission');
+ expect(relevancy).toEqual('6');
+ });
+});
diff --git a/e2e/paths/04-item/05_botanical.spec.js b/e2e/paths/04-item/05_botanical.spec.js
new file mode 100644
index 000000000..1671cc5d2
--- /dev/null
+++ b/e2e/paths/04-item/05_botanical.spec.js
@@ -0,0 +1,66 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item Create botanical path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('Ranged weapon pistol 9mm');
+ await page.accessToSection('item.card.botanical');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should create a new botanical for the item`, async() => {
+ await page.autocompleteSearch(selectors.itemBotanical.genus, 'Abelia');
+ await page.autocompleteSearch(selectors.itemBotanical.species, 'dealbata');
+ await page.waitToClick(selectors.itemBotanical.submitBotanicalButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the Genus for the item was created`, async() => {
+ await page.waitForTextInField(selectors.itemBotanical.genus, 'Abelia');
+ const result = await page
+ .waitToGetProperty(selectors.itemBotanical.genus, 'value');
+
+ expect(result).toEqual('Abelia');
+ });
+
+ it(`should confirm the Species for the item was created`, async() => {
+ const result = await page
+ .waitToGetProperty(selectors.itemBotanical.species, 'value');
+
+ expect(result).toEqual('dealbata');
+ });
+
+ it(`should edit botanical for the item`, async() => {
+ await page.autocompleteSearch(selectors.itemBotanical.genus, 'Abies');
+ await page.autocompleteSearch(selectors.itemBotanical.species, 'decurrens');
+ await page.waitToClick(selectors.itemBotanical.submitBotanicalButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the Genus for the item was edited`, async() => {
+ await page.waitForTextInField(selectors.itemBotanical.genus, 'Abies');
+ const result = await page
+ .waitToGetProperty(selectors.itemBotanical.genus, 'value');
+
+ expect(result).toEqual('Abies');
+ });
+
+ it(`should confirm the Species for the item was edited`, async() => {
+ const result = await page
+ .waitToGetProperty(selectors.itemBotanical.species, 'value');
+
+ expect(result).toEqual('decurrens');
+ });
+});
diff --git a/e2e/paths/04-item/06_barcode.spec.js b/e2e/paths/04-item/06_barcode.spec.js
new file mode 100644
index 000000000..36c9c39ae
--- /dev/null
+++ b/e2e/paths/04-item/06_barcode.spec.js
@@ -0,0 +1,37 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item Create barcodes path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('Ranged weapon longbow 200cm');
+ await page.accessToSection('item.card.itemBarcode');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should click create a new code and delete a former one`, async() => {
+ await page.waitToClick(selectors.itemBarcodes.firstCodeRemoveButton);
+ await page.waitToClick(selectors.itemBarcodes.addBarcodeButton);
+ await page.write(selectors.itemBarcodes.thirdCode, '5');
+ await page.waitToClick(selectors.itemBarcodes.submitBarcodesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the barcode 5 is created and it is now the third barcode as the first was deleted`, async() => {
+ await page.reloadSection('item.card.itemBarcode');
+ await page.waitForTextInField(selectors.itemBarcodes.thirdCode, '5');
+ const result = await page
+ .waitToGetProperty(selectors.itemBarcodes.thirdCode, 'value');
+
+ expect(result).toEqual('5');
+ });
+});
diff --git a/e2e/paths/04-item/07_create.spec.js b/e2e/paths/04-item/07_create.spec.js
new file mode 100644
index 000000000..c20be9ebc
--- /dev/null
+++ b/e2e/paths/04-item/07_create.spec.js
@@ -0,0 +1,65 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+const $ = {
+ form: 'vn-item-create form'
+};
+
+describe('Item Create', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should access to the create item view by clicking the create floating button', async() => {
+ await page.waitToClick(selectors.itemsIndex.createItemButton);
+ await page.waitForState('item.create');
+ });
+
+ it('should return to the item index by clickig the cancel button', async() => {
+ await page.waitToClick(selectors.itemCreateView.cancelButton);
+ await page.waitForState('item.index');
+ });
+
+ it('should now access to the create item view by clicking the create floating button', async() => {
+ await page.waitToClick(selectors.itemsIndex.createItemButton);
+ await page.waitForState('item.create');
+ });
+
+ it('should throw an error when insert an invalid priority', async() => {
+ const values = {
+ name: 'Infinity Gauntlet',
+ type: 'Crisantemo',
+ intrastat: 'Coral y materiales similares',
+ origin: 'Holand',
+ priority: null
+ };
+ const message = await page.sendForm($.form, values);
+
+ expect(message.text).toContain('Valid priorities');
+ });
+
+ it('should create the Infinity Gauntlet item', async() => {
+ const values = {
+ name: 'Infinity Gauntlet',
+ type: 'Crisantemo',
+ intrastat: 'Coral y materiales similares',
+ origin: 'Holand',
+ priority: '2'
+ };
+
+ await page.fillForm($.form, values);
+ const formValues = await page.fetchForm($.form, Object.keys(values));
+ const message = await page.sendForm($.form);
+
+ expect(message.isSuccess).toBeTrue();
+ expect(formValues).toEqual(values);
+ });
+});
diff --git a/e2e/paths/04-item/08_regularize.spec.js b/e2e/paths/04-item/08_regularize.spec.js
new file mode 100644
index 000000000..9b3074776
--- /dev/null
+++ b/e2e/paths/04-item/08_regularize.spec.js
@@ -0,0 +1,141 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item regularize path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'item');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should edit the user local warehouse', async() => {
+ await page.waitForSpinnerLoad();
+ await page.waitToClick(selectors.globalItems.userMenuButton);
+ await page.autocompleteSearch(selectors.globalItems.userLocalWarehouse, 'Warehouse Four');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should check the local settings were saved', async() => {
+ const userLocalWarehouse = await page
+ .waitToGetProperty(selectors.globalItems.userLocalWarehouse, 'value');
+
+ await page.closePopup();
+
+ expect(userLocalWarehouse).toContain('Warehouse Four');
+ });
+
+ it('should search for a specific item', async() => {
+ await page.accessToSearchResult('Ranged weapon pistol 9mm');
+ await page.waitForState('item.card.summary');
+ });
+
+ it('should open the regularize dialog and check the warehouse matches the local user settings', async() => {
+ await page.waitToClick(selectors.itemDescriptor.moreMenu);
+ await page.waitToClick(selectors.itemDescriptor.moreMenuRegularizeButton);
+ const result = await page.waitToGetProperty(selectors.itemDescriptor.regularizeWarehouse, 'value');
+
+ expect(result).toEqual('Warehouse Four');
+ });
+
+ it('should regularize the item', async() => {
+ await page.write(selectors.itemDescriptor.regularizeQuantity, '100');
+ await page.autocompleteSearch(selectors.itemDescriptor.regularizeWarehouse, 'Warehouse One');
+ await page.waitToClick(selectors.itemDescriptor.regularizeSaveButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should click on the Tickets button of the top bar menu', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await Promise.all([
+ page.waitForNavigation({waitUntil: ['load', 'networkidle0', 'domcontentloaded']}),
+ page.waitToClick(selectors.globalItems.ticketsButton)
+ ]);
+ await page.waitForState('ticket.index');
+ });
+
+ it('should clear the user local settings now', async() => {
+ await page.waitToClick(selectors.globalItems.userMenuButton);
+ await page.waitForContentLoaded();
+ await page.clearInput(selectors.globalItems.userConfigFirstAutocomplete);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should search for the ticket with alias missing', async() => {
+ await page.keyboard.press('Escape');
+ await page.accessToSearchResult('missing');
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should check the ticket sale quantity is showing a negative value`, async() => {
+ await page.waitForTextInElement(selectors.ticketSummary.firstSaleQuantity, '-100');
+ const result = await page
+ .waitToGetProperty(selectors.ticketSummary.firstSaleQuantity, 'innerText');
+
+ expect(result).toContain('-100');
+ });
+
+ it(`should check the ticket sale discount is 100%`, async() => {
+ const result = await page
+ .waitToGetProperty(selectors.ticketSummary.firstSaleDiscount, 'innerText');
+
+ expect(result).toContain('100 %');
+ });
+
+ it('should now click on the Items button of the top bar menu', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.itemsButton);
+ await page.waitForState('item.index');
+ });
+
+ it('should search for the item once again', async() => {
+ await page.accessToSearchResult('Ranged weapon pistol 9mm');
+ await page.waitForState('item.card.summary');
+ });
+
+ it('should regularize the item once more', async() => {
+ await page.waitToClick(selectors.itemDescriptor.moreMenu);
+ await page.waitToClick(selectors.itemDescriptor.moreMenuRegularizeButton);
+ await page.write(selectors.itemDescriptor.regularizeQuantity, '100');
+ await page.autocompleteSearch(selectors.itemDescriptor.regularizeWarehouse, 'Warehouse One');
+ await page.waitToClick(selectors.itemDescriptor.regularizeSaveButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should again click on the Tickets button of the top bar menu', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await Promise.all([
+ page.waitForNavigation({waitUntil: ['load', 'networkidle0', 'domcontentloaded']}),
+ page.waitToClick(selectors.globalItems.ticketsButton)
+ ]);
+ await page.waitForState('ticket.index');
+ });
+
+ it('should search for the ticket missing once again', async() => {
+ await page.accessToSearchResult('Missing');
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should check the ticket contains now two sales`, async() => {
+ await page.waitForTextInElement(selectors.ticketSummary.firstSaleQuantity, '-100');
+ const result = await page.countElement(selectors.ticketSummary.sale);
+
+ expect(result).toEqual(2);
+ });
+});
diff --git a/e2e/paths/04-item/09_index.spec.js b/e2e/paths/04-item/09_index.spec.js
new file mode 100644
index 000000000..6e0a4bd5c
--- /dev/null
+++ b/e2e/paths/04-item/09_index.spec.js
@@ -0,0 +1,84 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item index path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesPerson', 'item');
+ await page.waitToClick(selectors.globalItems.searchButton);
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should click on the fields to show button to open the list of columns to show', async() => {
+ await page.waitToClick(selectors.itemsIndex.shownColumns);
+ const visible = await page.isVisible(selectors.itemsIndex.shownColumnsList);
+
+ expect(visible).toBeTruthy();
+ });
+
+ it('should unmark all checkboxes except the first and the last ones', async() => {
+ await page.waitToClick(selectors.itemsIndex.idCheckbox);
+ await page.waitToClick(selectors.itemsIndex.stemsCheckbox);
+ await page.waitToClick(selectors.itemsIndex.sizeCheckbox);
+ await page.waitToClick(selectors.itemsIndex.typeCheckbox);
+ await page.waitToClick(selectors.itemsIndex.categoryCheckbox);
+ await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
+ await page.waitToClick(selectors.itemsIndex.originCheckbox);
+ await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
+ await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
+ await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should navigate forth and back to see the images column is still visible', async() => {
+ await page.closePopup();
+ await page.waitToClick(selectors.itemsIndex.firstSearchResult);
+ await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
+ await page.waitToClick(selectors.globalItems.searchButton);
+ await page.waitForSelector(selectors.itemsIndex.searchResult);
+ await page.waitImgLoad(selectors.itemsIndex.firstItemImage);
+ const imageVisible = await page.isVisible(selectors.itemsIndex.firstItemImageTd);
+
+ expect(imageVisible).toBeTruthy();
+ });
+
+ it('should check the ids column is not visible', async() => {
+ await page.waitForSelector(selectors.itemsIndex.firstItemId, {hidden: true});
+ });
+
+ it('should mark all unchecked boxes to leave the index as it was', async() => {
+ await page.waitToClick(selectors.itemsIndex.shownColumns);
+ await page.waitToClick(selectors.itemsIndex.idCheckbox);
+ await page.waitToClick(selectors.itemsIndex.stemsCheckbox);
+ await page.waitToClick(selectors.itemsIndex.sizeCheckbox);
+ await page.waitToClick(selectors.itemsIndex.typeCheckbox);
+ await page.waitToClick(selectors.itemsIndex.categoryCheckbox);
+ await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
+ await page.waitToClick(selectors.itemsIndex.originCheckbox);
+ await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
+ await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
+ await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should now navigate forth and back to see the ids column is now visible', async() => {
+ await page.closePopup();
+ await page.waitToClick(selectors.itemsIndex.firstSearchResult);
+ await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
+ await page.waitToClick(selectors.globalItems.searchButton);
+ await page.waitForSelector(selectors.itemsIndex.searchResult);
+ const idVisible = await page.isVisible(selectors.itemsIndex.firstItemId);
+
+ expect(idVisible).toBeTruthy();
+ });
+});
diff --git a/e2e/paths/04-item/10_item_log.spec.js b/e2e/paths/04-item/10_item_log.spec.js
new file mode 100644
index 000000000..c88fbd337
--- /dev/null
+++ b/e2e/paths/04-item/10_item_log.spec.js
@@ -0,0 +1,45 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item log path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('developer', 'item');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should search for the Knowledge artifact to confirm it isn't created yet`, async() => {
+ await page.doSearch('Knowledge artifact');
+ const nResults = await page.countElement(selectors.itemsIndex.searchResult);
+
+ expect(nResults).toEqual(1);
+ });
+
+ it('should access to the create item view by clicking the create floating button', async() => {
+ await page.waitToClick(selectors.itemsIndex.createItemButton);
+ await page.waitForState('item.create');
+ });
+
+ it('should create the Knowledge artifact item', async() => {
+ await page.write(selectors.itemCreateView.temporalName, 'Knowledge artifact');
+ await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
+ await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
+ await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
+ await page.waitToClick(selectors.itemCreateView.createButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should return to the items index by clicking the return to items button', async() => {
+ await page.waitToClick(selectors.itemBasicData.goToItemIndexButton);
+ await page.waitForSelector(selectors.itemsIndex.createItemButton);
+ await page.waitForState('item.index');
+ });
+});
diff --git a/e2e/paths/04-item/11_descriptor.spec.js b/e2e/paths/04-item/11_descriptor.spec.js
new file mode 100644
index 000000000..eb9ed2573
--- /dev/null
+++ b/e2e/paths/04-item/11_descriptor.spec.js
@@ -0,0 +1,41 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item descriptor path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('item.card.basicData');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should set the item to inactive', async() => {
+ await page.waitToClick(selectors.itemBasicData.isActiveCheckbox);
+ await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should reload the section and check the inactive icon is visible', async() => {
+ await page.reloadSection('item.card.basicData');
+ const visibleIcon = await page.isVisible(selectors.itemDescriptor.inactiveIcon);
+
+ expect(visibleIcon).toBeTruthy();
+ });
+
+ it('should set the item back to active', async() => {
+ await page.waitToClick(selectors.itemBasicData.isActiveCheckbox);
+ await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+});
diff --git a/e2e/paths/04-item/12_request.spec.js b/e2e/paths/04-item/12_request.spec.js
new file mode 100644
index 000000000..e0f3a1b45
--- /dev/null
+++ b/e2e/paths/04-item/12_request.spec.js
@@ -0,0 +1,45 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Item request path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSection('item.request');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should reach the item request section', async() => {
+ await page.waitForState('item.request');
+ });
+
+ it('should fill the id and quantity then check the concept was updated', async() => {
+ await page.writeOnEditableTD(selectors.itemRequest.firstRequestItemID, '4');
+ await page.writeOnEditableTD(selectors.itemRequest.firstRequestQuantity, '10');
+ await page.waitForTextInElement(selectors.itemRequest.firstRequestConcept, 'Melee weapon heavy shield 100cm');
+ let filledConcept = await page.waitToGetProperty(selectors.itemRequest.firstRequestConcept, 'innerText');
+
+ expect(filledConcept).toContain('Melee weapon heavy shield 100cm');
+ });
+
+ it('should check the status of the request should now be accepted', async() => {
+ let status = await page.waitToGetProperty(selectors.itemRequest.firstRequestStatus, 'innerText');
+
+ expect(status).toContain('Accepted');
+ });
+
+ it('should now click on the second declain request icon then type the reason', async() => {
+ await page.waitToClick(selectors.itemRequest.secondRequestDecline);
+ await page.write(selectors.itemRequest.declineReason, 'Not quite as expected');
+ await page.respondToDialog('accept');
+ let status = await page.waitToGetProperty(selectors.itemRequest.secondRequestStatus, 'innerText');
+
+ expect(status).toContain('Denied');
+ });
+});
diff --git a/e2e/paths/04-item/13_fixedPrice.spec.js b/e2e/paths/04-item/13_fixedPrice.spec.js
new file mode 100644
index 000000000..f36138e18
--- /dev/null
+++ b/e2e/paths/04-item/13_fixedPrice.spec.js
@@ -0,0 +1,97 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+const $ = selectors.itemFixedPrice;
+
+describe('Item fixed prices path', () => {
+ let browser;
+ let page;
+ let httpRequest;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyer', 'item');
+ await page.accessToSection('item.fixedPrice');
+ page.on('request', req => {
+ if (req.url().includes(`FixedPrices/filter`))
+ httpRequest = req.url();
+ });
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should filter using all the fields', async() => {
+ await page.write($.generalSearchFilter, 'item');
+ await page.keyboard.press('Enter');
+
+ expect(httpRequest).toContain('search=item');
+
+ await page.click($.chip);
+ await page.click($.reignFilter);
+
+ expect(httpRequest).toContain('categoryFk');
+
+ await page.autocompleteSearch($.typeFilter, 'Alstroemeria');
+
+ expect(httpRequest).toContain('typeFk');
+
+ await page.click($.chip);
+ await page.autocompleteSearch($.buyerFilter, 'buyerNick');
+
+ expect(httpRequest).toContain('buyerFk');
+
+ await page.click($.chip);
+ await page.autocompleteSearch($.warehouseFilter, 'Algemesi');
+
+ expect(httpRequest).toContain('warehouseFk');
+
+ await page.click($.chip);
+ await page.click($.mineFilter);
+
+ expect(httpRequest).toContain('mine=true');
+
+ await page.click($.chip);
+ await page.click($.hasMinPriceFilter);
+
+ expect(httpRequest).toContain('hasMinPrice=true');
+
+ await page.click($.chip);
+ await page.click($.addTag);
+ await page.autocompleteSearch($.tagFilter, 'Color');
+ await page.autocompleteSearch($.tagValueFilter, 'Brown');
+
+ expect(httpRequest).toContain('tags');
+
+ await page.click($.chip);
+ });
+
+ it('should click on the add new fixed price button', async() => {
+ await page.waitToClick($.add);
+ await page.waitForSelector($.fourthFixedPrice);
+ });
+
+ it('should fill the fixed price data', async() => {
+ const now = Date.vnNew();
+ await page.autocompleteSearch($.fourthWarehouse, 'Warehouse one');
+ await page.writeOnEditableTD($.fourthGroupingPrice, '1');
+ await page.writeOnEditableTD($.fourthPackingPrice, '1');
+ await page.write($.fourthMinPrice, '1');
+ await page.pickDate($.fourthStarted, now);
+ await page.pickDate($.fourthEnded, now);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should reload the section and check the created price has the expected ID', async() => {
+ await page.goto(`http://localhost:5000/#!/item/fixed-price`);
+ await page.autocompleteSearch($.warehouseFilter, 'Warehouse one');
+ await page.click($.chip);
+ const result = await page.waitToGetProperty($.fourthItemID, 'value');
+
+ expect(result).toContain('13');
+ });
+});
diff --git a/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js b/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js
new file mode 100644
index 000000000..ad0975889
--- /dev/null
+++ b/e2e/paths/05-ticket/01-sale/01_list_sales.spec.js
@@ -0,0 +1,99 @@
+import selectors from '../../../helpers/selectors.js';
+import getBrowser from '../../../helpers/puppeteer';
+
+describe('Ticket List sale path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('13');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should confirm the first ticket sale contains the colour tag', async() => {
+ const value = await page
+ .waitToGetProperty(selectors.ticketSales.firstSaleColour, 'innerText');
+
+ expect(value).toContain('Black');
+ });
+
+ it('should confirm the first sale contains the price', async() => {
+ const value = await page
+ .waitToGetProperty(selectors.ticketSales.firstSalePrice, 'innerText');
+
+ expect(value).toContain('1.72');
+ });
+
+ it('should confirm the first sale contains the discount', async() => {
+ const value = await page
+ .waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText');
+
+ expect(value).toContain('0.00%');
+ });
+
+ it('should confirm the first sale contains the total import', async() => {
+ const value = await page
+ .waitToGetProperty(selectors.ticketSales.firstSaleImport, 'innerText');
+
+ expect(value).toContain('34.40');
+ });
+
+ it('should add an empty item to the sale list', async() => {
+ await page.waitToClick(selectors.ticketSales.newItemButton);
+ const sales = await page
+ .countElement(selectors.ticketSales.saleLine);
+
+ expect(sales).toEqual(2);
+ });
+
+ it('should select a valid item to be added as the second item in the sales list', async() => {
+ let searchValue = 'Melee weapon heavy shield 100cm';
+ await page.autocompleteSearch(selectors.ticketSales.secondSaleIdAutocomplete, searchValue);
+ await page.waitToClick(selectors.ticketSales.secondSaleQuantityCell);
+ await page.type(selectors.ticketSales.secondSaleQuantity, '8');
+ await page.keyboard.press('Enter');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should update the description of the new sale', async() => {
+ await page.click(selectors.ticketSales.secondSaleConceptCell);
+ await page.write(selectors.ticketSales.secondSaleConceptInput, 'Aegis of Valor');
+ await page.keyboard.press('Enter');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should add a third empty item to the sale list', async() => {
+ await page.waitToClick(selectors.ticketSales.newItemButton);
+ await page.waitForNumberOfElements(selectors.ticketSales.saleLine, 3);
+ const sales = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(sales).toEqual(3);
+ });
+
+ it('should select the 2nd and 3th item and delete both', async() => {
+ await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.deleteSaleButton);
+ await page.waitToClick(selectors.globalItems.acceptButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should verify there's only 1 single line remaining`, async() => {
+ const sales = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(sales).toEqual(1);
+ });
+});
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
new file mode 100644
index 000000000..d9689e31a
--- /dev/null
+++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
@@ -0,0 +1,415 @@
+import selectors from '../../../helpers/selectors.js';
+import getBrowser from '../../../helpers/puppeteer';
+
+describe('Ticket Edit sale path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesPerson', 'ticket');
+ await page.accessToSearchResult('16');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should click on the first sale claim icon to navigate over there`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon);
+ await page.waitForNavigation();
+ await page.goBack();
+ await page.goBack();
+ });
+
+ it('should navigate to the tickets index', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.ticketsButton);
+ await page.waitForState('ticket.index');
+ });
+
+ it(`should search for a ticket and then navigate to it's sales`, async() => {
+ await page.accessToSearchResult('16');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it(`should set the ticket as libre`, async() => {
+ const searchValue = 'libre';
+ await page.waitToClick(selectors.ticketSales.stateMenuButton);
+ await page.write(selectors.ticketSales.moreMenuState, searchValue);
+ try {
+ await page.waitForFunction(searchValue => {
+ const element = document.querySelector('li.active');
+ if (element)
+ return element.innerText.toLowerCase().includes(searchValue.toLowerCase());
+ }, {}, searchValue);
+ } catch (error) {
+ const builtSelector = await page.selectorFormater(selectors.ticketSales.moreMenuState);
+ const inputValue = await page.evaluate(() => {
+ return document.querySelector('.vn-drop-down.shown vn-textfield input').value;
+ });
+ throw new Error(`${builtSelector} value is ${inputValue}! ${error}`);
+ }
+ await page.waitForState('ticket.card.sale');
+ await page.keyboard.press('Enter');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should check it's state is libre now`, async() => {
+ await page.waitForTextInElement(selectors.ticketDescriptor.stateLabelValue, 'Libre');
+ const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText');
+
+ expect(result).toEqual('State Libre');
+ });
+
+ it(`should set the ticket as OK`, async() => {
+ await page.waitToClick(selectors.ticketSales.setOk);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should check it's state is OK now`, async() => {
+ await page.waitForTextInElement(selectors.ticketDescriptor.stateLabelValue, 'OK');
+ const result = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText');
+
+ expect(result).toEqual('State OK');
+ });
+
+ it(`should check the zoomed image isn't present`, async() => {
+ const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
+
+ expect(result).toEqual(0);
+ });
+
+ it(`should click on the thumbnail image of the 1st sale and see the zoomed image`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleThumbnailImage);
+ const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
+
+ expect(result).toEqual(1);
+ });
+
+ it(`should click on the zoomed image to close it`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleZoomedImage);
+ const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
+
+ expect(result).toEqual(0);
+ });
+
+ it(`should click on the first sale ID making now the item descriptor visible`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleId);
+ await page.waitImgLoad(selectors.ticketSales.firstSaleDescriptorImage);
+ const visible = await page.isVisible(selectors.ticketSales.saleDescriptorPopover);
+
+ expect(visible).toBeTruthy();
+ });
+
+ it(`should click on the descriptor image of the 1st sale and see the zoomed image`, async() => {
+ await page.waitToClick('vn-item-descriptor img');
+ const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
+
+ expect(result).toEqual(1);
+ });
+
+ it(`should now click on the zoomed image to close it`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleZoomedImage);
+ const result = await page.countElement(selectors.ticketSales.firstSaleZoomedImage);
+
+ expect(result).toEqual(0);
+ });
+
+ it(`should click on the summary icon of the item-descriptor to access to the item summary`, async() => {
+ await page.waitToClick(selectors.ticketSales.saleDescriptorPopoverSummaryButton);
+ await page.waitForState('item.card.summary');
+ });
+
+ it('should return to ticket sales section', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.ticketsButton);
+ await page.accessToSearchResult('16');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it('should remove 1 from the first sale quantity', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell);
+ await page.waitForSelector(selectors.ticketSales.firstSaleQuantity);
+ await page.type(selectors.ticketSales.firstSaleQuantity, '9\u000d');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should update the price', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSalePrice);
+ await page.waitForSelector(selectors.ticketSales.firstSalePriceInput);
+ await page.type(selectors.ticketSales.firstSalePriceInput, '5\u000d');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the price have been updated', async() => {
+ const result = await page.waitToGetProperty(selectors.ticketSales.firstSalePrice, 'innerText');
+
+ expect(result).toContain('5.00');
+ });
+
+ it('should confirm the total price for that item have been updated', async() => {
+ const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleImport, 'innerText');
+
+ expect(result).toContain('45.00');
+ });
+
+ it('should update the discount', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleDiscount);
+ await page.waitForSelector(selectors.ticketSales.firstSaleDiscountInput);
+ await page.type(selectors.ticketSales.firstSaleDiscountInput, '50');
+ await page.waitToClick(selectors.ticketSales.saveSaleDiscountButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the discount have been updated', async() => {
+ await page.waitForTextInElement(selectors.ticketSales.firstSaleDiscount, '50.00%');
+ const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleDiscount, 'innerText');
+
+ expect(result).toContain('50.00%');
+ });
+
+ it('should confirm the total import for that item have been updated', async() => {
+ await page.waitForTextInElement(selectors.ticketSales.firstSaleImport, '22.50');
+ const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleImport, 'innerText');
+
+ expect(result).toContain('22.50');
+ });
+
+ it('should recalculate price of sales', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
+
+ await page.waitToClick(selectors.ticketSales.moreMenu);
+ await page.waitToClick(selectors.ticketSales.moreMenuRecalculatePrice);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should log in as salesAssistant and navigate to ticket sales', async() => {
+ await page.loginAndModule('salesAssistant', 'ticket');
+ await page.accessToSearchResult('15');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it('should select the first sale and create a refund with warehouse', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.moreMenu);
+ await page.waitToClick(selectors.ticketSales.moreMenuRefund);
+ await page.waitToClick(selectors.ticketSales.refundWithWarehouse);
+ await page.waitForSnackbar();
+ await page.waitForState('ticket.card.sale');
+ });
+
+ it('should select the first sale and create a refund without warehouse', async() => {
+ await page.accessToSearchResult('18');
+ await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.moreMenu);
+ await page.waitToClick(selectors.ticketSales.moreMenuRefund);
+ await page.waitToClick(selectors.ticketSales.refundWithoutWarehouse);
+ await page.waitForSnackbar();
+ await page.waitForState('ticket.card.sale');
+ });
+
+ it('should show error trying to delete a ticket with a refund', async() => {
+ await page.loginAndModule('salesPerson', 'ticket');
+ await page.accessToSearchResult('8');
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
+ await page.waitToClick(selectors.globalItems.acceptButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Tickets with associated refunds can\'t be deleted');
+ 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();
+ 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);
+ await page.waitForSpinnerLoad();
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the third sale was deleted`, async() => {
+ const result = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(result).toEqual(3);
+ });
+
+ it('should select the second sale and transfer it to a valid ticket', async() => {
+ const targetTicketId = '12';
+
+ await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.transferSaleButton);
+ await page.waitToClick(selectors.ticketSales.transferQuantityCell);
+ await page.type(selectors.ticketSales.transferQuantityInput, '10\u000d');
+ await page.type(selectors.ticketSales.moveToTicketInput, targetTicketId);
+ await page.waitToClick(selectors.ticketSales.moveToTicketButton);
+ await page.expectURL(`ticket/${targetTicketId}/sale`);
+ });
+
+ it('should confirm the transfered line is the correct one', async() => {
+ await page.waitForSelector(selectors.ticketSales.secondSaleText);
+ const result = await page.waitToGetProperty(selectors.ticketSales.secondSaleText, 'innerText');
+
+ expect(result).toContain(`Melee weapon heavy shield`);
+ });
+
+ it('should confirm the transfered quantity is the correct one', async() => {
+ const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleQuantityCell, 'innerText');
+
+ expect(result).toContain('20');
+ });
+
+ it('should go back to the original ticket sales section', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
+ await page.accessToSearchResult('16');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it(`should confirm the original ticket has still three lines`, async() => {
+ await page.waitForSelector(selectors.ticketSales.saleLine);
+ const result = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(result).toEqual(3);
+ });
+
+ it(`should confirm the second sale quantity is now half of it's original value after the transfer`, async() => {
+ const result = await page.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText');
+
+ expect(result).toContain('10');
+ });
+
+ it('should go back to the receiver ticket sales section', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
+ await page.accessToSearchResult('12');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it('should transfer the sale back to the original ticket', async() => {
+ const targetTicketId = '16';
+
+ await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.transferSaleButton);
+ await page.type(selectors.ticketSales.moveToTicketInput, targetTicketId);
+ await page.waitToClick(selectors.ticketSales.moveToTicketButton);
+ await page.expectURL(`ticket/${targetTicketId}/sale`);
+ });
+
+ it('should confirm the original ticket received the line', async() => {
+ const expectedLines = 4;
+ await page.waitForNumberOfElements(selectors.ticketSales.saleLine, expectedLines);
+ const result = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(result).toEqual(expectedLines);
+ });
+
+ it(`should throw an error when attempting to create a ticket for an inactive client`, async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
+ await page.waitToClick(selectors.ticketSales.transferSaleButton);
+ await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(`You can't create a ticket for an inactive client`);
+
+ await page.closePopup();
+ });
+
+ it('should go now to the ticket sales section of an active, not frozen client', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.goBackToModuleIndexButton);
+ await page.accessToSearchResult('13');
+ await page.accessToSection('ticket.card.sale');
+ });
+
+ it(`should select all sales, tranfer them to a new ticket and delete the sender ticket as it would've been left empty`, async() => {
+ const senderTicketId = '13';
+
+ await page.waitToClick(selectors.ticketSales.selectAllSalesCheckbox);
+ await page.waitToClick(selectors.ticketSales.transferSaleButton);
+ await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
+ await page.evaluate((selector, ticketId) => {
+ return document.querySelector(selector).innerText.toLowerCase().indexOf(`#${ticketId}`) == -1;
+ }, selectors.ticketDescriptor.id, senderTicketId);
+ await page.waitForState('ticket.card.sale');
+ });
+
+ it('should confirm the new ticket received the line', async() => {
+ const expectedLines = 1;
+ const result = await page.countElement(selectors.ticketSales.saleLine);
+
+ expect(result).toEqual(expectedLines);
+ });
+
+ it('should check the first sale reserved icon isnt visible', async() => {
+ const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon);
+
+ expect(result).toBeFalsy();
+ });
+
+ it('should mark the first sale as reserved', async() => {
+ await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
+
+ await page.waitToClick(selectors.ticketSales.moreMenu);
+ await page.waitToClick(selectors.ticketSales.moreMenuReserve);
+ await page.closePopup();
+ await page.waitForClassNotPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide');
+ const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon);
+
+ expect(result).toBeTruthy();
+ });
+
+ it('should unmark the first sale as reserved', async() => {
+ await page.waitToClick(selectors.ticketSales.moreMenu);
+ await page.waitToClick(selectors.ticketSales.moreMenuUnmarkReseved);
+ await page.waitForClassPresent(selectors.ticketSales.firstSaleReservedIcon, 'ng-hide');
+ const result = await page.isVisible(selectors.ticketSales.firstSaleReservedIcon);
+
+ expect(result).toBeFalsy();
+ });
+
+ it('should log in as Production role and go to a target ticket summary', async() => {
+ await page.loginAndModule('production', 'ticket');
+ await page.accessToSearchResult('13');
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should check the ticket is deleted`, async() => {
+ await page.waitForSelector(selectors.ticketDescriptor.isDeletedIcon);
+ });
+});
diff --git a/e2e/paths/05-ticket/01_observations.spec.js b/e2e/paths/05-ticket/01_observations.spec.js
new file mode 100644
index 000000000..cf37f9ff1
--- /dev/null
+++ b/e2e/paths/05-ticket/01_observations.spec.js
@@ -0,0 +1,50 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Create notes path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('5');
+ await page.accessToSection('ticket.card.observation');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should create a new note', async() => {
+ await page.waitToClick(selectors.ticketNotes.addNoteButton);
+ await page.autocompleteSearch(selectors.ticketNotes.firstNoteType, 'ItemPicker');
+ await page.write(selectors.ticketNotes.firstDescription, 'description');
+ await page.waitToClick(selectors.ticketNotes.submitNotesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the note is the expected one', async() => {
+ await page.reloadSection('ticket.card.observation');
+ const result = await page
+ .waitToGetProperty(selectors.ticketNotes.firstNoteType, 'value');
+
+ expect(result).toEqual('ItemPicker');
+
+ const firstDescription = await page
+ .waitToGetProperty(selectors.ticketNotes.firstDescription, 'value');
+
+ expect(firstDescription).toEqual('description');
+ });
+
+ it('should delete the note', async() => {
+ await page.waitToClick(selectors.ticketNotes.firstNoteRemoveButton);
+ await page.waitToClick(selectors.ticketNotes.submitNotesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+});
diff --git a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
new file mode 100644
index 000000000..4e8005043
--- /dev/null
+++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js
@@ -0,0 +1,32 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket expeditions and log path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('production', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.expedition');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => {
+ await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
+ await page.waitToClick(selectors.ticketExpedition.deleteExpeditionButton);
+ await page.waitToClick(selectors.globalItems.acceptButton);
+ await page.reloadSection('ticket.card.expedition');
+
+ await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
+ const result = await page
+ .countElement(selectors.ticketExpedition.expeditionRow);
+
+ expect(result).toEqual(6);
+ });
+});
diff --git a/e2e/paths/05-ticket/04_packages.spec.js b/e2e/paths/05-ticket/04_packages.spec.js
new file mode 100644
index 000000000..1e6a0a173
--- /dev/null
+++ b/e2e/paths/05-ticket/04_packages.spec.js
@@ -0,0 +1,78 @@
+import getBrowser from '../../helpers/puppeteer';
+
+const $ = {
+ firstPackage: 'vn-autocomplete[label="Package"]',
+ firstQuantity: 'vn-ticket-package vn-horizontal:nth-child(1) vn-input-number[ng-model="package.quantity"]',
+ firstRemovePackageButton: 'vn-icon-button[vn-tooltip="Remove package"]',
+ addPackageButton: 'vn-icon-button[vn-tooltip="Add package"]',
+ savePackagesButton: `button[type=submit]`
+};
+
+describe('Ticket Create packages path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.package');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should attempt create a new package but receive an error if package is blank`, async() => {
+ await page.waitToClick($.firstRemovePackageButton);
+ await page.waitToClick($.addPackageButton);
+ await page.write($.firstQuantity, '99');
+ await page.waitToClick($.savePackagesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Package cannot be blank');
+ });
+
+ it(`should delete the first package and receive and error to save a new one with blank quantity`, async() => {
+ await page.clearInput($.firstQuantity);
+ await page.autocompleteSearch($.firstPackage, 'Container medical box 100cm');
+ await page.waitToClick($.savePackagesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Some fields are invalid');
+ });
+
+ it(`should confirm the quantity input isn't invalid yet`, async() => {
+ const result = await page
+ .evaluate(selector => {
+ return document.querySelector(`${selector} input`).checkValidity();
+ }, $.firstQuantity);
+
+ expect(result).toBeTruthy();
+ });
+
+ it(`should create a new package with correct data`, async() => {
+ await page.clearInput($.firstQuantity);
+ await page.write($.firstQuantity, '-99');
+ await page.waitToClick($.savePackagesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the first select is the expected one`, async() => {
+ await page.reloadSection('ticket.card.package');
+ await page.waitForTextInField($.firstPackage, 'Container medical box 100cm');
+ const result = await page.waitToGetProperty($.firstPackage, 'value');
+
+ expect(result).toEqual('Container medical box 100cm');
+ });
+
+ it(`should confirm quantity is just a number and the string part was ignored by the imput number`, async() => {
+ await page.waitForTextInField($.firstQuantity, '-99');
+ const result = await page.waitToGetProperty($.firstQuantity, 'value');
+
+ expect(result).toEqual('-99');
+ });
+});
diff --git a/e2e/paths/05-ticket/05_tracking_state.spec.js b/e2e/paths/05-ticket/05_tracking_state.spec.js
new file mode 100644
index 000000000..5cfc1c9d4
--- /dev/null
+++ b/e2e/paths/05-ticket/05_tracking_state.spec.js
@@ -0,0 +1,72 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Create new tracking state path', () => {
+ let browser;
+ let page;
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ describe('as production', () => {
+ it('should log into the ticket 1 tracking', async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('production', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.tracking.index');
+ });
+
+ it('should access to the create state view by clicking the create floating button', async() => {
+ await page.waitToClick(selectors.ticketTracking.createStateButton);
+ await page.waitForSelector(selectors.createStateView.state, {visible: true});
+ await page.waitForState('ticket.card.tracking.edit');
+ });
+
+ it(`should create a new state`, async() => {
+ await page.autocompleteSearch(selectors.createStateView.state, 'OK');
+ await page.waitToClick(selectors.createStateView.saveStateButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+ });
+
+ describe('as salesPerson', () => {
+ it('should now log into the ticket 1 tracking', async() => {
+ await page.loginAndModule('salesPerson', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.tracking.index');
+ });
+
+ it('should now access to the create state view by clicking the create floating button', async() => {
+ await page.waitForSelector('.vn-popup', {hidden: true});
+ await page.waitToClick(selectors.ticketTracking.createStateButton);
+ await page.waitForState('ticket.card.tracking.edit');
+ });
+
+ it(`should attemp to create an state for which salesPerson doesn't have permissions`, async() => {
+ await page.autocompleteSearch(selectors.createStateView.state, 'Encajado');
+ await page.waitToClick(selectors.createStateView.saveStateButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(`You don't have enough privileges`);
+ });
+
+ it(`should make sure the worker gets autocomplete uppon selecting the assigned state`, async() => {
+ await page.autocompleteSearch(selectors.createStateView.state, 'asignado');
+ const result = await page
+ .waitToGetProperty(selectors.createStateView.worker, 'value');
+
+ expect(result).toEqual('salesperson');
+ });
+
+ it(`should succesfully create a valid state`, async() => {
+ await page.waitToClick(selectors.createStateView.saveStateButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+ });
+});
diff --git a/e2e/paths/05-ticket/06_basic_data_steps.spec.js b/e2e/paths/05-ticket/06_basic_data_steps.spec.js
new file mode 100644
index 000000000..77f0e0459
--- /dev/null
+++ b/e2e/paths/05-ticket/06_basic_data_steps.spec.js
@@ -0,0 +1,143 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Edit basic data path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('11');
+ await page.accessToSection('ticket.card.basicData.stepOne');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should confirm the zone autocomplete is disabled unless your role is productionBoss`, async() => {
+ await page.waitForSelector(selectors.ticketBasicData.zone, {});
+ const disabled = await page.evaluate(selector => {
+ return document.querySelector(selector).disabled;
+ }, `${selectors.ticketBasicData.zone} input`);
+
+ expect(disabled).toBeTruthy();
+ });
+
+ it(`should now log as productionBoss to perform the rest of the tests`, async() => {
+ await page.loginAndModule('productionBoss', 'ticket');
+ await page.accessToSearchResult('11');
+ await page.accessToSection('ticket.card.basicData.stepOne');
+ });
+
+ it(`should confirm the zone autocomplete is enabled for the role productionBoss`, async() => {
+ await page.waitForSpinnerLoad();
+ await page.waitForSelector(selectors.ticketBasicData.zone);
+ const disabled = await page.evaluate(selector => {
+ return document.querySelector(selector).disabled;
+ }, `${selectors.ticketBasicData.zone} input`);
+
+ expect(disabled).toBeFalsy();
+ });
+
+ it(`should check the zone is for Gotham247`, async() => {
+ let zone = await page
+ .waitToGetProperty(selectors.ticketBasicData.zone, 'value');
+
+ expect(zone).toContain('Zone 247 A');
+ });
+
+ it(`should edit the ticket agency then check there are no zones for it`, async() => {
+ await page.autocompleteSearch(selectors.ticketBasicData.agency, 'Super-Man delivery');
+ let emptyZone = await page
+ .expectPropertyValue(selectors.ticketBasicData.zone, 'value', '');
+
+ expect(emptyZone).toBeTruthy();
+ });
+
+ it(`should edit the ticket zone then check the agency is for the new zone`, async() => {
+ await page.clearInput(selectors.ticketBasicData.agency);
+ await page.autocompleteSearch(selectors.ticketBasicData.zone, 'Zone expensive A');
+ let zone = await page
+ .waitToGetProperty(selectors.ticketBasicData.agency, 'value');
+
+ expect(zone).toContain('Gotham247Expensive');
+ });
+
+ it(`should click next`, async() => {
+ await page.waitToClick(selectors.ticketBasicData.nextStepButton);
+ await page.waitForState('ticket.card.basicData.stepTwo');
+ });
+
+ it(`should have a price diference`, async() => {
+ const result = await page
+ .waitToGetProperty(selectors.ticketBasicData.stepTwoTotalPriceDif, 'innerText');
+
+ expect(result).toContain('-€228.25');
+ });
+
+ it(`should select a new reason for the changes made then click on finalize`, async() => {
+ await page.waitToClick(selectors.ticketBasicData.chargesReason);
+ await page.waitToClick(selectors.ticketBasicData.finalizeButton);
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should not find ticket`, async() => {
+ await page.doSearch('29');
+ const count = await page.countElement(selectors.ticketsIndex.searchResult);
+
+ expect(count).toEqual(0);
+ });
+
+ it(`should split ticket without negatives`, async() => {
+ const newAgency = 'Gotham247';
+ const newDate = Date.vnNew();
+ newDate.setDate(newDate.getDate() - 1);
+
+ await page.accessToSearchResult('14');
+ await page.accessToSection('ticket.card.basicData.stepOne');
+
+ await page.autocompleteSearch(selectors.ticketBasicData.agency, newAgency);
+ await page.pickDate(selectors.ticketBasicData.shipped, newDate);
+
+ await page.waitToClick(selectors.ticketBasicData.nextStepButton);
+
+ await page.waitToClick(selectors.ticketBasicData.finalizeButton);
+
+ await page.waitForState('ticket.card.summary');
+
+ const newTicketAgency = await page
+ .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryAgency, 'innerText');
+ const newTicketDate = await page
+ .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText');
+
+ expect(newAgency).toEqual(newTicketAgency);
+ expect(newTicketDate).toContain(newDate.getDate());
+ });
+
+ it(`should new ticket have sale of old ticket`, async() => {
+ await page.accessToSection('ticket.card.sale');
+ await page.waitForState('ticket.card.sale');
+
+ const item = await page.waitToGetProperty(selectors.ticketSales.firstSaleId, 'innerText');
+
+ expect(item).toEqual('4');
+ });
+
+ it(`should old ticket have old date and agency`, async() => {
+ const oldDate = Date.vnNew();
+ const oldAgency = 'Super-Man delivery';
+
+ await page.accessToSearchResult('14');
+
+ const oldTicketAgency = await page
+ .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryAgency, 'innerText');
+ const oldTicketDate = await page
+ .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText');
+
+ expect(oldTicketAgency).toEqual(oldAgency);
+ expect(oldTicketDate).toContain(oldDate.getDate());
+ });
+});
diff --git a/e2e/paths/05-ticket/08_components.spec.js b/e2e/paths/05-ticket/08_components.spec.js
new file mode 100644
index 000000000..ab2aa85b2
--- /dev/null
+++ b/e2e/paths/05-ticket/08_components.spec.js
@@ -0,0 +1,30 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket List components path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.components');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should confirm the total base is correct', async() => {
+ const name = 'Base €';
+ const minLength = name.length;
+
+ await page.waitPropertyLength(selectors.ticketComponents.base, 'innerText', minLength);
+ const base = await page.waitToGetProperty(selectors.ticketComponents.base, 'innerText');
+
+ expect(base).toContain('Base');
+ expect(base.length).toBeGreaterThan(minLength);
+ });
+});
diff --git a/e2e/paths/05-ticket/09_weekly.spec.js b/e2e/paths/05-ticket/09_weekly.spec.js
new file mode 100644
index 000000000..370d422e6
--- /dev/null
+++ b/e2e/paths/05-ticket/09_weekly.spec.js
@@ -0,0 +1,123 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket descriptor path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('buyerBoss', 'ticket');
+ await page.accessToSection('ticket.weekly.index');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should count the amount of tickets in the turns section', async() => {
+ const result = await page.countElement(selectors.ticketsIndex.weeklyTicket);
+
+ expect(result).toEqual(6);
+ });
+
+ it('should go back to the ticket index then search and access a ticket summary', async() => {
+ await page.accessToSection('ticket.index');
+ await page.accessToSearchResult('33');
+ });
+
+ it('should add the ticket to thursday turn using the descriptor more menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
+ await page.waitToClick(selectors.ticketDescriptor.thursdayButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Current ticket deleted and added to shift');
+ });
+
+ it('should again click on the Tickets button of the top bar menu', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.ticketsButton);
+ await page.waitForState('ticket.index');
+ });
+
+ it('should confirm the ticket 33 was added to thursday', async() => {
+ await page.accessToSection('ticket.weekly.index');
+ const result = await page.waitToGetProperty(selectors.ticketsIndex.thirdWeeklyTicket, 'value');
+
+ expect(result).toEqual('Thursday');
+ });
+
+ it('should click on the Tickets button of the top bar menu once more', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.ticketsButton);
+ await page.waitForState('ticket.index');
+ });
+
+ it('should now search for the ticket 33', async() => {
+ await page.accessToSearchResult('33');
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it('should add the ticket to saturday turn using the descriptor more menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuAddToTurn);
+ await page.waitToClick(selectors.ticketDescriptor.saturdayButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Current ticket deleted and added to shift');
+ });
+
+ it('should click on the Tickets button of the top bar menu once again', async() => {
+ await page.waitToClick(selectors.globalItems.applicationsMenuButton);
+ await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
+ await page.waitToClick(selectors.globalItems.ticketsButton);
+ await page.waitForState('ticket.index');
+ });
+
+ it('should confirm the ticket 33 was added on saturday', async() => {
+ await page.accessToSection('ticket.weekly.index');
+ await page.waitForTimeout(5000);
+
+ const result = await page.waitToGetProperty(selectors.ticketsIndex.thirdWeeklyTicket, 'value');
+
+ expect(result).toEqual('Saturday');
+ });
+
+ it('should now search for the weekly ticket 33', async() => {
+ await page.doSearch('33');
+ const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult);
+
+ expect(nResults).toEqual(2);
+ });
+
+ it('should delete the weekly ticket 33', async() => {
+ await page.waitToClick(selectors.ticketsIndex.firstWeeklyTicketDeleteIcon);
+ await page.waitToClick(selectors.ticketsIndex.acceptDeleteTurn);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the sixth weekly ticket was deleted', async() => {
+ await page.doSearch();
+ const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult);
+
+ expect(nResults).toEqual(6);
+ });
+
+ it('should update the agency then remove it afterwards', async() => {
+ await page.autocompleteSearch(selectors.ticketsIndex.firstWeeklyTicketAgency, 'Gotham247');
+ let message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+
+ await page.clearInput(selectors.ticketsIndex.firstWeeklyTicketAgency);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+});
diff --git a/e2e/paths/05-ticket/10_request.spec.js b/e2e/paths/05-ticket/10_request.spec.js
new file mode 100644
index 000000000..1b580aec2
--- /dev/null
+++ b/e2e/paths/05-ticket/10_request.spec.js
@@ -0,0 +1,77 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket purchase request path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesPerson', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.request.index');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should add a new request', async() => {
+ await page.waitToClick(selectors.ticketRequests.addRequestButton);
+ await page.write(selectors.ticketRequests.descriptionInput, 'New stuff');
+ await page.write(selectors.ticketRequests.quantity, '9');
+ await page.autocompleteSearch(selectors.ticketRequests.atender, 'buyerNick');
+ await page.write(selectors.ticketRequests.price, '999');
+ await page.waitToClick(selectors.ticketRequests.saveButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should have been redirected to the request index', async() => {
+ await page.waitForState('ticket.card.request.index');
+ });
+
+ it(`should edit the third request quantity as it's state is still new`, async() => {
+ await page.write(selectors.ticketRequests.thirdRequestQuantity, '9');
+ await page.keyboard.press('Enter');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should check the new request was added', async() => {
+ await page.reloadSection('ticket.card.request.index');
+ const result = await page.waitToGetProperty(selectors.ticketRequests.thirdRequestQuantity, 'value');
+
+ expect(result).toEqual('99');
+ });
+
+ it(`should check the first request can't be edited as its state is different to new`, async() => {
+ await page.waitForClassPresent(selectors.ticketRequests.firstRequestQuantity, 'disabled');
+ const result = await page.isDisabled(selectors.ticketRequests.firstRequestQuantity);
+
+ expect(result).toBe(true);
+ });
+
+ it(`should check the second request can't be edited as its state is different to new`, async() => {
+ await page.waitForClassPresent(selectors.ticketRequests.secondRequestQuantity, 'disabled');
+ const result = await page.isDisabled(selectors.ticketRequests.secondRequestQuantity);
+
+ expect(result).toBe(true);
+ });
+
+ it('should delete the added request', async() => {
+ await page.waitToClick(selectors.ticketRequests.thirdRemoveRequestButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should check the request was deleted', async() => {
+ await page.reloadSection('ticket.card.request.index');
+ await page.waitForSelector(selectors.ticketRequests.addRequestButton);
+ await page.waitForSelector(selectors.ticketRequests.thirdDescription, {hidden: true});
+ });
+});
diff --git a/e2e/paths/05-ticket/12_descriptor.spec.js b/e2e/paths/05-ticket/12_descriptor.spec.js
new file mode 100644
index 000000000..95a114c45
--- /dev/null
+++ b/e2e/paths/05-ticket/12_descriptor.spec.js
@@ -0,0 +1,148 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket descriptor path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesperson', 'ticket');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ describe('Delete ticket', () => {
+ it('should search for an specific ticket', async() => {
+ await page.accessToSearchResult('18');
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should update the shipped hour using the descriptor menu`, async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuChangeShippedHour);
+ await page.pickTime(selectors.ticketDescriptor.changeShippedHour, '08:15');
+ await page.waitToClick(selectors.ticketDescriptor.acceptChangeHourButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Shipped hour updated');
+ });
+
+ it(`should confirm the ticket descriptor shows the correct shipping hour`, async() => {
+ await page.waitForState('ticket.card.summary');
+ const result = await page
+ .waitToGetProperty(selectors.ticketDescriptor.descriptorDeliveryDate, 'innerText');
+
+ expect(result).toContain('08:15');
+ });
+
+ it('should delete the ticket using the descriptor menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
+ await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Ticket deleted. You can undo this action within the first hour');
+ });
+
+ it('should have been relocated to the ticket index', async() => {
+ await page.waitForState('ticket.index');
+ });
+
+ it(`should search for the deleted ticket and check the deletedTicket icon and it's date`, async() => {
+ await page.write(selectors.ticketsIndex.topbarSearch, '18');
+ await page.waitToClick(selectors.globalItems.searchButton);
+ await page.waitForState('ticket.card.summary');
+ await page.isVisible(selectors.ticketDescriptor.isDeletedIcon);
+ const result = await page.waitToGetProperty(selectors.ticketsIndex.searchResultDate, 'innerText');
+
+ expect(result).toContain(2000);
+ });
+ });
+
+ describe('Restore ticket', () => {
+ it('should restore the ticket using the descriptor menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuRestoreTicket);
+ await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
+ await page.waitForState('ticket.card.summary');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+ });
+
+ describe('Make invoice', () => {
+ it('should login as administrative role then search for a ticket', async() => {
+ const invoiceableTicketId = '14';
+
+ await page.loginAndModule('administrative', 'ticket');
+ await page.accessToSearchResult(invoiceableTicketId);
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should make sure the ticket doesn't have an invoiceOutFk yet`, async() => {
+ const result = await page
+ .waitToGetProperty(selectors.ticketSummary.invoiceOutRef, 'innerText');
+
+ expect(result).toEqual('-');
+ });
+
+ it('should invoice the ticket using the descriptor menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitForContentLoaded();
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuMakeInvoice);
+ await page.waitToClick(selectors.ticketDescriptor.acceptInvoiceOutButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Ticket invoiced');
+ });
+
+ it(`should make sure the ticket summary have an invoiceOutFk`, async() => {
+ await page.waitForTextInElement(selectors.ticketSummary.invoiceOutRef, 'T4444445');
+ const result = await page.waitToGetProperty(selectors.ticketSummary.invoiceOutRef, 'innerText');
+
+ expect(result).toEqual('T4444445');
+ });
+
+ it(`should regenerate the invoice using the descriptor menu`, async() => {
+ const expectedMessage = 'The invoice PDF document has been regenerated';
+
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitForContentLoaded();
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuRegenerateInvoice);
+ await page.respondToDialog('accept');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(expectedMessage);
+ });
+ });
+
+ describe('SMS', () => {
+ it('should send the payment SMS using the descriptor menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
+ await page.waitForSelector(selectors.ticketDescriptor.SMStext);
+ await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128);
+ await page.waitToClick(selectors.ticketDescriptor.sendSMSbutton);
+ const message = await page.waitForSnackbar();
+
+ expect(message).toBeDefined();
+ });
+
+ it('should send the import SMS using the descriptor menu', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSMSOptions);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuSendImportSms);
+ await page.waitForSelector(selectors.ticketDescriptor.SMStext);
+ await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 144);
+ await page.waitToClick(selectors.ticketDescriptor.sendSMSbutton);
+ const message = await page.waitForSnackbar();
+
+ expect(message).toBeDefined();
+ });
+ });
+});
diff --git a/e2e/paths/05-ticket/13_services.spec.js b/e2e/paths/05-ticket/13_services.spec.js
new file mode 100644
index 000000000..50df23582
--- /dev/null
+++ b/e2e/paths/05-ticket/13_services.spec.js
@@ -0,0 +1,127 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket services path', () => {
+ let browser;
+ let page;
+ const invoicedTicketId = '1';
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ describe('as employee', () => {
+ it('should log in as employee, search for an invoice and get to services', async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult(invoicedTicketId);
+ await page.accessToSection('ticket.card.service');
+ });
+
+ it('should find the add descripton button disabled for this user role', async() => {
+ await page.waitForClassPresent(selectors.ticketService.firstAddServiceTypeButton, 'disabled');
+ await page.waitToClick(selectors.ticketService.addServiceButton);
+ await page.waitForSelector(selectors.ticketService.firstAddServiceTypeButton);
+ const disabled = await page.isDisabled(selectors.ticketService.firstAddServiceTypeButton);
+
+ expect(disabled).toBe(true);
+ });
+
+ it('should receive an error if you attempt to save a service without access rights', async() => {
+ await page.clearInput(selectors.ticketService.firstPrice);
+ await page.write(selectors.ticketService.firstPrice, '999');
+ await page.waitToClick(selectors.ticketService.saveServiceButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(`The current ticket can't be modified`);
+ });
+ });
+
+ describe('as administrative', () => {
+ let editableTicketId = '16';
+ it('should navigate to the services of a target ticket', async() => {
+ await page.loginAndModule('administrative', 'ticket');
+ await page.accessToSearchResult(editableTicketId);
+ await page.accessToSection('ticket.card.service');
+ });
+
+ it('should click on the add button to prepare the form to create a new service', async() => {
+ await page.waitToClick(selectors.ticketService.addServiceButton);
+ const result = await page
+ .isVisible(selectors.ticketService.firstServiceType);
+
+ expect(result).toBeTruthy();
+ });
+
+ it('should receive an error if you attempt to save it with empty fields', async() => {
+ await page.waitToClick(selectors.ticketService.saveServiceButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(`can't be blank`);
+ });
+
+ it('should click on the add new service type to open the dialog', async() => {
+ await page.waitToClick(selectors.ticketService.firstAddServiceTypeButton);
+ await page.waitForSelector('.vn-dialog.shown');
+ const result = await page.isVisible(selectors.ticketService.newServiceTypeName);
+
+ expect(result).toBeTruthy();
+ });
+
+ it('should receive an error if service type is empty on submit', async() => {
+ await page.waitToClick(selectors.ticketService.saveServiceTypeButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain(`Name can't be empty`);
+ });
+
+ it('should create a new service type then add price then create the service', async() => {
+ await page.write(selectors.ticketService.newServiceTypeName, 'Documentos');
+ await page.waitToClick(selectors.ticketService.saveServiceTypeButton);
+ await page.write(selectors.ticketService.firstPrice, '999');
+ await page.waitToClick(selectors.ticketService.saveServiceButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the service description was created correctly', async() => {
+ await page.reloadSection('ticket.card.service');
+ const result = await page
+ .waitToGetProperty(selectors.ticketService.firstServiceType, 'value');
+
+ expect(result).toEqual('Documentos');
+ });
+
+ it('should confirm the service quantity was created correctly', async() => {
+ const result = await page
+ .waitToGetProperty(selectors.ticketService.firstQuantity, 'value');
+
+ expect(result).toEqual('1');
+ });
+
+ it('should confirm the service price was created correctly', async() => {
+ const result = await page
+ .waitToGetProperty(selectors.ticketService.firstPrice, 'value');
+
+ expect(result).toEqual('999');
+ });
+
+ it('should delete the service', async() => {
+ await page.waitToClick(selectors.ticketService.fistDeleteServiceButton);
+ await page.waitForNumberOfElements(selectors.ticketService.serviceLine, 0);
+ await page.waitToClick(selectors.ticketService.saveServiceButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should confirm the service was removed`, async() => {
+ await page.reloadSection('ticket.card.service');
+ const nResults = await page.countElement(selectors.ticketService.serviceLine);
+
+ expect(nResults).toEqual(0);
+ });
+ });
+});
diff --git a/e2e/paths/05-ticket/14_create_ticket.spec.js b/e2e/paths/05-ticket/14_create_ticket.spec.js
new file mode 100644
index 000000000..1f9c0c40a
--- /dev/null
+++ b/e2e/paths/05-ticket/14_create_ticket.spec.js
@@ -0,0 +1,69 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket create path', () => {
+ let browser;
+ let page;
+ let nextMonth = Date.vnNew();
+ nextMonth.setMonth(nextMonth.getMonth() + 1);
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('salesPerson', 'ticket');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should open the new ticket form', async() => {
+ await page.waitToClick(selectors.ticketsIndex.newTicketButton);
+ await page.waitForState('ticket.create');
+ });
+
+ it('should succeed to create a ticket', async() => {
+ await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
+ await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
+ await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse Two');
+ await page.autocompleteSearch(selectors.createTicketView.agency, 'Gotham247');
+ await page.waitToClick(selectors.createTicketView.createButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should check the url is now the summary of the ticket', async() => {
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it('should again open the new ticket form', async() => {
+ await page.waitToClick(selectors.globalItems.returnToModuleIndexButton);
+ await page.waitToClick(selectors.ticketsIndex.newTicketButton);
+ await page.waitForState('ticket.create');
+ });
+
+ it('should succeed to create another ticket for the same client', async() => {
+ await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
+ await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
+ await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
+ await page.autocompleteSearch(selectors.createTicketView.agency, 'Gotham247');
+ await page.waitToClick(selectors.createTicketView.createButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should check the url is now the summary of the created ticket', async() => {
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it('should delete the current ticket', async() => {
+ await page.waitToClick(selectors.ticketDescriptor.moreMenu);
+ await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
+ await page.waitToClick(selectors.ticketDescriptor.acceptDialog);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Ticket deleted. You can undo this action within the first hour');
+ });
+});
diff --git a/e2e/paths/05-ticket/15_create_ticket_from_client.spec.js b/e2e/paths/05-ticket/15_create_ticket_from_client.spec.js
new file mode 100644
index 000000000..51ead6461
--- /dev/null
+++ b/e2e/paths/05-ticket/15_create_ticket_from_client.spec.js
@@ -0,0 +1,37 @@
+import getBrowser from '../../helpers/puppeteer';
+
+const $ = {
+ form: 'vn-ticket-create-card',
+ moreMenu: 'vn-client-descriptor vn-icon-button[icon=more_vert]',
+ simpleTicketButton: '.vn-menu [name="simpleTicket"]'
+};
+
+describe('Ticket create from client path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'client');
+ await page.accessToSearchResult('Petter Parker');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should create simple ticket and check if the client details are the expected ones', async() => {
+ await page.waitToClick($.moreMenu);
+ await page.waitToClick($.simpleTicketButton);
+ await page.waitForState('ticket.create');
+
+ const values = {
+ client: 'Petter Parker',
+ address: 'Petter Parker'
+ };
+ const formValues = await page.fetchForm($.form, Object.keys(values));
+
+ expect(formValues).toEqual(values);
+ });
+});
diff --git a/e2e/paths/05-ticket/16_summary.spec.js b/e2e/paths/05-ticket/16_summary.spec.js
new file mode 100644
index 000000000..a6017e454
--- /dev/null
+++ b/e2e/paths/05-ticket/16_summary.spec.js
@@ -0,0 +1,108 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Summary path', () => {
+ let browser;
+ let page;
+ const ticketId = '20';
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should navigate to the target ticket summary section', async() => {
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult(ticketId);
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it(`should display details from the ticket and it's client on the top of the header`, async() => {
+ await page.waitForTextInElement(selectors.ticketSummary.header, 'Bruce Banner');
+ const result = await page.waitToGetProperty(selectors.ticketSummary.header, 'innerText');
+
+ expect(result).toContain(`Ticket #${ticketId}`);
+ expect(result).toContain('Bruce Banner (1109)');
+ expect(result).toContain('Somewhere in Thailand');
+ });
+
+ it('should display ticket details', async() => {
+ let result = await page
+ .waitToGetProperty(selectors.ticketSummary.state, 'innerText');
+
+ expect(result).toContain('Arreglar');
+ });
+
+ it('should display delivery details', async() => {
+ let result = await page
+ .waitToGetProperty(selectors.ticketSummary.route, 'innerText');
+
+ expect(result).toContain('3');
+ });
+
+ it('should display the ticket total', async() => {
+ let result = await page
+ .waitToGetProperty(selectors.ticketSummary.total, 'innerText');
+
+ expect(result).toContain('€155.54');
+ });
+
+ it('should display the ticket line(s)', async() => {
+ let result = await page
+ .waitToGetProperty(selectors.ticketSummary.firstSaleItemId, 'innerText');
+
+ expect(result).toContain('2');
+ });
+
+ it(`should click on the first sale ID to make the item descriptor visible`, async() => {
+ await page.waitToClick(selectors.ticketSummary.firstSaleItemId);
+ await page.waitImgLoad(selectors.ticketSummary.firstSaleDescriptorImage);
+ const visible = await page.isVisible(selectors.ticketSummary.itemDescriptorPopover);
+
+ expect(visible).toBeTruthy();
+ });
+
+ it(`should check the url for the item diary link of the descriptor is for the right item id`, async() => {
+ await page.waitForSelector(selectors.ticketSummary.itemDescriptorPopoverItemDiaryButton, {visible: true});
+ });
+
+ it('should log in as production then navigate to the summary of the same ticket', async() => {
+ await page.loginAndModule('production', 'ticket');
+ await page.accessToSearchResult(ticketId);
+ await page.waitForState('ticket.card.summary');
+ });
+
+ it('should set the ticket state to OK using the top right button', async() => {
+ const searchValue = 'OK';
+ await page.waitToClick(selectors.ticketSummary.stateButton);
+ await page.write(selectors.ticketSummary.stateAutocomplete, searchValue);
+ try {
+ await page.waitForFunction(text => {
+ const element = document.querySelector('li.active');
+ if (element)
+ return element.innerText.toLowerCase().includes(text.toLowerCase());
+ }, {}, searchValue);
+ } catch (error) {
+ const state = await page.evaluate(() => {
+ const stateSelector = 'vn-ticket-summary vn-label-value:nth-child(1) > section > span';
+ return document.querySelector(stateSelector).value;
+ });
+ throw new Error(`${stateSelector} innerText is ${state}! ${error}`);
+ }
+ await page.keyboard.press('Enter');
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the ticket state was updated', async() => {
+ await page.waitForSpinnerLoad();
+ const result = await page.waitToGetProperty(selectors.ticketSummary.state, 'innerText');
+
+ expect(result).toContain('OK');
+ });
+});
diff --git a/e2e/paths/05-ticket/17_log.spec.js b/e2e/paths/05-ticket/17_log.spec.js
new file mode 100644
index 000000000..e1da2df44
--- /dev/null
+++ b/e2e/paths/05-ticket/17_log.spec.js
@@ -0,0 +1,34 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket log path', () => {
+ let browser;
+ let page;
+ const ticketId = '5';
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should navigate to the target ticket notes section', async() => {
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult(ticketId);
+ await page.accessToSection('ticket.card.observation');
+ await page.waitForState('ticket.card.observation');
+ });
+
+ it('should create a new note for the test', async() => {
+ await page.waitToClick(selectors.ticketNotes.addNoteButton);
+ await page.autocompleteSearch(selectors.ticketNotes.firstNoteType, 'ItemPicker');
+ await page.write(selectors.ticketNotes.firstDescription, 'description');
+ await page.waitToClick(selectors.ticketNotes.submitNotesButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+});
diff --git a/e2e/paths/05-ticket/18_index_payout.spec.js b/e2e/paths/05-ticket/18_index_payout.spec.js
new file mode 100644
index 000000000..9c5518424
--- /dev/null
+++ b/e2e/paths/05-ticket/18_index_payout.spec.js
@@ -0,0 +1,70 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+const $ = {
+ newPayment: '.vn-dialog.shown',
+ anyBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr',
+ firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable'
+};
+
+describe('Ticket index payout path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('administrative', 'ticket');
+ await page.waitForState('ticket.index');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should check the second ticket from a client and 1 of another', async() => {
+ await page.waitToClick(selectors.globalItems.searchButton);
+ await page.waitToClick(selectors.ticketsIndex.thirdTicketCheckbox);
+ await page.waitToClick(selectors.ticketsIndex.fifthTicketCheckbox);
+ await page.waitToClick(selectors.ticketsIndex.payoutButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('You cannot make a payment on account from multiple clients');
+ });
+
+ it('should search for tickets of the same client then open the payout form', async() => {
+ await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
+ await page.write(selectors.ticketsIndex.advancedSearchClient, '1101');
+ await page.keyboard.press('Enter');
+ await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 10);
+ await page.waitToClick(selectors.ticketsIndex.firstTicketCheckbox);
+ await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);
+
+ await page.waitToClick(selectors.ticketsIndex.payoutButton);
+
+ await page.waitForSelector(selectors.ticketsIndex.payoutCompany);
+ });
+
+ it('should fill the company and bank to perform a payout and check a new balance line was entered', async() => {
+ await page.fillForm($.newPayment, {
+ company: 'VNL',
+ bank: 'cash',
+ amountPaid: 100,
+ description: 'Payment',
+ viewReceipt: false
+ });
+ await page.respondToDialog('accept');
+ const message = await page.waitForSnackbar();
+
+ await page.waitToClick(selectors.globalItems.homeButton);
+ await page.selectModule('client');
+ await page.accessToSearchResult('1101');
+ await page.accessToSection('client.card.balance.index');
+ await page.waitForSelector($.anyBalanceLine);
+ const count = await page.countElement($.anyBalanceLine);
+ const reference = await page.innerText($.firstLineReference);
+
+ expect(message.isSuccess).toBeTrue();
+ expect(count).toEqual(4);
+ expect(reference).toContain('Payment');
+ });
+});
diff --git a/e2e/paths/05-ticket/19_dms.spec.js b/e2e/paths/05-ticket/19_dms.spec.js
new file mode 100644
index 000000000..be2ac4338
--- /dev/null
+++ b/e2e/paths/05-ticket/19_dms.spec.js
@@ -0,0 +1,49 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket DMS path', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.dms.index');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should import a document', async() => {
+ await page.waitToClick(selectors.ticketDms.import);
+ await page.autocompleteSearch(selectors.ticketDms.document, '1');
+ await page.waitToClick(selectors.ticketDms.saveImport);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it(`should check there's a listed document now`, async() => {
+ const result = await page.countElement(selectors.ticketDms.anyDocument);
+
+ expect(result).toEqual(1);
+ });
+
+ it('should attempt to import an existing document on this ticket', async() => {
+ await page.waitToClick(selectors.ticketDms.import);
+ await page.autocompleteSearch(selectors.ticketDms.document, '1');
+ await page.waitToClick(selectors.ticketDms.saveImport);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('This document already exists on this ticket');
+ });
+
+ it(`should check there's still one document`, async() => {
+ const result = await page.countElement(selectors.ticketDms.anyDocument);
+
+ expect(result).toEqual(1);
+ });
+});
diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js
new file mode 100644
index 000000000..ae23c9c99
--- /dev/null
+++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js
@@ -0,0 +1,50 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket expeditions', () => {
+ let browser;
+ let page;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('production', 'ticket');
+ await page.accessToSearchResult('1');
+ await page.accessToSection('ticket.card.expedition');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should move one expedition to new ticket withoute route`, async() => {
+ await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox);
+ await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
+ await page.waitToClick(selectors.ticketExpedition.moreMenuWithoutRoute);
+ await page.waitToClick(selectors.ticketExpedition.saveButton);
+ await page.waitForState('ticket.card.summary');
+ await page.accessToSection('ticket.card.expedition');
+
+ await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
+ const result = await page
+ .countElement(selectors.ticketExpedition.expeditionRow);
+
+ expect(result).toEqual(2);
+ });
+
+ it(`should move one expedition to new ticket with route`, async() => {
+ await page.waitToClick(selectors.ticketExpedition.firstSaleCheckbox);
+ await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton);
+ await page.waitToClick(selectors.ticketExpedition.moreMenuWithRoute);
+ await page.write(selectors.ticketExpedition.newRouteId, '1');
+ await page.waitToClick(selectors.ticketExpedition.saveButton);
+ await page.waitForState('ticket.card.summary');
+ await page.accessToSection('ticket.card.expedition');
+
+ await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {});
+ const result = await page
+ .countElement(selectors.ticketExpedition.expeditionRow);
+
+ expect(result).toEqual(2);
+ });
+});
diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js
new file mode 100644
index 000000000..60bb9c38d
--- /dev/null
+++ b/e2e/paths/05-ticket/21_future.spec.js
@@ -0,0 +1,99 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Future path', () => {
+ let browser;
+ let page;
+ let httpRequest;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSection('ticket.future');
+ page.on('request', req => {
+ if (req.url().includes(`Tickets/getTicketsFuture`))
+ httpRequest = req.url();
+ });
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should search with required data, check three last tickets and move to the future', async() => {
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketFuture.warehouseFk);
+ await page.waitToClick(selectors.ticketFuture.submit);
+ let message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('warehouseFk is a required argument');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketFuture.futureScopeDays);
+ await page.waitToClick(selectors.ticketFuture.submit);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('futureScopeDays is a required argument');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketFuture.originScopeDays);
+ await page.waitToClick(selectors.ticketFuture.submit);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('originScopeDays is a required argument');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ expect(httpRequest).toBeDefined();
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+
+ await page.autocompleteSearch(selectors.ticketFuture.ipt, 'H');
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ expect(httpRequest).toContain('ipt=H');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+
+ await page.clearInput(selectors.ticketFuture.ipt);
+
+ await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'H');
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ expect(httpRequest).toContain('futureIpt=H');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+
+ await page.clearInput(selectors.ticketFuture.futureIpt);
+
+ await page.autocompleteSearch(selectors.ticketFuture.state, 'Free');
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ expect(httpRequest).toContain('state=0');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+
+ await page.clearInput(selectors.ticketFuture.state);
+
+ await page.autocompleteSearch(selectors.ticketFuture.futureState, 'Free');
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ expect(httpRequest).toContain('futureState=0');
+
+ await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketFuture.state);
+ await page.clearInput(selectors.ticketFuture.futureState);
+ await page.waitToClick(selectors.ticketFuture.submit);
+
+ await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 5);
+ await page.waitToClick(selectors.ticketFuture.multiCheck);
+ await page.waitToClick(selectors.ticketFuture.firstCheck);
+ await page.waitToClick(selectors.ticketFuture.moveButton);
+ await page.waitToClick(selectors.globalItems.acceptButton);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Tickets moved successfully!');
+ });
+});
diff --git a/e2e/paths/05-ticket/22_advance.spec.js b/e2e/paths/05-ticket/22_advance.spec.js
new file mode 100644
index 000000000..0e5b5e0c3
--- /dev/null
+++ b/e2e/paths/05-ticket/22_advance.spec.js
@@ -0,0 +1,79 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Ticket Advance path', () => {
+ let browser;
+ let page;
+ let httpRequest;
+
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'ticket');
+ await page.accessToSection('ticket.advance');
+ page.on('request', req => {
+ if (req.url().includes(`Tickets/getTicketsAdvance`))
+ httpRequest = req.url();
+ });
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should search with the required data, check the first ticket and move to the present', async() => {
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketAdvance.warehouseFk);
+
+ await page.waitToClick(selectors.ticketAdvance.submit);
+ let message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('warehouseFk is a required argument');
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketAdvance.dateToAdvance);
+ await page.waitToClick(selectors.ticketAdvance.submit);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('dateToAdvance is a required argument');
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketAdvance.dateFuture);
+ await page.waitToClick(selectors.ticketAdvance.submit);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('dateFuture is a required argument');
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.waitToClick(selectors.ticketAdvance.submit);
+
+ expect(httpRequest).toBeDefined();
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'H');
+ await page.waitToClick(selectors.ticketAdvance.submit);
+
+ expect(httpRequest).toContain('futureIpt=H');
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketAdvance.futureIpt);
+ await page.waitToClick(selectors.ticketAdvance.submit);
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'H');
+ await page.waitToClick(selectors.ticketAdvance.submit);
+
+ expect(httpRequest).toContain('ipt=H');
+
+ await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
+ await page.clearInput(selectors.ticketAdvance.ipt);
+ await page.waitToClick(selectors.ticketAdvance.submit);
+
+ await page.waitToClick(selectors.ticketAdvance.firstCheck);
+ await page.waitToClick(selectors.ticketAdvance.moveButton);
+ await page.waitToClick(selectors.ticketAdvance.acceptButton);
+ message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Tickets moved successfully!');
+ });
+});
diff --git a/loopback/locale/en.json b/loopback/locale/en.json
index 86ebebc89..6756db37d 100644
--- a/loopback/locale/en.json
+++ b/loopback/locale/en.json
@@ -223,7 +223,6 @@
"Shelving not valid": "Shelving not valid",
"printerNotExists": "The printer does not exist",
"There are not picking tickets": "There are not picking tickets",
- "ticketCommercial": "The ticket {{ ticket }} for the salesperson {{ salesMan }} is in preparation. (automatically generated message)",
"This password can only be changed by the user themselves": "This password can only be changed by the user themselves",
"They're not your subordinate": "They're not your subordinate",
"InvoiceIn is already booked": "InvoiceIn is already booked",
@@ -241,10 +240,11 @@
"The height must be greater than 50cm": "The height must be greater than 50cm",
"The maximum height of the wagon is 200cm": "The maximum height of the wagon is 200cm",
"The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line",
+ "There are tickets for this area, delete them first": "There are tickets for this area, delete them first",
+ "You do not have permission to modify the booked field": "You do not have permission to modify the booked field",
"Invalid or expired verification code": "Invalid or expired verification code",
- "There are tickets for this area, delete them first": "There are tickets for this area, delete them first",
"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"
-
}
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index 8bf9f31c4..0b10d4702 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -359,7 +359,6 @@
"This workCenter is already assigned to this agency": "Este centro de trabajo ya está asignado a esta agencia",
"Select ticket or client": "Elija un ticket o un client",
"It was not able to create the invoice": "No se pudo crear la factura",
- "ticketCommercial": "El ticket {{ ticket }} para el vendedor {{ salesMan }} está en preparación. (mensaje generado automáticamente)",
"Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE",
"You can not use the same password": "No puedes usar la misma contraseña",
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario",
@@ -386,7 +385,10 @@
"type cannot be blank": "Se debe rellenar el tipo",
"There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero",
"There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén",
+ "You do not have permission to modify the booked field": "No tienes permisos para modificar el campo contabilizada",
"ticketLostExpedition": "El ticket [{{ticketId}}]({{{ticketUrl}}}) tiene la siguiente expedición perdida:{{ expeditionId }}",
"The web user's email already exists": "El correo del usuario web ya existe",
- "Sales already moved": "Ya han sido transferidas"
+ "Sales already moved": "Ya han sido transferidas",
+ "The raid information is not correct": "La información de la redada no es correcta"
}
+
diff --git a/loopback/locale/fr.json b/loopback/locale/fr.json
index 0e876f89c..9941358be 100644
--- a/loopback/locale/fr.json
+++ b/loopback/locale/fr.json
@@ -362,8 +362,9 @@
"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",
+ "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à"
-}
\ No newline at end of file
+}
diff --git a/loopback/locale/pt.json b/loopback/locale/pt.json
index e08336273..e84b30f3d 100644
--- a/loopback/locale/pt.json
+++ b/loopback/locale/pt.json
@@ -366,4 +366,4 @@
"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."
-}
\ No newline at end of file
+}
diff --git a/modules/entry/back/locale/entry/en.yml b/modules/entry/back/locale/entry/en.yml
index ab8c0fd1c..6bc2333e6 100644
--- a/modules/entry/back/locale/entry/en.yml
+++ b/modules/entry/back/locale/entry/en.yml
@@ -9,6 +9,7 @@ columns:
notes: notes
isConfirmed: confirmed
isVirtual: virtual
+ isRaid: raid
commission: commission
isOrdered: price3
created: created
diff --git a/modules/entry/back/locale/entry/es.yml b/modules/entry/back/locale/entry/es.yml
index 18bf1ca33..a892b05d2 100644
--- a/modules/entry/back/locale/entry/es.yml
+++ b/modules/entry/back/locale/entry/es.yml
@@ -9,6 +9,7 @@ columns:
notes: notas
isConfirmed: confirmado
isVirtual: virtual
+ isRaid: redada
commission: comisión
isOrdered: pedida
created: creado
diff --git a/modules/entry/back/methods/entry/filter.js b/modules/entry/back/methods/entry/filter.js
index 1e9e1f686..32a94f743 100644
--- a/modules/entry/back/methods/entry/filter.js
+++ b/modules/entry/back/methods/entry/filter.js
@@ -192,6 +192,7 @@ module.exports = Self => {
e.evaNotes observation,
e.isConfirmed,
e.isOrdered,
+ t.isRaid,
t.daysInForward,
e.commission,
e.created,
diff --git a/modules/entry/back/methods/entry/getEntry.js b/modules/entry/back/methods/entry/getEntry.js
index 4612de9a5..92be9a9dd 100644
--- a/modules/entry/back/methods/entry/getEntry.js
+++ b/modules/entry/back/methods/entry/getEntry.js
@@ -49,8 +49,8 @@ module.exports = Self => {
'isReceived',
'isDelivered',
'ref',
- 'daysInForward',
- ],
+ 'isRaid',
+ 'daysInForward'],
include: [
{
relation: 'agency',
diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js
index 8ca79f531..593f3fdcc 100644
--- a/modules/entry/back/models/entry.js
+++ b/modules/entry/back/models/entry.js
@@ -1,3 +1,4 @@
+const UserError = require('vn-loopback/util/user-error');
const LoopBackContext = require('loopback-context');
module.exports = Self => {
require('../methods/entry/filter')(Self);
@@ -18,11 +19,20 @@ module.exports = Self => {
const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
+ const loopBackContext = LoopBackContext.getCurrentContext();
+ const accessToken = {req: loopBackContext.active};
+ const hasChanges = orgData && changes;
+
+ const isBookedChanged = changes.isBooked !== undefined && orgData.isBooked !== changes.isBooked;
+
+ if (isBookedChanged) {
+ const canEditIsBooked = await Self.app.models.ACL.checkAccessAcl(accessToken, 'Entry', 'isBooked', 'READ');
+ if (!canEditIsBooked)
+ throw new UserError('You do not have permission to modify the booked field');
+ }
const observation = changes.observation || orgData.observation;
- const hasChanges = orgData && changes;
- const observationChanged = hasChanges
- && orgData.observation != observation;
+ const observationChanged = hasChanges && orgData.observation != observation;
if (observationChanged) {
let tx;
@@ -37,8 +47,7 @@ module.exports = Self => {
}
try {
- const loopbackContext = LoopBackContext.getCurrentContext();
- const userId = loopbackContext.active.accessToken.userId;
+ const userId = loopBackContext.active.accessToken.userId;
const id = changes.id || orgData.id;
const entry = await Self.app.models.Entry.findById(id, null, myOptions);
await entry.updateAttribute('observationEditorFk', userId, myOptions);
diff --git a/modules/entry/back/models/specs/entry.spec.js b/modules/entry/back/models/specs/entry.spec.js
new file mode 100644
index 000000000..15a8202c4
--- /dev/null
+++ b/modules/entry/back/models/specs/entry.spec.js
@@ -0,0 +1,97 @@
+const {models} = require('vn-loopback/server/server');
+const LoopBackContext = require('loopback-context');
+
+describe('entry_isEditable trigger', () => {
+ const activeCtx = {
+ accessToken: {userId: 5},
+ http: {
+ req: {
+ headers: {origin: 'http://localhost'}
+ }
+ }
+ };
+ const ctx = {req: activeCtx};
+ const entryId = 1;
+ let tx;
+ let options;
+ let entry;
+
+ beforeEach(async() => {
+ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
+ tx = await models.Entry.beginTransaction({});
+ options = {transaction: tx};
+
+ entry = await models.Entry.findById(entryId, null, options);
+ });
+
+ afterEach(async() => {
+ await tx.rollback();
+ });
+
+ async function prepareEntry(isBooked, typeFk) {
+ let newCreated = Date.vnNew();
+ await entry.updateAttributes({isBooked, typeFk}, options);
+ await entry.updateAttributes({dated: newCreated}, options);
+ }
+
+ it('should throw an error when entry is booked and typeFk is null', async() => {
+ let error;
+ try {
+ await prepareEntry(true, null);
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error.message).toContain(`Entry ${entryId} is not editable`);
+ });
+
+ it('should throw an error when entry is booked and typeFk is not informal', async() => {
+ let error;
+ try {
+ const type = await models.EntryType.findOne({where: {isInformal: false}}, options);
+ await prepareEntry(true, type.code);
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error.message).toContain(`Entry ${entryId} is not editable`);
+ });
+
+ it('should not throw an error when entry is booked and typeFk is informal', async() => {
+ let error;
+ try {
+ const type = await models.EntryType.findOne({where: {isInformal: true}}, options);
+ await prepareEntry(true, type.code);
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeUndefined();
+ });
+
+ it('should not throw an error when entry is not booked', async() => {
+ let error;
+ try {
+ const type = await models.EntryType.findOne({}, options);
+ await prepareEntry(false, type.code);
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeUndefined();
+ });
+
+ it('should not throw an error when @isModeInventory is true', async() => {
+ let error;
+ try {
+ await models.Application.rawSql('SET @isModeInventory = TRUE;', null, options);
+ await prepareEntry(true, null);
+ } catch (e) {
+ error = e;
+ } finally {
+ await models.Application.rawSql('SET @isModeInventory = FALSE;', null, options);
+ }
+
+ expect(error).toBeUndefined();
+ });
+});
diff --git a/modules/entry/front/descriptor/index.html b/modules/entry/front/descriptor/index.html
index 40625a4d5..957bcafb5 100644
--- a/modules/entry/front/descriptor/index.html
+++ b/modules/entry/front/descriptor/index.html
@@ -30,7 +30,7 @@
+ |
+ + Item ID + | ++ Description + | ++ Grouping price + | ++ Packing price + | ++ Min price + | ++ Started + | ++ Ended + | ++ Warehouse + | ++ |
---|---|---|---|---|---|---|---|---|---|
+ |
+
+ {{id}}
+
+ |
+
+
+
+ {{itemFk.selection.name}}
+
+
+ {{price.subName}}+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+ |
+
+ | + Identifier + | ++ Grouping + | ++ Packing + | ++ Description + | ++ Stems + | ++ Size + | ++ Type + | ++ Category + | ++ Intrastat + | ++ Origin + | ++ Buyer + | ++ Weight/Piece + | ++ Multiplier + | ++ Active + | ++ Producer + | ++ Landed + | ++ |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+ + | ++ + {{::item.id}} + + | +{{::item.grouping | dashIfEmpty}} | +{{::item.packing | dashIfEmpty}} | +
+
+
+ {{::item.subName}}+ |
+ {{::item.stems}} | +{{::item.size}} | ++ {{::item.typeName}} + | ++ {{::item.category}} + | ++ {{::item.intrastat}} + | +{{::item.origin}} | ++ + {{::item.userName}} + + | +{{::item.weightByPiece}} | +{{::item.stemMultiplier}} | +
+ |
+ {{::item.producer | dashIfEmpty}} | +{{::item.landed | date:'dd/MM/yyyy'}} | +
+ |
+
+ |
+ + Created + | ++ Item + | ++ Concept + | ++ Parking + | ++ Shelving + | ++ Etiqueta + | ++ Packing + | +
---|---|---|---|---|---|---|---|
+ |
+ {{::itemShelvingPlacementSupplyStock.created | date: 'dd/MM/yyyy'}} | ++ {{::itemShelvingPlacementSupplyStock.itemFk}} + | ++ + {{itemShelvingPlacementSupplyStock.longName}} + + | ++ {{::itemShelvingPlacementSupplyStock.parking}} + | ++ {{::itemShelvingPlacementSupplyStock.shelving}} + | ++ {{(itemShelvingPlacementSupplyStock.stock / itemShelvingPlacementSupplyStock.packing).toFixed(2)}} + | ++ {{::itemShelvingPlacementSupplyStock.packing}} + | +
Visible
+{{$ctrl.summary.visible}}
+Available
+{{$ctrl.summary.available}}
+
+
+ {{$ctrl.summary.item.description}} +
++ {{barcode.code}} +
+