Merge branch 'dev' into 7404-deprecateStockBuyedAndRelatedProcs
gitea/salix/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Pablo Natek 2024-10-01 13:05:54 +00:00
commit c6a823e6aa
119 changed files with 604 additions and 3936 deletions

View File

@ -15,9 +15,6 @@
"nickname": {
"type": "string",
"required": true
},
"display": {
"type": "boolean"
}
},
"acls": [

View File

@ -179,12 +179,12 @@ INSERT INTO `vn`.`country`(`id`, `name`, `isUeeMember`, `code`, `currencyFk`, `i
(30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2);
INSERT INTO `vn`.`warehouse`(`id`, `name`, `code`, `isComparative`, `isInventory`, `hasAvailable`, `isManaged`, `hasDms`, `hasComission`, `countryFk`, `hasProduction`, `isOrigin`, `isDestiny`)
VALUES (1, 'Warehouse One', 'ALG', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
VALUES
(1, 'Warehouse One', 'ALG', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
(2, 'Warehouse Two', NULL, 1, 1, 1, 1, 0, 1, 13, 1, 1, 0),
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0),
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1),
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0),
(6, 'Warehouse six', 'VNH', 1, 1, 1, 1, 0, 0, 1, 1, 0, 0),
(13, 'Inventory', 'inv', 1, 1, 1, 0, 0, 0, 1, 0, 0, 0),
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0);
@ -2442,7 +2442,8 @@ INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `
(1107, CONCAT(util.VN_CURDATE(), ' 10:20'), TRUE, 'middle', 1),
(1107, CONCAT(util.VN_CURDATE(), ' 14:50'), TRUE, 'out', 1);
INSERT INTO `vn`.`dmsType`(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`)
INSERT INTO `vn`.`dmsType`
(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`)
VALUES
(1, 'Facturas Recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', NULL, NULL, 'officialDoc'),
@ -2465,7 +2466,8 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `readRoleFk`, `writeRoleFk`, `code`)
(19, 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 1, 1, 'claim'),
(21, 'Entrada', 1, 1, 'entry'),
(22, 'Proveedor', 1, 1, 'supplier');
(22, 'Proveedor', 1, 1, 'supplier'),
(23, 'Termografos', 35, 35, 'thermograph');
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
VALUES
@ -2473,7 +2475,7 @@ INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `wa
(2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', util.VN_CURDATE()),
(3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', util.VN_CURDATE()),
(4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', util.VN_CURDATE()),
(5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', util.VN_CURDATE()),
(5, 23, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', util.VN_CURDATE()),
(6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', util.VN_CURDATE()),
(7, 20, '7.jpg', 'image/jpeg', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()),
(8, 20, '8.mp4', 'video/mp4', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', util.VN_CURDATE()),
@ -3939,32 +3941,38 @@ INSERT INTO vn.medicalReview
(id, workerFk, centerFk, `date`, `time`, isFit, amount, invoice, remark)
VALUES(3, 9, 2, '2000-01-01', '8:00', 1, 150.0, NULL, NULL);
INSERT INTO vn.stockBought (workerFk, bought, reserve, dated)
VALUES(35, 1.00, 1.00, '2001-01-01');
INSERT INTO vn.auctionConfig (id,conversionCoefficient,warehouseFk)
VALUES (1,0.6,6);
INSERT INTO vn.payrollComponent (id, name, isSalaryAgreed, isVariable, isException)
VALUES (1, 'Salario1', 1, 0, 0),
INSERT INTO vn.payrollComponent
(id, name, isSalaryAgreed, isVariable, isException)
VALUES
(1, 'Salario1', 1, 0, 0),
(2, 'Salario2', 1, 1, 0),
(3, 'Salario3', 1, 0, 1);
INSERT INTO vn.workerIncome (debit, credit, incomeTypeFk, paymentDate, workerFk, concept)
VALUES (1000.00, 900.00, 2, '2000-01-01', 1106, NULL),
INSERT INTO vn.workerIncome
(debit, credit, incomeTypeFk, paymentDate, workerFk, concept)
VALUES
(1000.00, 900.00, 2, '2000-01-01', 1106, NULL),
(1001.00, 800.00, 2, '2000-01-01', 1106, NULL);
INSERT INTO dipole.printer (id, description) VALUES(1, '');
INSERT INTO dipole.expedition_PrintOut (expeditionFk, ticketFk, addressFk, street, postalCode, city, shopName, isPrinted, created, printerFk, routeFk, parkingCode, truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments)
INSERT INTO dipole.printer (id, description)
VALUES(1, '');
INSERT INTO dipole.expedition_PrintOut (expeditionFk, ticketFk, addressFk, street, postalCode, city, shopName, isPrinted, created, printerFk, routeFk, parkingCode,
truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments)
VALUES(1, 1, 0, ' ', ' ', ' ', ' ', 0, '2001-01-01 00:00:00', 1, 0, ' ', ' ', 0, NULL, '', NULL, 0.000, NULL, 10, NULL, NULL, 'NCC', NULL);
INSERT INTO vn.accountDetail (id, value, accountDetailTypeFk, supplierAccountFk)
VALUES (21, 'ES12345B12345678', 3, 241),
INSERT INTO vn.accountDetail
(id, value, accountDetailTypeFk, supplierAccountFk)
VALUES
(21, 'ES12345B12345678', 3, 241),
(35, 'ES12346B12345679', 3, 241);
INSERT INTO vn.accountDetailType (id, description, code)
VALUES (1, 'IBAN', 'iban'),
INSERT INTO vn.accountDetailType
(id, description, code)
VALUES
(1, 'IBAN', 'iban'),
(2, 'SWIFT', 'swift'),
(3, 'Referencia Remesas', 'remRef'),
(4, 'Referencia Transferencias', 'trnRef'),

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` EVENT `vn`.`travel_setDelivered`
CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `vn`.`travel_setDelivered`
ON SCHEDULE EVERY 1 DAY
STARTS '2024-07-12 00:10:00.000'
ON COMPLETION PRESERVE

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_getUltimate`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`buy_getUltimate`(
vItemFk INT,
vWarehouseFk SMALLINT,
vDated DATE

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_getUltimateFromInterval`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`buy_getUltimateFromInterval`(
vItemFk INT,
vWarehouseFk SMALLINT,
vStarted DATE,

View File

@ -41,9 +41,12 @@ BEGIN
AND (pd.originFk IS NULL OR pd.originFk = i.originFk)
AND (pd.producerFk IS NULL OR pd.producerFk = i.producerFk)
AND (pd.warehouseFk IS NULL OR pd.warehouseFk = vWarehouseFk)
LEFT JOIN zoneGeo zg ON zg.id = pd.zoneGeoFk
LEFT JOIN zoneGeo zg2 ON zg2.id = address_getGeo(vAddressFk)
WHERE (pd.fromDated IS NULL OR pd.fromDated <= vShipped)
AND (pd.toDated IS NULL OR pd.toDated >= vShipped)
GROUP BY i.id;
AND (pd.zoneGeoFk IS NULL OR zg2.lft BETWEEN zg.lft AND zg.rgt)
GROUP BY itemFk;
CREATE OR REPLACE TEMPORARY TABLE tSpecialPrice
(INDEX (itemFk))

View File

@ -55,24 +55,20 @@ BEGIN
SELECT ts.saleFk,
ts.itemFk,
CAST(0 AS DECIMAL(10,0)) saleOrder,
IF(ish.visible > 0 OR iss.id, 1, 100000) *
IFNULL(p2.pickingOrder, p.pickingOrder) `order`,
TO_SECONDS(IF(iss.id,
iss.created - INTERVAL vCurrentYear YEAR,
ish.created - INTERVAL YEAR(ish.created) YEAR)) priority,
(IF(ish.visible > 0 OR iss.id, 1, 100000) *
COALESCE(p2.pickingOrder, p.pickingOrder)) `order`,
TO_SECONDS(COALESCE(iss.created, ish.created)) - TO_SECONDS(MAKEDATE(IFNULL(YEAR(iss.created), YEAR(ish.created)), 1)) priority,
CONCAT(
IF(iss.id,
CONCAT('< ', IFNULL(wk.`code`, '---'),' > '),
''),
p.`code`) COLLATE utf8_general_ci placement,
IF(iss.id, CONCAT('< ', COALESCE(wk.`code`, '---'),' > '), ''),
p.`code`
) COLLATE utf8_general_ci placement,
sh.priority shelvingPriority,
sh.code COLLATE utf8_general_ci shelving,
ish.created,
ish.visible,
IFNULL(
IF(st.code = 'previousByPacking', ish.packing, g.`grouping`),
1) `grouping`,
st.code = 'previousPrepared' isPreviousPrepared,
COALESCE(
IF(st.code = 'previousByPacking', ish.packing, g.`grouping`),1) `grouping`,
(st.code = 'previousPrepared') isPreviousPrepared,
iss.id itemShelvingSaleFk,
ts.ticketFk,
iss.id,
@ -80,11 +76,12 @@ BEGIN
iss.userFk,
ts.quantity
FROM tSale ts
LEFT JOIN (SELECT DISTINCT saleFk
LEFT JOIN (SELECT st.saleFk
FROM saleTracking st
JOIN state s ON s.id = st.stateFk
WHERE st.isChecked
AND s.semaphore = 1) st ON st.saleFk = ts.saleFk
AND s.semaphore = 1
GROUP BY st.saleFk) st ON st.saleFk = ts.saleFk
JOIN itemShelving ish ON ish.itemFk = ts.itemFk
JOIN shelving sh ON sh.code = ish.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
@ -100,7 +97,7 @@ BEGIN
LEFT JOIN parking p2 ON p2.id = sg.parkingFk
WHERE w.id = vWarehouseFk
AND NOT sc.isHideForPickers
HAVING (iss.id AND st.saleFk) OR salePreviousPrepared IS NULL;
AND ((iss.id AND st.saleFk) OR st.saleFk IS NULL);
CREATE OR REPLACE TEMPORARY TABLE tSalePlacementList2
(INDEX(saleFk), INDEX(olderPriority))

View File

@ -23,7 +23,7 @@ BEGIN
JOIN vn.ticketCollection tc ON tc.ticketFk = tob.ticketFk
LEFT JOIN vn.observationType ot ON ot.id = tob.observationTypeFk
WHERE ot.`code` = 'itemPicker'
AND tc.collectionFk = vParamFk
AND tc.collectionFk = vParamFk OR tc.ticketFk = vParamFk
)
SELECT t.id ticketFk,
IF(!(vItemPackingTypeFk <=> 'V'), cc.code, CONCAT(SUBSTRING('ABCDEFGH', tc.wagon, 1), '-', tc.`level`)) `level`,

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`collection_mergeSales`(vCollectionFk INT)
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`collection_mergeSales`(vCollectionFk INT)
BEGIN
DECLARE vDone BOOL;
DECLARE vTicketFk INT;

View File

