Merge branch 'dev' into 6321_negative_tickets
gitea/salix/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Javier Segarra 2024-06-04 09:26:15 +02:00
commit 149aeac54e
77 changed files with 1091 additions and 528 deletions

8
Jenkinsfile vendored
View File

@ -121,7 +121,7 @@ pipeline {
steps { steps {
script { script {
def packageJson = readJSON file: 'package.json' def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
} }
sh 'docker-compose build back' sh 'docker-compose build back'
} }
@ -159,7 +159,7 @@ pipeline {
steps { steps {
script { script {
def packageJson = readJSON file: 'package.json' def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
} }
sh 'gulp build' sh 'gulp build'
sh 'docker-compose build front' sh 'docker-compose build front'
@ -179,7 +179,7 @@ pipeline {
steps { steps {
script { script {
def packageJson = readJSON file: 'package.json' def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
} }
sh 'docker login --username $CREDENTIALS_USR --password $CREDENTIALS_PSW $REGISTRY' sh 'docker login --username $CREDENTIALS_USR --password $CREDENTIALS_PSW $REGISTRY'
sh 'docker-compose push' sh 'docker-compose push'
@ -210,7 +210,7 @@ pipeline {
steps { steps {
script { script {
def packageJson = readJSON file: 'package.json' def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
} }
withKubeConfig([ withKubeConfig([
serverUrl: "$KUBERNETES_API", serverUrl: "$KUBERNETES_API",

View File

@ -26,16 +26,24 @@
<mrw:Nif><%= expeditionData.fi %></mrw:Nif> <mrw:Nif><%= expeditionData.fi %></mrw:Nif>
<mrw:Nombre><%= expeditionData.clientName %></mrw:Nombre> <mrw:Nombre><%= expeditionData.clientName %></mrw:Nombre>
<mrw:Telefono><%= expeditionData.phone %></mrw:Telefono> <mrw:Telefono><%= expeditionData.phone %></mrw:Telefono>
<mrw:Observaciones><%= expeditionData.deliveryObservation %></mrw:Observaciones>
</mrw:DatosEntrega> </mrw:DatosEntrega>
<mrw:DatosServicio> <mrw:DatosServicio>
<mrw:Fecha><%= expeditionData.created %></mrw:Fecha> <mrw:Fecha><%= expeditionData.created %></mrw:Fecha>
<mrw:Referencia><%= expeditionData.expeditionDataId %></mrw:Referencia> <mrw:Referencia><%= expeditionData.reference %></mrw:Referencia>
<mrw:CodigoServicio><%= expeditionData.serviceType %></mrw:CodigoServicio> <mrw:CodigoServicio><%= expeditionData.serviceType %></mrw:CodigoServicio>
<mrw:NumeroBultos>1</mrw:NumeroBultos> <mrw:NumeroBultos>1</mrw:NumeroBultos>
<mrw:EntregaSabado><%= expeditionData.weekDays %></mrw:EntregaSabado> <mrw:EntregaSabado><%= expeditionData.weekDays %></mrw:EntregaSabado>
<mrw:Peso><%= expeditionData.kg %></mrw:Peso>
<mrw:Reembolso/> <mrw:Reembolso/>
<mrw:ImporteReembolso/> <mrw:ImporteReembolso/>
<mrw:Bultos>
<mrw:BultoRequest>
<mrw:Alto><%= mrw.defaultHeight %></mrw:Alto>
<mrw:Largo><%= mrw.defaultLength %></mrw:Largo>
<mrw:Ancho><%= mrw.defaultWidth %></mrw:Ancho>
<mrw:Peso><%= mrw.defaultWeight %></mrw:Peso>
</mrw:BultoRequest>
</mrw:Bultos>
</mrw:DatosServicio> </mrw:DatosServicio>
</mrw:request> </mrw:request>
</mrw:TransmEnvio> </mrw:TransmEnvio>

View File

@ -45,7 +45,7 @@ module.exports = Self => {
`SELECT `SELECT
CASE co.code CASE co.code
WHEN 'ES' THEN a.postalCode WHEN 'ES' THEN a.postalCode
WHEN 'PT' THEN LEFT(a.postalCode, 4) WHEN 'PT' THEN LEFT(a.postalCode, mc.portugalPostCodeTrim)
WHEN 'AD' THEN REPLACE(a.postalCode, 'AD', '00') WHEN 'AD' THEN REPLACE(a.postalCode, 'AD', '00')
END postalCode, END postalCode,
a.city, a.city,
@ -56,9 +56,10 @@ module.exports = Self => {
c.phone, c.phone,
DATE_FORMAT(t.shipped, '%d/%m/%Y') created, DATE_FORMAT(t.shipped, '%d/%m/%Y') created,
t.shipped, t.shipped,
e.id expeditionId, CONCAT( e.ticketFk, LPAD(e.counter, mc.counterWidth, '0')) reference,
LPAD(IF(mw.params IS NULL, ms.serviceType, mw.serviceType), 4 ,'0') serviceType, LPAD(IF(mw.params IS NULL, ms.serviceType, mw.serviceType), mc.serviceTypeWidth,'0') serviceType,
IF(mw.weekdays, 'S', 'N') weekDays IF(mw.weekdays, 'S', 'N') weekDays,
oa.description deliveryObservation
FROM expedition e FROM expedition e
JOIN ticket t ON e.ticketFk = t.id JOIN ticket t ON e.ticketFk = t.id
JOIN agencyMode am ON am.id = t.agencyModeFk JOIN agencyMode am ON am.id = t.agencyModeFk
@ -66,8 +67,12 @@ module.exports = Self => {
LEFT JOIN mrwServiceWeekday mw ON mw.weekdays = DATE_FORMAT(t.shipped, '%a') LEFT JOIN mrwServiceWeekday mw ON mw.weekdays = DATE_FORMAT(t.shipped, '%a')
JOIN client c ON t.clientFk = c.id JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id JOIN address a ON t.addressFk = a.id
LEFT JOIN addressObservation oa ON oa.addressFk = a.id
LEFT JOIN observationType ot ON ot.id = oa.observationTypeFk
AND ot.code = 'delivery'
JOIN province p ON a.provinceFk = p.id JOIN province p ON a.provinceFk = p.id
JOIN country co ON co.id = p.countryFk JOIN country co ON co.id = p.countryFk
JOIN mrwConfig mc
WHERE e.id = ? WHERE e.id = ?
LIMIT 1`; LIMIT 1`;

View File

@ -1,6 +1,16 @@
{ {
"name": "Collection", "name": "Collection",
"base": "VnModel", "base": "VnModel",
"properties": {
"id": {
"id": true,
"type": "number",
"required": true
},
"workerFk": {
"type": "number"
}
},
"options": { "options": {
"mysql": { "mysql": {
"table": "collection" "table": "collection"

View File

@ -27,6 +27,18 @@
}, },
"subscriberCode": { "subscriberCode": {
"type": "string" "type": "string"
},
"defaultHeight": {
"type": "number"
},
"defaultLength": {
"type": "number"
},
"defaultWidth": {
"type": "number"
},
"defaultWeight": {
"type": "number"
} }
} }
} }

View File

@ -762,7 +762,12 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL), (32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL); (33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL),
(34, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1103, 'BEJAR', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(35, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Somewhere in Philippines', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(36, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Ant-Man Adventure', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(37, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1110, 'Deadpool swords', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`) INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES VALUES
(1, 11, 1, 'ready'), (1, 11, 1, 'ready'),
@ -808,7 +813,10 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`)
(21, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)), (21, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)), (22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(23, 16, 21, util.VN_NOW()), (23, 16, 21, util.VN_NOW()),
(24, 16, 21, util.VN_NOW()); (24, 16, 21, util.VN_NOW()),
(34, 14, 49, util.VN_NOW()),
(35, 14, 18, util.VN_NOW()),
(36, 14, 18, util.VN_NOW());
INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`) INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`)
VALUES VALUES
@ -1068,7 +1076,10 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
(37, 4, 31, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), (37, 4, 31, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(36, 4, 30, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()), (36, 4, 30, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)), (38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
(39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE()); (39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE()),
(40, 2, 34, 'Melee weapon combat fist 15cm', 10.00, 3.91, 0, 0, 0, util.VN_CURDATE()),
(41, 2, 35, 'Melee weapon combat fist 15cm', 8.00, 3.01, 0, 0, 0, util.VN_CURDATE()),
(42, 2, 36, 'Melee weapon combat fist 15cm', 6.00, 2.50, 0, 0, 0, util.VN_CURDATE());
INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`) INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
VALUES VALUES
@ -1247,14 +1258,20 @@ INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPacki
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`) INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1), (1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),
(2, 1106, 14, util.VN_CURDATE(), 1); (2, 1106, 14, util.VN_CURDATE(), 1),
(4, 49, 5, util.VN_CURDATE(), 1),
(5, 18, 5, util.VN_CURDATE(), 1),
(6, 18, 5, util.VN_CURDATE(), 1);
INSERT INTO `vn`.`ticketCollection`(`ticketFk`, `collectionFk`, `level`) INSERT INTO `vn`.`ticketCollection`(`ticketFk`, `collectionFk`, `level`)
VALUES VALUES
(1, 1, 1), (1, 1, 1),
(2, 1, NULL), (2, 1, NULL),
(3, 2, NULL), (3, 2, NULL),
(23, 1, NULL); (23, 1, NULL),
(34, 4, 1),
(35, 5, 1),
(8, 6, 1);
INSERT INTO `vn`.`genus`(`id`, `name`) INSERT INTO `vn`.`genus`(`id`, `name`)
VALUES VALUES
@ -3714,7 +3731,8 @@ INSERT IGNORE INTO vn.saleGroup
SET id = 4, SET id = 4,
userFk = 1, userFk = 1,
parkingFk = 9, parkingFk = 9,
sectorFk = 9992; sectorFk = 9992,
ticketFk = 36;
INSERT IGNORE INTO vn.sectorCollectionSaleGroup INSERT IGNORE INTO vn.sectorCollectionSaleGroup
SET id = 9999, SET id = 9999,
@ -3816,3 +3834,27 @@ INSERT INTO `vn`.`ledgerCompany` SET
INSERT INTO `vn`.`ledgerConfig` SET INSERT INTO `vn`.`ledgerConfig` SET
maxTolerance = 0.01; maxTolerance = 0.01;
INSERT INTO vn.sectorCollection
SET id = 2,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 8,
sectorCollectionFk = 2,
saleGroupFk = 4;
INSERT INTO vn.saleGroup (userFk, parkingFk, sectorFk, ticketFk)
VALUES
(1, 1, 1, 37);
INSERT INTO vn.sectorCollection
SET id = 3,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 9,
sectorCollectionFk = 3,
saleGroupFk = 6;

View File

@ -48,7 +48,7 @@ BEGIN
-- Marcamos para no repetir -- Marcamos para no repetir
UPDATE vn.claimEnd ce UPDATE vn.claimEnd ce
JOIN vn.claim c ON c.id = ce.claimFk JOIN vn.claim c ON c.id = ce.claimFk
SET c.isChargedToMana = TRUE SET ce.isGreuge = TRUE
WHERE ce.claimDestinationFk NOT IN (1,5) WHERE ce.claimDestinationFk NOT IN (1,5)
AND NOT ce.isGreuge AND NOT ce.isGreuge
AND c.claimStateFk = 3; AND c.claimStateFk = 3;

View File

@ -44,8 +44,8 @@ proc:BEGIN
addressFk addressFk
) )
SELECT CONCAT(i.name, ' by ',a.nickname), SELECT CONCAT(i.name, ' by ',a.nickname),
i.minPrice + apc.deliveryCost, r.price + apc.deliveryCost,
i.id, r.itemFk,
vLanded, vLanded,
vPostalCode, vPostalCode,
it.name, it.name,
@ -53,13 +53,15 @@ proc:BEGIN
i.description, i.description,
apc.addressFk apc.addressFk
FROM vn.item i FROM vn.item i
JOIN (SELECT itemFk, SUM(quantity * cost) price
FROM recipe
GROUP BY itemFk) r ON r.itemFk = i.id
JOIN vn.itemType it ON it.id = i.typeFk JOIN vn.itemType it ON it.id = i.typeFk
JOIN addressPostCode apc JOIN addressPostCode apc
ON apc.dayOfWeek = dayOfWeek(vLanded) ON apc.dayOfWeek = dayOfWeek(vLanded)
AND NOW() < vLanded - INTERVAL apc.hoursInAdvance HOUR AND NOW() < vLanded - INTERVAL apc.hoursInAdvance HOUR
AND apc.postCode = vPostalCode AND apc.postCode = vPostalCode
JOIN vn.address a ON a.id = apc.addressFk JOIN vn.address a ON a.id = apc.addressFk;
WHERE it.code IN ('FNR','FNP');
SELECT * SELECT *
FROM catalogue FROM catalogue

View File

@ -18,10 +18,29 @@ proc:BEGIN
DECLARE vBodyEmail TEXT; DECLARE vBodyEmail TEXT;
DECLARE vZoneFk INT; DECLARE vZoneFk INT;
DECLARE exit handler FOR SQLEXCEPTION
BEGIN
ROLLBACK;
GET DIAGNOSTICS CONDITION 2 @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;
SELECT CONCAT('ERROR ', IFNULL(@errno,0), ': ', ifnull(@text,'texto')) AS `SQLEXCEPTION`;
CALL vn.mail_insert(
'floranet@verdnatura.es,pako@verdnatura.es',
'noreply@verdnatura.es',
'Floranet.order_confirm failure',
CONCAT('CatalogueFk: ', vCatalogueFk, '\n','ERROR ', IFNULL(@errno, 0), ': ', ifnull(@text, 'texto'))
);
END;
IF (SELECT isPaid FROM `order` WHERE catalogueFk = vCatalogueFk) THEN IF (SELECT isPaid FROM `order` WHERE catalogueFk = vCatalogueFk) THEN
CALL util.throw('Esta orden ya está confirmada'); SELECT CONCAT('CatalogueFk: ', vCatalogueFk, ' Esta orden ya está confirmada') AS `ERROR`;
LEAVE proc;
END IF; END IF;
START TRANSACTION;
UPDATE `order` UPDATE `order`
SET isPaid = TRUE, SET isPaid = TRUE,
payed = NOW() payed = NOW()
@ -90,6 +109,23 @@ proc:BEGIN
AND apc.dayOfWeek = dayOfWeek(c.dated) AND apc.dayOfWeek = dayOfWeek(c.dated)
WHERE c.id = vCatalogueFk; WHERE c.id = vCatalogueFk;
INSERT INTO vn.sale(
ticketFk,
itemFk,
concept,
price,
quantity)
SELECT
vNewTicketFk,
r.elementFk,
i.longName,
r.cost,
r.quantity
FROM catalogue c
JOIN recipe r ON r.itemFk = c.itemFk
JOIN vn.item i ON i.id = r.elementFk
WHERE c.id = vCatalogueFk;
SELECT cl.email, SELECT cl.email,
cf.email, cf.email,
CONCAT('Nuevo pedido FLORANET para entrega el ',c.dated), CONCAT('Nuevo pedido FLORANET para entrega el ',c.dated),
@ -124,5 +160,8 @@ proc:BEGIN
SELECT isPaid, vNewTicketFk SELECT isPaid, vNewTicketFk
FROM `order` FROM `order`
WHERE catalogueFk = vCatalogueFk; WHERE catalogueFk = vCatalogueFk;
COMMIT;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -11,10 +11,7 @@ BEGIN
*/ */
DECLARE vClient INT DEFAULT NULL; DECLARE vClient INT DEFAULT NULL;
-- SET vPhone = vPhone COLLATE 'utf8_unicode_ci'; CREATE OR REPLACE TEMPORARY TABLE tClient
DROP TEMPORARY TABLE IF EXISTS tClient;
CREATE TEMPORARY TABLE tClient
ENGINE = MEMORY ENGINE = MEMORY
SELECT id clientFk SELECT id clientFk
FROM `client` FROM `client`
@ -27,13 +24,14 @@ BEGIN
OR mobile = vPhone OR mobile = vPhone
UNION UNION
SELECT clientFk SELECT clientFk
FROM vn.clientContact FROM clientContact
WHERE phone = vPhone; WHERE phone = vPhone;
SELECT t.clientFk INTO vClient SELECT t.clientFk INTO vClient
FROM tClient t FROM tClient t
JOIN `client` c ON c.id = t.clientFk JOIN `client` c ON c.id = t.clientFk
WHERE c.isActive WHERE c.isActive
AND c.salesPersonFk
LIMIT 1; LIMIT 1;
DROP TEMPORARY TABLE tClient; DROP TEMPORARY TABLE tClient;

View File

@ -13,28 +13,12 @@ BEGIN
* @param vCollectionFk Id de colección * @param vCollectionFk Id de colección
*/ */
DECLARE vHasTooMuchCollections BOOL; DECLARE vHasTooMuchCollections BOOL;
DECLARE vItemPackingTypeFk VARCHAR(1);
DECLARE vWarehouseFk INT;
DECLARE vLockName VARCHAR(215);
DECLARE vLockTime INT DEFAULT 30;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
IF vLockName IS NOT NULL THEN
DO RELEASE_LOCK(vLockName);
CALL util.debugAdd('releaseLock', vLockName); -- Tmp
END IF;
RESIGNAL;
END;
-- Si hay colecciones sin terminar, sale del proceso -- Si hay colecciones sin terminar, sale del proceso
CALL collection_get(vUserFk); CALL collection_get(vUserFk);
SELECT (pc.maxNotReadyCollections - COUNT(*)) <= 0, SELECT (pc.maxNotReadyCollections - COUNT(*)) <= 0
collection_assign_lockname INTO vHasTooMuchCollections
INTO vHasTooMuchCollections,
vLockName
FROM productionConfig pc FROM productionConfig pc
LEFT JOIN tCollection ON TRUE; LEFT JOIN tCollection ON TRUE;
@ -44,21 +28,6 @@ BEGIN
CALL util.throw('Hay colecciones pendientes'); CALL util.throw('Hay colecciones pendientes');
END IF; END IF;
SELECT warehouseFk, itemPackingTypeFk
INTO vWarehouseFk, vItemPackingTypeFk
FROM operator
WHERE 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;
-- Se eliminan las colecciones sin asignar que estan obsoletas -- Se eliminan las colecciones sin asignar que estan obsoletas
INSERT INTO ticketTracking(stateFk, ticketFk) INSERT INTO ticketTracking(stateFk, ticketFk)
SELECT s.id, tc.ticketFk SELECT s.id, tc.ticketFk
@ -80,8 +49,7 @@ BEGIN
VALUES(vUserFk); VALUES(vUserFk);
-- Comprueba si hay colecciones disponibles que se ajustan a su configuracion -- Comprueba si hay colecciones disponibles que se ajustan a su configuracion
SELECT MIN(c.id) SELECT MIN(c.id) INTO vCollectionFk
INTO vCollectionFk
FROM `collection` c FROM `collection` c
JOIN operator o JOIN operator o
ON (o.itemPackingTypeFk = c.itemPackingTypeFk OR c.itemPackingTypeFk IS NULL) ON (o.itemPackingTypeFk = c.itemPackingTypeFk OR c.itemPackingTypeFk IS NULL)
@ -107,7 +75,5 @@ BEGIN
UPDATE `collection` UPDATE `collection`
SET workerFk = vUserFk SET workerFk = vUserFk
WHERE id = vCollectionFk; WHERE id = vCollectionFk;
DO RELEASE_LOCK(vLockName);
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -6,12 +6,12 @@ BEGIN
* *
* @param vDuaFk Id del dua a recalcular * @param vDuaFk Id del dua a recalcular
*/ */
DECLARE done BOOL DEFAULT FALSE; DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vInvoiceFk INT; DECLARE vInvoiceFk INT;
DECLARE vASIEN BIGINT DEFAULT 0; DECLARE vBookEntry INT;
DECLARE vCounter INT DEFAULT 0; DECLARE vFiscalYear INT;
DECLARE rs CURSOR FOR DECLARE vInvoicesIn CURSOR FOR
SELECT DISTINCT e.invoiceInFk SELECT DISTINCT e.invoiceInFk
FROM entry e FROM entry e
JOIN duaEntry de ON de.entryFk = e.id JOIN duaEntry de ON de.entryFk = e.id
@ -20,9 +20,7 @@ BEGIN
AND de.customsValue AND de.customsValue
AND ii.isBooked = FALSE; AND ii.isBooked = FALSE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
OPEN rs;
UPDATE invoiceIn ii UPDATE invoiceIn ii
JOIN entry e ON e.invoiceInFk = ii.id JOIN entry e ON e.invoiceInFk = ii.id
@ -36,38 +34,36 @@ BEGIN
e.isConfirmed = TRUE e.isConfirmed = TRUE
WHERE d.id = vDuaFk; WHERE d.id = vDuaFk;
SELECT IFNULL(ASIEN,0) INTO vASIEN SELECT ASIEN INTO vBookEntry FROM dua WHERE id = vDuaFk;
FROM dua
WHERE id = vDuaFk;
FETCH rs INTO vInvoiceFk;
WHILE NOT done DO
CALL invoiceIn_booking(vInvoiceFk);
IF vCounter > 0 OR vASIEN > 0 THEN
UPDATE XDiario x
JOIN ledgerConfig lc ON lc.lastBookEntry = x.ASIEN
SET x.ASIEN = vASIEN;
ELSE
SELECT lastBookEntry INTO vASIEN FROM ledgerConfig;
IF vBookEntry IS NULL THEN
SELECT YEAR(IFNULL(ii.bookEntried, d.bookEntried)) INTO vFiscalYear
FROM invoiceIn ii
JOIN entry e ON e.invoiceInFk = ii.id
JOIN duaEntry de ON de.entryFk = e.id
JOIN dua d ON d.id = de.duaFk
WHERE d.id = vDuaFk
LIMIT 1;
CALL ledger_next(vFiscalYear, FALSE, vBookEntry);
END IF; END IF;
SET vCounter = vCounter + 1; OPEN vInvoicesIn;
FETCH rs INTO vInvoiceFk; l: LOOP
SET vDone = FALSE;
FETCH vInvoicesIn INTO vInvoiceFk;
END WHILE; IF vDone THEN
LEAVE l;
END IF;
CLOSE rs; CALL invoiceIn_booking(vInvoiceFk, vBookEntry);
END LOOP;
CLOSE vInvoicesIn;
UPDATE dua UPDATE dua
SET ASIEN = vASIEN SET ASIEN = vBookEntry
WHERE id = vDuaFk; WHERE id = vDuaFk;
UPDATE invoiceIn ii UPDATE invoiceIn ii

View File

@ -12,7 +12,7 @@ BEGIN
WHERE id = vDuaFk; WHERE id = vDuaFk;
IF vBookNumber IS NULL OR NOT vBookNumber THEN IF vBookNumber IS NULL OR NOT vBookNumber THEN
CALL ledger_next(YEAR(vBookDated), vBookNumber); CALL ledger_next(YEAR(vBookDated), FALSE, vBookNumber);
END IF; END IF;
-- Apunte de la aduana -- Apunte de la aduana

View File

@ -1,7 +1,17 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceIn_booking`(vSelf INT) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceIn_booking`(
vSelf INT,
vBookNumber INT
)
BEGIN BEGIN
DECLARE vBookNumber INT; /**
* Genera la contabilidad para una factura y la marca como contabilizada
* Cuadra el asiento generado en si encuentra problemas derivados
* de los calculos con decimales
*
* @param vSelf Id invoiceIn
* @param vBookEntry Id de asiento, si es NULL se genera uno nuevo
*/
DECLARE vFiscalYear INT; DECLARE vFiscalYear INT;
CREATE OR REPLACE TEMPORARY TABLE tInvoiceIn CREATE OR REPLACE TEMPORARY TABLE tInvoiceIn
@ -58,7 +68,10 @@ BEGIN
WHERE ii.id = vSelf; WHERE ii.id = vSelf;
SELECT YEAR(bookEntried) INTO vFiscalYear FROM tInvoiceIn LIMIT 1; SELECT YEAR(bookEntried) INTO vFiscalYear FROM tInvoiceIn LIMIT 1;
CALL ledger_next(vFiscalYear, vBookNumber);
IF vBookNumber IS NULL THEN
CALL ledger_next(vFiscalYear, FALSE, vBookNumber);
END IF;
-- Apunte del proveedor -- Apunte del proveedor
INSERT INTO XDiario( INSERT INTO XDiario(

View File

@ -61,7 +61,7 @@ BEGIN
WHERE io.id = vInvoice; WHERE io.id = vInvoice;
SELECT YEAR(FECHA) INTO vFiscalYear FROM rs LIMIT 1; SELECT YEAR(FECHA) INTO vFiscalYear FROM rs LIMIT 1;
CALL ledger_next(vFiscalYear, vBookNumber); CALL ledger_next(vFiscalYear, FALSE, vBookNumber);
-- Linea del cliente -- Linea del cliente
INSERT INTO XDiario( INSERT INTO XDiario(
ASIEN, ASIEN,

View File

@ -28,7 +28,7 @@ BEGIN
DECLARE vIsOriginalAClient BOOL; DECLARE vIsOriginalAClient BOOL;
DECLARE vPayMethodCompensation INT; DECLARE vPayMethodCompensation INT;
CALL ledger_next(YEAR(vDated), vNewBookEntry); CALL ledger_next(YEAR(vDated), FALSE, vNewBookEntry);
SELECT COUNT(id) INTO vIsOriginalAClient SELECT COUNT(id) INTO vIsOriginalAClient
FROM client FROM client

View File

@ -1,6 +1,7 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ledger_next`( CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ledger_next`(
IN vFiscalYear INT, IN vFiscalYear INT,
IN vIsManageTransaction BOOLEAN,
OUT vLastBookEntry INT OUT vLastBookEntry INT
) )
BEGIN BEGIN
@ -22,6 +23,7 @@ BEGIN
CALL util.throw('Fiscal year is required'); CALL util.throw('Fiscal year is required');
END IF; END IF;
IF NOT vIsManageTransaction THEN
SELECT @@in_transaction INTO vHasStartTransaction; SELECT @@in_transaction INTO vHasStartTransaction;
IF NOT vHasStartTransaction THEN IF NOT vHasStartTransaction THEN
@ -29,6 +31,7 @@ BEGIN
ELSE ELSE
SAVEPOINT sp; SAVEPOINT sp;
END IF; END IF;
END IF;
SELECT bookEntry + 1 INTO vLastBookEntry SELECT bookEntry + 1 INTO vLastBookEntry
FROM ledgerCompany FROM ledgerCompany
@ -46,10 +49,12 @@ BEGIN
SET bookEntry = vLastBookEntry SET bookEntry = vLastBookEntry
WHERE fiscalYear = vFiscalYear; WHERE fiscalYear = vFiscalYear;
IF NOT vIsManageTransaction THEN
IF vHasStartTransaction THEN IF vHasStartTransaction THEN
RELEASE SAVEPOINT sp; RELEASE SAVEPOINT sp;
ELSE ELSE
COMMIT; COMMIT;
END IF; END IF;
END IF;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -39,7 +39,7 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`xdiario_new`(
*/ */
BEGIN BEGIN
IF vBookNumber IS NULL THEN IF vBookNumber IS NULL THEN
CALL ledger_next(YEAR(vDated), vBookNumber); CALL ledger_next(YEAR(vDated), FALSE, vBookNumber);
END IF; END IF;
INSERT INTO XDiario INSERT INTO XDiario

View File

@ -9,7 +9,6 @@ BEGIN
DECLARE vIsCash BOOLEAN; DECLARE vIsCash BOOLEAN;
DECLARE vIsSupplierActive BOOLEAN; DECLARE vIsSupplierActive BOOLEAN;
-- PAK 10/02/15 No se asientan los pagos directamente, salvo en el caso de las cajas de CASH
SELECT (at2.code = 'cash') INTO vIsCash SELECT (at2.code = 'cash') INTO vIsCash
FROM accounting a FROM accounting a
JOIN accountingType at2 ON at2.id = a.accountingTypeFk JOIN accountingType at2 ON at2.id = a.accountingTypeFk
@ -24,7 +23,7 @@ BEGIN
FROM supplier FROM supplier
WHERE id = NEW.supplierFk; WHERE id = NEW.supplierFk;
CALL ledger_next(YEAR(NEW.received), NEW.companyFk, vNewBookEntry); CALL ledger_next(YEAR(NEW.received), TRUE, vNewBookEntry);
INSERT INTO XDiario ( INSERT INTO XDiario (
ASIEN, ASIEN,

View File

@ -0,0 +1 @@
ALTER TABLE vn.item ADD COLUMN photoMotivation VARCHAR(255);

View File

@ -0,0 +1,46 @@
use account;
INSERT INTO role
SET name = 'reviewer',
description = 'Revisor de producción',
hasLogin = TRUE,
created = util.VN_CURDATE(),
modified = util.VN_CURDATE(),
editorFk = NULL;
INSERT INTO roleInherit(
role,
inheritsFrom
)
SELECT r1.id,
r2.id
FROM role r1
JOIN role r2
WHERE r1.name = 'reviewer'
AND r2.name = 'production'
UNION
SELECT ri.role,
r2.id
FROM roleInherit ri
JOIN role r1 ON r1.id = ri.role
JOIN role r2 ON r2.name = 'reviewer'
WHERE r1.name IN ('claimManager', 'productionBoss')
GROUP BY ri.role;
DELETE ri
FROM roleInherit ri
JOIN role r1 ON ri.role = r1.id
JOIN role r2 ON ri.inheritsFrom = r2.id
WHERE r1.name = 'replenisher'
AND r2.name = 'buyer';
UPDATE salix.ACL
SET principalId = 'reviewer'
WHERE property = 'isInPreparing';
UPDATE user u
JOIN vn.workerDepartment wd ON wd.workerFk = u.id
JOIN vn.department d ON wd.departmentFk = d.id
JOIN role r ON r.name = 'reviewer'
SET u.role = r.id
WHERE d.name IN ('REVISION', 'PREVIA');

View File

@ -0,0 +1,24 @@
REVOKE UPDATE ON vn. invoiceIn FROM administrative, hrBoss, buyer, logistic;
GRANT UPDATE (id,
serialNumber,
serial,
supplierFk,
issued,
supplierRef,
currencyFk,
created,
companyFk,
docFk,
booked,
operated,
siiTypeInvoiceInFk,
cplusRectificationTypeFk,
cplusSubjectOpFk,
cplusTaxBreakFk,
siiTrascendencyInvoiceInFk,
bookEntried,
isVatDeductible,
withholdingSageFk,
expenseFkDeductible,
editorFk
) ON vn.invoiceIn TO administrative, hrBoss, buyer, logistic;

View File

@ -0,0 +1,23 @@
UPDATE salix.ACL
SET accessType = 'READ'
WHERE principalId IN ('administrative','buyer')
AND model = 'invoiceIn'
AND property = '*';
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
VALUES
('InvoiceIn', 'updateInvoiceIn', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'clone', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'corrective', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'exchangeRateUpdate', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'toBook', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'toUnbook', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceIn', 'updateInvoiceIn', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'clone', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'corrective', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'exchangeRateUpdate', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'toBook', 'WRITE', 'ALLOW', 'ROLE', 'buyer'),
('InvoiceIn', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'buyer');

View File

@ -0,0 +1,20 @@
-- Place your SQL code here
-- floranet.recipe definition
CREATE OR REPLACE TABLE floranet.`recipe`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`itemFk` int(11) NOT NULL COMMENT 'Bouquet or plant name',
`elementFk` int(11) NOT NULL COMMENT 'Item detail for bouquet''s composition',
`quantity` int(10) unsigned NOT NULL DEFAULT 1,
`cost` decimal(10,2) NOT NULL DEFAULT 1.00,
PRIMARY KEY (`id`),
KEY `recipe_FK` (`itemFk`),
KEY `recipe_FK_1` (`elementFk`),
CONSTRAINT `recipe_FK` FOREIGN KEY (`itemFk`) REFERENCES `vn`.`item` (`id`) ON UPDATE CASCADE,
CONSTRAINT `recipe_item_FK` FOREIGN KEY (`elementFk`) REFERENCES `vn`.`item` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='Links handmade products with their elements';
DROP TABLE IF EXISTS floranet.`element`;

View File

@ -0,0 +1,5 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS defaultHeight INT UNSIGNED NULL COMMENT 'default height in centimeters';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS defaultLength INT UNSIGNED NULL COMMENT 'default length in centimeters';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS defaultWidth INT UNSIGNED NULL COMMENT 'default width in centimeters';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS defaultWeight INT UNSIGNED NULL COMMENT 'default weight in centimeters';

View File

@ -0,0 +1,2 @@
ALTER TABLE vn.productionConfig ADD scannablePreviusCodeType enum('qr','barcode')
CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT 'barcode' NOT NULL;

View File

@ -0,0 +1,3 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS expeditionDeadLine TIME NULL
COMMENT 'This field stores the latest time by which expeditions can be generated to be sent today';

View File

@ -0,0 +1,9 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS counterWidth INT UNSIGNED NULL
COMMENT 'If it does not reach the required value, it will be padded with zeros on the left to meet the specified length.';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS serviceTypeWidth INT UNSIGNED NULL
COMMENT 'If it does not reach the required value, it will be padded with zeros on the left to meet the specified length.';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS portugalPostCodeTrim INT UNSIGNED NULL
COMMENT 'It will trim the last characters of the postal code';

View File

@ -0,0 +1,3 @@
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Ticket','refund','WRITE','ALLOW','ROLE','logistic');

View File

@ -225,7 +225,7 @@ describe('Ticket Edit sale path', () => {
}); });
it('should show error trying to delete a ticket with a refund', async() => { it('should show error trying to delete a ticket with a refund', async() => {
await page.loginAndModule('production', 'ticket'); await page.loginAndModule('salesPerson', 'ticket');
await page.accessToSearchResult('8'); await page.accessToSearchResult('8');
await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);

View File

@ -164,6 +164,7 @@ export default class UploadPhoto extends Component {
const options = { const options = {
type: 'blob', type: 'blob',
size: 'original'
}; };
return this.editor.result(options) return this.editor.result(options)
.then(blob => this.newPhoto.blob = blob) .then(blob => this.newPhoto.blob = blob)

View File

@ -61,7 +61,8 @@
"Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "I have created the claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "I have created the claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "I have changed the price of [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale price": "I have changed the price of [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "I have changed the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}* of the ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "I have changed {{changes}} of the ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}*",
"Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Bought {{quantity}} units of [{{itemId}} {{concept}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})", "Bought units from buy request": "Bought {{quantity}} units of [{{itemId}} {{concept}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*", "MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",

View File

@ -124,7 +124,8 @@
"Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "He cambiado la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "He cambiado {{changes}} del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}*",
"State": "Estado", "State": "Estado",
"regular": "normal", "regular": "normal",
"reserved": "reservado", "reserved": "reservado",
@ -358,8 +359,8 @@
"Select ticket or client": "Elija un ticket o un client", "Select ticket or client": "Elija un ticket o un client",
"It was not able to create the invoice": "No se pudo crear la factura", "It was not able to create the invoice": "No se pudo crear la factura",
"ticketCommercial": "El ticket {{ ticket }} para el vendedor {{ salesMan }} está en preparación. (mensaje generado automáticamente)", "ticketCommercial": "El ticket {{ ticket }} para el vendedor {{ salesMan }} está en preparación. (mensaje generado automáticamente)",
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario", "This PDA is already assigned to another user": "Esta PDA ya está asignado a otro usuario",
"You can only have one PDA": "Solo puedes tener un PDA", "You can only have one PDA": "Solo puedes tener una PDA",
"Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE", "Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE",
"You can not use the same password": "No puedes usar la misma contraseña" "You can not use the same password": "No puedes usar la misma contraseña"
} }

View File

@ -123,8 +123,9 @@
"Added sale to ticket": "J'ai ajouté la ligne suivante au ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", "Added sale to ticket": "J'ai ajouté la ligne suivante au ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
"Changed sale discount": "J'ai changé le rabais des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "J'ai changé le rabais des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "J'ai créé la réclamation [{{claimId}}]({{{claimUrl}}}) des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "J'ai créé la réclamation [{{claimId}}]({{{claimUrl}}}) des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "J'ai changé le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale price": " le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})",,
"Changed sale quantity": "J'ai changé la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}} du ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "J'ai changé {{changes}} du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}}",
"State": "État", "State": "État",
"regular": "normal", "regular": "normal",
"reserved": "réservé", "reserved": "réservé",

View File

@ -124,7 +124,8 @@
"Changed sale discount": "Desconto da venda alterado no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Changed sale discount": "Desconto da venda alterado no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "Reclamação criada [{{claimId}}]({{{claimUrl}}}) no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", "Created claim": "Reclamação criada [{{claimId}}]({{{claimUrl}}}) no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "Preço da venda alterado para [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* no ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale price": "Preço da venda alterado para [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "Quantidade da venda alterada para [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* no ticket [{{ticketId}}]({{{ticketUrl}}})", "Changed sale quantity": "Quantidade da venda alterada para {{changes}} no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": " [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* ",
"State": "Estado", "State": "Estado",
"regular": "normal", "regular": "normal",
"reserved": "reservado", "reserved": "reservado",

View File

@ -85,8 +85,12 @@ exports.translateValues = async(instance, changes, options = {}) => {
exports.getChanges = (original, changes) => { exports.getChanges = (original, changes) => {
const oldChanges = {}; const oldChanges = {};
const newChanges = {}; const newChanges = {};
const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
for (let property in changes) { for (let property in changes) {
if (dateRegex.test(original[property]))
original[property] = new Date(Date.parse(original[property]));
const firstChar = property.substring(0, 1); const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$'; const isPrivate = firstChar == '$';
if (isPrivate) return; if (isPrivate) return;

View File

@ -6,6 +6,7 @@ describe('claimBeginning', () => {
const claimManagerId = 72; const claimManagerId = 72;
const activeCtx = { const activeCtx = {
accessToken: {userId: claimManagerId}, accessToken: {userId: claimManagerId},
__: value => value
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};

View File

@ -88,7 +88,8 @@ module.exports = Self => {
const updatedClaim = await claim.updateAttributes(args, myOptions); const updatedClaim = await claim.updateAttributes(args, myOptions);
// When pickup has been changed // When pickup has been changed
if (salesPerson && changedPickup && updatedClaim.pickup) if (salesPerson) {
if (changedPickup && updatedClaim.pickup)
await notifyPickUp(ctx, salesPerson.id, claim); await notifyPickUp(ctx, salesPerson.id, claim);
// When claimState has been changed // When claimState has been changed
@ -98,6 +99,7 @@ module.exports = Self => {
if (newState.code == 'canceled') if (newState.code == 'canceled')
await notifyStateChange(ctx, claim.workerFk, claim, newState.description); await notifyStateChange(ctx, claim.workerFk, claim, newState.description);
} }
}
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -76,6 +76,15 @@
}, },
"enlazadoSage": { "enlazadoSage": {
"type": "boolean" "type": "boolean"
},
"enlazado": {
"type": "boolean"
},
"key": {
"type": "number",
"mysql": {
"columnName": "CLAVE"
}
} }
}, },
"relations": { "relations": {

View File

@ -22,5 +22,8 @@
}, },
"EntryObservation": { "EntryObservation": {
"dataSource": "vn" "dataSource": "vn"
},
"EntryType": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,25 @@
{
"name": "EntryType",
"base": "VnModel",
"mixins": {
"Loggable": true
},
"options": {
"mysql": {
"table": "entryType"
}
},
"properties": {
"code": {
"type": "string",
"id": true,
"description": "Identifier"
},
"description": {
"type": "string"
},
"isInformal": {
"type": "boolean"
}
}
}

View File

@ -101,6 +101,11 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "Account",
"foreignKey": "observationEditorFk" "foreignKey": "observationEditorFk"
},
"entryType": {
"type": "belongsTo",
"model": "EntryType",
"foreignKey": "typeFk"
} }
} }
} }

View File

@ -54,6 +54,20 @@ module.exports = Self => {
value: rate value: rate
}); });
} }
const monday = 1;
if (xmlDateWithoutTime.getDay() === monday) {
const saturday = new Date(xmlDateWithoutTime);
saturday.setDate(xmlDateWithoutTime.getDate() - 2);
const sunday = new Date(xmlDateWithoutTime);
sunday.setDate(xmlDateWithoutTime.getDate() - 1);
for (const date of [saturday, sunday]) {
await models.ReferenceRate.upsertWithWhere(
{currencyFk: currency.id, dated: date},
{currencyFk: currency.id, dated: date, value: rate}
);
}
}
} }
} }
} }

View File

@ -0,0 +1,32 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceIn toUnbook()', () => {
it('should check that invoiceIn is unbooked', async() => {
const userId = 1;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const invoiceInId = 1;
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceIn.toBook(ctx, invoiceInId, options);
const bookEntry = await models.InvoiceIn.toUnbook(ctx, invoiceInId, options);
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
expect(bookEntry.accountingEntries).toEqual(4);
expect(bookEntry.isLinked).toBeFalsy();
expect(invoiceIn.isBooked).toEqual(false);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -32,7 +32,7 @@ module.exports = Self => {
} }
try { try {
await Self.rawSql(`CALL vn.invoiceIn_booking(?)`, [id], myOptions); await Self.rawSql(`CALL vn.invoiceIn_booking(?, NULL)`, [id], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();

View File

@ -0,0 +1,80 @@
module.exports = Self => {
Self.remoteMethodCtx('toUnbook', {
description: 'To unbook the invoiceIn',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: '/:id/toUnbook',
verb: 'POST'
}
});
Self.toUnbook = async(ctx, invoiceInId, options) => {
let tx;
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
let isLinked;
let accountingEntries;
let bookEntry = await models.Xdiario.findOne({
fields: ['ASIEN'],
where: {
and: [
{key: invoiceInId},
{enlazado: false},
{enlazadoSage: false}
]
}
}, myOptions);
let asien = bookEntry?.ASIEN;
if (asien) {
accountingEntries = await models.Xdiario.count({ASIEN: asien}, myOptions);
await models.Xdiario.destroyAll({ASIEN: asien}, myOptions);
await Self.updateAll({id: invoiceInId}, {isBooked: false}, myOptions);
} else {
const linkedBookEntry = await models.Xdiario.findOne({
fields: ['ASIEN'],
where: {
key: invoiceInId,
and: [{or: [{enlazado: true, enlazadoSage: true}]}]
}
}, myOptions);
asien = linkedBookEntry?.ASIEN;
isLinked = true;
}
if (tx) await tx.commit();
return {
isLinked,
bookEntry: asien,
accountingEntries
};
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,104 @@
module.exports = Self => {
Self.remoteMethodCtx('updateInvoiceIn', {
description: 'To update the invoiceIn attributes',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
http: {source: 'path'}
}, {
arg: 'supplierFk',
type: 'number',
required: true
}, {
arg: 'supplierRef',
type: 'any',
}, {
arg: 'issued',
type: 'any',
}, {
arg: 'operated',
type: 'any',
}, {
arg: 'deductibleExpenseFk',
type: 'any',
}, {
arg: 'dmsFk',
type: 'any',
}, {
arg: 'bookEntried',
type: 'any',
}, {
arg: 'booked',
type: 'any',
}, {
arg: 'currencyFk',
type: 'number',
required: true
}, {
arg: 'companyFk',
type: 'any',
}, {
arg: 'withholdingSageFk',
type: 'any',
},
],
returns: {
type: 'object',
root: true
},
http: {
path: '/:id/updateInvoiceIn',
verb: 'PATCH'
}
});
Self.updateInvoiceIn = async(ctx,
id,
supplierFk,
supplierRef,
issued,
operated,
deductibleExpenseFk,
dmsFk,
bookEntried,
booked,
currencyFk,
companyFk,
withholdingSageFk,
options
) => {
let tx;
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object') Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const invoiceIn = await Self.findById(id, null, myOptions);
invoiceIn.updateAttributes({supplierFk,
supplierRef,
issued,
operated,
deductibleExpenseFk,
dmsFk,
bookEntried,
booked,
currencyFk,
companyFk,
withholdingSageFk
}, myOptions);
if (tx) await tx.commit();
return invoiceIn;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -11,6 +11,8 @@ module.exports = Self => {
require('../methods/invoice-in/getSerial')(Self); require('../methods/invoice-in/getSerial')(Self);
require('../methods/invoice-in/corrective')(Self); require('../methods/invoice-in/corrective')(Self);
require('../methods/invoice-in/exchangeRateUpdate')(Self); require('../methods/invoice-in/exchangeRateUpdate')(Self);
require('../methods/invoice-in/toUnbook')(Self);
require('../methods/invoice-in/updateInvoiceIn')(Self);
Self.rewriteDbError(function(err) { Self.rewriteDbError(function(err) {
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn')) if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))

View File

@ -1,4 +1,4 @@
<mg-ajax path="InvoiceIns/{{patch.params.id}}" options="vnPatch"></mg-ajax> <mg-ajax path="InvoiceIns/{{patch.params.id}}/updateInvoiceIn" options="vnPatch"></mg-ajax>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.invoiceIn" data="$ctrl.invoiceIn"

View File

@ -4,7 +4,7 @@ module.exports = Self => {
accessType: 'READ', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'barcode', arg: 'barcode',
type: 'number', type: 'string',
required: true, required: true,
description: 'barcode' description: 'barcode'
}], }],
@ -18,7 +18,7 @@ module.exports = Self => {
} }
}); });
Self.toItem = async(barcode, options) => { Self.toItem = async (barcode, options) => {
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')

View File

@ -64,7 +64,7 @@ describe('item getBalance()', () => {
const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options); const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options);
expect(firstItemBalance[9].claimFk).toEqual(null); expect(firstItemBalance[9].claimFk).toEqual(null);
expect(secondItemBalance[4].claimFk).toEqual(2); expect(secondItemBalance[7].claimFk).toEqual(2);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -9,7 +9,7 @@
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "number", "type": "string",
"description": "Identifier" "description": "Identifier"
}, },
"name": { "name": {

View File

@ -155,6 +155,9 @@
"minQuantity": { "minQuantity": {
"type": "number", "type": "number",
"description": "Min quantity" "description": "Min quantity"
},
"photoMotivation": {
"type": "string"
} }
}, },
"relations": { "relations": {

View File

@ -151,7 +151,7 @@ describe('SalesMonitor salesFilter()', () => {
const result = await models.SalesMonitor.salesFilter(ctx, filter, options); const result = await models.SalesMonitor.salesFilter(ctx, filter, options);
const firstRow = result[0]; const firstRow = result[0];
expect(result.length).toEqual(12); expect(result.length).toEqual(15);
expect(firstRow.alertLevel).not.toEqual(0); expect(firstRow.alertLevel).not.toEqual(0);
await tx.rollback(); await tx.rollback();

View File

@ -11,6 +11,12 @@
"Sector": { "Sector": {
"dataSource": "vn" "dataSource": "vn"
}, },
"SectorCollection": {
"dataSource": "vn"
},
"SectorCollectionSaleGroup": {
"dataSource": "vn"
},
"Train": { "Train": {
"dataSource": "vn" "dataSource": "vn"
} }

View File

@ -0,0 +1,24 @@
{
"name": "SectorCollection",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollection"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
},
"userFk": {
"type": "number"
},
"sectorFk": {
"type": "number"
}
}
}

View File

@ -0,0 +1,30 @@
{
"name": "SectorCollectionSaleGroup",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollectionSaleGroup"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
}
},
"relations": {
"sectorCollection": {
"type": "belongsTo",
"model": "SectorCollection",
"foreignKey": "sectorCollectionFk"
},
"saleGroup": {
"type": "belongsTo",
"model": "SaleGroup",
"foreignKey": "saleGroupFk"
}
}
}

View File

@ -33,7 +33,7 @@ module.exports = Self => {
JOIN vn.item i ON i.id = b.itemFk JOIN vn.item i ON i.id = b.itemFk
WHERE e.id = ? AND e.supplierFk = ? WHERE e.id = ? AND e.supplierFk = ?
GROUP BY i.id GROUP BY i.id
) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal, et.printedStickers ) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal, et.printedStickers, ic.url
FROM vn.buy b FROM vn.buy b
JOIN vn.item i ON i.id = b.itemFk JOIN vn.item i ON i.id = b.itemFk
JOIN vn.entry e ON e.id = b.entryFk JOIN vn.entry e ON e.id = b.entryFk
@ -41,6 +41,7 @@ module.exports = Self => {
JOIN vn.buyConfig bc ON bc.monthsAgo JOIN vn.buyConfig bc ON bc.monthsAgo
JOIN vn.travel t ON t.id = e.travelFk JOIN vn.travel t ON t.id = e.travelFk
LEFT JOIN entryTmp et ON et.id = i.id LEFT JOIN entryTmp et ON et.id = i.id
JOIN hedera.imageConfig ic
WHERE e.supplierFk = ? WHERE e.supplierFk = ?
AND i.family IN ('EMB', 'CONT') AND i.family IN ('EMB', 'CONT')
AND b.created > (util.VN_CURDATE() - INTERVAL bc.monthsAgo MONTH) AND b.created > (util.VN_CURDATE() - INTERVAL bc.monthsAgo MONTH)

View File

@ -2,10 +2,12 @@ const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('ticket setDelivered()', () => { describe('ticket setDelivered()', () => {
const userId = 50; const userId = 49;
const activeCtx = { const activeCtx = {
accessToken: {userId: userId}, accessToken: {userId: userId},
__: value => value
}; };
const ctx = {req: activeCtx};
beforeAll(async() => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
@ -19,8 +21,6 @@ describe('ticket setDelivered()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 49}}};
const originalTicketOne = await models.Ticket.findById(8, null, options); const originalTicketOne = await models.Ticket.findById(8, null, options);
const originalTicketTwo = await models.Ticket.findById(10, null, options); const originalTicketTwo = await models.Ticket.findById(10, null, options);

View File

@ -10,8 +10,8 @@ module.exports = Self => {
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'itemId', arg: 'barcode',
type: 'number', type: 'any',
required: true required: true
}, },
{ {
@ -29,7 +29,7 @@ module.exports = Self => {
} }
}); });
Self.addSale = async(ctx, id, itemId, quantity, options) => { Self.addSale = async(ctx, id, barcode, quantity, options) => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const models = Self.app.models; const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId}; const myOptions = {userId: ctx.req.accessToken.userId};
@ -46,7 +46,9 @@ module.exports = Self => {
try { try {
await models.Ticket.isEditableOrThrow(ctx, id, myOptions); await models.Ticket.isEditableOrThrow(ctx, id, myOptions);
const itemId = await models.ItemBarcode.toItem(barcode, myOptions);
const item = await models.Item.findById(itemId, null, myOptions); const item = await models.Item.findById(itemId, null, myOptions);
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
include: { include: {
relation: 'client', relation: 'client',

View File

@ -1,56 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('addSaleByCode', {
description: 'Add a collection',
accessType: 'WRITE',
accepts: [
{
arg: 'barcode',
type: 'string',
required: true
}, {
arg: 'quantity',
type: 'number',
required: true
}, {
arg: 'ticketFk',
type: 'number',
required: true
}, {
arg: 'warehouseFk',
type: 'number',
required: true
},
],
http: {
path: `/addSaleByCode`,
verb: 'POST'
},
});
Self.addSaleByCode = async(ctx, barcode, quantity, ticketFk, warehouseFk, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const [[item]] = await Self.rawSql('CALL vn.item_getInfo(?,?)', [barcode, warehouseFk], myOptions);
if (!item?.available) throw new UserError('We do not have availability for the selected item');
await Self.rawSql('CALL vn.collection_addItem(?, ?, ?)', [item.id, quantity, ticketFk], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -8,18 +8,13 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const state = await models.TicketState.findOne({ const state = await models.TicketState.findOne({where: {ticketFk: id}}, myOptions);
where: {ticketFk: id}
}, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const isProductionReviewer = await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*');
const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE'); const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE');
const alertLevel = state ? state.alertLevel : null; const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'], fields: ['clientFk'], include: {relation: 'client'}
include: {
relation: 'client'
}
}, myOptions); }, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions); const isLocked = await models.Ticket.isLocked(id, myOptions);
@ -29,10 +24,24 @@ module.exports = Self => {
const isNormalClient = ticket && ticket.client().typeFk == 'normal'; const isNormalClient = ticket && ticket.client().typeFk == 'normal';
const isEditable = !(alertLevelGreaterThanZero && isNormalClient); const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
const ticketCollection = await models.TicketCollection.findOne({
include: {relation: 'collection'}, where: {ticketFk: id}
}, myOptions);
let isOwner = ticketCollection?.collection()?.workerFk === ctx.req.accessToken.userId;
if (!isOwner) {
const saleGroup = await models.SaleGroup.findOne({fields: ['id'], where: {ticketFk: id}}, myOptions);
const sectorCollectionSaleGroup = saleGroup && await models.SectorCollectionSaleGroup.findOne({
include: {relation: 'sectorCollection'}, where: {saleGroupFk: saleGroup.id}
}, myOptions);
isOwner = sectorCollectionSaleGroup?.sectorCollection()?.userFk === ctx.req.accessToken.userId;
}
if (!ticket) if (!ticket)
throw new ForbiddenError(`The ticket doesn't exist.`); throw new ForbiddenError(`The ticket doesn't exist.`);
if (!isEditable && !isRoleAdvanced) if (!isEditable && !isRoleAdvanced && !isProductionReviewer && !isOwner)
throw new ForbiddenError(`This ticket is not editable.`); throw new ForbiddenError(`This ticket is not editable.`);
if (isLocked && !isWeekly) if (isLocked && !isWeekly)

View File

@ -1,39 +0,0 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Ticket addSaleByCode()', () => {
const quantity = 3;
const ticketFk = 13;
const warehouseFk = 1;
beforeAll(async() => {
activeCtx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost'},
__: value => value
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should add a new sale', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const code = '1111111111';
const salesBefore = await models.Sale.find(null, options);
await models.Ticket.addSaleByCode(activeCtx, code, quantity, ticketFk, warehouseFk, options);
const salesAfter = await models.Sale.find(null, options);
expect(salesAfter.length).toEqual(salesBefore.length + 1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -68,7 +68,7 @@ describe('ticket filter()', () => {
const filter = {}; const filter = {};
const result = await models.Ticket.filter(ctx, filter, options); const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(7); expect(result.length).toEqual(10);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -42,7 +42,7 @@ describe('sale priceDifference()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1106}}}; const ctx = {req: {accessToken: {userId: 1105}}};
ctx.args = { ctx.args = {
id: 1, id: 1,
landed: Date.vnNew(), landed: Date.vnNew(),
@ -84,7 +84,7 @@ describe('sale priceDifference()', () => {
const {items} = await models.Ticket.priceDifference(ctx, options); const {items} = await models.Ticket.priceDifference(ctx, options);
expect(items[0].movable).toEqual(410); expect(items[0].movable).toEqual(386);
expect(items[1].movable).toEqual(1810); expect(items[1].movable).toEqual(1810);
await tx.rollback(); await tx.rollback();

View File

@ -7,6 +7,7 @@ describe('ticket state()', () => {
const productionId = 49; const productionId = 49;
const activeCtx = { const activeCtx = {
accessToken: {userId: 9}, accessToken: {userId: 9},
__: value => value
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
const now = Date.vnNew(); const now = Date.vnNew();
@ -88,7 +89,8 @@ describe('ticket state()', () => {
const ticket = await models.Ticket.create(sampleTicket, options); const ticket = await models.Ticket.create(sampleTicket, options);
activeCtx.accessToken.userId = productionId; activeCtx.accessToken.userId = productionId;
const params = {ticketFk: ticket.id, stateFk: 3}; const stateOk = await models.State.findOne({where: {code: 'OK'}}, options);
const params = {ticketFk: ticket.id, stateFk: stateOk.id};
const ticketTracking = await models.Ticket.state(ctx, params, options); const ticketTracking = await models.Ticket.state(ctx, params, options);
@ -112,16 +114,68 @@ describe('ticket state()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options); const ticket = await models.Ticket.create(sampleTicket, options);
const ctx = {req: {accessToken: {userId: 18}}}; activeCtx.accessToken.userId = salesPersonId;
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options); const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const params = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1}; const paramsAssigned = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const res = await models.Ticket.state(ctx, params, options); const resAssigned = await models.Ticket.state(ctx, paramsAssigned, options);
expect(res.ticketFk).toBe(params.ticketFk); expect(resAssigned.ticketFk).toBe(paramsAssigned.ticketFk);
expect(res.stateFk).toBe(params.stateFk); expect(resAssigned.stateFk).toBe(paramsAssigned.stateFk);
expect(res.userFk).toBe(params.userFk); expect(resAssigned.userFk).toBe(paramsAssigned.userFk);
expect(res.userFk).toBe(1); expect(resAssigned.userFk).toBe(1);
expect(res.id).toBeDefined(); expect(resAssigned.id).toBeDefined();
activeCtx.accessToken.userId = productionId;
const packedState = await models.State.findOne({where: {code: 'PACKED'}}, options);
const paramsPacked = {ticketFk: ticket.id, stateFk: packedState.id, userFk: salesPersonId};
const resPacked = await models.Ticket.state(ctx, paramsPacked, options);
expect(resPacked.stateFk).toBe(paramsPacked.stateFk);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('Should equalize the quantities of quantity and originalQuantity' +
' if they are different', async() => {
const tx = await models.TicketTracking.beginTransaction({});
try {
const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options);
activeCtx.accessToken.userId = salesPersonId;
const sampleSale = {
ticketFk: ticket.id,
itemFk: 1,
concept: 'Test',
quantity: 10,
originalQuantity: 6
};
await models.Sale.create(sampleSale, options);
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const paramsAssigned = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const resAssigned = await models.Ticket.state(ctx, paramsAssigned, options);
expect(resAssigned.ticketFk).toBe(paramsAssigned.ticketFk);
expect(resAssigned.stateFk).toBe(paramsAssigned.stateFk);
expect(resAssigned.userFk).toBe(paramsAssigned.userFk);
expect(resAssigned.userFk).toBe(1);
expect(resAssigned.id).toBeDefined();
activeCtx.accessToken.userId = productionId;
const packedState = await models.State.findOne({where: {code: 'PACKED'}}, options);
const paramsPacked = {ticketFk: ticket.id, stateFk: packedState.id, userFk: salesPersonId};
const resPacked = await models.Ticket.state(ctx, paramsPacked, options);
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
expect(resPacked.stateFk).toBe(paramsPacked.stateFk);
expect(sale.quantity).toBe(sale.originalQuantity);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -64,7 +64,63 @@ module.exports = Self => {
if ((ticketState && !oldStateAllowed) || !newStateAllowed) if ((ticketState && !oldStateAllowed) || !newStateAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED'); throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [params.ticketFk, params.code], myOptions); const ticket = await models.Ticket.findById(params.ticketFk, {
include: [{
relation: 'client',
scope: {
fields: ['salesPersonFk']
}
}],
fields: ['id', 'clientFk']
}, myOptions);
const salesPersonFk = ticket.client().salesPersonFk;
if (salesPersonFk) {
const sales = await Self.rawSql(`
SELECT DISTINCT s.id,
s.itemFk,
s.concept,
s.originalQuantity AS oldQuantity,
s.quantity AS newQuantity
FROM vn.sale s
JOIN vn.saleTracking st ON st.saleFk = s.id
JOIN vn.ticket t ON t.id = s.ticketFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state s2 ON s2.id = ts.stateFk
WHERE s.ticketFk = ?
AND st.isChecked
AND s.originalQuantity IS NOT NULL
AND s.originalQuantity <> s.quantity
AND s2.\`order\` < (SELECT \`order\` FROM vn.state WHERE code = 'CHECKED')
ORDER BY st.created DESC
`, [params.ticketFk], myOptions);
let changes = '';
const url = await models.Url.getUrl();
const $t = ctx.req.__;
for (let sale of sales) {
changes += `\r\n-` + $t('Changes in sales', {
itemId: sale.itemFk,
concept: sale.concept,
oldQuantity: sale.oldQuantity,
newQuantity: sale.newQuantity,
itemUrl: `${url}item/${sale.itemFk}/summary`
});
const currentSale = await models.Sale.findById(sale.id, null, myOptions);
await currentSale.updateAttributes({
originalQuantity: currentSale.quantity
}, myOptions);
}
const message = $t('Changed sale quantity', {
ticketId: ticket.id,
changes: changes,
ticketUrl: `${url}ticket/${ticket.id}/sale`
});
await models.Chat.sendCheckingPresence(ctx, salesPersonFk, message, myOptions);
}
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticket.id, params.code], myOptions);
const ticketTracking = await models.TicketTracking.findOne({ const ticketTracking = await models.TicketTracking.findOne({
where: {ticketFk: params.ticketFk}, where: {ticketFk: params.ticketFk},

View File

@ -14,6 +14,9 @@
}, },
"parkingFk": { "parkingFk": {
"type": "number" "type": "number"
},
"ticketFk": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -1,361 +1,318 @@
/* eslint max-len: ["error", { "code": 150 }]*/ /* eslint max-len: ["error", { "code": 150 }]*/
const {models} = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('sale model ', () => { describe('sale model ', () => {
const ctx = { const developerId = 9;
req: { const buyerId = 35;
accessToken: {userId: 9}, const employeeId = 1;
headers: {origin: 'localhost:5000'}, const productionId = 49;
__: () => {} const salesPersonId = 18;
} const reviewerId = 130;
};
function getActiveCtx(userId) { const barcode = '4444444444';
return { const ticketCollectionProd = 34;
active: { const ticketCollectionSalesPerson = 35;
accessToken: {userId}, const previaTicketSalesPerson = 36;
http: { const previaTicketProd = 37;
req: { const notEditableError = 'This ticket is not editable.';
headers: {origin: 'http://localhost'}
} const ctx = getCtx(developerId);
} let tx;
} let opts;
};
function getCtx(userId, active = false) {
const accessToken = {userId};
const headers = {origin: 'localhost:5000'};
if (!active) return {req: {accessToken, headers, __: () => {}}};
return {active: {accessToken, http: {req: {headers}}}};
} }
beforeEach(async() => {
tx = await models.Sale.beginTransaction({});
opts = {transaction: tx};
});
afterEach(async() => await tx.rollback());
describe('quantity field ', () => { describe('quantity field ', () => {
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => { it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17; const saleId = 17;
const buyerId = 35; const ctx = getCtx(buyerId);
const ctx = {
req: { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(buyerId, true));
accessToken: {userId: buyerId}, spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`; SELECT 100 as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
try {
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true); expect(isRoleAdvanced).toEqual(true);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
expect(originalLine.quantity).toEqual(30); expect(originalLine.quantity).toEqual(30);
const newQuantity = originalLine.quantity + 1; const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
expect(modifiedLine.quantity).toEqual(newQuantity); expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should update the quantity of a given sale current line', async() => { it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 25; const saleId = 25;
const newQuantity = 4; const newQuantity = 4;
try { const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(20); expect(originalLine.quantity).toEqual(20);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
expect(modifiedLine.quantity).toEqual(newQuantity); expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw an error if the quantity is negative and it is not a refund ticket', async() => { it('should throw an error if the quantity is negative and it is not a refund ticket', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const saleId = 17; const saleId = 17;
const newQuantity = -10; const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try { try {
const options = {transaction: tx}; await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('You can only add negative amounts in refund tickets'));
error = e;
} }
expect(error).toEqual(new Error('You can only add negative amounts in refund tickets'));
}); });
it('should update a negative quantity when is a ticket refund', async() => { it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 32; const saleId = 32;
const newQuantity = -10; const newQuantity = -10;
try { await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity); expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => { it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = minQuantity - 1; const newQuantity = minQuantity - 1;
let error;
try { try {
const options = {transaction: tx}; const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`; SELECT 100 as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('The amount cannot be less than the minimum'));
error = e;
} }
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
}); });
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => { it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = minQuantity - 1; const newQuantity = minQuantity - 1;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT ${newQuantity} as available;`; SELECT ${newQuantity} as available;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
describe('newPrice', () => { describe('newPrice', () => {
it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => { it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = ` sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should increase quantity when the new price is lower than the previous one', async() => { it('should increase quantity when the new price is lower than the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
try { const item = await models.Item.findById(itemId, null, opts);
const options = {transaction: tx}; await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = ` sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should throw error when increase quantity and the new price is higher than the previous one', async() => { it('should throw error when increase quantity and the new price is higher than the previous one', async() => {
const ctx = { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
const tx = await models.Sale.beginTransaction({});
const itemId = 2; const itemId = 2;
const saleId = 17; const saleId = 17;
const minQuantity = 30; const minQuantity = 30;
const newQuantity = 31; const newQuantity = 31;
let error;
try { try {
const options = {transaction: tx}; const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options); spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) { if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = ` sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`;
params = null; params = null;
} }
return models.Ticket.rawSql(sqlStatement, params, options); return models.Ticket.rawSql(sqlStatement, params, opts);
}); });
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e).toEqual(new Error('The price of the item changed'));
error = e; }
});
});
});
describe('add a sale from a collection or previa ticket', () => {
it('if is allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
await models.Ticket.addSale(ctx, previaTicketProd, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
} }
expect(error).toEqual(new Error('The price of the item changed')); try {
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
}); });
it('if is allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
});
it('if is a reviewer', async() => {
const ctx = getCtx(reviewerId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(reviewerId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
}); });
}); });
}); });

View File

@ -46,7 +46,6 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTicketsAndPdf')(Self); require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self); require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self); require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/addSaleByCode')(Self);
require('../methods/ticket/clone')(Self); require('../methods/ticket/clone')(Self);
require('../methods/ticket/itemLack')(Self); require('../methods/ticket/itemLack')(Self);
require('../methods/ticket/itemLackDetail')(Self); require('../methods/ticket/itemLackDetail')(Self);

View File

@ -152,7 +152,7 @@
</vn-item> </vn-item>
<vn-item class="dropdown" <vn-item class="dropdown"
vn-click-stop="refundMenu.show($event, 'left')" vn-click-stop="refundMenu.show($event, 'left')"
vn-acl="invoicing, claimManager, salesAssistant" vn-acl="invoicing, claimManager, salesAssistant, logistic"
vn-acl-action="remove" vn-acl-action="remove"
vn-tooltip="Create a single ticket with all the content of the current ticket" vn-tooltip="Create a single ticket with all the content of the current ticket"
translate> translate>

View File

@ -476,7 +476,7 @@ class Controller extends Section {
*/ */
addSale(sale) { addSale(sale) {
const data = { const data = {
itemId: sale.itemFk, barcode: sale.itemFk,
quantity: sale.quantity quantity: sale.quantity
}; };
const query = `tickets/${this.ticket.id}/addSale`; const query = `tickets/${this.ticket.id}/addSale`;

View File

@ -659,7 +659,7 @@ describe('Ticket', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
const newSale = {itemFk: 4, quantity: 10}; const newSale = {itemFk: 4, quantity: 10};
const expectedParams = {itemId: 4, quantity: 10}; const expectedParams = {barcode: 4, quantity: 10};
const expectedResult = { const expectedResult = {
id: 30, id: 30,
quantity: 10, quantity: 10,

View File

@ -20,7 +20,7 @@ module.exports = Self => {
const notifications = await models.NotificationQueue.find( const notifications = await models.NotificationQueue.find(
{where: {created: {gte: new Date(Date.vnNow() - (backupPrinterNotificationDelay * 1000))}, {where: {created: {gte: new Date(Date.vnNow() - (backupPrinterNotificationDelay * 1000))},
notificationFk: notificationName, notificationFk: notificationName,
status: 'sent' status: {neq: 'error'}
} }
}); });

View File

@ -54,8 +54,8 @@
<vn-autocomplete <vn-autocomplete
ng-model="$ctrl.worker.originCountryFk" ng-model="$ctrl.worker.originCountryFk"
url="Countries" url="Countries"
fields="['id', 'country', 'code']" fields="['id', 'name', 'code']"
show-field="country" show-field="name"
value-field="id" value-field="id"
label="Origin country"> label="Origin country">
</vn-autocomplete> </vn-autocomplete>

View File

@ -5,6 +5,7 @@ describe('zone deletezone()', () => {
const userId = 9; const userId = 9;
const activeCtx = { const activeCtx = {
accessToken: {userId: userId}, accessToken: {userId: userId},
__: value => value
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
const zoneId = 9; const zoneId = 9;
@ -15,7 +16,6 @@ describe('zone deletezone()', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx
}); });
try {
const originalTickets = await models.Ticket.find({ const originalTickets = await models.Ticket.find({
where: { where: {
zoneFk: zoneId zoneFk: zoneId
@ -25,9 +25,6 @@ describe('zone deletezone()', () => {
originalTicketStates = await models.TicketState.find({where: { originalTicketStates = await models.TicketState.find({where: {
ticketFk: {inq: ticketIDs}, ticketFk: {inq: ticketIDs},
code: 'FIXING'}}); code: 'FIXING'}});
} catch (error) {
console.error(error);
}
}); });
it('should delete a zone and update their tickets', async() => { it('should delete a zone and update their tickets', async() => {

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "24.24.1", "version": "24.24.3",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",