@ -1,5 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`collection_new`(vUserFk INT, OUT vCollectionFk INT)
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`collection_new`(
vUserFk INT,
OUT vCollectionFk INT
)
BEGIN
/**
* Genera colecciones de tickets sin asignar trabajador.
@ -12,30 +15,29 @@ BEGIN
DECLARE vLinesLimit INT;
DECLARE vTicketLines INT;
DECLARE vVolumeLimit DECIMAL;
DECLARE vTicketVolume DECIMAL;
DECLARE vSizeLimit INT;
DECLARE vTicketVolume DECIMAL;
DECLARE vMaxTickets INT;
DECLARE vStateFk VARCHAR(45);
DECLARE vStateCode VARCHAR(45);
DECLARE vFirstTicketFk INT;
DECLARE vHour INT;
DECLARE vMinute INT;
DECLARE vWorkerCode VARCHAR(3);
DECLARE vWagonCounter INT DEFAULT 0;
DECLARE vWagonCounter INT DEFAULT 1;
DECLARE vTicketFk INT;
DECLARE vItemPackingTypeFk VARCHAR(1);
DECLARE vHasAssignedTickets BOOLEAN;
DECLARE vHasAssignedTickets BOOL;
DECLARE vHasUniqueCollectionTime BOOL;
DECLARE vDone INT DEFAULT FALSE;
DECLARE vLockName VARCHAR(215);
DECLARE vLockTime INT DEFAULT 30;
DECLARE vHeight INT;
DECLARE vVolume INT;
DECLARE vLiters INT;
DECLARE vLines INT;
DECLARE vTotalLines INT DEFAULT 0;
DECLARE vTotalVolume INT DEFAULT 0;
DECLARE vFreeWagonFk INT;
DECLARE vErrorNumber INT;
DECLARE vErrorMsg TEXT;
DECLARE vDone INT DEFAULT FALSE;
DECLARE c1 CURSOR FOR
DECLARE vTickets CURSOR FOR
SELECT ticketFk, `lines`, m3
FROM tmp.productionBuffer
WHERE ticketFk <> vFirstTicketFk
ORDER BY HH,
mm,
productionOrder DESC,
@ -48,26 +50,6 @@ BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
vErrorNumber = MYSQL_ERRNO,
vErrorMsg = MESSAGE_TEXT;
CALL util.debugAdd('collection_new', JSON_OBJECT(
'errorNumber', vErrorNumber,
'errorMsg', vErrorMsg,
'lockName', vLockName,
'userFk', vUserFk,
'ticketFk', vTicketFk
)); -- Tmp
IF vLockName IS NOT NULL THEN
DO RELEASE_LOCK(vLockName);
END IF;
RESIGNAL;
END;
SELECT pc.ticketTrolleyMax * o.numberOfWagons,
pc.hasUniqueCollectionTime,
w.code,
@ -78,36 +60,26 @@ BEGIN
o.trainFk,
o.linesLimit,
o.volumeLimit,
o.sizeLimit,
pc.collection_new_lockname
o.sizeLimit
INTO vMaxTickets,
vHasUniqueCollectionTime,
vWorkerCode,
vWarehouseFk,
vItemPackingTypeFk,
vStateFk,
vStateCode,
vWagons,
vTrainFk,
vLinesLimit,
vVolumeLimit,
vSizeLimit,
vLockName
FROM productionConfig pc
JOIN worker w ON w.id = vUserFk
vSizeLimit
FROM worker w
JOIN operator o ON o.workerFk = w.id
JOIN state st ON st.`code` = 'ON_PREPARATION'
JOIN operator o ON o.workerFk = vUserFk;
SET vLockName = CONCAT_WS('/',
vLockName,
vWarehouseFk,
vItemPackingTypeFk
);
IF NOT GET_LOCK(vLockName, vLockTime) THEN
CALL util.throw(CONCAT('Cannot get lock: ', vLockName));
END IF;
JOIN productionConfig pc
WHERE w.id = vUserFk;
-- Se prepara el tren, con tantos vagones como sea necesario.
CREATE OR REPLACE TEMPORARY TABLE tTrain
(wagon INT,
shelve INT,
@ -118,59 +90,60 @@ BEGIN
PRIMARY KEY(wagon, shelve))
ENGINE = MEMORY;
WHILE vWagons > vWagonCounter DO
SET vWagonCounter = vWagonCounter + 1;
INSERT INTO tTrain (wagon, shelve, liters, `lines`, height)
SELECT vWagonCounter, cv.`level` , cv.liters , cv.`lines` , cv.height
FROM collectionVolumetry cv
WHERE cv.trainFk = vTrainFk
WITH RECURSIVE wagonSequence AS (
SELECT vWagonCounter wagon
UNION ALL
SELECT wagon + 1 wagon
FROM wagonSequence
WHERE wagon < vWagonCounter + vWagons -1
)
SELECT ws.wagon, cv.`level`, cv.liters, cv.`lines`, cv.height
FROM wagonSequence ws
JOIN vn.collectionVolumetry cv ON cv.trainFk = vTrainFk
AND cv.itemPackingTypeFk = vItemPackingTypeFk;
END WHILE;
-- Esto desaparecerá cuando tengamos la table cache.ticket
CALL productionControl(vWarehouseFk, 0);
ALTER TABLE tmp.productionBuffer
ADD COLUMN liters INT,
ADD COLUMN height INT;
-- Se obtiene nº de colección.
INSERT INTO collection
SET itemPackingTypeFk = vItemPackingTypeFk,
trainFk = vTrainFk,
wagons = vWagons,
warehouseFk = vWarehouseFk;
SELECT LAST_INSERT_ID() INTO vCollectionFk;
-- Los tickets de recogida en Algemesí sólo se sacan si están asignados.
-- Los pedidos con riesgo no se sacan aunque se asignen.
DELETE pb.*
DELETE pb
FROM tmp.productionBuffer pb
JOIN state s ON s.id = pb.state
WHERE (pb.agency = 'REC_ALGEMESI'
AND s.code <> 'PICKER_DESIGNED')
OR pb.problem LIKE '%RIESGO%';
-- Comprobamos si hay tickets asignados. En ese caso, nos centramos
-- exclusivamente en esos tickets y los sacamos independientemente
-- de problemas o tamaños
SELECT COUNT(*) INTO vHasAssignedTickets
-- Si hay tickets asignados, nos centramos exclusivamente en esos tickets
-- y los sacamos independientemente de problemas o tamaños
SELECT EXISTS (
SELECT TRUE
FROM tmp.productionBuffer pb
JOIN state s ON s.id = pb.state
WHERE s.code = 'PICKER_DESIGNED'
AND pb.workerCode = vWorkerCode;
WHERE (s.code = 'PICKER_DESIGNED'
AND pb.workerCode = vWorkerCode)
OR s.code = 'LAST_CALL'
) INTO vHasAssignedTickets;
-- Se dejan en la tabla tmp.productionBuffer sólo aquellos tickets adecuados
IF vHasAssignedTickets THEN
DELETE pb.*
DELETE pb
FROM tmp.productionBuffer pb
JOIN state s ON s.id = pb.state
WHERE s.code <> 'PICKER_DESIGNED'
OR pb.workerCode <> vWorkerCode;
WHERE (s.code <> 'PICKER_DESIGNED'
OR pb.workerCode <> vWorkerCode)
AND s.code <> 'LAST_CALL';
ELSE
DELETE pb.*
DELETE pb
FROM tmp.productionBuffer pb
JOIN state s ON s.id = pb.state
JOIN agencyMode am ON am.id = pb.agencyModeFk
@ -193,24 +166,23 @@ BEGIN
OR (NOT pb.H AND pb.V > 0 AND vItemPackingTypeFk = 'H')
OR (NOT pb.V AND vItemPackingTypeFk = 'V')
OR (pc.isPreviousPreparationRequired AND pb.previousWithoutParking)
OR LENGTH(pb.problem) > 0
OR LENGTH(pb.problem)
OR pb.lines > vLinesLimit
OR pb.m3 > vVolumeLimit
OR sub.maxSize > vSizeLimit
OR pb.hasPlantTray;
END IF;
-- Es importante que el primer ticket se coja en todos los casos
SELECT ticketFk,
HH,
mm,
`lines`,
m3
INTO vFirstTicketFk,
vHour,
vMinute,
vTicketLines,
vTicketVolume
-- Hay que excluir aquellos que no tengan la misma hora de preparacion, si procede
IF vHasUniqueCollectionTime THEN
DELETE pb
FROM tmp.productionBuffer pb
JOIN tmp.productionBuffer pb2 ON pb2.ticketFk = vFirstTicketFk
AND (pb.HH <> pb2.HH OR pb.mm <> pb2.mm);
END IF;
SELECT ticketFk INTO vFirstTicketFk
FROM tmp.productionBuffer
ORDER BY HH,
mm,
@ -222,44 +194,37 @@ BEGIN
ticketFk
LIMIT 1;
-- Hay que excluir aquellos que no tengan la misma hora de preparacion, si procede
IF vHasUniqueCollectionTime THEN
DELETE FROM tmp.productionBuffer
WHERE HH <> vHour
OR mm <> vMinute;
OPEN vTickets;
l: LOOP
SET vDone = FALSE;
FETCH vTickets INTO vTicketFk, vTicketLines, vTicketVolume;
IF vDone THEN
LEAVE l;
END IF;
SET vTicketFk = vFirstTicketFk;
SET @lines = 0;
SET @volume = 0;
OPEN c1;
read_loop: LOOP
SET vDone = FALSE;
-- Buscamos un ticket que cumpla con los requisitos en el listado
IF ((vTicketLines + @lines) <= vLinesLimit OR vLinesLimit IS NULL)
AND ((vTicketVolume + @volume) <= vVolumeLimit OR vVolumeLimit IS NULL) THEN
IF (vLinesLimit IS NULL OR (vTotalLines + vTicketLines) <= vLinesLimit)
AND (vVolumeLimit IS NULL OR (vTotalVolume + vTicketVolume) <= vVolumeLimit) THEN
CALL ticket_splitItemPackingType(vTicketFk, vItemPackingTypeFk);
DROP TEMPORARY TABLE tmp.ticketIPT;
UPDATE tmp.productionBuffer pb
JOIN (
SELECT SUM(litros) liters,
@lines:= COUNT(*) + @lines,
COUNT(*) `lines`,
MAX(i.`size`) height,
@volume := SUM(sv.volume) + @volume,
SUM(sv.volume) volume
SELECT COUNT(*), SUM(litros), MAX(i.`size`), SUM(sv.volume)
INTO vLines, vLiters, vHeight, vVolume
FROM saleVolume sv
JOIN sale s ON s.id = sv.saleFk
JOIN item i ON i.id = s.itemFk
WHERE sv.ticketFk = vTicketFk
) sub
SET pb.liters = sub.liters,
pb.`lines` = sub.`lines`,
pb.height = sub.height
WHERE sv.ticketFk = vTicketFk;
SET vTotalVolume = vTotalVolume + vVolume,
vTotalLines = vTotalLines + vLines;
UPDATE tmp.productionBuffer pb
SET pb.liters = vLiters,
pb.`lines` = vLines,
pb.height = vHeight
WHERE pb.ticketFk = vTicketFk;
UPDATE tTrain tt
@ -276,17 +241,13 @@ BEGIN
tt.height
LIMIT 1;
-- Si no le encuentra una balda adecuada, intentamos darle un carro entero si queda alguno libre
-- Si no le encuentra una balda, intentamos darle un carro entero libre
IF NOT (SELECT COUNT(*) FROM tTrain WHERE ticketFk) THEN
SELECT tt.wagon
INTO vFreeWagonFk
FROM tTrain tt
LEFT JOIN (
SELECT DISTINCT wagon
SELECT wagon INTO vFreeWagonFk
FROM tTrain
WHERE ticketFk IS NOT NULL
) nn ON nn.wagon = tt.wagon
WHERE nn.wagon IS NULL
GROUP BY wagon
HAVING COUNT(ticketFk) = 0
ORDER BY wagon
LIMIT 1;
@ -295,38 +256,35 @@ BEGIN
SET ticketFk = vFirstTicketFk
WHERE wagon = vFreeWagonFk;
-- Se anulan el resto de carros libres para que sólo uno lleve un pedido excesivo
DELETE tt.*
FROM tTrain tt
LEFT JOIN (
SELECT DISTINCT wagon
FROM tTrain
WHERE ticketFk IS NOT NULL
) nn ON nn.wagon = tt.wagon
WHERE nn.wagon IS NULL;
END IF;
END IF;
-- Se anulan el resto de carros libres,
-- máximo un carro con pedido excesivo
FETCH c1 INTO vTicketFk, vTicketLines, vTicketVolume;
IF vDone OR NOT (SELECT COUNT(*) FROM tTrain WHERE ticketFk IS NULL) THEN
LEAVE read_loop;
DELETE tt
FROM tTrain tt
JOIN (SELECT wagon
FROM tTrain
GROUP BY wagon
HAVING COUNT(ticketFk) = 0
) sub ON sub.wagon = tt.wagon;
END IF;
ELSE
FETCH c1 INTO vTicketFk, vTicketLines, vTicketVolume;
IF vDone THEN
LEAVE read_loop;
END IF;
END IF;
END LOOP;
CLOSE c1;
CLOSE vTickets;
IF (SELECT COUNT(*) FROM tTrain WHERE ticketFk) THEN
UPDATE collection c
JOIN state st ON st.code = 'ON_PREPARATION'
SET c.stateFk = st.id
WHERE c.id = vCollectionFk;
-- Se obtiene nº de colección
INSERT INTO collection
SET itemPackingTypeFk = vItemPackingTypeFk,
trainFk = vTrainFk,
wagons = vWagons,
warehouseFk = vWarehouseFk;
SELECT LAST_INSERT_ID() INTO vCollectionFk;
-- Asigna las bandejas
INSERT IGNORE INTO ticketCollection(ticketFk, collectionFk, `level`, wagon, liters)
SELECT tt.ticketFk, vCollectionFk, tt.shelve, tt.wagon, tt.liters
FROM tTrain tt
@ -334,15 +292,17 @@ BEGIN
ORDER BY tt.wagon, tt.shelve;
-- Actualiza el estado de los tickets
CALL collection_setState(vCollectionFk, vStateFk);
CALL collection_setState(vCollectionFk, vStateCode);
-- Aviso para la preparacion previa
INSERT INTO ticketDown(ticketFk, collectionFk)
SELECT tc.ticketFk, tc.collectionFk
FROM ticketCollection tc
WHERE tc.collectionFk = vCollectionFk;
CALL sales_mergeByCollection(vCollectionFk);
CALL collection_mergeSales(vCollectionFk);
UPDATE `collection` c
JOIN(
@ -356,15 +316,10 @@ BEGIN
SET c.saleTotalCount = sub.saleTotalCount,
c.salePickedCount = sub.salePickedCount
WHERE c.id = vCollectionFk;
ELSE
DELETE FROM `collection`
WHERE id = vCollectionFk;
SET vCollectionFk = NULL;
END IF;
DO RELEASE_LOCK(vLockName);
DROP TEMPORARY TABLE
tTrain,
tmp.productionBuffer;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemMinimumQuantity_check`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemMinimumQuantity_check`(
vSelf INT,
vItemFk INT,
vStarted DATE,

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelvingSale_addBySaleGroup`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemShelvingSale_addBySaleGroup`(
vSaleGroupFk INT(11)
)
BEGIN

View File

@ -52,13 +52,13 @@ BEGIN
FROM vn.sale s
JOIN vn.ticket t ON t.id = s.ticketFk
LEFT JOIN vn.itemShelvingSale iss ON iss.saleFk = s.id
WHERE t.shipped BETWEEN CURDATE() AND CURDATE() + INTERVAL vDaysInForward DAY
WHERE t.shipped >= CURDATE() + INTERVAL vDaysInForward DAY
AND iss.saleFk IS NULL
AND t.warehouseFk = vWarehouseFk
GROUP BY s.itemFk
)
SELECT i.id itemFk,
CAST(sd.quantity AS INT) advanceable,
LEAST(CAST(sd.quantity AS INT), sk.stock) advanceable,
i.longName,
i.subName,
i.tag5,

View File

@ -15,13 +15,11 @@ proc: BEGIN
DECLARE vEndingDate DATETIME;
DECLARE vIsTodayRelative BOOLEAN;
SELECT util.dayEnd(util.VN_CURDATE()) + INTERVAL LEAST(vScopeDays, maxProductionScopeDays) DAY
INTO vEndingDate
FROM productionConfig;
SELECT isTodayRelative INTO vIsTodayRelative
FROM worker
WHERE id = getUser(); -- Cambiar por account.myUser_getId(), falta dar permisos
SELECT w.isTodayRelative, util.dayEnd(util.VN_CURDATE()) + INTERVAL LEAST(vScopeDays, pc.maxProductionScopeDays) DAY
INTO vIsTodayRelative,vEndingDate
FROM worker w
JOIN productionConfig pc
WHERE w.id = account.myUser_getId();
CALL prepareTicketList(util.yesterday(), vEndingDate);
@ -272,7 +270,6 @@ proc: BEGIN
AND lb.item_id = s.itemFk
JOIN buy b ON b.id = lb.buy_id
JOIN packaging p ON p.id = b.packagingFk
JOIN productionConfig pc
SET pb.hasPlantTray = TRUE
WHERE p.isPlantTray
AND s.quantity >= b.packing

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`stockBought_calculate`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`stockBought_calculate`(
vDated DATE
)
proc: BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE vn.supplier_statementWithEntries(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE vn.supplier_statementWithEntries(
vSupplierFk INT,
vCurrencyFk INT,
vCompanyFk INT,

View File

@ -1,14 +1,27 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_mergeSales`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_mergeSales`(
vSelf INT
)
BEGIN
/**
* Para un ticket se agrupa las diferentes líneas de venta de un mismo artículo en una sola
* siempre y cuando tengan el mismo precio y dto.
*
* @param vSelf Id de ticket
*/
DECLARE vHasSalesToMerge BOOL;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
SELECT id INTO vSelf
FROM ticket
WHERE id = vSelf FOR UPDATE;
CREATE OR REPLACE TEMPORARY TABLE tSalesToPreserve
(PRIMARY KEY (id))
ENGINE = MEMORY
@ -18,26 +31,24 @@ BEGIN
JOIN itemType it ON it.id = i.typeFk
WHERE s.ticketFk = vSelf
AND it.isMergeable
GROUP BY s.itemFk, s.price, s.discount;
GROUP BY s.itemFk, s.price, s.discount
HAVING COUNT(*) > 1;
START TRANSACTION;
SELECT COUNT(*) INTO vHasSalesToMerge FROM tSalesToPreserve;
IF vHasSalesToMerge THEN
UPDATE sale s
JOIN tSalesToPreserve stp ON stp.id = s.id
SET s.quantity = newQuantity
WHERE s.ticketFk = vSelf;
SET s.quantity = newQuantity;
DELETE s.*
DELETE s
FROM sale s
LEFT JOIN tSalesToPreserve stp ON stp.id = s.id
JOIN item i ON i.id = s.itemFk
JOIN itemType it ON it.id = i.typeFk
JOIN tSalesToPreserve stp ON stp.itemFk = s.itemFk
WHERE s.ticketFk = vSelf
AND stp.id IS NULL
AND it.isMergeable;
AND s.id <> stp.id;
END IF;
COMMIT;
DROP TEMPORARY TABLE tSalesToPreserve;
END$$
DELIMITER ;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setProblemRiskByClient`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setProblemRiskByClient`(
vClientFk INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setVolume`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setVolume`(
vSelf INT
)
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setVolumeItemCost`(
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_setVolumeItemCost`(
vItemFk INT
)
BEGIN

View File

@ -3,83 +3,62 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_splitItemPacki
vSelf INT,
vOriginalItemPackingTypeFk VARCHAR(1)
)
BEGIN
proc:BEGIN
/**
* Clona y reparte las ventas de un ticket en funcion del tipo de empaquetado.
* Respeta el id inicial para el tipo propuesto.
* Clona y reparte las líneas de ventas de un ticket en funcion del tipo de empaquetado.
* Respeta el id de ticket original para el tipo de empaquetado propuesto.
*
* @param vSelf Id ticket
* @param vOriginalItemPackingTypeFk Tipo para el que se reserva el número de ticket original
* @param vOriginalItemPackingTypeFk Tipo empaquetado que se mantiene el ticket original
* @return table tmp.ticketIPT(ticketFk, itemPackingTypeFk)
*/
DECLARE vItemPackingTypeFk VARCHAR(1) DEFAULT 'H';
DECLARE vNewTicketFk INT;
DECLARE vPackingTypesToSplit INT;
DECLARE vDone INT DEFAULT FALSE;
DECLARE vHasItemPackingType BOOL;
DECLARE vItemPackingTypeFk INT;
DECLARE vNewTicketFk 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;
START TRANSACTION;
SELECT id
FROM sale
WHERE ticketFk = vSelf
AND NOT quantity
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
FROM sale s
SELECT COUNT(*) INTO vHasItemPackingType
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
LEFT JOIN saleVolume sv ON sv.saleFk = s.id
WHERE s.ticketFk = vSelf;
CREATE OR REPLACE TEMPORARY TABLE tSaleGroup
ENGINE = MEMORY
SELECT itemPackingTypeFk, SUM(litros) totalLitros
FROM tSale
GROUP BY itemPackingTypeFk;
SELECT COUNT(*) INTO vPackingTypesToSplit
FROM tSaleGroup
WHERE itemPackingTypeFk IS NOT NULL;
WHERE t.id = vSelf
AND i.itemPackingTypeFk = vOriginalItemPackingTypeFk;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketIPT(
ticketFk INT,
itemPackingTypeFk VARCHAR(1)
) ENGINE=MEMORY
SELECT vSelf ticketFk, vOriginalItemPackingTypeFk itemPackingTypeFk;
IF NOT vHasItemPackingType THEN
LEAVE proc;
END IF;
CREATE OR REPLACE TEMPORARY TABLE tSalesToMove (
ticketFk INT,
saleFk INT,
itemPackingTypeFk INT
) ENGINE=MEMORY;
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;
INSERT INTO tSalesToMove (saleFk, itemPackingTypeFk)
SELECT s.id, i.itemPackingTypeFk
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.id = vSelf
AND i.itemPackingTypeFk <> vOriginalItemPackingTypeFk;
INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
VALUES(vSelf, vItemPackingTypeFk);
OPEN vItemPackingTypes;
l: LOOP
SET vDone = FALSE;
FETCH vSaleGroup INTO vItemPackingTypeFk;
FETCH vItemPackingTypes INTO vItemPackingTypeFk;
IF vDone THEN
LEAVE l;
@ -87,40 +66,24 @@ BEGIN
CALL ticket_Clone(vSelf, vNewTicketFk);
INSERT INTO tmp.ticketIPT(ticketFk, itemPackingTypeFk)
VALUES(vNewTicketFk, vItemPackingTypeFk);
UPDATE tSalesToMove
SET ticketFk = vNewTicketFk
WHERE itemPackingTypeFk = 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;
CLOSE vItemPackingTypes;
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;
JOIN tSalesToMove stm ON stm.saleFk = s.id
SET s.ticketFk = stm.ticketFk
WHERE stm.ticketFk;
SELECT itemPackingTypeFk INTO vItemPackingTypeFk
FROM tSaleGroup sg
WHERE sg.itemPackingTypeFk IS NOT NULL
ORDER BY sg.itemPackingTypeFk
LIMIT 1;
INSERT INTO tmp.ticketIPT (ticketFk, itemPackingTypeFk)
SELECT ticketFk, itemPackingTypeFk
FROM tSalesToMove
GROUP BY ticketFk;
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;
COMMIT;
DROP TEMPORARY TABLE
tSale,
tSaleGroup;
DROP TEMPORARY TABLE tSalesToMove;
END$$
DELIMITER ;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`host_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`host_beforeInsert`
BEFORE INSERT ON `host`
FOR EACH ROW
BEGIN

View File

@ -0,0 +1,18 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`itemShelving_afterInsert`
AFTER INSERT ON `itemShelving`
FOR EACH ROW
BEGIN
INSERT INTO itemShelvingLog
SET itemShelvingFk = NEW.id,
workerFk = account.myUser_getId(),
accion = 'CREA REGISTRO',
itemFk = NEW.itemFk,
shelvingFk = NEW.shelvingFk,
visible = NEW.visible,
`grouping` = NEW.`grouping`,
packing = NEW.packing,
available = NEW.available;
END$$
DELIMITER ;

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_beforeInsert`
BEFORE INSERT ON `roadmap`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`roadmap_beforeUpdate`
BEFORE UPDATE ON `roadmap`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeInsert`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeInsert`
BEFORE INSERT ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_afterDelete`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_afterDelete`
AFTER DELETE ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -1,5 +1,5 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeUpdate`
CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`saleGroupDetail_beforeUpdate`
BEFORE UPDATE ON `saleGroupDetail`
FOR EACH ROW
BEGIN

View File

@ -4,5 +4,14 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`travelThermograph_befor
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
IF NEW.travelFk IS NULL AND
(SELECT COUNT(*) FROM travelThermograph
WHERE thermographFk = NEW.thermographFk
AND travelFk IS NULL
AND id <> NEW.id) > 0
THEN
CALL util.throw('Duplicate thermographFk without travelFk not allowed.');
END IF;
END$$
DELIMITER ;

View File

@ -4,5 +4,14 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `vn`.`travelThermograph_befor
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
IF NEW.travelFk IS NULL AND
(SELECT COUNT(*) FROM travelThermograph
WHERE thermographFk = NEW.thermographFk
AND travelFk IS NULL
AND id <> NEW.id) > 0
THEN
CALL util.throw('Duplicate thermographFk without travelFk not allowed.');
END IF;
END$$
DELIMITER ;

View File

@ -2,12 +2,10 @@ CREATE OR REPLACE DEFINER=`vn`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`buyer`
AS SELECT DISTINCT `u`.`id` AS `userFk`,
`u`.`nickname` AS `nickname`,
`ic`.`display` AS `display`
`u`.`nickname` AS `nickname`
FROM (
`account`.`user` `u`
JOIN `vn`.`itemType` `it` ON(`it`.`workerFk` = `u`.`id`)
JOIN `vn`.`itemCategory` `ic` ON(`ic`.`id` = `it`.`categoryFk`)
)
WHERE `u`.`active` <> 0
ORDER BY `u`.`nickname`

View File

@ -13,6 +13,5 @@ AS SELECT `am`.`id` AS `Id_Agencia`,
`am`.`reportMail` AS `send_mail`,
`am`.`isActive` AS `tpv`,
`am`.`code` AS `code`,
`am`.`showAgencyName` AS `show_AgencyName`,
`am`.`isRiskFree` AS `isRiskFree`
FROM `vn`.`agencyMode` `am`

View File

@ -0,0 +1,2 @@
ALTER TABLE vn.agencyMode
CHANGE IF EXISTS showAgencyName showAgencyName__ tinyint(1) DEFAULT 1 COMMENT '@deprecated 2024-09-24';

View File

@ -0,0 +1,31 @@
-- vn.priceDelta definition
CREATE OR REPLACE TABLE vn.priceDelta (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`itemTypeFk` smallint(5) unsigned NOT NULL,
`minSize` int(10) unsigned DEFAULT NULL COMMENT 'Minimum item.size',
`maxSize` int(10) unsigned DEFAULT NULL COMMENT 'Maximum item.size',
`inkFk` varchar(3) DEFAULT NULL,
`originFk` tinyint(2) unsigned DEFAULT NULL,
`producerFk` mediumint(3) unsigned DEFAULT NULL,
`fromDated` date DEFAULT NULL,
`toDated` date DEFAULT NULL,
`absIncreasing` decimal(10,3) DEFAULT NULL COMMENT 'Absolute increasing of final price',
`ratIncreasing` int(11) DEFAULT NULL COMMENT 'Increasing ratio for the cost price',
`warehouseFk` smallint(6) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
`editorFk` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `priceDelta_itemType_FK` (`itemTypeFk`),
KEY `priceDelta_ink_FK` (`inkFk`),
KEY `priceDelta_producer_FK` (`producerFk`),
KEY `priceDelta_warehouse_FK` (`warehouseFk`),
KEY `priceDelta_worker_FK` (`editorFk`),
CONSTRAINT `priceDelta_ink_FK` FOREIGN KEY (`inkFk`) REFERENCES `ink` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_itemType_FK` FOREIGN KEY (`itemTypeFk`) REFERENCES `itemType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_producer_FK` FOREIGN KEY (`producerFk`) REFERENCES `producer` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_warehouse_FK` FOREIGN KEY (`warehouseFk`) REFERENCES `warehouse` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_worker_FK` FOREIGN KEY (`editorFk`) REFERENCES `worker` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Defines the increasing o decreasing for ranges of items';
GRANT INSERT, SELECT, UPDATE, DELETE ON TABLE vn.priceDelta TO buyer;

View File

@ -0,0 +1,32 @@
-- Place your SQL code here
-- vn.priceDelta definition
CREATE OR REPLACE TABLE vn.priceDelta (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`itemTypeFk` smallint(5) unsigned NOT NULL,
`minSize` int(10) unsigned DEFAULT NULL COMMENT 'Minimum item.size',
`maxSize` int(10) unsigned DEFAULT NULL COMMENT 'Maximum item.size',
`inkFk` varchar(3) DEFAULT NULL,
`originFk` tinyint(2) unsigned DEFAULT NULL,
`producerFk` mediumint(3) unsigned DEFAULT NULL,
`fromDated` date DEFAULT NULL,
`toDated` date DEFAULT NULL,
`absIncreasing` decimal(10,3) DEFAULT NULL COMMENT 'Absolute increasing of final price',
`ratIncreasing` int(11) DEFAULT NULL COMMENT 'Increasing ratio for the cost price',
`warehouseFk` smallint(6) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
`editorFk` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `priceDelta_itemType_FK` (`itemTypeFk`),
KEY `priceDelta_ink_FK` (`inkFk`),
KEY `priceDelta_producer_FK` (`producerFk`),
KEY `priceDelta_warehouse_FK` (`warehouseFk`),
KEY `priceDelta_worker_FK` (`editorFk`),
CONSTRAINT `priceDelta_ink_FK` FOREIGN KEY (`inkFk`) REFERENCES `ink` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_itemType_FK` FOREIGN KEY (`itemTypeFk`) REFERENCES `itemType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_producer_FK` FOREIGN KEY (`producerFk`) REFERENCES `producer` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_warehouse_FK` FOREIGN KEY (`warehouseFk`) REFERENCES `warehouse` (`id`) ON UPDATE CASCADE,
CONSTRAINT `priceDelta_worker_FK` FOREIGN KEY (`editorFk`) REFERENCES `worker` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Defines the increasing o decreasing for ranges of items';
GRANT INSERT, SELECT, UPDATE, DELETE ON TABLE vn.priceDelta TO buyer;

View File

@ -0,0 +1,3 @@
-- Place your SQL code here
ALTER TABLE vn.priceDelta ADD IF NOT EXISTS zoneGeoFk int(11) NULL COMMENT 'Application area for the bonus component';
ALTER TABLE vn.priceDelta ADD CONSTRAINT priceDelta_zoneGeo_FK FOREIGN KEY IF NOT EXISTS (zoneGeoFk) REFERENCES vn.zoneGeo(id) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1 @@
ALTER TABLE vn.address MODIFY COLUMN isEqualizated tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE vn.autonomy MODIFY COLUMN isUeeMember tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE vn.chat MODIFY COLUMN checkUserStatus tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.warehouse
MODIFY COLUMN isOrigin tinyint(1) DEFAULT FALSE NOT NULL,
MODIFY COLUMN isDestiny tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE vn.zoneIncluded MODIFY COLUMN isIncluded tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE bs.defaulter MODIFY COLUMN hasChanged tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -0,0 +1 @@
ALTER TABLE account.user MODIFY COLUMN emailVerified tinyint(1) DEFAULT FALSE NOT NULL;

View File

@ -1,68 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('SmartTable SearchBar integration', () => {
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 search by type in searchBar, reload page and have same results', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
await page.waitToClick(selectors.itemsIndex.advancedSearchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4);
await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
});
it('should filter in section without smart-table and search in searchBar go to zone section', async() => {
await page.loginAndModule('salesPerson', 'zone');
await page.waitToClick(selectors.globalItems.searchButton);
await page.doSearch('A');
const firstCount = await page.countElement(selectors.zoneIndex.searchResult);
await page.doSearch('A');
const secondCount = await page.countElement(selectors.zoneIndex.searchResult);
expect(firstCount).toEqual(7);
expect(secondCount).toEqual(7);
});
it('should order orders by first id and order by last id, reload page and have same order', async() => {
await page.loginAndModule('developer', 'item');
await page.accessToSection('item.fixedPrice');
await page.keyboard.press('Enter');
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '1');
await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
await page.reload({
waitUntil: 'networkidle2'
});
await page.waitForTextInField(selectors.itemFixedPrice.firstItemID, '3');
});
});

View File

@ -1,104 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryAssistant',
'zone'); // turns up the zone module name and route aint the same lol
await page.accessToSearchResult('10');
await page.accessToSection('zone.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should reach the basic data section', async() => {
await page.waitForState('zone.card.basicData');
});
it('should edit de form and then save', async() => {
await page.clearInput(selectors.zoneBasicData.name);
await page.write(selectors.zoneBasicData.name, 'Brimstone teleportation');
await page.autocompleteSearch(selectors.zoneBasicData.agency, 'Quantum break device');
await page.clearInput(selectors.zoneBasicData.maxVolume);
await page.write(selectors.zoneBasicData.maxVolume, '10');
await page.clearInput(selectors.zoneBasicData.travelingDays);
await page.write(selectors.zoneBasicData.travelingDays, '1');
await page.clearInput(selectors.zoneBasicData.closing);
await page.pickTime(selectors.zoneBasicData.closing, '21:00');
await page.clearInput(selectors.zoneBasicData.price);
await page.write(selectors.zoneBasicData.price, '999');
await page.clearInput(selectors.zoneBasicData.bonus);
await page.write(selectors.zoneBasicData.bonus, '100');
await page.clearInput(selectors.zoneBasicData.inflation);
await page.write(selectors.zoneBasicData.inflation, '200');
await page.waitToClick(selectors.zoneBasicData.volumetric);
await page.waitToClick(selectors.zoneBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should now reload the section', async() => {
await page.reloadSection('zone.card.basicData');
});
it('should confirm the name was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.name, 'value');
expect(result).toEqual('Brimstone teleportation');
});
it('should confirm the agency was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.agency, 'value');
expect(result).toEqual('Quantum break device');
});
it('should confirm the max volume was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.maxVolume, 'value');
expect(result).toEqual('10');
});
it('should confirm the traveling days were updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.travelingDays, 'value');
expect(result).toEqual('1');
});
it('should confirm the closing hour was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.closing, 'value');
expect(result).toEqual('21:00');
});
it('should confirm the price was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.price, 'value');
expect(result).toEqual('999');
});
it('should confirm the bonus was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.bonus, 'value');
expect(result).toEqual('100');
});
it('should confirm the inflation was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.inflation, 'value');
expect(result).toEqual('200');
});
it('should confirm the volumetric checkbox was checked', async() => {
await page.waitForClassPresent(selectors.zoneBasicData.volumetric, 'checked');
});
});

View File

@ -1,32 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('deliveryAssistant', 'zone');
await page.accessToSearchResult('13');
});
afterAll(async() => {
await browser.close();
});
it('should eliminate the zone using the descriptor option', async() => {
await page.waitToClick(selectors.zoneDescriptor.menu);
await page.waitToClick(selectors.zoneDescriptor.deleteZone);
await page.respondToDialog('accept');
await page.waitForState('zone.index');
});
it('should search for the deleted zone to find no results', async() => {
await page.doSearch('13');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(0);
});
});

View File

@ -57,7 +57,7 @@ export default class App {
getUrl(route, appName = 'lilium') {
const index = window.location.hash.indexOf(route.toLowerCase());
const newRoute = index < 0 ? route : window.location.hash.substring(index);
let newRoute = index < 0 ? route : window.location.hash.substring(index);
const env = process.env.NODE_ENV;
const filter = {
where: {and: [
@ -66,6 +66,11 @@ export default class App {
]}
};
if (this.logger.$params.q)
newRoute = newRoute.concat(`?table=${this.logger.$params.q}`);
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
if (res && res.data)

View File

@ -235,10 +235,10 @@
"Cannot add holidays on this day": "Cannot add holidays on this day",
"Cannot send mail": "Cannot send mail",
"CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`",
"This postcode already exists": "This postcode already exists",
"Original invoice not found": "Original invoice not found",
"There is already a tray with the same height": "There is already a tray with the same height",
"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",
"This postcode already exists": "This postcode already exists",
"This buyer has already made a reservation for this date": "This buyer has already made a reservation for this date"
"The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line"
}

View File

@ -366,9 +366,11 @@
"The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
"Payment method is required": "El método de pago es obligatorio",
"Cannot send mail": "No se ha podido enviar el correo",
"Cannot send mail": "Não é possível enviar o email",
"CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos",
"The sale not exists in the item shelving": "La venta no existe en la estantería del artículo",
"The entry not have stickers": "La entrada no tiene etiquetas",
"Too many records": "Demasiados registros",
"Original invoice not found": "Factura original no encontrada",
"The entry has no lines or does not exist": "La entrada no tiene lineas o no existe",
"Weight already set": "El peso ya está establecido",
@ -377,7 +379,7 @@
"The height must be greater than 50cm": "La altura debe ser superior a 50cm",
"The maximum height of the wagon is 200cm": "La altura máxima es 200cm",
"The entry does not have stickers": "La entrada no tiene etiquetas",
"Too many records": "Demasiados registros",
"This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha",
"No valid travel thermograph found": "No se encontró un termógrafo válido"
"No valid travel thermograph found": "No se encontró un termógrafo válido",
"The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea"
}

View File

@ -361,6 +361,6 @@
"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"
"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"
}

View File

@ -361,5 +361,6 @@
"The invoices have been created but the PDFs could not be generated": "Foi faturado, mas o PDF não pôde ser gerado",
"It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso",
"Original invoice not found": "Fatura original não encontrada",
"Cannot send mail": "Não é possível enviar o email"
"Cannot send mail": "Não é possível enviar o email",
"The quantity claimed cannot be greater than the quantity of the line": "O valor reclamado não pode ser superior ao valor da linha"
}

View File

@ -1,6 +1,5 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.rewriteDbError(function(err) {

View File

@ -0,0 +1,55 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ClaimBeginning model()', () => {
const claimFk = 1;
const activeCtx = {
accessToken: {userId: 18},
headers: {origin: 'localhost:5000'},
__: () => {}
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should change quantity claimed', async() => {
const tx = await models.ClaimBeginning.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const claim = await models.ClaimBeginning.findOne({where: {claimFk}}, options);
const sale = await models.Sale.findById(claim.saleFk, {}, options);
await claim.updateAttribute('quantity', sale.quantity - 1, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error).toBeUndefined();
});
it('should throw error when quantity claimed is greater than quantity of the sale', async() => {
const tx = await models.ClaimBeginning.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const claim = await models.ClaimBeginning.findOne({where: {claimFk}}, options);
const sale = await models.Sale.findById(claim.saleFk, {}, options);
await claim.updateAttribute('quantity', sale.quantity + 1, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.toString()).toContain('The quantity claimed cannot be greater than the quantity of the line');
});
});

View File

@ -10,16 +10,21 @@ module.exports = Self => {
});
Self.observe('before save', async ctx => {
if (ctx.isNewInstance) {
const models = Self.app.models;
const options = ctx.options;
const instance = ctx.instance;
const ticket = await models.Sale.findById(instance.saleFk, {fields: ['ticketFk']}, options);
const claim = await models.Claim.findById(instance.claimFk, {fields: ['ticketFk']}, options);
if (ticket.ticketFk != claim.ticketFk)
const models = Self.app.models;
const saleFk = ctx?.currentInstance?.saleFk || ctx?.instance?.saleFk;
const sale = await models.Sale.findById(saleFk, {fields: ['ticketFk', 'quantity']}, options);
if (ctx.isNewInstance) {
const claim = await models.Claim.findById(ctx.instance.claimFk, {fields: ['ticketFk']}, options);
if (sale.ticketFk != claim.ticketFk)
throw new UserError(`Cannot create a new claimBeginning from a different ticket`);
}
await claimIsEditable(ctx);
if (sale?.quantity && ctx.data?.quantity && ctx.data.quantity > sale?.quantity)
throw new UserError('The quantity claimed cannot be greater than the quantity of the line');
});
Self.observe('before delete', async ctx => {

View File

@ -52,7 +52,7 @@ module.exports = Self => {
await merger.add(new Uint8Array(pdfBuffer[0]));
}
if (!merger._doc) throw new UserError('The entry does not have stickers');
if (!merger._doc) throw new UserError('The entry not have stickers');
await Self.rawSql(`
UPDATE buy

View File

@ -39,7 +39,7 @@ describe('Entry filter()', () => {
const result = await models.Entry.filter(ctx, options);
expect(result.length).toEqual(12);
expect(result.length).toEqual(11);
await tx.rollback();
} catch (e) {
@ -131,7 +131,7 @@ describe('Entry filter()', () => {
const result = await models.Entry.filter(ctx, options);
expect(result.length).toEqual(11);
expect(result.length).toEqual(10);
await tx.rollback();
} catch (e) {

View File

@ -64,7 +64,7 @@ module.exports = Self => {
try {
const client = await models.Client.findById(args.clientId, {
fields: ['id', 'hasToInvoiceByAddress']
}, options);
}, myOptions);
if (client.hasToInvoiceByAddress) {
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
@ -72,13 +72,13 @@ module.exports = Self => {
args.maxShipped,
args.addressId,
args.companyFk
], options);
], myOptions);
} else {
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
args.maxShipped,
client.id,
args.companyFk
], options);
], myOptions);
}
const invoiceId = await models.Ticket.makeInvoice(
@ -87,7 +87,7 @@ module.exports = Self => {
args.companyFk,
args.invoiceDate,
null,
options
myOptions
);
if (tx) await tx.commit();

View File

@ -38,9 +38,13 @@ module.exports = Self => {
const itemShelving = itemShelvingSale.itemShelving();
const quantity = itemShelving.visible + itemShelvingSale.quantity;
const available = itemShelving.available + itemShelvingSale.quantity;
await itemShelving.updateAttributes(
{visible: quantity},
{
visible: quantity,
available: available
},
myOptions
);
if (tx) await tx.commit();

View File

@ -75,7 +75,11 @@ module.exports = Self => {
const itemShelving = await models.ItemShelving.findById(itemShelvingFk, null, myOptions);
await itemShelving.updateAttributes({visible: itemShelving.visible - quantity}, myOptions);
await itemShelving.updateAttributes(
{
visible: itemShelving.visible - quantity,
available: itemShelving.available - quantity
}, myOptions);
await Self.updateAll(
{saleFk},

View File

@ -117,7 +117,8 @@ module.exports = Self => {
result: state,
maxTemperature,
minTemperature,
temperatureFk
temperatureFk,
warehouseFk: warehouseId,
}, myOptions);
if (tx) await tx.commit();

View File

@ -4,7 +4,7 @@ describe('Thermograph saveThermograph()', () => {
const ctx = beforeAll.getCtx();
const travelFk = 1;
const thermographId = '138350-0';
const warehouseFk = '1';
const warehouseFk = 1;
const state = 'COMPLETED';
const maxTemperature = 30;
const minTemperature = 10;
@ -41,7 +41,7 @@ describe('Thermograph saveThermograph()', () => {
maxTemperature,
minTemperature,
temperatureFk,
null,
warehouseFk,
null,
null,
null,

View File

@ -82,7 +82,7 @@ module.exports = Self => {
const getDataOperator = await models.Operator.findOne({
where: {workerFk: user.id},
fields: ['numberOfWagons', 'warehouseFk', 'itemPackingTypeFk', 'sectorFk', 'sector',
'trainFk', 'train', 'labelerFk', 'printer'],
'trainFk', 'train', 'labelerFk', 'printer', 'isOnReservationMode'],
include: [
{
relation: 'sector',

View File

@ -1,114 +0,0 @@
<mg-ajax path="Zones/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.zone"
form="form"
save="patch">
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.zone.name"
vn-acl="deliveryAssistant"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.zone.agencyModeFk"
url="AgencyModes"
show-field="name"
value-field="id"
label="Agency"
vn-acl="deliveryAssistant"
rule>
</vn-autocomplete>
<vn-input-number
vn-one
label="Max m³"
ng-model="$ctrl.zone.itemMaxSize"
min="0"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-number
vn-one
label="Maximum m³"
ng-model="$ctrl.zone.m3Max"
min="0"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.zone.travelingDays"
min="0"
step="1"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-time
label="Closing"
ng-model="$ctrl.zone.hour"
vn-acl="deliveryAssistant"
rule>
</vn-input-time>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.zone.price"
min="0"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.zone.bonus"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
vn-one
label="Inflation"
ng-model="$ctrl.zone.inflation"
min="0"
step="0.01"
vn-acl="deliveryAssistant"
rule>
</vn-input-number>
<vn-check
vn-one
label="Volumetric"
ng-model="$ctrl.zone.isVolumetric"
vn-acl="deliveryAssistant"
rule>
</vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
vn-acl="deliveryAssistant"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
onSubmit() {
this.$.watcher.submit().then(() =>
this.card.reload()
);
}
}
ngModule.vnComponent('vnZoneBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,33 +0,0 @@
<vn-card>
<div class="header">
<vn-button
icon="navigate_before"
ng-click="$ctrl.step(-1)"
class="flat">
</vn-button>
<div>
<span translate>{{$ctrl.firstDay | date:'MMMM'}}</span>
<span>{{$ctrl.firstDay | date:'yyyy'}} -</span>
<span translate>{{$ctrl.lastDay | date:'MMMM'}}</span>
<span>{{$ctrl.lastDay | date:'yyyy'}}</span>
</div>
<vn-button
icon="navigate_next"
ng-click="$ctrl.step(1)"
class="flat">
</vn-button>
</div>
<div class="calendars vn-pa-md">
<vn-calendar
ng-repeat="date in $ctrl.months track by date.getTime()"
default-date="date"
display-controls="false"
hide-contiguous="true"
has-events="$ctrl.hasEvents($day)"
get-class="$ctrl.getClass($day)"
on-selection="$ctrl.onSelection($event, $days, $type, $weekday)"
class="vn-pa-md"
style="min-width: 250px; flex: 1;">
</vn-calendar>
</div>
</vn-card>

View File

@ -1,191 +0,0 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $, vnWeekDays) {
super($element, $);
this.vnWeekDays = vnWeekDays;
this.nMonths = 4;
let date = Date.vnNew();
date.setDate(1);
date.setHours(0, 0, 0, 0);
this.date = date;
}
get date() {
return this._date;
}
set date(value) {
this._date = value;
let stamp = value.getTime();
let firstDay = new Date(stamp);
firstDay.setDate(1);
this.firstDay = firstDay;
let lastDay = new Date(stamp);
lastDay.setMonth(lastDay.getMonth() + this.nMonths);
lastDay.setDate(0);
this.lastDay = lastDay;
this.months = [];
for (let i = 0; i < this.nMonths; i++) {
let monthDate = new Date(stamp);
monthDate.setMonth(value.getMonth() + i);
this.months.push(monthDate);
}
this.refreshEvents();
}
step(direction) {
let date = new Date(this.date.getTime());
date.setMonth(date.getMonth() + (this.nMonths * direction));
this.date = date;
this.emit('step');
}
get data() {
return this._data;
}
set data(value) {
this._data = value;
value = value || {};
this.events = value.events;
function toStamp(date) {
return date && new Date(date).setHours(0, 0, 0, 0);
}
this.exclusions = {};
let exclusions = value.exclusions;
if (exclusions) {
for (let exclusion of exclusions) {
let stamp = toStamp(exclusion.dated);
if (!this.exclusions[stamp]) this.exclusions[stamp] = [];
this.exclusions[stamp].push(exclusion);
}
}
this.geoExclusions = {};
let geoExclusions = value.geoExclusions;
if (geoExclusions) {
for (let geoExclusion of geoExclusions) {
let stamp = toStamp(geoExclusion.dated);
if (!this.geoExclusions[stamp]) this.geoExclusions[stamp] = [];
this.geoExclusions[stamp].push(geoExclusion);
}
}
let events = value.events;
if (events) {
for (let event of events) {
event.dated = toStamp(event.dated);
event.ended = toStamp(event.ended);
event.started = toStamp(event.started);
event.wdays = this.vnWeekDays.fromSet(event.weekDays);
}
}
this.refreshEvents();
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
refreshEvents() {
this.days = {};
if (!this.data) return;
let day = new Date(this.firstDay.getTime());
while (day <= this.lastDay) {
let stamp = day.getTime();
let wday = day.getDay();
let dayEvents = [];
let exclusions = this.exclusions[stamp] || [];
if (this.events) {
for (let event of this.events) {
let match;
switch (event.type) {
case 'day':
match = event.dated == stamp;
break;
default:
match = event.wdays[wday]
&& (!event.started || stamp >= event.started)
&& (!event.ended || stamp <= event.ended);
break;
}
if (match && !exclusions.find(e => e.zoneFk == event.zoneFk))
dayEvents.push(event);
}
}
if (dayEvents.length)
this.days[stamp] = dayEvents;
day.setDate(day.getDate() + 1);
}
}
onSelection($event, $days, $type, $weekday) {
let $events = [];
let $exclusions = [];
let $geoExclusions = [];
for (let day of $days) {
let stamp = day.getTime();
$events = $events.concat(this.days[stamp] || []);
$exclusions = $exclusions.concat(this.exclusions[stamp] || []);
$geoExclusions = $geoExclusions.concat(this.geoExclusions[stamp] || []);
}
this.emit('selection', {
$event,
$days,
$type,
$weekday,
$events,
$exclusions,
$geoExclusions
});
}
hasEvents(day) {
let stamp = day.getTime();
return this.days[stamp] || this.exclusions[stamp] || this.geoExclusions[stamp];
}
getClass(day) {
let stamp = day.getTime();
if (this.geoExclusions[stamp])
return 'geoExcluded';
else if (this.exclusions[stamp])
return 'excluded';
else return '';
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnZoneCalendar', {
template: require('./index.html'),
controller: Controller,
bindings: {
data: '<?'
}
});

View File

@ -1,172 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneCalendar', () => {
let $scope;
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-calendar></vn-zone-calendar>`);
controller = $componentController('vnZoneCalendar', {$element, $scope});
controller.$.model = crudModel;
controller.zone = {id: 1};
controller.days = [];
controller.exclusions = [];
controller.geoExclusions = [];
}));
describe('date() setter', () => {
it('should set the month property and then call the refreshEvents() method', () => {
jest.spyOn(controller, 'refreshEvents').mockReturnThis();
controller.date = Date.vnNew();
expect(controller.refreshEvents).toHaveBeenCalledWith();
expect(controller.months.length).toEqual(4);
});
});
describe('step()', () => {
it('should set the date month to 4 months backwards', () => {
const now = Date.vnNew();
now.setDate(15);
now.setMonth(now.getMonth() - 4);
controller.step(-1);
const expectedMonth = now.getMonth();
const currentMonth = controller.date.getMonth();
expect(currentMonth).toEqual(expectedMonth);
});
it('should set the date month to 4 months forwards', () => {
const now = Date.vnNew();
now.setDate(15);
now.setMonth(now.getMonth() + 4);
controller.step(1);
const expectedMonth = now.getMonth();
const currentMonth = controller.date.getMonth();
expect(currentMonth).toEqual(expectedMonth);
});
});
describe('data() setter', () => {
it('should set the events, exclusions and geoExclusions and then call the refreshEvents() method', () => {
jest.spyOn(controller, 'refreshEvents').mockReturnThis();
controller.data = {
exclusions: [{
dated: Date.vnNew()
}],
events: [{
dated: Date.vnNew()
}],
geoExclusions: [{
dated: Date.vnNew()
}],
};
expect(controller.refreshEvents).toHaveBeenCalledWith();
expect(controller.events).toBeDefined();
expect(controller.events.length).toEqual(1);
expect(controller.exclusions).toBeDefined();
expect(controller.geoExclusions).toBeDefined();
expect(Object.keys(controller.exclusions).length).toEqual(1);
});
});
describe('refreshEvents()', () => {
it('should fill the days property with the events.', () => {
controller.data = [];
controller.firstDay = Date.vnNew();
const lastDay = Date.vnNew();
lastDay.setDate(lastDay.getDate() + 10);
controller.lastDay = lastDay;
const firstEventStamp = controller.firstDay.getTime();
const lastEventStamp = controller.lastDay.getTime();
controller.events = [{
type: 'day',
dated: firstEventStamp
},
{
type: 'day',
dated: lastEventStamp
}];
controller.refreshEvents();
const expectedDays = Object.keys(controller.days);
expect(expectedDays.length).toEqual(2);
});
});
describe('onSelection()', () => {
it('should call the emit() method', () => {
jest.spyOn(controller, 'emit');
const $event = {};
const $days = [Date.vnNew()];
const $type = 'day';
const $weekday = 1;
controller.onSelection($event, $days, $type, $weekday);
expect(controller.emit).toHaveBeenCalledWith('selection',
{
$days: $days,
$event: {},
$events: [],
$exclusions: [],
$type: 'day',
$weekday: 1,
$geoExclusions: [],
}
);
});
});
describe('hasEvents()', () => {
it('should return true for an existing event on a date', () => {
const dated = Date.vnNew();
controller.days[dated.getTime()] = true;
const result = controller.hasEvents(dated);
expect(result).toBeTruthy();
});
});
describe('getClass()', () => {
it('should return the className "excluded" for an excluded date', () => {
const dated = Date.vnNew();
controller.exclusions = [];
controller.exclusions[dated.getTime()] = true;
const result = controller.getClass(dated);
expect(result).toEqual('excluded');
});
it('should return the className "geoExcluded" for a date with geo excluded', () => {
const dated = Date.vnNew();
controller.geoExclusions = [];
controller.geoExclusions[dated.getTime()] = true;
const result = controller.getClass(dated);
expect(result).toEqual('geoExcluded');
});
});
});

View File

@ -1,43 +0,0 @@
@import "variables";
vn-zone-calendar {
display: block;
& > vn-card {
& > .header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $color-main;
color: white;
font-weight: bold;
height: 45px;
& > .vn-button {
color: inherit;
height: 100%;
}
}
& > .calendars {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
& > .vn-calendar {
max-width: 288px;
#days-container .day {
&.event .day-number {
background-color: $color-success;
}
&.excluded .day-number {
background-color: $color-alert;
}
&.geoExcluded .day-number {
background-color: $color-main;
}
}
}
}
}
}

View File

@ -1,5 +0,0 @@
<vn-portal slot="menu">
<vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: {
relation: 'agencyMode',
scope: {fields: ['name']}
}
};
this.$http.get(`Zones/${this.$params.id}`, {filter})
.then(res => this.zone = res.data);
}
}
ngModule.vnComponent('vnZoneCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,26 +0,0 @@
import './index.js';
describe('Zone Component vnZoneCard', () => {
let controller;
let $httpBackend;
let data = {id: 1, name: 'fooName'};
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_, $stateParams) => {
$httpBackend = _$httpBackend_;
let $element = angular.element('<div></div>');
controller = $componentController('vnZoneCard', {$element});
$stateParams.id = data.id;
$httpBackend.whenRoute('GET', 'Zones/:id').respond(data);
}));
it('should request data and set it on the controller', () => {
controller.reload();
$httpBackend.flush();
expect(controller.zone).toEqual(data);
});
});

View File

@ -1,98 +0,0 @@
<vn-watcher
vn-id="watcher"
url="Zones"
data="$ctrl.zone"
insert-mode="true"
form="form">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses"
order="name">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="AgencyModes/isActive"
data="activeAgencyModes"
order="name">
</vn-crud-model>
<form
name="form"
vn-http-submit="$ctrl.onSubmit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.zone.name"
rule
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
label="Warehouse"
ng-model="$ctrl.zone.warehouseFk"
data="warehouses"
show-field="name"
value-field="id"
label="Warehouse"
rule>
</vn-autocomplete>
<vn-autocomplete
label="Agency"
ng-model="$ctrl.zone.agencyModeFk"
data="activeAgencyModes"
show-field="name"
value-field="id"
label="Agency"
rule>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.zone.travelingDays"
min="0"
step="1"
rule>
</vn-input-number>
<vn-input-time
label="Closing hour"
ng-model="$ctrl.zone.hour"
rule>
</vn-input-time>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.zone.price"
min="0"
step="0.01"
rule>
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.zone.bonus"
min="0"
step="0.01"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-check ng-model="$ctrl.zone.isVolumetric" label="Volumetric"></vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="zone.index">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,24 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
$onInit() {
this.zone = {
travelingDays: 0,
price: 0.20,
bonus: 0.20,
hour: Date.vnNew()
};
}
onSubmit() {
return this.$.watcher.submit().then(res =>
this.$state.go('zone.card.location', {id: res.data.id})
);
}
}
ngModule.vnComponent('vnZoneCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,40 +0,0 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Zone Component vnZoneCreate', () => {
let $scope;
let $state;
let controller;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = watcher;
$scope.watcher.submit = () => {
return {
then: callback => {
callback({data: {id: 1234}});
}
};
};
const $element = angular.element('<vn-zone-create></vn-zone-create>');
controller = $componentController('vnZoneCreate', {$element, $scope});
}));
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
jest.spyOn($state, 'go');
controller.zone = {
name: 'Zone One'
};
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('zone.card.location', Object({id: 1234}));
});
});
});

View File

@ -1,3 +0,0 @@
Traveling days: Días de viaje
Closing hour: Hora de cierre
Bonus: Bonificación

View File

@ -1,102 +0,0 @@
<div class="vn-w-md">
<vn-zone-calendar
data="data"
on-selection="$ctrl.onSelection($event, $events, $days)">
</vn-zone-calendar>
</div>
<vn-side-menu side="right">
<form ng-submit="$ctrl.fetchData()" class="vn-pa-md">
<vn-radio
label="Pick up"
val="pickUp"
ng-model="$ctrl.deliveryMethodFk"
on-change="$ctrl.agencyModeFk = null"
tabindex="-1">
</vn-radio>
<vn-radio
label="Delivery"
val="delivery"
ng-model="$ctrl.deliveryMethodFk"
on-change="$ctrl.agencyModeFk = null"
class="vn-mb-sm"
tabindex="-1">
</vn-radio>
<vn-autocomplete
vn-one
ng-if="$ctrl.deliveryMethodFk === 'delivery'"
vn-focus
label="Postcode"
ng-model="$ctrl.geoFk"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="geoFk"
show-field="code">
<tpl-item>
<div>
{{code}} {{town.name}}
</div>
<div class="text-caption text-secondary">
{{town.province.name}}, {{town.province.country.name}}
</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
label="{{$ctrl.deliveryMethodFk == 'delivery' ? 'Agency' : 'Warehouse'}}"
ng-model="$ctrl.agencyModeFk"
url="AgencyModes/isActive"
where="$ctrl.agencyFilter"
vn-id="agencymode">
</vn-autocomplete>
<vn-submit label="Query" class="vn-mt-sm"></vn-submit>
</form>
</vn-side-menu>
<!-- Zone Popover -->
<vn-popover vn-id="zoneEvents">
<div class="zoneEvents">
<div class="header vn-pa-sm" translate>Zones</div>
<vn-data-viewer
data="::$ctrl.zoneClosing"
class="vn-w-md vn-mb-xl">
<vn-card>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="zone in $ctrl.zoneClosing"
ui-sref="zone.card.summary({id: zone.id})"
class="clickable search-result">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td>{{::zone.agencyModeName}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="$ctrl.preview(zone)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
</div>
</vn-popover>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary>
</vn-popup>

View File

@ -1,95 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
$onInit() {
this.setParams();
}
$postLink() {
this.deliveryMethodFk = 'delivery';
}
setParams() {
const hasParams = this.$params.deliveryMethodFk || this.$params.geoFk || this.$params.agencyModeFk;
if (hasParams) {
if (this.$params.deliveryMethodFk)
this.deliveryMethodFk = this.$params.deliveryMethodFk;
if (this.$params.geoFk)
this.geoFk = this.$params.geoFk;
if (this.$params.agencyModeFk)
this.agencyModeFk = this.$params.agencyModeFk;
this.fetchData();
}
}
fetchData() {
const params = {
deliveryMethodFk: this.deliveryMethodFk,
geoFk: this.geoFk,
agencyModeFk: this.agencyModeFk
};
this.$.data = null;
this.$http.get(`Zones/getEvents`, {params})
.then(res => {
let data = res.data;
this.$.data = data;
if (!data.events.length)
this.vnApp.showMessage(this.$t('No service for the specified zone'));
this.$state.go(this.$state.current.name, params);
});
}
get deliveryMethodFk() {
return this._deliveryMethodFk;
}
set deliveryMethodFk(value) {
this._deliveryMethodFk = value;
let filter;
if (value === 'pickUp')
filter = {where: {code: 'PICKUP'}};
else
filter = {where: {code: {inq: ['DELIVERY', 'AGENCY']}}};
this.$http.get(`DeliveryMethods`, {filter}).then(res => {
const deliveryMethods = res.data.map(deliveryMethod => deliveryMethod.id);
this.agencyFilter = {deliveryMethodFk: {inq: deliveryMethods}};
});
}
onSelection($event, $events, $days) {
if (!$events.length) return;
const day = $days[0];
const zoneIds = [];
for (let event of $events)
zoneIds.push(event.zoneFk);
const params = {
zoneIds: zoneIds,
date: day
};
this.$http.post(`Zones/getZoneClosing`, params)
.then(res => this.zoneClosing = res.data)
.then(() => this.$.zoneEvents.show($event.target));
}
preview(zone) {
this.selectedZone = zone;
this.$.summary.show();
}
}
ngModule.vnComponent('vnZoneDeliveryDays', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,126 +0,0 @@
import './index.js';
import popover from 'core/mocks/popover';
import crudModel from 'core/mocks/crud-model';
describe('Zone Component vnZoneDeliveryDays', () => {
let $httpBackend;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = angular.element('<vn-zone-delivery-days></vn-zone-delivery-days');
controller = $componentController('vnZoneDeliveryDays', {$element});
controller.$.zoneEvents = popover;
controller.$.params = {};
controller.$.zoneModel = crudModel;
}));
describe('deliveryMethodFk() setter', () => {
it('should set the deliveryMethodFk property as pickup and then perform a query that sets the filter', () => {
$httpBackend.expect('GET', 'DeliveryMethods').respond([{id: 999}]);
controller.deliveryMethodFk = 'pickUp';
$httpBackend.flush();
expect(controller.agencyFilter).toEqual({deliveryMethodFk: {inq: [999]}});
});
});
describe('setParams()', () => {
it('should do nothing when no params are received', () => {
controller.setParams();
expect(controller.deliveryMethodFk).toBeUndefined();
expect(controller.geoFk).toBeUndefined();
expect(controller.agencyModeFk).toBeUndefined();
});
it('should set the controller properties when the params are provided', () => {
controller.$params = {
deliveryMethodFk: 3,
geoFk: 2,
agencyModeFk: 1
};
controller.setParams();
expect(controller.deliveryMethodFk).toEqual(controller.$params.deliveryMethodFk);
expect(controller.geoFk).toEqual(controller.$params.geoFk);
expect(controller.agencyModeFk).toEqual(controller.$params.agencyModeFk);
});
});
describe('fetchData()', () => {
it('should make an HTTP GET query and then call the showMessage() method', () => {
jest.spyOn(controller.vnApp, 'showMessage');
jest.spyOn(controller.$state, 'go');
controller.agencyModeFk = 1;
controller.deliveryMethodFk = 2;
controller.geoFk = 3;
controller.$state.current.name = 'myState';
const expectedData = {events: []};
const url = 'Zones/getEvents?agencyModeFk=1&deliveryMethodFk=2&geoFk=3';
$httpBackend.when('GET', 'DeliveryMethods').respond([]);
$httpBackend.expect('GET', url).respond({events: []});
controller.fetchData();
$httpBackend.flush();
expect(controller.$.data).toEqual(expectedData);
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No service for the specified zone');
expect(controller.$state.go).toHaveBeenCalledWith(
controller.$state.current.name,
{
agencyModeFk: 1,
deliveryMethodFk: 2,
geoFk: 3
}
);
});
});
describe('onSelection()', () => {
it('should not call the show popover method if events array is empty', () => {
jest.spyOn(controller.$.zoneEvents, 'show');
const event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent(event);
const events = [];
controller.onSelection(event, events);
expect(controller.$.zoneEvents.show).not.toHaveBeenCalled();
});
it('should call the show() method and call getZoneClosing() with the expected ids', () => {
jest.spyOn(controller.$.zoneEvents, 'show');
const event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent(event);
const day = Date.vnNew();
const events = [
{zoneFk: 1},
{zoneFk: 2},
{zoneFk: 8}
];
const params = {
zoneIds: [1, 2, 8],
date: [day][0]
};
const response = [{id: 1, hour: ''}];
$httpBackend.when('POST', 'Zones/getZoneClosing', params).respond({response});
controller.onSelection(event, events, [day]);
$httpBackend.flush();
expect(controller.$.zoneEvents.show).toHaveBeenCalledWith(target);
expect(controller.zoneClosing.id).toEqual(response.id);
});
});
});

View File

@ -1,36 +0,0 @@
@import "variables";
vn-zone-delivery-days {
vn-zone-calendar {
display: flex;
justify-content: center;
flex-wrap: wrap;
& > vn-calendar {
min-width: 264px;
}
}
form {
display: flex;
flex-direction: column;
}
}
.zoneEvents {
width: 700px;
max-height: 450px;
vn-data-viewer {
margin-bottom: 0;
vn-pagination {
padding: 0
}
}
& > .header {
background-color: $color-main;
color: white;
font-weight: bold;
text-align: center
}
}

View File

@ -1,4 +0,0 @@
<slot-descriptor>
<vn-zone-descriptor>
</vn-zone-descriptor>
</slot-descriptor>

View File

@ -1,9 +0,0 @@
import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnZoneDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -1,57 +0,0 @@
<vn-descriptor-content
module="zone"
description="$ctrl.zone.name"
summary="$ctrl.$.summary">
<slot-menu>
<vn-item class="vn-item"
ng-click="$ctrl.onDelete()"
translate>
Delete
</vn-item>
<vn-item
ng-click="clone.show()"
name="cloneZone"
translate>
Clone
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
label="Agency"
value="{{$ctrl.zone.agencyMode.name}}">
</vn-label-value>
<vn-label-value
label="Closing hour"
value="{{$ctrl.zone.hour | date: 'HH:mm'}}">
</vn-label-value>
<vn-label-value
label="Traveling days"
value="{{$ctrl.zone.travelingDays}}">
</vn-label-value>
<vn-label-value
label="Price"
value="{{$ctrl.zone.price | currency: 'EUR': 2}}">
</vn-label-value>
<vn-label-value
label="Bonus"
value="{{$ctrl.zone.bonus | currency: 'EUR': 2}}">
</vn-label-value>
</div>
</slot-body>
</vn-descriptor-content>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.zone"></vn-zone-summary>
</vn-popup>
<vn-confirm
vn-id="deleteZone"
on-accept="$ctrl.deleteZone()"
message="This zone will be removed">
</vn-confirm>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -1,65 +0,0 @@
import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get zone() {
return this.entity;
}
set zone(value) {
this.entity = value;
}
loadData() {
const filter = {
include: [
{
relation: 'agencyMode',
scope: {
fields: ['name'],
}
}
]
};
return this.getData(`Zones/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
onDelete() {
const $t = this.$translate.instant;
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const filter = {where: {zoneFk: this.id, shipped: {gte: today}}};
this.$http.get(`Tickets`, {filter}).then(res => {
const ticketsAmount = res.data.length;
if (ticketsAmount) {
const params = {ticketsAmount};
const question = $t('This zone contains tickets', params, null, null, 'sanitizeParameters');
this.$.deleteZone.question = question;
this.$.deleteZone.show();
} else
this.deleteZone();
});
}
deleteZone() {
return this.$http.post(`Zones/${this.id}/deleteZone`).then(() => {
this.$state.go('zone.index');
this.vnApp.showSuccess(this.$t('Zone deleted'));
});
}
onCloneAccept() {
return this.$http.post(`Zones/${this.id}/clone`).
then(res => this.$state.go('zone.card.basicData', {id: res.data.id}));
}
}
ngModule.vnComponent('vnZoneDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
}
});

View File

@ -1,74 +0,0 @@
import './index.js';
describe('Zone descriptor', () => {
let $httpBackend;
let controller;
let $element;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = angular.element('<vn-zone-descriptor></vn-zone-descriptor');
controller = $componentController('vnZoneDescriptor', {$element});
controller.zone = {id: 1};
controller.id = 1;
controller.$.deleteZone = {
hide: () => {},
show: () => {}
};
}));
describe('onDelete()', () => {
it('should make an HTTP POST query and then call the deleteZone show() method', () => {
jest.spyOn(controller.$.deleteZone, 'show');
const expectedData = [{id: 16}];
$httpBackend.when('GET', 'Tickets').respond(expectedData);
controller.onDelete();
$httpBackend.flush();
expect(controller.$.deleteZone.show).toHaveBeenCalledWith();
});
it('should make an HTTP POST query and then call the deleteZone() method', () => {
jest.spyOn(controller, 'deleteZone').mockReturnThis();
const expectedData = [];
$httpBackend.when('GET', 'Tickets').respond(expectedData);
controller.onDelete();
$httpBackend.flush();
expect(controller.deleteZone).toHaveBeenCalledWith();
});
});
describe('deleteZone()', () => {
it('should make an HTTP POST query and then call the showMessage() method', () => {
jest.spyOn(controller.$state, 'go').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
const stateName = 'zone.index';
$httpBackend.when('POST', 'Zones/1/deleteZone').respond(200);
controller.deleteZone();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith(stateName);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Zone deleted');
});
});
describe('onCloneAccept()', () => {
it('should make an HTTP POST query and then call the state go() method', () => {
jest.spyOn(controller.$state, 'go').mockReturnThis();
const stateName = 'zone.card.basicData';
const expectedData = {id: 1};
$httpBackend.when('POST', 'Zones/1/clone').respond(expectedData);
controller.onCloneAccept();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith(stateName, expectedData);
});
});
});

View File

@ -1,4 +0,0 @@
This zone contains tickets: Esta zona contiene {{ticketsAmount}} tickets por servir. ¿Seguro que quieres eliminar esta zona?
Do you want to clone this zone?: ¿Quieres clonar esta zona?
All it's properties will be copied: Todas sus propiedades serán copiadas
Zone deleted: Zona eliminada

View File

@ -1,277 +0,0 @@
<vn-zone-calendar
id="calendar"
vn-id="calendar"
data="data"
on-selection="$ctrl.onSelection($days, $type, $weekday, $events, $exclusions, $geoExclusions)"
on-step="$ctrl.refresh()"
class="vn-w-md">
</vn-zone-calendar>
<vn-side-menu side="right">
<div class="vn-pa-md">
<h6
class="text-secondary"
style="font-weight: normal;"
translate>
Edit mode
</h6>
<vn-vertical>
<vn-radio
label="Include"
val="include"
ng-model="$ctrl.editMode">
</vn-radio>
<vn-radio
label="Exclude"
val="exclude"
ng-model="$ctrl.editMode">
</vn-radio>
</vn-vertical>
</div>
<h6
class="text-secondary vn-px-md"
style="font-weight: normal;"
translate>
Events
</h6>
<vn-data-viewer
data="data.events"
is-loading="!data.events">
<div class="vn-list separated">
<a
ng-repeat="row in data.events"
translate-attr="{title: 'Edit'}"
ng-click="$ctrl.onEditClick(row, $event)"
class="vn-item">
<vn-item-section>
<div
ng-if="::row.type == 'day'"
class="vn-mb-sm">
{{::row.dated | date:'dd/MM/yy'}}
</div>
<div
ng-if="::row.type != 'day'"
class="vn-mb-sm ellipsize">
<span ng-if="row.weekDays">
{{::$ctrl.formatWdays(row.weekDays)}}
</span>
<span ng-if="row.type == 'range'">
({{::row.started | date:'dd/MM/yy'}} - {{::row.ended | date:'dd/MM/yy'}})
</span>
</div>
<vn-label-value
label="Closing"
value="{{::row.hour | date:'HH:mm'}}">
</vn-label-value>
<vn-label-value
label="Traveling days"
value="{{::row.travelingDays}}">
</vn-label-value>
<vn-label-value
label="Price"
value="{{::row.price | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Bonus"
value="{{::row.bonus | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Max m³"
value="{{::row.m3Max}}">
</vn-label-value>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
icon="delete"
translate-attr="{title: 'Delete'}"
ng-click="$ctrl.onDeleteClick(row.id, $event)">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-data-viewer>
</vn-side-menu>
<vn-float-button
ng-click="$ctrl.createInclusion('weekday')"
icon="add"
vn-tooltip="Add event"
vn-bind="+"
fixed-bottom-right>
</vn-float-button>
<vn-dialog
vn-id="includeDialog"
on-response="$ctrl.onIncludeResponse($response)"
message="{{$ctrl.isNew ? 'Add event' : 'Edit event'}}">
<tpl-body>
<vn-vertical>
<vn-vertical class="vn-pb-md">
<vn-radio
ng-model="$ctrl.selected.type"
label="One day"
val="day">
</vn-radio>
<vn-radio
ng-model="$ctrl.selected.type"
label="Indefinitely"
val="indefinitely">
</vn-radio>
<vn-radio
ng-model="$ctrl.selected.type"
label="Range of dates"
val="range">
</vn-radio>
</vn-vertical>
<vn-wday-picker
ng-if="$ctrl.selected.type != 'day'"
ng-model="$ctrl.selected.wdays"
class="vn-mt-sm vn-mb-md">
</vn-wday-picker>
<vn-date-picker
ng-if="$ctrl.selected.type == 'day'"
label="Day"
ng-model="$ctrl.selected.dated">
</vn-date-picker>
<vn-horizontal
ng-if="$ctrl.selected.type == 'range'">
<vn-date-picker
label="From"
ng-model="$ctrl.selected.started">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="$ctrl.selected.ended">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-input-time
label="Closing"
ng-model="$ctrl.selected.hour">
</vn-input-time>
<vn-input-number
label="Traveling days"
ng-model="$ctrl.selected.travelingDays"
min="0"
step="1">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Price"
ng-model="$ctrl.selected.price"
min="0"
step="0.01">
</vn-input-number>
<vn-input-number
label="Bonus"
ng-model="$ctrl.selected.bonus"
min="0"
step="0.01">
</vn-input-number>
</vn-horizontal>
<vn-input-number
label="Max m³"
ng-model="$ctrl.selected.m3Max"
min="0"
step="0.01">
</vn-input-number>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input
type="button"
response="cancel"
translate-attr="{value: 'Cancel'}">
</input>
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate-attr="{value: 'Delete'}">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons>
</vn-dialog>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?">
</vn-confirm>
<vn-dialog
vn-id="excludeDialog"
on-response="$ctrl.onExcludeResponse($response)"
message="{{$ctrl.isNew ? 'Add exclusion' : 'Edit exclusion'}}"
on-open="$ctrl.onSearch($params)"
on-close="$ctrl.resetExclusions()">
<tpl-body>
<vn-date-picker
label="Day"
ng-model="$ctrl.excludeSelected.dated">
</vn-date-picker>
<vn-vertical class="width">
<vn-vertical class="vn-pb-md">
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="All"
on-change="$ctrl.test()"
val="all">
</vn-radio>
<vn-radio
ng-model="$ctrl.excludeSelected.type"
label="Specific locations"
on-change="$ctrl.onSearch($params)"
val="specificLocations">
</vn-radio>
</vn-vertical>
<vn-crud-model
vn-id="model"
url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="$ctrl.filter">
</vn-crud-model>
<div ng-if="$ctrl.excludeSelected.type == 'specificLocations'">
<vn-textfield
label="Search"
ng-keydown="$ctrl.onKeyDown($event)"
ng-model="$ctrl.excludeSearch">
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
</vn-textfield>
<div class="treeview">
<vn-treeview
vn-id="treeview"
root-label="Locations where it is not distributed"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check
ng-model="item.checked"
ng-click="$event.preventDefault()"
on-change="$ctrl.onItemCheck(item.id, value)"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</div>
</div>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input
type="button"
response="cancel"
translate-attr="{value: 'Cancel'}"
tabindex="0">
</input>
<input
type="button"
ng-if="!$ctrl.isNew"
response="delete"
translate-attr="{value: 'Delete'}"
tabindex="0">
</input>
<button response="accept">
<span ng-if="$ctrl.isNew" translate>Add</span>
<span ng-if="!$ctrl.isNew" translate>Save</span>
</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,316 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnWeekDays) {
super($element, $);
this.vnWeekDays = vnWeekDays;
this.editMode = 'include';
}
$onInit() {
this.refresh();
}
get path() {
return `Zones/${this.$params.id}/events`;
}
get exclusionsPath() {
return `Zones/${this.$params.id}/exclusions`;
}
get checked() {
const geos = this.$.model.data || [];
const checkedLines = [];
for (let geo of geos) {
if (geo.checked)
checkedLines.push(geo);
}
return checkedLines;
}
refresh() {
this.$.data = null;
this.$.$applyAsync(() => {
const params = {
zoneFk: this.$params.id,
started: this.$.calendar.firstDay,
ended: this.$.calendar.lastDay
};
this.$http.get(`Zones/getEventsFiltered`, {params}).then(res => {
const data = res.data;
this.$.data = data;
});
});
}
formatWdays(weekDays) {
if (!weekDays) return;
let abrWdays = weekDays
.split(',')
.map(wday => this.vnWeekDays.map[wday].localeAbr);
return abrWdays.length < 7
? abrWdays.join(', ')
: this.$t('Everyday');
}
onSelection(days, type, weekday, events, exclusions, exclusionGeos) {
if (this.editMode == 'include') {
if (events.length)
return this.editInclusion(events[0]);
return this.createInclusion(type, days, weekday);
} else if (this.editMode == 'exclude') {
if (exclusions.length || exclusionGeos.length)
return this.editExclusion(exclusions[0] || {}, exclusionGeos);
return this.createExclusion(days);
}
}
editExclusion(exclusion, exclusionGeos) {
this.isNew = false;
this.excludeSelected = angular.copy(exclusion);
this.excludeSelected.type = exclusionGeos.length ?
'specificLocations' : 'all';
this.exclusionGeos = new Set();
if (exclusionGeos.length) {
this.excludeSelected.id = exclusionGeos[0].zoneExclusionFk;
exclusionGeos.forEach(x => this.exclusionGeos.add(x.geoFk));
}
this.$.excludeDialog.show();
}
createExclusion(days) {
this.isNew = true;
this.excludeSelected = {
type: 'all',
dated: days[0]
};
this.exclusionGeos = new Set();
this.$.excludeDialog.show();
}
onEditClick(row, event) {
if (event.defaultPrevented) return;
this.editInclusion(row);
}
editInclusion(row) {
this.isNew = false;
this.selected = angular.copy(row);
this.selected.wdays = this.vnWeekDays.fromSet(row.weekDays);
this.$.includeDialog.show();
}
createInclusion(type, days, weekday) {
this.isNew = true;
if (type == 'weekday') {
let wdays = [];
if (weekday) wdays[weekday] = true;
this.selected = {
type: 'indefinitely',
wdays
};
} else {
this.selected = {
type: 'day',
dated: days[0]
};
}
this.$.includeDialog.show();
}
onIncludeResponse(response) {
switch (response) {
case 'accept': {
let selected = this.selected;
let type = selected.type;
selected.weekDays = this.vnWeekDays.toSet(selected.wdays);
if (type == 'day')
selected.weekDays = '';
else
selected.dated = null;
if (type != 'range') {
selected.started = null;
selected.ended = null;
}
let req;
if (this.isNew)
req = this.$http.post(this.path, selected);
else
req = this.$http.put(`${this.path}/${selected.id}`, selected);
return req.then(() => {
this.selected = null;
this.isNew = null;
this.refresh();
});
}
case 'delete':
return this.onDelete(this.selected.id)
.then(response => response == 'accept');
}
}
onExcludeResponse(response) {
const type = this.excludeSelected.type;
switch (response) {
case 'accept': {
if (type == 'all')
return this.exclusionCreate();
return this.exclusionGeoCreate();
}
case 'delete':
return this.exclusionDelete(this.excludeSelected);
}
}
onDeleteClick(id, event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.onDelete(id);
}
onDelete(id) {
return this.$.confirm.show(
response => this.onDeleteResponse(response, id));
}
onDeleteResponse(response, id) {
if (response != 'accept' || !id) return;
return this.$http.delete(`${this.path}/${id}`)
.then(() => this.refresh());
}
exclusionCreate() {
const excludeSelected = this.excludeSelected;
const dated = excludeSelected.dated;
let req;
if (this.isNew)
req = this.$http.post(this.exclusionsPath, [{dated}]);
if (!this.isNew)
req = this.$http.put(`${this.exclusionsPath}/${excludeSelected.id}`, {dated});
return req.then(() => {
this.refresh();
});
}
exclusionGeoCreate() {
const excludeSelected = this.excludeSelected;
let req;
const geoIds = [];
this.exclusionGeos.forEach(id => geoIds.push(id));
if (this.isNew) {
const params = {
zoneFk: parseInt(this.$params.id),
date: excludeSelected.dated,
geoIds
};
req = this.$http.post(`Zones/exclusionGeo`, params);
} else {
const params = {
zoneExclusionFk: this.excludeSelected.id,
geoIds
};
req = this.$http.post(`Zones/updateExclusionGeo`, params);
}
return req.then(() => this.refresh());
}
exclusionDelete(exclusion) {
const path = `${this.exclusionsPath}/${exclusion.id}`;
return this.$http.delete(path)
.then(() => this.refresh());
}
set excludeSearch(value) {
this._excludeSearch = value;
if (!value) this.onSearch();
}
get excludeSearch() {
return this._excludeSearch;
}
onKeyDown(event) {
if (event.key == 'Enter') {
event.preventDefault();
this.onSearch();
}
}
onSearch() {
const params = {search: this._excludeSearch};
if (this.excludeSelected.type == 'specificLocations') {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
this.$.treeview.data = data;
});
}
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.getChecked(data);
return data;
});
}
onSort(a, b) {
if (b.selected !== a.selected) {
if (a.selected == null)
return 1;
if (b.selected == null)
return -1;
return b.selected - a.selected;
}
return a.name.localeCompare(b.name);
}
getChecked(data) {
for (let geo of data) {
geo.checked = this.exclusionGeos.has(geo.id);
if (geo.childs) this.getChecked(geo.childs);
}
}
onItemCheck(geoId, checked) {
if (checked)
this.exclusionGeos.add(geoId);
else
this.exclusionGeos.delete(geoId);
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
ngModule.vnComponent('vnZoneEvents', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,340 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneEvents', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-events></vn-zone-events>`);
controller = $componentController('vnZoneEvents', {$element, $scope});
controller.$params = {id: 1};
}));
describe('refresh()', () => {
it('should set the zone and then call both getSummary() and getWarehouses()', () => {
const date = '2021-10-01';
controller.$params.id = 999;
controller.$.calendar = {
firstDay: date,
lastDay: date
};
const params = {
zoneFk: controller.$params.id,
started: date,
ended: date
};
const query = `Zones/getEventsFiltered?ended=${date}&started=${date}&zoneFk=${params.zoneFk}`;
const response = {
events: 'myEvents',
exclusions: 'myExclusions',
geoExclusions: 'myGeoExclusions',
};
$httpBackend.whenGET(query).respond(response);
controller.refresh();
$httpBackend.flush();
const data = controller.$.data;
expect(data.events).toBeDefined();
expect(data.exclusions).toBeDefined();
});
});
describe('onSelection()', () => {
it('should call the editInclusion() method', () => {
jest.spyOn(controller, 'editInclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [{name: 'Event'}];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editInclusion).toHaveBeenCalledWith({name: 'Event'});
});
it('should call the createInclusion() method', () => {
jest.spyOn(controller, 'createInclusion').mockReturnThis();
const weekday = {dated: Date.vnNew()};
const days = [weekday];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'include';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.createInclusion).toHaveBeenCalledWith(type, days, weekday);
});
it('should call the editExclusion() method with exclusions', () => {
jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [];
const exclusions = [{name: 'Exclusion'}];
const exclusionsGeo = [];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editExclusion).toHaveBeenCalled();
});
it('should call the editExclusion() method with exclusionsGeo', () => {
jest.spyOn(controller, 'editExclusion').mockReturnThis();
const weekday = {};
const days = [];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [{name: 'GeoExclusion'}];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.editExclusion).toHaveBeenCalled();
});
it('should call the createExclusion() method', () => {
jest.spyOn(controller, 'createExclusion').mockReturnThis();
const weekday = {};
const days = [{dated: Date.vnNew()}];
const type = 'EventType';
const events = [];
const exclusions = [];
const exclusionsGeo = [];
controller.editMode = 'exclude';
controller.onSelection(days, type, weekday, events, exclusions, exclusionsGeo);
expect(controller.createExclusion).toHaveBeenCalledWith(days);
});
});
describe('editExclusion()', () => {
it('shoud set the excludeSelected.type = "specificLocations" and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [{id: 1}];
const exclusions = [];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('specificLocations');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
it('shoud set the excludeSelected.type = "all" and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const exclusionGeos = [];
const exclusions = [{id: 1}];
controller.editExclusion(exclusions, exclusionGeos);
expect(controller.excludeSelected.type).toEqual('all');
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createExclusion()', () => {
it('shoud set the excludeSelected property and then call the excludeDialog show() method', () => {
controller.$.excludeDialog = {show: jest.fn()};
const days = [Date.vnNew()];
controller.createExclusion(days);
expect(controller.excludeSelected).toBeDefined();
expect(controller.isNew).toBeTruthy();
expect(controller.$.excludeDialog.show).toHaveBeenCalledWith();
});
});
describe('createInclusion()', () => {
it('shoud set the selected property and then call the includeDialog show() method', () => {
controller.$.includeDialog = {show: jest.fn()};
const type = 'weekday';
const days = [Date.vnNew()];
const weekday = 1;
controller.createInclusion(type, days, weekday);
const selection = controller.selected;
const firstWeekday = selection.wdays[weekday];
expect(selection.type).toEqual('indefinitely');
expect(firstWeekday).toBeTruthy();
expect(controller.isNew).toBeTruthy();
expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
});
it('shoud set the selected property with the first day and then call the includeDialog show() method', () => {
controller.$.includeDialog = {show: jest.fn()};
const type = 'nonListedType';
const days = [Date.vnNew()];
const weekday = 1;
controller.createInclusion(type, days, weekday);
const selection = controller.selected;
expect(selection.type).toEqual('day');
expect(selection.dated).toEqual(days[0]);
expect(controller.isNew).toBeTruthy();
expect(controller.$.includeDialog.show).toHaveBeenCalledWith();
});
});
describe('onIncludeResponse()', () => {
it('shoud call the onDelete() method', () => {
jest.spyOn(controller, 'onDelete').mockReturnValue(
new Promise(accept => accept())
);
controller.selected = {id: 1};
controller.onIncludeResponse('delete');
expect(controller.onDelete).toHaveBeenCalledWith(1);
});
it('shoud make an HTTP POST query to create a new one and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.selected = {id: 1};
controller.isNew = true;
$httpBackend.when('POST', `Zones/1/events`).respond(200);
controller.onIncludeResponse('accept');
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
it('shoud make an HTTP PUT query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.selected = {id: 1};
controller.isNew = false;
const eventId = 1;
$httpBackend.when('PUT', `Zones/1/events/${eventId}`).respond(200);
controller.onIncludeResponse('accept');
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('onExcludeResponse()', () => {
it('should call the exclusionCreate() method', () => {
jest.spyOn(controller, 'exclusionCreate').mockReturnThis();
controller.excludeSelected = {type: 'all'};
controller.onExcludeResponse('accept');
expect(controller.exclusionCreate).toHaveBeenCalledWith();
});
it('should call the exclusionGeoCreate() method', () => {
jest.spyOn(controller, 'exclusionGeoCreate').mockReturnThis();
controller.excludeSelected = {type: 'specificLocations'};
controller.onExcludeResponse('accept');
expect(controller.exclusionGeoCreate).toHaveBeenCalledWith();
});
it('should call the exclusionDelete() method', () => {
jest.spyOn(controller, 'exclusionDelete').mockReturnThis();
controller.excludeSelected = {id: 1, type: 'all'};
controller.onExcludeResponse('delete');
expect(controller.exclusionDelete).toHaveBeenCalledWith(controller.excludeSelected);
});
});
describe('onDeleteResponse()', () => {
it('shoud make an HTTP DELETE query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
const eventId = 1;
$httpBackend.expect('DELETE', `Zones/1/events/1`).respond({id: 1});
controller.onDeleteResponse('accept', eventId);
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('exclusionCreate()', () => {
it('shoud make an HTTP POST query and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
controller.excludeSelected = {};
controller.isNew = true;
$httpBackend.expect('POST', `Zones/1/exclusions`).respond({id: 1});
controller.exclusionCreate();
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('exclusionDelete()', () => {
it('shoud make an HTTP DELETE query once and then call the refresh() method', () => {
jest.spyOn(controller, 'refresh').mockReturnThis();
const exclusions = {id: 1};
const firstExclusionId = 1;
$httpBackend.expectDELETE(`Zones/1/exclusions/${firstExclusionId}`).respond(200);
controller.exclusionDelete(exclusions);
$httpBackend.flush();
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('onSearch()', () => {
it('should call the applyFilter() method and then set the data', () => {
jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
controller.$.treeview = {};
controller.$.model = crudModel;
controller.excludeSelected = {type: 'specificLocations'};
controller._excludeSearch = 'es';
controller.onSearch();
const treeviewData = controller.$.treeview.data;
expect(treeviewData).toBeDefined();
expect(treeviewData.length).toEqual(3);
});
});
describe('onFetch()', () => {
it('should call the applyFilter() method and then return the model data', () => {
jest.spyOn(controller, 'getChecked').mockReturnValue([1, 2, 3]);
controller.$.model = crudModel;
const result = controller.onFetch();
expect(result.length).toEqual(3);
});
});
});

View File

@ -1,12 +0,0 @@
Edit mode: Modo de edición
Include: Incluir
Exclude: Excluir
Events: Eventos
Add event: Añadir evento
Edit event: Editar evento
All: Todo
Specific locations: Localizaciones concretas
Locations where it is not distributed: Localizaciones en las que no se reparte
You must select a location: Debes seleccionar una localización
Add exclusion: Añadir exclusión
Edit exclusion: Editar exclusión

View File

@ -1,11 +0,0 @@
@import "variables";
.width{
width: 600px
}
.treeview{
max-height: 300px;
overflow: auto;
}

View File

@ -1,19 +1,3 @@
export * from './module';
import './main';
import './index/';
import './delivery-days';
import './summary';
import './card';
import './descriptor';
import './descriptor-popover';
import './search-panel';
import './create';
import './basic-data';
import './warehouses';
import './events';
import './calendar';
import './location';
import './calendar';
import './upcoming-deliveries';
import './log';

View File

@ -1,68 +0,0 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-md vn-mb-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="name" expand>Name</vn-th>
<vn-th field="agencyModeFk">Agency</vn-th>
<vn-th field="hour" shrink>Closing</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a
ng-repeat="zone in model.data"
ui-sref="zone.card.summary({id: zone.id})"
class="clickable vn-tr search-result">
<vn-td number>{{::zone.id}}</vn-td>
<vn-td expand>{{::zone.name}}</vn-td>
<vn-td expand>{{::zone.agencyMode.name}}</vn-td>
<vn-td shrink>{{::zone.hour | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::zone.price | currency: 'EUR':2}}</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
vn-click-stop="clone.show(zone)"
vn-tooltip="Clone"
icon="icon-clone"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(zone)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-popup vn-id="summary">
<vn-zone-summary zone="$ctrl.selectedZone"></vn-zone-summary>
</vn-popup>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept($data)"
question="Do you want to clone this zone?"
message="All it's properties will be copied">
</vn-confirm>
<a ui-sref="zone.create"
vn-tooltip="New zone"
vn-bind="+"
fixed-bottom-right>
<vn-float-button
icon="add"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-float-button>
</a>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
preview(zone) {
this.selectedZone = zone;
this.$.summary.show();
}
onCloneAccept(zone) {
return this.$http.post(`Zones/${zone.id}/clone`)
.then(res => {
this.$state.go('zone.card.basicData', {id: res.data.id});
});
}
}
ngModule.vnComponent('vnZoneIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,2 +0,0 @@
Do you want to clone this zone?: ¿Seguro que quieres clonar esta zona?
All it's properties will be copied: Todas sus propiedades serán copiadas

View File

@ -1,28 +0,0 @@
<vn-crud-model
vn-id="model"
url="Zones/{{$ctrl.$params.id}}/getLeaves"
filter="$ctrl.filter">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
on-search="$ctrl.onSearch($params)"
auto-state="false">
</vn-searchbar>
</vn-portal>
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-treeview
vn-id="treeview"
root-label="Locations"
fetch-func="$ctrl.onFetch($item)"
sort-func="$ctrl.onSort($a, $b)">
<vn-check acl-role="deliveryAssistant"
ng-model="item.selected"
on-change="$ctrl.onSelection(value, item)"
triple-state="true"
ng-click="$event.preventDefault()"
label="{{::item.name}}">
</vn-check>
</vn-treeview>
</vn-card>
</div>

View File

@ -1,56 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
onSearch(params) {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.$.treeview.data = data;
});
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params)
.then(() => this.$.model.data);
}
onSort(a, b) {
if (b.selected !== a.selected) {
if (a.selected == null)
return 1;
if (b.selected == null)
return -1;
return b.selected - a.selected;
}
return a.name.localeCompare(b.name);
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return {name: {like: `%${value}%`}};
}
}
onSelection(value, item) {
if (value == null)
value = undefined;
const params = {geoId: item.id, isIncluded: value};
const path = `zones/${this.zone.id}/toggleIsIncluded`;
this.$http.post(path, params);
}
}
ngModule.vnComponent('vnZoneLocation', {
template: require('./index.html'),
controller: Controller,
bindings: {
zone: '<'
},
require: {
card: '^vnZoneCard'
}
});

View File

@ -1,50 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('component vnZoneLocation', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('zone'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element(`<vn-zone-location></vn-zone-location>`);
controller = $componentController('vnZoneLocation', {$element, $scope});
controller.$.model = crudModel;
controller.zone = {id: 1};
}));
describe('onSearch()', () => {
it('should call the applyFilter() method and then set the data', () => {
controller.$.treeview = {};
controller.onSearch({});
const treeviewData = controller.$.treeview.data;
expect(treeviewData).toBeDefined();
expect(treeviewData.length).toEqual(3);
});
});
describe('onFetch()', () => {
it('should call the applyFilter() method and then return the model data', () => {
const result = controller.onFetch();
expect(result.length).toEqual(3);
});
});
describe('onSelection()', () => {
it('should make an HTTP POST query', () => {
const item = {id: 123};
const expectedParams = {geoId: 123, isIncluded: true};
$httpBackend.expect('POST', `zones/1/toggleIsIncluded`, expectedParams).respond(200);
controller.onSelection(true, item);
$httpBackend.flush();
});
});
});

View File

@ -1,21 +0,0 @@
@import "variables";
vn-zone-location {
vn-treeview-child {
.content > .vn-check:not(.indeterminate):not(.checked) {
color: $color-alert;
& > .btn {
border-color: $color-alert;
}
}
.content > .vn-check.checked {
color: $color-notice;
& > .btn {
background-color: $color-notice;
border-color: $color-notice
}
}
}
}

View File

@ -1 +0,0 @@
<vn-log url="ZoneLogs" origin-id="$ctrl.$params.id"></vn-log>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnZoneLog', {
template: require('./index.html'),
controller: Section,
});

Some files were not shown because too many files have changed in this diff Show More