diff --git a/back/methods/workerActivity/add.js b/back/methods/workerActivity/add.js index 89131491d1..b5b1bd65de 100644 --- a/back/methods/workerActivity/add.js +++ b/back/methods/workerActivity/add.js @@ -13,6 +13,11 @@ module.exports = Self => { type: 'string', description: 'Origin model from insert' }, + { + arg: 'description', + type: 'string', + description: 'Action description' + }, ], http: { @@ -21,7 +26,7 @@ module.exports = Self => { } }); - Self.add = async(ctx, code, model, options) => { + Self.add = async(ctx, code, model, description, options) => { const userId = ctx.req.accessToken.userId; const myOptions = {}; @@ -29,8 +34,8 @@ module.exports = Self => { Object.assign(myOptions, options); return await Self.rawSql(` - INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model) - SELECT ?, ?, ? + INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model, description) + SELECT ?, ?, ?, ? FROM workerTimeControlConfig wtcc LEFT JOIN ( SELECT wa.workerFk, @@ -43,8 +48,8 @@ module.exports = Self => { LIMIT 1 ) sub ON TRUE WHERE sub.workerFk IS NULL - OR sub.code <> ? + OR sub.code <> ? OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcc.dayBreak;` - , [userId, code, model, userId, code], myOptions); + , [userId, code, model, description, userId, code], myOptions); }; }; diff --git a/back/methods/workerActivity/specs/add.spec.js b/back/methods/workerActivity/specs/add.spec.js index 751cce009e..ad6cc7fa70 100644 --- a/back/methods/workerActivity/specs/add.spec.js +++ b/back/methods/workerActivity/specs/add.spec.js @@ -13,7 +13,7 @@ describe('workerActivity insert()', () => { {'code': 'TEST', 'description': 'TEST'}, options ); - await models.WorkerActivity.add(ctx, 'TEST', 'APP', options); + await models.WorkerActivity.add(ctx, 'TEST', 'APP', 'description', options); count = await models.WorkerActivity.count( {'workerFK': 1106}, options diff --git a/back/models/agency-workCenter.json b/back/models/agency-workCenter.json index adf1e5bcb0..71de17cb25 100644 --- a/back/models/agency-workCenter.json +++ b/back/models/agency-workCenter.json @@ -24,7 +24,7 @@ "relations": { "agency": { "type": "belongsTo", - "model": "WorkCenter", + "model": "Agency", "foreignKey": "agencyFk" }, "workCenter": { diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index 7e3facd591..ef105b76ae 100644 --- a/db/dump/fixtures.before.sql +++ b/db/dump/fixtures.before.sql @@ -195,7 +195,7 @@ INSERT INTO `vn`.`sectorType` (`id`, `code`) INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `code`, `typeFk`) VALUES (1, 'First sector', 1, 'FIRST', 1), - (2, 'Second sector', 2, 'SECOND',1); + (2, 'Second sector', 6, 'SECOND',1); INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`) VALUES @@ -730,7 +730,8 @@ INSERT INTO `vn`.`zoneWarehouse` (`id`, `zoneFk`, `warehouseFk`) (10, 10, 3), (11, 11, 5), (12, 12, 4), - (13, 13, 5); + (13, 13, 5), + (14, 7, 4); INSERT INTO `vn`.`zoneClosure` (`zoneFk`, `dated`, `hour`) VALUES @@ -853,7 +854,8 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`) INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`) VALUES - (1, 'Gotham','1007 Mountain Drive, Gotham'); + (1, 'Gotham','1007 Mountain Drive, Gotham'), + (6, 'Stark Tower','200 Park Avenue, Nueva York'); INSERT INTO `vn`.`vehicle`(`id`, `numberPlate`, `tradeMark`, `model`, `companyFk`, `warehouseFk`, `description`, `m3`, `isActive`, `deliveryPointFk`, `chassis`, `leasing`, `supplierFk`, `fuelTypeFk`, `bankPolicyFk`) VALUES @@ -1302,9 +1304,10 @@ INSERT INTO `vn`.`train`(`id`, `name`) INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`) VALUES - ('1106', '1', '1', 'H', '1', '1', '1'), - ('9', '2', '1', 'H', '1', '1', '1'), - ('1107', '1', '1', 'V', '1', '1', '1'); + (1106, '1', '1', 'H', '1', '1', '1'), + (9, '2', '1', 'H', '1', '1', '1'), + (1107, '1', '1', 'V', '1', '1', '1'), + (72, '1', '1', 'V', '1', '1', '1'); INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`) VALUES @@ -1615,6 +1618,7 @@ INSERT INTO `bs`.`waste`(`buyerFk`, `year`, `week`, `itemFk`, `itemTypeFk`, `sal (19, 100, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 'grouping', NULL, 0.00, 99.6, 99.4, 0, 1, 0, NULL, 1, util.VN_CURDATE()), (20, 100, 2, 5, 450, 3, 2, 1.000, 1.000, 0.000, 10, 10, NULL, NULL, 0.00, 7.30, 7.00, 0, 1, 0, NULL, 2.5, util.VN_CURDATE()), (21, 100,72, 55, 500, 5, 3, 1.000, 1.000, 0.000, 1, 1, 'packing', NULL, 0.00, 78.3, 75.6, 0, 1, 0, 1, 3, util.VN_CURDATE()), + (22, 100, 4, 55, 0, 5, 0, 0, 0, 0.000, 1, 1, 'packing', NULL, 0.00, 78.3, 75.6, 0, 1, 0, 1, 3, util.VN_CURDATE()), (10000002, 12,88, 50.0000, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 'grouping', NULL, 0.00, 99.60, 99.40, 0, 1, 0, 1.00, 1,util.VN_CURDATE() - INTERVAL 2 MONTH); INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`,`total`, `date_make`, `first_row_stamp`, `confirm_date`) @@ -1957,11 +1961,11 @@ INSERT INTO `vn`.`claimBeginning`(`id`, `claimFk`, `saleFk`, `quantity`) INSERT INTO `vn`.`claimDestination`(`id`, `description`, `addressFk`) VALUES - (1, 'Bueno', NULL), - (2, 'Basura/Perd.', 12), + (1, 'Bueno', 11), + (2, 'Basura/Perd.', NULL), (3, 'Confeccion', NULL), - (4, 'Reclam.PRAG', 12), - (5, 'Corregido', 11); + (4, 'Reclam.PRAG', NULL), + (5, 'Corregido', NULL); INSERT INTO `vn`.`claimDevelopment`(`id`, `claimFk`, `claimResponsibleFk`, `workerFk`, `claimReasonFk`, `claimResultFk`, `claimRedeliveryFk`, `claimDestinationFk`) VALUES @@ -1976,9 +1980,9 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina (1, 31, 4, 21, 2), (2, 32, 3, 21, 3); -INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`, `monthsToRefund`, `minShipped`,`daysToClaim`) +INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`, `monthsToRefund`, `minShipped`,`daysToClaim`, `pickupDeliveryFk`, `warehouseFk`) VALUES - (1, 5, 4, '2016-10-01', 7); + (1, 5, 4, '2016-10-01', 7, 8, 4); INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) VALUES @@ -3063,9 +3067,10 @@ INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) ('salix', 'development', 'http://localhost:5000/#!/'), ('docuware', 'development', 'http://docuware'); -INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`) +INSERT INTO `vn`.`report` (`name`, `method`) VALUES - (3, 'invoice', NULL, 'InvoiceOuts/{refFk}/invoice-out-pdf'); + ('invoice', 'InvoiceOuts/{refFk}/invoice-out-pdf'), + ('LabelBuy', 'Entries/{id}/{labelType}/buy-label'); INSERT INTO `vn`.`payDemDetail` (`id`, `detail`) VALUES @@ -4145,3 +4150,6 @@ INSERT IGNORE INTO vn.vehicleType (id, name) INSERT INTO edi.tableMultiConfig (fileName, toTable, file, `method`, updated) VALUES ('FG', 'genus', 'florecompc2', 'VBN/Genus', '2001-01-01'); + +INSERT INTO vn.addressWaste (addressFk, type) + VALUES (11, 'fault'); diff --git a/db/routines/stock/events/log_clean.sql b/db/routines/stock/events/log_clean.sql deleted file mode 100644 index 68dec73858..0000000000 --- a/db/routines/stock/events/log_clean.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `stock`.`log_clean` - ON SCHEDULE EVERY 1 DAY - STARTS '2022-01-28 09:29:18.000' - ON COMPLETION PRESERVE - ENABLE -DO CALL log_clean$$ -DELIMITER ; diff --git a/db/routines/stock/events/log_syncNoWait.sql b/db/routines/stock/events/log_syncNoWait.sql deleted file mode 100644 index e8f719ac29..0000000000 --- a/db/routines/stock/events/log_syncNoWait.sql +++ /dev/null @@ -1,8 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `stock`.`log_syncNoWait` - ON SCHEDULE EVERY 5 SECOND - STARTS '2017-06-27 17:15:02.000' - ON COMPLETION NOT PRESERVE - DISABLE -DO CALL log_syncNoWait$$ -DELIMITER ; diff --git a/db/routines/stock/events/stock_clean.sql b/db/routines/stock/events/stock_clean.sql new file mode 100644 index 0000000000..86f8a15fc8 --- /dev/null +++ b/db/routines/stock/events/stock_clean.sql @@ -0,0 +1,8 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `stock`.`stock_clean` + ON SCHEDULE EVERY 60 SECOND + STARTS '2025-01-01 00:00:00.000' + ON COMPLETION PRESERVE + ENABLE +DO CALL stock_clean$$ +DELIMITER ; diff --git a/db/routines/stock/events/stock_sync.sql b/db/routines/stock/events/stock_sync.sql new file mode 100644 index 0000000000..d598cfc81a --- /dev/null +++ b/db/routines/stock/events/stock_sync.sql @@ -0,0 +1,8 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` EVENT `stock`.`stock_sync` + ON SCHEDULE EVERY 5 SECOND + STARTS '2025-01-01 00:00:00.000' + ON COMPLETION PRESERVE + DISABLE +DO CALL stock_sync$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyLot_refresh.sql b/db/routines/stock/procedures/buyLot_refresh.sql new file mode 100644 index 0000000000..dcfcd4bb8c --- /dev/null +++ b/db/routines/stock/procedures/buyLot_refresh.sql @@ -0,0 +1,91 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyLot_refresh`( + `vTable` ENUM('lot', 'entry', 'travel'), + `vId` INT) +BEGIN + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + CREATE OR REPLACE TEMPORARY TABLE tBuyAlive + ENGINE = MEMORY + SELECT + t.id travelFk, + t.landed, + t.landingHour, + t.warehouseInFk, + t.isReceived, + t.isRaid, + e.id entryFk, + b.lotFk, + b.itemFk, + b.life, + b.quantity + FROM tLotStatus ls + JOIN vn.buy b ON b.lotFk = ls.lotFk + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.travel t ON t.id = e.travelFk + WHERE ls.isIncluded; + + START TRANSACTION; + + -- Delete excluded/deleted/dead lots + + DELETE l FROM buyLot l + JOIN tLotStatus ls USING(lotFk) + WHERE NOT ls.isIncluded; + + -- Delete undead lot picks + + UPDATE buyOut o + JOIN buyPick p ON p.outFk = o.outFk + JOIN tLotStatus ls ON ls.lotFk = p.lotFk + SET o.isSync = FALSE, + o.lack = o.lack + p.quantity + WHERE ls.isExcluded OR ls.isIncluded; + + DELETE p FROM buyPick p + JOIN tLotStatus ls USING(lotFk) + WHERE ls.isExcluded OR ls.isIncluded; + + -- Update alive outs + + INSERT INTO buyLot ( + lotFk, + isSync, + isPicked, + warehouseFk, + itemFk, + dated, + expired, + quantity, + available + ) + SELECT + lotFk, + FALSE, + isReceived, + warehouseInFk, + itemFk, + @dated := ADDTIME(landed, IFNULL(landingHour, '00:00:00')), + @dated + INTERVAL life DAY, + quantity, + NULL + FROM tBuyAlive + ON DUPLICATE KEY UPDATE + isSync = VALUES(isSync), + isPicked = VALUES(isPicked), + warehouseFk = VALUES(warehouseFk), + itemFk = VALUES(itemFk), + dated = VALUES(dated), + expired = VALUES(expired), + quantity = VALUES(quantity), + available = VALUES(available); + + COMMIT; + + DROP TEMPORARY TABLE tBuyAlive; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyLot_requestQuantity.sql b/db/routines/stock/procedures/buyLot_requestQuantity.sql new file mode 100644 index 0000000000..c04f778528 --- /dev/null +++ b/db/routines/stock/procedures/buyLot_requestQuantity.sql @@ -0,0 +1,64 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyLot_requestQuantity`( + vSelf INT, + vRequested INT, + vDated DATETIME, + vOutFk INT, + OUT vSupplied INT) +BEGIN +/** + * Disassociates lot picks after the given date until the demanded quantity is + * satisfied. + * + * @param vSelf The buyLot reference + * @param vRequested The requested quantity + * @param vDate The starting date for the associated outs + * @param vOutFk The if of requesting out + * @param vSupplied The supplied quantity + */ + DECLARE vPickFk INT; + DECLARE vPickOutFk INT; + DECLARE vPickQuantity INT; + DECLARE vPickGranted INT; + DECLARE vDone BOOL; + + DECLARE vPicks CURSOR FOR + SELECT p.id, o.outFk, p.quantity + FROM buyPick p + JOIN buyOut o USING(outFk) + WHERE p.lotFk = vSelf + AND (o.dated, o.outFk) > (vDated, vOutFk) + ORDER BY o.dated DESC, o.created DESC, o.outFk DESC; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + SET vSupplied = 0; + + OPEN vPicks; + + myLoop: LOOP + SET vDone = FALSE; + FETCH vPicks INTO vPickFk, vPickOutFk, vPickQuantity; + + IF vDone THEN + LEAVE myLoop; + END IF; + + SET vPickGranted = LEAST(vRequested - vSupplied, vPickQuantity); + SET vSupplied = vSupplied + vPickGranted; + CALL buyPick_remove(vPickFk, vPickGranted, vPickQuantity); + + UPDATE buyOut + SET isSync = FALSE, + lack = lack + vPickGranted + WHERE outFk = vPickOutFk; + + IF vSupplied >= vRequested THEN + LEAVE myLoop; + END IF; + END LOOP; + + CLOSE vPicks; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/inbound_sync.sql b/db/routines/stock/procedures/buyLot_sync.sql similarity index 65% rename from db/routines/stock/procedures/inbound_sync.sql rename to db/routines/stock/procedures/buyLot_sync.sql index 7d463e70d0..a9bc81d071 100644 --- a/db/routines/stock/procedures/inbound_sync.sql +++ b/db/routines/stock/procedures/buyLot_sync.sql @@ -1,10 +1,10 @@ DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`inbound_sync`(vSelf INT) +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyLot_sync`(vSelf INT) BEGIN /** - * Associates a inbound with their possible outbounds, updating it's available. + * Associates a lot with their possible outs, updating it's available. * - * @param vSelf The inbound identifier + * @param vSelf The lot id */ DECLARE vDated DATETIME; DECLARE vExpired DATETIME; @@ -14,37 +14,37 @@ BEGIN DECLARE vAvailable INT; DECLARE vSupplied INT; DECLARE vSuppliedFromRequest INT; - DECLARE vOutboundFk INT; + DECLARE vOutFk INT; DECLARE vLack INT; DECLARE vHasPicks BOOL; DECLARE vDone BOOL; - DECLARE vOutbounds CURSOR FOR - SELECT id, lack, lack < quantity - FROM outbound + DECLARE vOuts CURSOR FOR + SELECT outFk, lack, lack < quantity + FROM buyOut WHERE warehouseFk = vWarehouse AND itemFk = vItem AND dated >= vDated AND (vExpired IS NULL OR dated < vExpired) - ORDER BY dated, created; + ORDER BY dated, created, outFk; DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; SELECT warehouseFk, itemFk, available, quantity, expired, dated INTO vWarehouse, vItem, vAvailable, vQuantity, vExpired, vDated - FROM inbound - WHERE id = vSelf; + FROM buyLot + WHERE lotFk = vSelf; IF vAvailable IS NULL THEN SET vAvailable = vQuantity; END IF; - OPEN vOutbounds; + OPEN vOuts; myLoop: LOOP SET vDone = FALSE; - FETCH vOutbounds INTO vOutboundFk, vLack, vHasPicks; + FETCH vOuts INTO vOutFk, vLack, vHasPicks; IF vDone THEN LEAVE myLoop; @@ -54,19 +54,19 @@ BEGIN IF vSupplied > 0 THEN SET vAvailable = vAvailable - vSupplied; - UPDATE outbound + UPDATE buyOut SET lack = lack - vSupplied - WHERE id = vOutboundFk; + WHERE outFk = vOutFk; END IF; IF vHasPicks AND vAvailable > 0 THEN - CALL outbound_requestQuantity(vOutboundFk, vAvailable, vDated, vSuppliedFromRequest); + CALL buyOut_requestQuantity(vOutFk, vAvailable, vDated, vSelf, vSuppliedFromRequest); SET vSupplied = vSupplied + vSuppliedFromRequest; SET vAvailable = vAvailable - vSuppliedFromRequest; END IF; IF vSupplied > 0 THEN - CALL inbound_addPick(vSelf, vOutboundFk, vSupplied); + CALL buyPick_add(vSelf, vOutFk, vSupplied); END IF; IF vAvailable <= 0 THEN @@ -74,11 +74,11 @@ BEGIN END IF; END LOOP; - CLOSE vOutbounds; + CLOSE vOuts; - UPDATE inbound + UPDATE buyLot SET isSync = TRUE, available = vAvailable - WHERE id = vSelf; + WHERE lotFk = vSelf; END$$ DELIMITER ; diff --git a/db/routines/stock/procedures/buyOut_refresh.sql b/db/routines/stock/procedures/buyOut_refresh.sql new file mode 100644 index 0000000000..895e28716c --- /dev/null +++ b/db/routines/stock/procedures/buyOut_refresh.sql @@ -0,0 +1,95 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_refresh`( + `vTable` VARCHAR(255), + `vId` INT, + `vSource` VARCHAR(255)) +BEGIN +/** + * This procedure contains the common code used to refresh the out lot cache. + * + * @param vTable The id source table + * @param vId The lot id + * @param vSource The lot source + * @table tLotStatus Lots to modify + * @table tLotAlive Updated/Created alive lots data + */ + DECLARE vHasLots BOOL; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + IF vTable = 'lot' THEN + SELECT COUNT(*) > 0 INTO vHasLots FROM tLotStatus; + + IF NOT vHasLots THEN + INSERT INTO tLotStatus + SET lotFk = vId, + isExcluded = TRUE, + isIncluded = FALSE; + END IF; + END IF; + + START TRANSACTION; + + -- Delete excluded/deleted/dead outs + + DELETE o FROM buyOut o + JOIN tLotStatus ls ON ls.lotFk = o.outFk + WHERE NOT ls.isIncluded; + + -- Delete undead out picks + + UPDATE buyLot l + JOIN buyPick p ON p.lotFk = l.lotFk + JOIN tLotStatus ls ON ls.lotFk = p.outFk + SET l.isSync = FALSE, + l.available = l.available + p.quantity + WHERE ls.isExcluded OR ls.isIncluded; + + DELETE p FROM buyPick p + JOIN tLotStatus ls ON ls.lotFk = p.outFk + WHERE ls.isExcluded OR ls.isIncluded; + + -- Update alive outs + + INSERT INTO buyOut ( + outFk, + source, + isSync, + warehouseFk, + dated, + itemFk, + quantity, + lack, + created, + isPicked + ) + SELECT + lotFk, + vSource, + FALSE, + warehouseFk, + dated, + itemFk, + quantity, + quantity, + created, + isPicked + FROM tLotAlive + ON DUPLICATE KEY UPDATE + source = VALUES(source), + warehouseFk = VALUES(warehouseFk), + dated = VALUES(dated), + itemFk = VALUES(itemFk), + quantity = VALUES(quantity), + lack = VALUES(lack), + created = VALUES(created), + isPicked = VALUES(isPicked), + isSync = VALUES(isSync); + + COMMIT; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyOut_refreshBuy.sql b/db/routines/stock/procedures/buyOut_refreshBuy.sql new file mode 100644 index 0000000000..69b3878358 --- /dev/null +++ b/db/routines/stock/procedures/buyOut_refreshBuy.sql @@ -0,0 +1,39 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_refreshBuy`( + `vTable` VARCHAR(255), + `vId` INT) +BEGIN + CREATE OR REPLACE TEMPORARY TABLE tLotStatus + ENGINE = MEMORY + SELECT lotFk, + @isExcluded := b.quantity <= 0 isExcluded, + NOT @isExcluded AND b.isAlive isIncluded + FROM vn.buy b + JOIN vn.entry e ON e.id = b.entryFk + WHERE + (vTable = 'lot' AND b.lotFk = vId) + OR (vTable = 'entry' AND e.id = vId) + OR (vTable = 'travel' AND e.travelFk = vId); + + CREATE OR REPLACE TEMPORARY TABLE tLotAlive + ENGINE = MEMORY + SELECT + ls.lotFk, + t.warehouseOutFk warehouseFk, + ADDTIME(t.shipped, IFNULL(shipmentHour, '00:00:00')) dated, + t.isDelivered isPicked, + b.itemFk, + b.quantity, + b.created + FROM tLotStatus ls + JOIN vn.buy b ON b.lotFk = ls.lotFk + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.travel t ON t.id = e.travelFk + WHERE ls.isIncluded; + + CALL buyOut_refresh(vTable, vId, 'buy'); + CALL buyLot_refresh(vTable, vId); + + DROP TEMPORARY TABLE tLotStatus, tLotAlive; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyOut_refreshOrder.sql b/db/routines/stock/procedures/buyOut_refreshOrder.sql new file mode 100644 index 0000000000..610630ea20 --- /dev/null +++ b/db/routines/stock/procedures/buyOut_refreshOrder.sql @@ -0,0 +1,36 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_refreshOrder`( + `vTable` VARCHAR(255), + `vId` INT) +BEGIN + CREATE OR REPLACE TEMPORARY TABLE tLotStatus + ENGINE = MEMORY + SELECT lotFk, + @isExcluded := o.confirmed OR NOT isReserved OR r.amount <= 0 isExcluded, + NOT @isExcluded isIncluded + FROM hedera.orderRow r + JOIN hedera.`order` o ON o.id = r.orderFk + WHERE + (vTable = 'lot' AND r.lotFk = vId) + OR (vTable = 'order' AND o.id = vId); + + CREATE OR REPLACE TEMPORARY TABLE tLotAlive + ENGINE = MEMORY + SELECT + ls.lotFk, + r.warehouseFk, + r.shipment dated, + r.itemFk, + r.amount quantity, + r.created, + FALSE isPicked + FROM tLotStatus ls + JOIN hedera.orderRow r ON r.lotFk = ls.lotFk + JOIN hedera.`order` o ON o.id = r.orderFk + WHERE ls.isIncluded; + + CALL buyOut_refresh(vTable, vId, 'orderRow'); + + DROP TEMPORARY TABLE tLotStatus, tLotAlive; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyOut_refreshSale.sql b/db/routines/stock/procedures/buyOut_refreshSale.sql new file mode 100644 index 0000000000..48ddbf499a --- /dev/null +++ b/db/routines/stock/procedures/buyOut_refreshSale.sql @@ -0,0 +1,42 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_refreshSale`( + `vTable` VARCHAR(255), + `vId` INT) +BEGIN + DECLARE vAliveDate DATE; + + SELECT util.VN_CURDATE() - INTERVAL saleLife MONTH + INTO vAliveDate + FROM config LIMIT 1; + + CREATE OR REPLACE TEMPORARY TABLE tLotStatus + ENGINE = MEMORY + SELECT lotFk, + @isExcluded := s.quantity < 0 isExcluded, + NOT @isExcluded AND t.isAlive isIncluded + FROM vn.sale s + JOIN vn.ticket t ON t.id = s.ticketFk + WHERE + (vTable = 'lot' AND s.lotFk = vId) + OR (vTable = 'ticket' AND t.id = vId); + + CREATE OR REPLACE TEMPORARY TABLE tLotAlive + ENGINE = MEMORY + SELECT + ls.lotFk, + t.warehouseFk, + t.shipped dated, + s.itemFk, + s.quantity, + s.created, + s.isPicked + FROM tLotStatus ls + JOIN vn.sale s ON s.lotFk = ls.lotFk + JOIN vn.ticket t ON t.id = s.ticketFk + WHERE ls.isIncluded; + + CALL buyOut_refresh(vTable, vId, 'sale'); + + DROP TEMPORARY TABLE tLotStatus, tLotAlive; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyOut_requestQuantity.sql b/db/routines/stock/procedures/buyOut_requestQuantity.sql new file mode 100644 index 0000000000..bd475e4b02 --- /dev/null +++ b/db/routines/stock/procedures/buyOut_requestQuantity.sql @@ -0,0 +1,64 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_requestQuantity`( + vSelf INT, + vRequested INT, + vDated DATETIME, + vLotFk INT, + OUT vSupplied INT) +BEGIN +/** + * Disassociates out picks after the given date until the demanded quantity is + * satisfied. + * + * @param vSelf The buyOut reference + * @param vRequested The requested quantity + * @param vDate The starting date for the associated lots + * @param vLotFk The if of requesting lot + * @param vSupplied The supplied quantity + */ + DECLARE vPickFk INT; + DECLARE vPickLotFk INT; + DECLARE vPickQuantity INT; + DECLARE vPickGranted INT; + DECLARE vDone BOOL; + + DECLARE vPicks CURSOR FOR + SELECT p.id, p.lotFk, p.quantity + FROM buyPick p + JOIN buyLot l USING(lotFk) + WHERE p.outFk = vSelf + AND (l.dated, p.lotFk) > (vDated, vLotFk) + ORDER BY l.dated, p.lotFk; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + SET vSupplied = 0; + + OPEN vPicks; + + myLoop: LOOP + SET vDone = FALSE; + FETCH vPicks INTO vPickFk, vPickLotFk, vPickQuantity; + + IF vDone THEN + LEAVE myLoop; + END IF; + + SET vPickGranted = LEAST(vRequested - vSupplied, vPickQuantity); + SET vSupplied = vSupplied + vPickGranted; + CALL buyPick_remove(vPickFk, vPickGranted, vPickQuantity); + + UPDATE buyLot + SET isSync = FALSE, + available = available + vPickGranted + WHERE lotFk = vPickLotFk; + + IF vSupplied >= vRequested THEN + LEAVE myLoop; + END IF; + END LOOP; + + CLOSE vPicks; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/outbound_sync.sql b/db/routines/stock/procedures/buyOut_sync.sql similarity index 61% rename from db/routines/stock/procedures/outbound_sync.sql rename to db/routines/stock/procedures/buyOut_sync.sql index 94b65e1b6c..fc212f082c 100644 --- a/db/routines/stock/procedures/outbound_sync.sql +++ b/db/routines/stock/procedures/buyOut_sync.sql @@ -1,10 +1,10 @@ DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`outbound_sync`(vSelf INT) +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyOut_sync`(vSelf INT) BEGIN /** - * Attaches a outbound with available inbounds. + * Attaches an out with available lots. * - * @param vSelf The outbound reference + * @param vSelf The buyOut reference */ DECLARE vDated DATETIME; DECLARE vItem INT; @@ -12,33 +12,33 @@ BEGIN DECLARE vLack INT; DECLARE vSupplied INT; DECLARE vSuppliedFromRequest INT; - DECLARE vInboundFk INT; + DECLARE vLotFk INT; DECLARE vAvailable INT; DECLARE vHasPicks BOOL; DECLARE vDone BOOL; - DECLARE vInbounds CURSOR FOR - SELECT id, available, available < quantity - FROM inbound + DECLARE vBuyLots CURSOR FOR + SELECT lotFk, available, available < quantity + FROM buyLot WHERE warehouseFk = vWarehouse AND itemFk = vItem AND dated <= vDated AND (expired IS NULL OR expired > vDated) - ORDER BY dated; + ORDER BY dated, lotFk; DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; SELECT warehouseFk, itemFk, dated, lack INTO vWarehouse, vItem, vDated, vLack - FROM outbound - WHERE id = vSelf; + FROM buyOut + WHERE outFk = vSelf; - OPEN vInbounds; + OPEN vBuyLots; myLoop: LOOP SET vDone = FALSE; - FETCH vInbounds INTO vInboundFk, vAvailable, vHasPicks; + FETCH vBuyLots INTO vLotFk, vAvailable, vHasPicks; IF vDone THEN LEAVE myLoop; @@ -48,19 +48,19 @@ BEGIN IF vSupplied > 0 THEN SET vLack = vLack - vSupplied; - UPDATE inbound + UPDATE buyLot SET available = available - vSupplied - WHERE id = vInboundFk; + WHERE lotFk = vLotFk; END IF; IF vHasPicks AND vLack > 0 THEN - CALL inbound_requestQuantity(vInboundFk, vLack, vDated, vSuppliedFromRequest); + CALL buyLot_requestQuantity(vLotFk, vLack, vDated, vSelf, vSuppliedFromRequest); SET vSupplied = vSupplied + vSuppliedFromRequest; SET vLack = vLack - vSuppliedFromRequest; END IF; IF vSupplied > 0 THEN - CALL inbound_addPick(vInboundFk, vSelf, vSupplied); + CALL buyPick_add(vLotFk, vSelf, vSupplied); END IF; IF vLack = 0 THEN @@ -68,11 +68,11 @@ BEGIN END IF; END LOOP; - CLOSE vInbounds; + CLOSE vBuyLots; - UPDATE outbound + UPDATE buyOut SET isSync = TRUE, lack = vLack - WHERE id = vSelf; + WHERE outFk = vSelf; END$$ DELIMITER ; diff --git a/db/routines/stock/procedures/buyPick_add.sql b/db/routines/stock/procedures/buyPick_add.sql new file mode 100644 index 0000000000..79012f50f0 --- /dev/null +++ b/db/routines/stock/procedures/buyPick_add.sql @@ -0,0 +1,15 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyPick_add`( + vLotFk INT, + vOutFk INT, + vQuantity INT +) +BEGIN + INSERT INTO buyPick + SET lotFk = vLotFk, + outFk = vOutFk, + quantity = vQuantity + ON DUPLICATE KEY UPDATE + quantity = quantity + vQuantity; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/buyPick_remove.sql b/db/routines/stock/procedures/buyPick_remove.sql new file mode 100644 index 0000000000..9cef0e9865 --- /dev/null +++ b/db/routines/stock/procedures/buyPick_remove.sql @@ -0,0 +1,17 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`buyPick_remove`( + vSelf INT, + vQuantity INT, + vTotalQuantity INT +) +BEGIN + IF vQuantity < vTotalQuantity THEN + UPDATE buyPick + SET quantity = quantity - vQuantity + WHERE id = vSelf; + ELSE + DELETE FROM buyPick + WHERE id = vSelf; + END IF; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/inbound_addPick.sql b/db/routines/stock/procedures/inbound_addPick.sql deleted file mode 100644 index 136ade6c8e..0000000000 --- a/db/routines/stock/procedures/inbound_addPick.sql +++ /dev/null @@ -1,16 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`inbound_addPick`( - vSelf INT, - vOutboundFk INT, - vQuantity INT -) -BEGIN - INSERT INTO inboundPick - SET - inboundFk = vSelf, - outboundFk = vOutboundFk, - quantity = vQuantity - ON DUPLICATE KEY UPDATE - quantity = quantity + vQuantity; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/inbound_removePick.sql b/db/routines/stock/procedures/inbound_removePick.sql deleted file mode 100644 index 75883bcb2f..0000000000 --- a/db/routines/stock/procedures/inbound_removePick.sql +++ /dev/null @@ -1,20 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`inbound_removePick`( - vSelf INT, - vOutboundFk INT, - vQuantity INT, - vTotalQuantity INT -) -BEGIN - IF vQuantity < vTotalQuantity THEN - UPDATE inboundPick - SET quantity = quantity - vQuantity - WHERE inboundFk = vSelf - AND outboundFk = vOutboundFk; - ELSE - DELETE FROM inboundPick - WHERE inboundFk = vSelf - AND outboundFk = vOutboundFk; - END IF; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/inbound_requestQuantity.sql b/db/routines/stock/procedures/inbound_requestQuantity.sql deleted file mode 100644 index 4c6fb4295d..0000000000 --- a/db/routines/stock/procedures/inbound_requestQuantity.sql +++ /dev/null @@ -1,61 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`inbound_requestQuantity`( - vSelf INT, - vRequested INT, - vDated DATETIME, - OUT vSupplied INT) -BEGIN -/** - * Disassociates inbound picks after the given date until the - * demanded quantity is satisfied. - * - * @param vSelf The inbound reference - * @param vRequested The requested quantity - * @param vDate The starting date for the associated outbounds - * @param vSupplied The supplied quantity - */ - DECLARE vOutboundFk INT; - DECLARE vPickQuantity INT; - DECLARE vPickGranted INT; - DECLARE vDone BOOL; - - DECLARE vPicks CURSOR FOR - SELECT p.outboundFk, p.quantity - FROM inboundPick p - JOIN outbound o ON o.id = p.outboundFk - WHERE p.inboundFk = vSelf - AND o.dated > vDated - ORDER BY o.dated DESC, o.created DESC; - - DECLARE CONTINUE HANDLER FOR NOT FOUND - SET vDone = TRUE; - - SET vSupplied = 0; - - OPEN vPicks; - - myLoop: LOOP - SET vDone = FALSE; - FETCH vPicks INTO vOutboundFk, vPickQuantity; - - IF vDone THEN - LEAVE myLoop; - END IF; - - SET vPickGranted = LEAST(vRequested - vSupplied, vPickQuantity); - SET vSupplied = vSupplied + vPickGranted; - CALL inbound_removePick(vSelf, vOutboundFk, vPickGranted, vPickQuantity); - - UPDATE outbound - SET isSync = FALSE, - lack = lack + vPickGranted - WHERE id = vOutboundFk; - - IF vSupplied >= vRequested THEN - LEAVE myLoop; - END IF; - END LOOP; - - CLOSE vPicks; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_clean.sql b/db/routines/stock/procedures/log_clean.sql deleted file mode 100644 index 9215246e1b..0000000000 --- a/db/routines/stock/procedures/log_clean.sql +++ /dev/null @@ -1,7 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_clean`() -BEGIN - DELETE FROM inbound WHERE dated = vn.getInventoryDate(); - DELETE FROM outbound WHERE dated = vn.getInventoryDate(); -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_delete.sql b/db/routines/stock/procedures/log_delete.sql deleted file mode 100644 index 8bf2aed569..0000000000 --- a/db/routines/stock/procedures/log_delete.sql +++ /dev/null @@ -1,19 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_delete`(vTableName VARCHAR(255), vTableId INT) -proc: BEGIN -/** - * Processes orphan transactions. - */ - IF vTableName NOT IN ('buy', 'sale', 'orderRow') THEN - LEAVE proc; - END IF; - - DELETE FROM inbound - WHERE tableName = vTableName COLLATE utf8_general_ci - AND tableId = vTableId; - - DELETE FROM outbound - WHERE tableName = vTableName COLLATE utf8_general_ci - AND tableId = vTableId; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_refreshAll.sql b/db/routines/stock/procedures/log_refreshAll.sql deleted file mode 100644 index 9415379af1..0000000000 --- a/db/routines/stock/procedures/log_refreshAll.sql +++ /dev/null @@ -1,33 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_refreshAll`() -BEGIN -/** - * Recalculates the entire cache. It takes a considerable time, - * please avoid calls to this procedure from commonly used operations. - */ - DECLARE EXIT HANDLER FOR SQLEXCEPTION - BEGIN - DO RELEASE_LOCK('stock.log_sync'); - RESIGNAL; - END; - - IF !GET_LOCK('stock.log_sync', 30) THEN - CALL util.throw('Lock timeout exceeded'); - END IF; - - TRUNCATE TABLE stock.`log`; - TRUNCATE TABLE stock.`inbound`; - TRUNCATE TABLE stock.`inboundPick`; - TRUNCATE TABLE stock.`outbound`; - TRUNCATE TABLE stock.`visible`; - - CALL log_refreshSale(NULL, NULL); - CALL log_refreshBuy(NULL, NULL); - CALL log_refreshOrder(NULL, NULL); - - UPDATE outbound SET isSync = TRUE; - CALL log_sync(TRUE); - - DO RELEASE_LOCK('stock.log_sync'); -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_refreshBuy.sql b/db/routines/stock/procedures/log_refreshBuy.sql deleted file mode 100644 index e8f2fd407a..0000000000 --- a/db/routines/stock/procedures/log_refreshBuy.sql +++ /dev/null @@ -1,73 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_refreshBuy`( - `vTableName` VARCHAR(255), - `vTableId` INT) -BEGIN - DROP TEMPORARY TABLE IF EXISTS tValues; - CREATE TEMPORARY TABLE tValues - ENGINE = MEMORY - SELECT b.id buyFk, - e.id entryFk, - t.id travelFk, - b.itemFk, - t.isRaid, - ADDTIME(t.shipped, - IFNULL(t.shipmentHour, '00:00:00')) shipped, - t.warehouseOutFk, - t.isDelivered, - ADDTIME(t.landed, - IFNULL(t.landingHour, '00:00:00')) landed, - t.warehouseInFk, - t.isReceived, - tp.life, - ABS(b.quantity) quantity, - b.created, - b.quantity > 0 isIn, - t.shipped < vn.getInventoryDate() lessThanInventory - FROM vn.buy b - JOIN vn.entry e ON e.id = b.entryFk - JOIN vn.travel t ON t.id = e.travelFk - JOIN vn.item i ON i.id = b.itemFk - JOIN vn.itemType tp ON tp.id = i.typeFk - WHERE ( - vTableId IS NULL - OR (vTableName = 'travel' AND t.id = vTableId) - OR (vTableName = 'entry' AND e.id = vTableId) - OR (vTableName = 'buy' AND b.id = vTableId) - ) - AND t.landed >= vn.getInventoryDate() - AND b.quantity != 0; - - REPLACE INTO inbound ( - tableName, tableId, warehouseFk, dated, - itemFk, expired, quantity, isPicked - ) - SELECT 'buy', - buyFk, - IF(isIn, warehouseInFk, warehouseOutFk), - @dated := IF(isIn, landed, shipped), - itemFk, - TIMESTAMPADD(DAY, life, @dated), - quantity, - IF(isIn, isReceived, isDelivered) AND NOT isRaid - FROM tValues - WHERE isIn OR !lessThanInventory; - - REPLACE INTO outbound ( - tableName, tableId, warehouseFk, dated, - itemFk, created, quantity, isPicked - ) - SELECT 'buy', - buyFk, - IF(isIn, warehouseOutFk, warehouseInFk), - IF(isIn, shipped, landed), - itemFk, - created, - quantity, - IF(isIn, isDelivered, isReceived) AND NOT isRaid - FROM tValues - WHERE !isIn OR !lessThanInventory; - - DROP TEMPORARY TABLE tValues; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_refreshOrder.sql b/db/routines/stock/procedures/log_refreshOrder.sql deleted file mode 100644 index 787fb6aa58..0000000000 --- a/db/routines/stock/procedures/log_refreshOrder.sql +++ /dev/null @@ -1,47 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_refreshOrder`( - `vTableName` VARCHAR(255), - `vTableId` INT) -BEGIN - DECLARE vExpireTime INT DEFAULT 20; - DECLARE vExpired DATETIME DEFAULT TIMESTAMPADD(MINUTE, -vExpireTime, util.VN_NOW()); - - DROP TEMPORARY TABLE IF EXISTS tValues; - CREATE TEMPORARY TABLE tValues - ENGINE = MEMORY - SELECT - r.id rowFk, - r.itemFk, - r.warehouseFk, - r.shipment shipped, - r.amount quantity, - r.created - FROM hedera.orderRow r - JOIN hedera.`order` o ON o.id = r.orderFk - WHERE ( - vTableId IS NULL - OR (vTableName = 'order' AND o.id = vTableId) - OR (vTableName = 'orderRow' AND r.id = vTableId) - ) - AND !o.confirmed - AND r.shipment >= vn.getInventoryDate() - AND r.created >= vExpired - AND r.amount != 0; - - REPLACE INTO outbound ( - tableName, tableId, warehouseFk, dated, - itemFk, created, expired, quantity - ) - SELECT 'orderRow', - rowFk, - warehouseFk, - shipped, - itemFk, - created, - TIMESTAMPADD(MINUTE, vExpireTime, created), - quantity - FROM tValues; - - DROP TEMPORARY TABLE tValues; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_refreshSale.sql b/db/routines/stock/procedures/log_refreshSale.sql deleted file mode 100644 index f982d405ed..0000000000 --- a/db/routines/stock/procedures/log_refreshSale.sql +++ /dev/null @@ -1,66 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_refreshSale`( - `vTableName` VARCHAR(255), - `vTableId` INT) -BEGIN - DROP TEMPORARY TABLE IF EXISTS tValues; - CREATE TEMPORARY TABLE tValues - ENGINE = MEMORY - SELECT - m.id saleFk, - m.ticketFk, - m.itemFk, - t.warehouseFk, - t.shipped, - ABS(m.quantity) quantity, - m.created, - TIMESTAMPADD(DAY, tp.life, t.shipped) expired, - m.quantity < 0 isIn, - m.isPicked OR s.alertLevel > al.id isPicked - FROM vn.sale m - JOIN vn.ticket t ON t.id = m.ticketFk - JOIN vn.ticketState s ON s.ticketFk = t.id - JOIN vn.item i ON i.id = m.itemFk - JOIN vn.itemType tp ON tp.id = i.typeFk - JOIN vn.alertLevel al ON al.code = 'ON_PREPARATION' - WHERE ( - vTableId IS NULL - OR (vTableName = 'ticket' AND t.id = vTableId) - OR (vTableName = 'sale' AND m.id = vTableId) - ) - AND t.shipped >= vn.getInventoryDate() - AND m.quantity != 0; - - REPLACE INTO inbound ( - tableName, tableId, warehouseFk, dated, - itemFk, expired, quantity, isPicked - ) - SELECT 'sale', - saleFk, - warehouseFk, - shipped, - itemFk, - expired, - quantity, - isPicked - FROM tValues - WHERE isIn; - - REPLACE INTO outbound ( - tableName, tableId, warehouseFk, dated, - itemFk, created, quantity, isPicked - ) - SELECT 'sale', - saleFk, - warehouseFk, - shipped, - itemFk, - created, - quantity, - isPicked - FROM tValues - WHERE !isIn; - - DROP TEMPORARY TABLE tValues; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_sync.sql b/db/routines/stock/procedures/log_sync.sql deleted file mode 100644 index 349e4b3dbf..0000000000 --- a/db/routines/stock/procedures/log_sync.sql +++ /dev/null @@ -1,123 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_sync`(vSync BOOL) -proc: BEGIN - DECLARE vDone BOOL; - DECLARE vLogId INT; - DECLARE vHasPendingSync BOOL; - DECLARE vOperation VARCHAR(255); - DECLARE vTableName VARCHAR(255); - DECLARE vTableId VARCHAR(255); - DECLARE vInboundFk INT; - DECLARE vOutboundFk INT; - - DECLARE cInbound CURSOR FOR - SELECT id FROM inbound - WHERE !isSync - ORDER BY dated; - - DECLARE cOutbound CURSOR FOR - SELECT id FROM outbound - WHERE !isSync - ORDER BY dated; - - DECLARE CONTINUE HANDLER FOR NOT FOUND - SET vDone = TRUE; - - DECLARE EXIT HANDLER FOR SQLEXCEPTION - BEGIN - ROLLBACK; - RESIGNAL; - END; - - -- Applies changes - - opsLoop: LOOP - START TRANSACTION; - - SET vDone = FALSE; - SELECT id, operation, tableName, tableId - INTO vLogId, vOperation, vTableName, vTableId - FROM `log` - ORDER BY id LIMIT 1 - FOR UPDATE; - - IF vDone THEN - COMMIT; - LEAVE opsLoop; - END IF; - - CALL log_delete(vTableName, vTableId); - - IF vOperation = 'insert' THEN - IF vTableName IN ('travel', 'entry', 'buy') THEN - CALL log_refreshBuy(vTableName, vTableId); - ELSEIF vTableName IN ('ticket', 'sale') THEN - CALL log_refreshSale(vTableName, vTableId); - ELSEIF vTableName IN ('order', 'orderRow') THEN - CALL log_refreshOrder(vTableName, vTableId); - END IF; - END IF; - - DELETE FROM `log` WHERE id = vLogId; - SET vSync = TRUE; - - COMMIT; - END LOOP; - - IF !vSync THEN - LEAVE proc; - END IF; - - -- Deletes expired outbounds - - DELETE FROM outbound WHERE expired <= util.VN_NOW(); - - -- Attaches desync inbounds - - REPEAT - OPEN cInbound; - SET vHasPendingSync = FALSE; - - inboundLoop: LOOP - SET vDone = FALSE; - FETCH cInbound INTO vInboundFk; - - IF vDone THEN - LEAVE inboundLoop; - END IF; - - START TRANSACTION; - CALL inbound_sync(vInboundFk); - COMMIT; - - SET vHasPendingSync = TRUE; - END LOOP; - - CLOSE cInbound; - UNTIL !vHasPendingSync END REPEAT; - - -- Attaches desync outbounds - - REPEAT - OPEN cOutbound; - SET vHasPendingSync = FALSE; - - outboundLoop: LOOP - SET vDone = FALSE; - FETCH cOutbound INTO vOutboundFk; - - IF vDone THEN - LEAVE outboundLoop; - END IF; - - START TRANSACTION; - CALL outbound_sync(vOutboundFk); - COMMIT; - - SET vHasPendingSync = TRUE; - END LOOP; - - CLOSE cOutbound; - UNTIL !vHasPendingSync END REPEAT; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/log_syncNoWait.sql b/db/routines/stock/procedures/log_syncNoWait.sql deleted file mode 100644 index 897195f4df..0000000000 --- a/db/routines/stock/procedures/log_syncNoWait.sql +++ /dev/null @@ -1,16 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`log_syncNoWait`() -BEGIN - DECLARE EXIT HANDLER FOR SQLEXCEPTION - BEGIN - DO RELEASE_LOCK('stock.log_sync'); - RESIGNAL; - END; - - IF GET_LOCK('stock.log_sync', 0) THEN - CALL log_sync(FALSE); - END IF; - - DO RELEASE_LOCK('stock.log_sync'); -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/outbound_requestQuantity.sql b/db/routines/stock/procedures/outbound_requestQuantity.sql deleted file mode 100644 index 2ee2624676..0000000000 --- a/db/routines/stock/procedures/outbound_requestQuantity.sql +++ /dev/null @@ -1,61 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`outbound_requestQuantity`( - vSelf INT, - vRequested INT, - vDated DATETIME, - OUT vSupplied INT) -BEGIN -/** - * Disassociates outbound picks after the given date until the - * demanded quantity is satisfied. - * - * @param vSelf The outbound reference - * @param vRequested The requested quantity - * @param vDate The starting date for the associated inbounds - * @param vSupplied The supplied quantity - */ - DECLARE vInboundFk INT; - DECLARE vPickQuantity INT; - DECLARE vPickGranted INT; - DECLARE vDone BOOL; - - DECLARE vPicks CURSOR FOR - SELECT p.inboundFk, p.quantity - FROM inboundPick p - JOIN inbound i ON i.id = p.inboundFk - WHERE p.outboundFk = vSelf - AND i.dated > vDated - ORDER BY i.dated DESC; - - DECLARE CONTINUE HANDLER FOR NOT FOUND - SET vDone = TRUE; - - SET vSupplied = 0; - - OPEN vPicks; - - myLoop: LOOP - SET vDone = FALSE; - FETCH vPicks INTO vInboundFk, vPickQuantity; - - IF vDone THEN - LEAVE myLoop; - END IF; - - SET vPickGranted = LEAST(vRequested - vSupplied, vPickQuantity); - SET vSupplied = vSupplied + vPickGranted; - CALL inbound_removePick(vInboundFk, vSelf, vPickGranted, vPickQuantity); - - UPDATE inbound - SET isSync = FALSE, - available = available + vPickGranted - WHERE id = vInboundFk; - - IF vSupplied >= vRequested THEN - LEAVE myLoop; - END IF; - END LOOP; - - CLOSE vPicks; -END$$ -DELIMITER ; diff --git a/db/routines/stock/procedures/stock_clean.sql b/db/routines/stock/procedures/stock_clean.sql new file mode 100644 index 0000000000..fda30b7754 --- /dev/null +++ b/db/routines/stock/procedures/stock_clean.sql @@ -0,0 +1,32 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`stock_clean`() +BEGIN +/** + * Cleans current time dependent cache records. + */ + DECLARE vExpired DATETIME; + DECLARE vAliveDate DATE; + + -- Expired order reserves + + SELECT SUBTIME(util.VN_NOW(), reserveTime) + INTO vExpired + FROM hedera.orderConfig LIMIT 1; + + UPDATE hedera.order + SET isReserved = FALSE + WHERE created < vExpired + AND isReserved; + + -- Frozen old sales + + SELECT util.VN_CURDATE() - INTERVAL saleLife DAY + INTO vAliveDate + FROM config LIMIT 1; + + UPDATE vn.ticket + SET isAlive = FALSE + WHERE shipped < vAliveDate + AND isAlive; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/stock_refreshAll.sql b/db/routines/stock/procedures/stock_refreshAll.sql new file mode 100644 index 0000000000..dc6ce32a54 --- /dev/null +++ b/db/routines/stock/procedures/stock_refreshAll.sql @@ -0,0 +1,87 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`stock_refreshAll`() +BEGIN +/** + * Recalculates the entire cache. It takes a considerable time, + * please avoid calls to this procedure from commonly used operations. + */ + DECLARE vDone BOOL; + DECLARE vId INT; + + DECLARE vBuys CURSOR FOR + SELECT lotFk FROM vn.buy WHERE isAlive; + + DECLARE vTickets CURSOR FOR + SELECT id FROM vn.ticket WHERE isAlive; + + DECLARE vOrders CURSOR FOR + SELECT lotFk FROM hedera.orderRow WHERE isReserved; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + DO RELEASE_LOCK('stock.stock_refreshAll'); + RESIGNAL; + END; + + IF NOT GET_LOCK('stock.stock_refreshAll', 30) THEN + CALL util.throw('Lock timeout exceeded'); + END IF; + + -- Prune cache + + DELETE p FROM buyPick p JOIN buyLot l USING(lotFk); + TRUNCATE TABLE buyLot; + TRUNCATE TABLE buyOut; + + -- Populate cache + + OPEN vBuys; + buyLoop: LOOP + SET vDone = FALSE; + FETCH vBuys INTO vId; + + IF vDone THEN + LEAVE buyLoop; + END IF; + + CALL buyOut_refreshBuy('lot', vId); + END LOOP; + CLOSE vBuys; + + OPEN vOrders; + orderLoop: LOOP + SET vDone = FALSE; + FETCH vOrders INTO vId; + + IF vDone THEN + LEAVE orderLoop; + END IF; + + CALL buyOut_refreshOrder('lot', vId); + END LOOP; + CLOSE vOrders; + + OPEN vTickets; + saleLoop: LOOP + SET vDone = FALSE; + FETCH vTickets INTO vId; + + IF vDone THEN + LEAVE saleLoop; + END IF; + + CALL buyOut_refreshSale('ticket', vId); + END LOOP; + CLOSE vTickets; + + -- Synchronize + + UPDATE buyOut SET isSync = TRUE; + CALL stock_sync; + + DO RELEASE_LOCK('stock.stock_refreshAll'); +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/stock_sync.sql b/db/routines/stock/procedures/stock_sync.sql new file mode 100644 index 0000000000..41501f4f1f --- /dev/null +++ b/db/routines/stock/procedures/stock_sync.sql @@ -0,0 +1,34 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`stock_sync`() +BEGIN +/** + * Synchronizes all out of sync items. It can be called in parallel + * since it generates a lock for each item (and warehouse) synchronization + * process, see stock_syncItem(). + */ + DECLARE vDone BOOL; + DECLARE vWarehouseFk INT; + DECLARE vItemFk INT; + + DECLARE vItems CURSOR FOR + SELECT itemFk, warehouseFk FROM buyLot WHERE NOT isSync + UNION + SELECT itemFk, warehouseFk FROM buyOut WHERE NOT isSync; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + OPEN vItems; + itemLoop: LOOP + SET vDone = FALSE; + FETCH vItems INTO vItemFk, vWarehouseFk; + + IF vDone THEN + LEAVE itemLoop; + END IF; + + CALL stock_syncItem(vItemFk, vWarehouseFk, 0); + END LOOP; + CLOSE vItems; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/stock_syncItem.sql b/db/routines/stock/procedures/stock_syncItem.sql new file mode 100644 index 0000000000..65ef46736b --- /dev/null +++ b/db/routines/stock/procedures/stock_syncItem.sql @@ -0,0 +1,88 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`stock_syncItem`( + vItemFk INT, + vWarehouseFk INT, + vWait INT) +myProc: BEGIN +/** + * Synchronizes out of sync item. It generates a lock for each item and + * warehouse synchronization process. + * + * @param vItemFk The item id + * @param vWarehouseFk The item warehouse id + * @param vWait Maximum waiting time, see GET_LOCK() + */ + DECLARE vDone BOOL; + DECLARE vHasPendingSync BOOL; + DECLARE vLotFk INT; + DECLARE vOutFk INT; + DECLARE vLock VARCHAR(255); + + DECLARE vLots CURSOR FOR + SELECT lotFk FROM buyLot + WHERE NOT isSync + AND (itemFk, warehouseFk) = (vItemFk, vWarehouseFk) + ORDER BY dated, lotFk; + + DECLARE vOuts CURSOR FOR + SELECT outFk FROM buyOut + WHERE NOT isSync + AND (itemFk, warehouseFk) = (vItemFk, vWarehouseFk) + ORDER BY dated, created, outFk; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + DO RELEASE_LOCK(vLock); + ROLLBACK; + RESIGNAL; + END; + + SET vLock = CONCAT_WS('/', 'stock.stock_syncItem', vWarehouseFk, vItemFk); + + IF NOT GET_LOCK(vLock, vWait) THEN + LEAVE myProc; + END IF; + + REPEAT + SET vHasPendingSync = FALSE; + + OPEN vLots; + lotLoop: LOOP + SET vDone = FALSE; + FETCH vLots INTO vLotFk; + + IF vDone THEN + LEAVE lotLoop; + END IF; + + START TRANSACTION; + CALL buyLot_sync(vLotFk); + COMMIT; + + SET vHasPendingSync = TRUE; + END LOOP; + CLOSE vLots; + + OPEN vOuts; + outLoop: LOOP + SET vDone = FALSE; + FETCH vOuts INTO vOutFk; + + IF vDone THEN + LEAVE outLoop; + END IF; + + START TRANSACTION; + CALL buyOut_sync(vOutFk); + COMMIT; + + SET vHasPendingSync = TRUE; + END LOOP; + CLOSE vOuts; + + UNTIL NOT vHasPendingSync END REPEAT; +END$$ +DELIMITER ; diff --git a/db/routines/stock/procedures/visible_log.sql b/db/routines/stock/procedures/visible_log.sql deleted file mode 100644 index cb11e9d3e7..0000000000 --- a/db/routines/stock/procedures/visible_log.sql +++ /dev/null @@ -1,20 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `stock`.`visible_log`( - vIsPicked BOOL, - vWarehouseFk INT, - vItemFk INT, - vQuantity INT -) -proc: BEGIN - IF !vIsPicked THEN - LEAVE proc; - END IF; - - INSERT INTO visible - SET itemFk = vItemFk, - warehouseFk = vWarehouseFk, - quantity = vQuantity - ON DUPLICATE KEY UPDATE - quantity = quantity + VALUES(quantity); -END$$ -DELIMITER ; diff --git a/db/routines/stock/triggers/inbound_afterDelete.sql b/db/routines/stock/triggers/inbound_afterDelete.sql deleted file mode 100644 index a436d04a01..0000000000 --- a/db/routines/stock/triggers/inbound_afterDelete.sql +++ /dev/null @@ -1,22 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `stock`.`inbound_afterDelete` - AFTER DELETE ON `inbound` - FOR EACH ROW -BEGIN - UPDATE outbound o - JOIN inboundPick ou ON ou.outboundFk = o.id - SET o.lack = o.lack + ou.quantity, - o.isSync = FALSE - WHERE ou.inboundFk = OLD.id; - - DELETE FROM inboundPick - WHERE inboundFk = OLD.id; - - CALL visible_log( - OLD.isPicked, - OLD.warehouseFk, - OLD.itemFk, - -OLD.quantity - ); -END$$ -DELIMITER ; diff --git a/db/routines/stock/triggers/inbound_beforeInsert.sql b/db/routines/stock/triggers/inbound_beforeInsert.sql deleted file mode 100644 index 3707a2b2a7..0000000000 --- a/db/routines/stock/triggers/inbound_beforeInsert.sql +++ /dev/null @@ -1,15 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `stock`.`inbound_beforeInsert` - BEFORE INSERT ON `inbound` - FOR EACH ROW -BEGIN - SET NEW.isPicked = NEW.isPicked OR NEW.dated < util.VN_CURDATE(); - - CALL visible_log( - NEW.isPicked, - NEW.warehouseFk, - NEW.itemFk, - NEW.quantity - ); -END$$ -DELIMITER ; diff --git a/db/routines/stock/triggers/outbound_afterDelete.sql b/db/routines/stock/triggers/outbound_afterDelete.sql deleted file mode 100644 index 1c90c32932..0000000000 --- a/db/routines/stock/triggers/outbound_afterDelete.sql +++ /dev/null @@ -1,22 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `stock`.`outbound_afterDelete` - AFTER DELETE ON `outbound` - FOR EACH ROW -BEGIN - UPDATE inbound i - JOIN inboundPick ou ON ou.inboundFk = i.id - SET i.available = i.available + ou.quantity, - i.isSync = FALSE - WHERE ou.outboundFk = OLD.id; - - DELETE FROM inboundPick - WHERE outboundFk = OLD.id; - - CALL visible_log( - OLD.isPicked, - OLD.warehouseFk, - OLD.itemFk, - OLD.quantity - ); -END$$ -DELIMITER ; diff --git a/db/routines/stock/triggers/outbound_beforeInsert.sql b/db/routines/stock/triggers/outbound_beforeInsert.sql deleted file mode 100644 index e0560d8f65..0000000000 --- a/db/routines/stock/triggers/outbound_beforeInsert.sql +++ /dev/null @@ -1,16 +0,0 @@ -DELIMITER $$ -CREATE OR REPLACE DEFINER=`vn`@`localhost` TRIGGER `stock`.`outbound_beforeInsert` - BEFORE INSERT ON `outbound` - FOR EACH ROW -BEGIN - SET NEW.lack = NEW.quantity; - SET NEW.isPicked = NEW.isPicked OR NEW.dated < util.VN_CURDATE(); - - CALL visible_log( - NEW.isPicked, - NEW.warehouseFk, - NEW.itemFk, - -NEW.quantity - ); -END$$ -DELIMITER ; diff --git a/db/routines/vn/functions/item_getLife.sql b/db/routines/vn/functions/item_getLife.sql new file mode 100644 index 0000000000..e413399003 --- /dev/null +++ b/db/routines/vn/functions/item_getLife.sql @@ -0,0 +1,15 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`vn`@`localhost` FUNCTION `vn`.`item_getLife`(vItemFk INT) + RETURNS INT + NOT DETERMINISTIC +BEGIN + DECLARE vLife INT; + + SELECT t.life INTO vLife + FROM itemType t + JOIN item i ON i.typeFk = t.id + WHERE i.id = vItemFk; + + RETURN vLife; +END$$ +DELIMITER ; diff --git a/db/routines/vn/procedures/catalog_calculate.sql b/db/routines/vn/procedures/catalog_calculate.sql index 09f42f09aa..63d1d40eb4 100644 --- a/db/routines/vn/procedures/catalog_calculate.sql +++ b/db/routines/vn/procedures/catalog_calculate.sql @@ -104,6 +104,24 @@ BEGIN LEFT JOIN agencyModeItemType ait ON ait.agencyModeFk = vAgencyModeFk AND ait.itemTypeFk = itt.id + LEFT JOIN ( + SELECT i.id + FROM item i + JOIN priceDelta pd + ON pd.itemTypeFk = i.typeFk + AND (pd.minSize IS NULL OR pd.minSize <= i.`size`) + AND (pd.maxSize IS NULL OR pd.maxSize >= i.`size`) + AND (pd.inkFk IS NULL OR pd.inkFk = i.inkFk) + AND (pd.originFk IS NULL OR pd.originFk = i.originFk) + AND (pd.producerFk IS NULL OR pd.producerFk = i.producerFk) + AND (pd.warehouseFk IS NULL OR pd.warehouseFk = vWarehouseFk) + LEFT JOIN zoneGeo zg ON zg.id = pd.zoneGeoFk + LEFT JOIN zoneGeo zg2 ON zg2.id = address_getGeo(vAddressFk) + WHERE (pd.fromDated IS NULL OR pd.fromDated <= vShipped) + AND (pd.toDated IS NULL OR pd.toDated >= vShipped) + AND (pd.zoneGeoFk IS NULL OR zg2.lft BETWEEN zg.lft AND zg.rgt) + AND pd.isHidden + GROUP BY i.id) pd ON pd.id = i.itemFk WHERE a.calc_id = vAvailableCalc AND a.available > 0 AND (ag.isAnyVolumeAllowed OR NOT itt.isUnconventionalSize) @@ -113,7 +131,9 @@ BEGIN it.size <= z.itemMaxSize OR z.itemMaxSize IS NULL)) AND cit.id IS NULL AND zit.id IS NULL - AND ait.id IS NULL; + AND ait.id IS NULL + AND pd.id IS NULL + ; DROP TEMPORARY TABLE tmp.buyUltimate; diff --git a/db/routines/vn/procedures/report_print.sql b/db/routines/vn/procedures/report_print.sql index a5e08538eb..17c0c921bb 100644 --- a/db/routines/vn/procedures/report_print.sql +++ b/db/routines/vn/procedures/report_print.sql @@ -1,83 +1,86 @@ DELIMITER $$ CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`report_print`( - vReportName VARCHAR(100), - vPrinterFk INT, - vUserFk INT, - vParams JSON, - vPriorityName VARCHAR(100) - ) + vReportName VARCHAR(100), + vPrinterFk INT, + vUserFk INT, + vParams JSON, + vPriorityName VARCHAR(100) +) BEGIN -/** - * Inserts in the print queue the report to be printed and the necessary parameters for this - * one taking into account the paper size of both the printer and the report. - * - * @param vReportName the report to be printed. - * @param vPrinterFk the printer selected. - * @param vUserFk user id. - * @param vParams JSON with report parameters. - * @param vPriorityName the printing priority. - */ - DECLARE vI INT DEFAULT 0; - DECLARE vKeys TEXT DEFAULT JSON_KEYS(vParams); - DECLARE vLength INT DEFAULT JSON_LENGTH(vKeys); - DECLARE vKey VARCHAR(255); - DECLARE vVal VARCHAR(255); - DECLARE vPrintQueueFk INT; - DECLARE vReportSize VARCHAR(255); - DECLARE vIsThePrinterReal INT; - DECLARE vPrinteSize VARCHAR(255); - DECLARE vPriorityFk INT; - DECLARE vReportFk INT; - DECLARE EXIT HANDLER FOR SQLEXCEPTION - BEGIN - ROLLBACK; - RESIGNAL; - END; + /** + * Inserts in the print queue the report to be printed and the necessary parameters for this + * one taking into account the paper size of both the printer and the report. + * + * @param vReportName the report to be printed. + * @param vPrinterFk the printer selected. + * @param vUserFk user id. + * @param vParams JSON with report parameters. + * @param vPriorityName the printing priority. + */ + DECLARE vI INT DEFAULT 0; + DECLARE vKeys TEXT DEFAULT JSON_KEYS(vParams); + DECLARE vLength INT DEFAULT JSON_LENGTH(vKeys); + DECLARE vKey VARCHAR(255); + DECLARE vVal VARCHAR(255); + DECLARE vPrintQueueFk INT; + DECLARE vReportSize VARCHAR(255); + DECLARE vIsThePrinterReal INT; + DECLARE vPrinterSize VARCHAR(255); + DECLARE vPriorityFk INT; + DECLARE vReportFk INT; + DECLARE vTx BOOLEAN DEFAULT NOT @@in_transaction; - SELECT id, paperSizeFk INTO vReportFk, vReportSize - FROM report - WHERE name = vReportName; + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + CALL util.tx_rollback(vTx); + RESIGNAL; + END; - SELECT id, paperSizeFk INTO vIsThePrinterReal, vPrinteSize - FROM printer - WHERE id = vPrinterFk; - SELECT id INTO vPriorityFk - FROM queuePriority - WHERE code = vPriorityName; + SELECT id, paperSizeFk INTO vReportFk, vReportSize + FROM report + WHERE name = vReportName; - IF vIsThePrinterReal IS NULL THEN - CALL util.throw('printerNotExists'); - END IF; + SELECT id, paperSizeFk INTO vIsThePrinterReal, vPrinterSize + FROM printer + WHERE id = vPrinterFk; - IF vReportFk IS NULL THEN - CALL util.throw('reportNotExists'); - END IF; + SELECT id INTO vPriorityFk + FROM queuePriority + WHERE code = vPriorityName; - IF vReportSize <> vPrinteSize THEN - CALL util.throw('incorrectSize'); - END IF; + IF vIsThePrinterReal IS NULL THEN + CALL util.throw('printerNotExists'); + END IF; - START TRANSACTION; - INSERT INTO printQueue - SET printerFk = vPrinterFk, - priorityFk = vPriorityFk, - reportFk = vReportFk, - workerFk = vUserFk; - - SET vPrintQueueFk = LAST_INSERT_ID(); + IF vReportFk IS NULL THEN + CALL util.throw('reportNotExists'); + END IF; - WHILE vI < vLength DO - SET vKey = JSON_VALUE(vKeys, CONCAT('$[', vI ,']')); - SET vVal = JSON_VALUE(vParams, CONCAT('$.', vKey)); + IF vReportSize <> vPrinterSize THEN + CALL util.throw('incorrectSize'); + END IF; + CALL util.tx_start(vTx); + INSERT INTO printQueue + SET printerFk = vPrinterFk, + priorityFk = vPriorityFk, + reportFk = vReportFk, + workerFk = vUserFk; - INSERT INTO printQueueArgs - SET printQueueFk = vPrintQueueFk, - name = vKey, - value = vVal; + SET vPrintQueueFk = LAST_INSERT_ID(); - SET vI = vI + 1; - END WHILE; - COMMIT; + WHILE vI < vLength DO + SET vKey = JSON_VALUE(vKeys, CONCAT('$[', vI ,']')); + SET vVal = JSON_VALUE(vParams, CONCAT('$.', vKey)); + + INSERT INTO printQueueArgs + SET printQueueFk = vPrintQueueFk, + name = vKey, + value = vVal; + + SET vI = vI + 1; + END WHILE; + + CALL util.tx_commit(vTx); END$$ DELIMITER ; diff --git a/db/routines/vn/triggers/buy_beforeInsert.sql b/db/routines/vn/triggers/buy_beforeInsert.sql index 9c10592661..112b35e416 100644 --- a/db/routines/vn/triggers/buy_beforeInsert.sql +++ b/db/routines/vn/triggers/buy_beforeInsert.sql @@ -22,6 +22,10 @@ trig: BEGIN SET NEW.editorFk = account.myUser_getId(); + IF NEW.life IS NULL THEN + SET NEW.life = item_getLife(NEW.itemFk); + END IF; + SELECT it.workerFk INTO vBuyerFk FROM item i JOIN itemType it ON it.id = i.typeFk @@ -57,7 +61,7 @@ trig: BEGIN IF NEW.groupingMode IS NULL THEN SET NEW.groupingMode = vGroupingMode; END IF; - + -- Generics SELECT i.genericFk INTO vGenericFk FROM item i @@ -78,7 +82,7 @@ trig: BEGIN END IF; IF NEW.quantity < 0 THEN - SET NEW.isIgnored = TRUE; + SET NEW.isIgnored = TRUE; END IF; IF NEW.weight AND NEW.packing diff --git a/db/routines/vn/triggers/buy_beforeUpdate.sql b/db/routines/vn/triggers/buy_beforeUpdate.sql index c67d44f6f3..f3a672a479 100644 --- a/db/routines/vn/triggers/buy_beforeUpdate.sql +++ b/db/routines/vn/triggers/buy_beforeUpdate.sql @@ -25,6 +25,10 @@ trig:BEGIN SET NEW.editorFk = account.myUser_getId(); + IF NOT (NEW.itemFk <=> OLD.itemFk) AND NEW.life <=> OLD.life THEN + SET NEW.life = item_getLife(NEW.itemFk); + END IF; + SELECT defaultEntry INTO vDefaultEntry FROM entryConfig; diff --git a/db/versions/10996-pinkPhormium/03-dropStockLog.sql b/db/versions/10996-pinkPhormium/03-dropStockLog.sql new file mode 100644 index 0000000000..ab5b0a254f --- /dev/null +++ b/db/versions/10996-pinkPhormium/03-dropStockLog.sql @@ -0,0 +1 @@ +DROP TABLE stock.log; diff --git a/db/versions/11009-tealRuscus/00-buyPick.sql b/db/versions/11009-tealRuscus/00-buyPick.sql new file mode 100644 index 0000000000..806329e78e --- /dev/null +++ b/db/versions/11009-tealRuscus/00-buyPick.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS stock.inboundPick; + +CREATE TABLE stock.buyPick ( + id INT UNSIGNED auto_increment NOT NULL, + lotFk INT(11) NOT NULL + COMMENT 'Buy id', + outFk INT(11) NOT NULL + COMMENT 'Out id', + quantity INT UNSIGNED NOT NULL + COMMENT 'Picked quantity', + PRIMARY KEY (id), + CONSTRAINT buyPick_unique UNIQUE KEY (lotFk, outFk) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb3 +COLLATE=utf8mb3_unicode_ci; diff --git a/db/versions/11009-tealRuscus/01-alterBuy.sql b/db/versions/11009-tealRuscus/01-alterBuy.sql new file mode 100644 index 0000000000..0b40452b38 --- /dev/null +++ b/db/versions/11009-tealRuscus/01-alterBuy.sql @@ -0,0 +1,6 @@ +ALTER TABLE vn.buy + ADD life INT UNSIGNED NULL + COMMENT 'Lot life expressed in days', + ADD isAlive BOOL NOT NULL DEFAULT TRUE + COMMENT 'Whether it is alive', + ADD INDEX(isAlive); diff --git a/db/versions/11009-tealRuscus/01-alterOrderRow.sql b/db/versions/11009-tealRuscus/01-alterOrderRow.sql new file mode 100644 index 0000000000..5fed7b23da --- /dev/null +++ b/db/versions/11009-tealRuscus/01-alterOrderRow.sql @@ -0,0 +1,4 @@ +ALTER TABLE hedera.orderRow + ADD isReserved BOOL NOT NULL DEFAULT TRUE + COMMENT 'Whether has an available reservation', + ADD INDEX(isReserved); diff --git a/db/versions/11009-tealRuscus/01-alterTicket.sql b/db/versions/11009-tealRuscus/01-alterTicket.sql new file mode 100644 index 0000000000..857415838a --- /dev/null +++ b/db/versions/11009-tealRuscus/01-alterTicket.sql @@ -0,0 +1,4 @@ +ALTER TABLE vn.ticket + ADD isAlive BOOL NOT NULL DEFAULT TRUE + COMMENT 'Whether it is alive', + ADD INDEX(isAlive); diff --git a/db/versions/11009-tealRuscus/02-buyLot.sql b/db/versions/11009-tealRuscus/02-buyLot.sql new file mode 100644 index 0000000000..cfdca064b9 --- /dev/null +++ b/db/versions/11009-tealRuscus/02-buyLot.sql @@ -0,0 +1,10 @@ +RENAME TABLE IF EXISTS stock.inbound TO stock.buyLot; + +ALTER TABLE stock.buyLot + DROP KEY source, + DROP COLUMN tableName, + CHANGE tableId lotFk int(10) unsigned NOT NULL, + CHANGE isSync isSync tinyint(4) NOT NULL AFTER lotFk, + DROP PRIMARY KEY, + DROP COLUMN id, + ADD PRIMARY KEY (lotFk); diff --git a/db/versions/11009-tealRuscus/03-removeStockVisible.sql b/db/versions/11009-tealRuscus/03-removeStockVisible.sql new file mode 100644 index 0000000000..bc5c9a84eb --- /dev/null +++ b/db/versions/11009-tealRuscus/03-removeStockVisible.sql @@ -0,0 +1 @@ +DROP TABLE stock.visible; diff --git a/db/versions/11009-tealRuscus/04-buyOut.sql b/db/versions/11009-tealRuscus/04-buyOut.sql new file mode 100644 index 0000000000..e2be18da56 --- /dev/null +++ b/db/versions/11009-tealRuscus/04-buyOut.sql @@ -0,0 +1,10 @@ +RENAME TABLE IF EXISTS stock.outbound TO stock.buyOut; + +ALTER TABLE stock.buyOut + CHANGE IF EXISTS tableName source enum('buy','sale','orderRow') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + CHANGE IF EXISTS id outFk int(10) UNSIGNED NOT NULL, + DROP INDEX IF EXISTS source, + DROP COLUMN IF EXISTS tableId, + DROP INDEX IF EXISTS expired, + DROP COLUMN IF EXISTS expired, + ADD INDEX IF NOT EXISTS (source); diff --git a/db/versions/11009-tealRuscus/05-stockConfig.sql b/db/versions/11009-tealRuscus/05-stockConfig.sql new file mode 100644 index 0000000000..3813206aec --- /dev/null +++ b/db/versions/11009-tealRuscus/05-stockConfig.sql @@ -0,0 +1,8 @@ +CREATE TABLE stock.config ( + id INT UNSIGNED auto_increment NOT NULL, + saleLife INT UNSIGNED NOT NULL COMMENT 'Maximum sales cache lifetime in days', + CONSTRAINT config_pk PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb3 +COLLATE=utf8mb3_general_ci; diff --git a/db/versions/11009-tealRuscus/06-stockConfigInsert.sql b/db/versions/11009-tealRuscus/06-stockConfigInsert.sql new file mode 100644 index 0000000000..780e5222f6 --- /dev/null +++ b/db/versions/11009-tealRuscus/06-stockConfigInsert.sql @@ -0,0 +1,2 @@ +INSERT INTO stock.config (id, saleLife) + VALUES (1, 90); diff --git a/db/versions/11009-tealRuscus/07-buyLotSeq.sql b/db/versions/11009-tealRuscus/07-buyLotSeq.sql new file mode 100644 index 0000000000..8b97509d97 --- /dev/null +++ b/db/versions/11009-tealRuscus/07-buyLotSeq.sql @@ -0,0 +1 @@ +CREATE SEQUENCE IF NOT EXISTS vn.buyLot; diff --git a/db/versions/11009-tealRuscus/08-saleLotFk.sql b/db/versions/11009-tealRuscus/08-saleLotFk.sql new file mode 100644 index 0000000000..6f32413a55 --- /dev/null +++ b/db/versions/11009-tealRuscus/08-saleLotFk.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.sale + ADD COLUMN IF NOT EXISTS lotFk INT UNSIGNED NOT NULL DEFAULT nextval(vn.buyLot) AFTER id; diff --git a/db/versions/11009-tealRuscus/09-orderRowLotFk.sql b/db/versions/11009-tealRuscus/09-orderRowLotFk.sql new file mode 100644 index 0000000000..fde39f7e7d --- /dev/null +++ b/db/versions/11009-tealRuscus/09-orderRowLotFk.sql @@ -0,0 +1,2 @@ +ALTER TABLE hedera.orderRow + ADD COLUMN IF NOT EXISTS lotFk INT UNSIGNED NOT NULL DEFAULT nextval(vn.buyLot) AFTER id; diff --git a/db/versions/11009-tealRuscus/10-buyLotFk.sql b/db/versions/11009-tealRuscus/10-buyLotFk.sql new file mode 100644 index 0000000000..602c9fc2c5 --- /dev/null +++ b/db/versions/11009-tealRuscus/10-buyLotFk.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.buy + ADD COLUMN IF NOT EXISTS lotFk INT UNSIGNED NOT NULL DEFAULT nextval(vn.buyLot) AFTER id; diff --git a/db/versions/11009-tealRuscus/11-saleLotIndex.sql b/db/versions/11009-tealRuscus/11-saleLotIndex.sql new file mode 100644 index 0000000000..75042be564 --- /dev/null +++ b/db/versions/11009-tealRuscus/11-saleLotIndex.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.sale + ADD UNIQUE IF NOT EXISTS (lotFk); diff --git a/db/versions/11009-tealRuscus/12-orderRowLotIndex.sql b/db/versions/11009-tealRuscus/12-orderRowLotIndex.sql new file mode 100644 index 0000000000..80d25e5803 --- /dev/null +++ b/db/versions/11009-tealRuscus/12-orderRowLotIndex.sql @@ -0,0 +1,2 @@ +ALTER TABLE hedera.orderRow + ADD UNIQUE IF NOT EXISTS (lotFk); diff --git a/db/versions/11009-tealRuscus/13-buyLotIndex.sql b/db/versions/11009-tealRuscus/13-buyLotIndex.sql new file mode 100644 index 0000000000..12e3b709be --- /dev/null +++ b/db/versions/11009-tealRuscus/13-buyLotIndex.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.buy + ADD UNIQUE IF NOT EXISTS (lotFk); diff --git a/db/versions/11401-azureMoss/00-claimConfig.sql b/db/versions/11401-azureMoss/00-claimConfig.sql new file mode 100644 index 0000000000..8942de33b4 --- /dev/null +++ b/db/versions/11401-azureMoss/00-claimConfig.sql @@ -0,0 +1,14 @@ +ALTER TABLE `vn`.`claimConfig` + ADD COLUMN `pickupDeliveryFk` INT(11) + COMMENT 'Agencia utilizada para las recogidas mediante reparto', + ADD COLUMN `warehouseFk` smallint(6) unsigned + COMMENT 'Almacén usado para los tickets de reclamaciones', + + ADD CONSTRAINT `fk_claimConfig_pickupdeliveryFk` + FOREIGN KEY (`pickupdeliveryFk`) + REFERENCES `agencyMode` (`id`), + + ADD CONSTRAINT `fk_claimConfig_warehouseFk` + FOREIGN KEY (`warehouseFk`) + REFERENCES `warehouse` (`id`); + diff --git a/db/versions/11401-azureMoss/01-createState.sql b/db/versions/11401-azureMoss/01-createState.sql new file mode 100644 index 0000000000..8162e45a9d --- /dev/null +++ b/db/versions/11401-azureMoss/01-createState.sql @@ -0,0 +1,2 @@ +INSERT IGNORE INTO vn.state (name,`order`,alertLevel,code,isPreviousPreparable,isPicked) + VALUES ('Recogido',3,4,'PICKED_UP',0,1) diff --git a/db/versions/11401-azureMoss/02-claimEnd.sql b/db/versions/11401-azureMoss/02-claimEnd.sql new file mode 100644 index 0000000000..8cc03279d2 --- /dev/null +++ b/db/versions/11401-azureMoss/02-claimEnd.sql @@ -0,0 +1,5 @@ +ALTER TABLE `vn`.`claimEnd` + ADD COLUMN `shelvingFk` INT(11) DEFAULT NULL AFTER `editorFk`, + ADD INDEX `claimEnd_fk_shelving` (`shelvingFk`), + ADD CONSTRAINT `claimEnd_fk_shelving` FOREIGN KEY (`shelvingFk`) + REFERENCES `shelving` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/db/versions/11401-azureMoss/03-claimDestination.sql b/db/versions/11401-azureMoss/03-claimDestination.sql new file mode 100644 index 0000000000..09da25a88e --- /dev/null +++ b/db/versions/11401-azureMoss/03-claimDestination.sql @@ -0,0 +1,8 @@ +UPDATE vn.claimDestination cd + SET cd.addressFk = NULL + WHERE code IN ('garbage/loss','manufacturing','supplierClaim','corrected'); + +UPDATE vn.claimDestination cd + JOIN vn.addressWaste aw ON aw.`type` = 'fault' + SET cd.addressFk = aw.addressFk + WHERE code IN ('good'); diff --git a/db/versions/11444-orangeMastic/00-firstScript.sql b/db/versions/11444-orangeMastic/00-firstScript.sql new file mode 100644 index 0000000000..2476ea6f2c --- /dev/null +++ b/db/versions/11444-orangeMastic/00-firstScript.sql @@ -0,0 +1,8 @@ +UPDATE vn.claimEnd ce + JOIN( + SELECT id + FROM vn.claimEnd + WHERE claimDestinationFk NOT IN + (SELECT id FROM vn.claimDestination WHERE id IS NOT NULL) + ) s ON ce.id = s.id + SET ce.claimDestinationFk = 1; \ No newline at end of file diff --git a/db/versions/11444-orangeMastic/01-firstScript.sql b/db/versions/11444-orangeMastic/01-firstScript.sql new file mode 100644 index 0000000000..38cd7c0914 --- /dev/null +++ b/db/versions/11444-orangeMastic/01-firstScript.sql @@ -0,0 +1,9 @@ +ALTER TABLE vn.claimEnd + MODIFY COLUMN claimDestinationFk tinyint(3) unsigned NOT NULL DEFAULT 1; + +ALTER TABLE vn.claimEnd + ADD CONSTRAINT fk_claimEnd_claimDestination + FOREIGN KEY (claimDestinationFk) + REFERENCES claimDestination(id) + ON UPDATE CASCADE + ON DELETE RESTRICT; \ No newline at end of file diff --git a/db/versions/11454-orangeDracena/00-firstScript.sql b/db/versions/11454-orangeDracena/00-firstScript.sql new file mode 100644 index 0000000000..05b2a8c059 --- /dev/null +++ b/db/versions/11454-orangeDracena/00-firstScript.sql @@ -0,0 +1,3 @@ +-- Place your SQL code here +INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId) + VALUES ('Worker','getWorkerBusiness','READ','ALLOW','ROLE','hr'); diff --git a/db/versions/11455-orangeTulip/00-firstScript.sql b/db/versions/11455-orangeTulip/00-firstScript.sql new file mode 100644 index 0000000000..d64c8e324e --- /dev/null +++ b/db/versions/11455-orangeTulip/00-firstScript.sql @@ -0,0 +1,9 @@ + + +USE vn; + +INSERT INTO vn.workerActivityType (code, description) +VALUES('SHELVING_CLEAN_START', 'SE INICIA LIMPIEZA CARRO'), + ('SHELVING_CLEAN_STOP', 'SE FINALIZA LIMPIEZA CARRO'); + + diff --git a/db/versions/11456-goldenDracena/00-firstScript.sql b/db/versions/11456-goldenDracena/00-firstScript.sql new file mode 100644 index 0000000000..fba3ba8088 --- /dev/null +++ b/db/versions/11456-goldenDracena/00-firstScript.sql @@ -0,0 +1,3 @@ +ALTER TABLE vn.priceDelta ADD IF NOT EXISTS isHidden BOOL + DEFAULT FALSE NOT NULL + COMMENT 'Hides the itemType when building de catalog recordset'; diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 07a82d214d..d9cb3ed476 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -258,5 +258,6 @@ "clonedFromTicketWeekly": ", that is a cloned sale from ticket {{ ticketWeekly }}", "negativeReplaced": "Replaced item [#{{oldItemId}}]({{{oldItemUrl}}}) {{oldItem}} with [#{{newItemId}}]({{{newItemUrl}}}) {{newItem}} from ticket [{{ticketId}}]({{{ticketUrl}}})", "The tag and priority can't be repeated": "The tag and priority can't be repeated", - "duplicateWarehouse": "The introduced warehouse already exists" + "The introduced warehouse already exists": "The introduced warehouse already exists", + "The code already exists": "The code already exists" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 6463608818..7783182c01 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -1,403 +1,404 @@ { - "Phone format is invalid": "El formato del teléfono no es correcto", - "You are not allowed to change the credit": "No tienes privilegios para modificar el crédito", - "Unable to mark the equivalence surcharge": "No se puede marcar el recargo de equivalencia", - "The default consignee can not be unchecked": "No se puede desmarcar el consignatario predeterminado", - "Unable to default a disabled consignee": "No se puede poner predeterminado un consignatario desactivado", - "Can't be blank": "No puede estar en blanco", - "Invalid TIN": "NIF/CIF inválido", - "TIN must be unique": "El NIF/CIF debe ser único", - "A client with that Web User name already exists": "Ya existe un cliente con ese Usuario Web", - "Is invalid": "Es inválido", - "Quantity cannot be zero": "La cantidad no puede ser cero", - "Enter an integer different to zero": "Introduce un entero distinto de cero", - "Package cannot be blank": "El embalaje no puede estar en blanco", - "The company name must be unique": "La razón social debe ser única", - "Invalid email": "Correo electrónico inválido", - "The IBAN does not have the correct format": "El IBAN no tiene el formato correcto", - "That payment method requires an IBAN": "El método de pago seleccionado requiere un IBAN", - "That payment method requires a BIC": "El método de pago seleccionado requiere un BIC", - "State cannot be blank": "El estado no puede estar en blanco", - "Worker cannot be blank": "El trabajador no puede estar en blanco", - "Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado", - "can't be blank": "El campo no puede estar vacío", - "Observation type must be unique": "El tipo de observación no puede repetirse", + "Phone format is invalid": "El formato del teléfono no es correcto", + "You are not allowed to change the credit": "No tienes privilegios para modificar el crédito", + "Unable to mark the equivalence surcharge": "No se puede marcar el recargo de equivalencia", + "The default consignee can not be unchecked": "No se puede desmarcar el consignatario predeterminado", + "Unable to default a disabled consignee": "No se puede poner predeterminado un consignatario desactivado", + "Can't be blank": "No puede estar en blanco", + "Invalid TIN": "NIF/CIF inválido", + "TIN must be unique": "El NIF/CIF debe ser único", + "A client with that Web User name already exists": "Ya existe un cliente con ese Usuario Web", + "Is invalid": "Es inválido", + "Quantity cannot be zero": "La cantidad no puede ser cero", + "Enter an integer different to zero": "Introduce un entero distinto de cero", + "Package cannot be blank": "El embalaje no puede estar en blanco", + "The company name must be unique": "La razón social debe ser única", + "Invalid email": "Correo electrónico inválido", + "The IBAN does not have the correct format": "El IBAN no tiene el formato correcto", + "That payment method requires an IBAN": "El método de pago seleccionado requiere un IBAN", + "That payment method requires a BIC": "El método de pago seleccionado requiere un BIC", + "State cannot be blank": "El estado no puede estar en blanco", + "Worker cannot be blank": "El trabajador no puede estar en blanco", + "Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado", + "can't be blank": "El campo no puede estar vacío", + "Observation type must be unique": "El tipo de observación no puede repetirse", "The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero", - "The grade must be similar to the last one": "El grade debe ser similar al último", - "Only manager can change the credit": "Solo el gerente puede cambiar el credito de este cliente", - "Name cannot be blank": "El nombre no puede estar en blanco", - "Phone cannot be blank": "El teléfono no puede estar en blanco", - "Period cannot be blank": "El periodo no puede estar en blanco", - "Choose a company": "Selecciona una empresa", - "Se debe rellenar el campo de texto": "Se debe rellenar el campo de texto", - "Description should have maximum of 45 characters": "La descripción debe tener maximo 45 caracteres", - "Cannot be blank": "El campo no puede estar en blanco", - "The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero", - "Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco", - "Description cannot be blank": "Se debe rellenar el campo de texto", - "The price of the item changed": "El precio del artículo cambió", - "The value should not be greater than 100%": "El valor no debe de ser mayor de 100%", - "The value should be a number": "El valor debe ser un numero", - "This order is not editable": "Esta orden no se puede modificar", - "You can't create an order for a frozen client": "No puedes crear una orden para un cliente congelado", - "You can't create an order for a client that has a debt": "No puedes crear una orden para un cliente con deuda", - "is not a valid date": "No es una fecha valida", - "Barcode must be unique": "El código de barras debe ser único", - "The warehouse can't be repeated": "El almacén no puede repetirse", - "The tag or priority can't be repeated for an item": "El tag o prioridad no puede repetirse para un item", - "The observation type can't be repeated": "El tipo de observación no puede repetirse", - "A claim with that sale already exists": "Ya existe una reclamación para esta línea", - "You don't have enough privileges to change that field": "No tienes permisos para cambiar ese campo", - "Warehouse cannot be blank": "El almacén no puede quedar en blanco", - "Agency cannot be blank": "La agencia no puede quedar en blanco", - "Not enough privileges to edit a client with verified data": "No tienes permisos para hacer cambios en un cliente con datos comprobados", - "This address doesn't exist": "Este consignatario no existe", - "You must delete the claim id %d first": "Antes debes borrar la reclamación %d", - "You don't have enough privileges": "No tienes suficientes permisos", - "Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF", - "You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos básicos de una orden con artículos", - "INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no está permitido el uso de la letra ñ", - "You can't create a ticket for a frozen client": "No puedes crear un ticket para un cliente congelado", - "You can't create a ticket for an inactive client": "No puedes crear un ticket para un cliente inactivo", - "Tag value cannot be blank": "El valor del tag no puede quedar en blanco", - "ORDER_EMPTY": "Cesta vacía", - "You don't have enough privileges to do that": "No tienes permisos para cambiar esto", - "NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT", - "Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido", - "Street cannot be empty": "Dirección no puede estar en blanco", - "City cannot be empty": "Ciudad no puede estar en blanco", - "Code cannot be blank": "Código no puede estar en blanco", - "You cannot remove this department": "No puedes eliminar este departamento", - "The extension must be unique": "La extensión debe ser unica", - "The secret can't be blank": "La contraseña no puede estar en blanco", - "We weren't able to send this SMS": "No hemos podido enviar el SMS", - "This client can't be invoiced": "Este cliente no puede ser facturado", - "You must provide the correction information to generate a corrective invoice": "Debes informar la información de corrección para generar una factura rectificativa", - "This ticket can't be invoiced": "Este ticket no puede ser facturado", - "You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado", - "This ticket can not be modified": "Este ticket no puede ser modificado", - "The introduced hour already exists": "Esta hora ya ha sido introducida", - "INFINITE_LOOP": "Existe una dependencia entre dos Jefes", - "The sales of the receiver ticket can't be modified": "Las lineas del ticket al que envias no pueden ser modificadas", - "NO_AGENCY_AVAILABLE": "No hay una zona de reparto disponible con estos parámetros", - "ERROR_PAST_SHIPMENT": "No puedes seleccionar una fecha de envío en pasado", - "The current ticket can't be modified": "El ticket actual no puede ser modificado", - "The current claim can't be modified": "La reclamación actual no puede ser modificada", - "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", - "The sales do not exists": "La(s) línea(s) seleccionada(s) no existe(n)", - "Please select at least one sale": "Por favor selecciona al menos una linea", - "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", - "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", - "This item doesn't exists": "El artículo no existe", - "NOT_ZONE_WITH_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", - "Extension format is invalid": "El formato de la extensión es inválido", - "Invalid parameters to create a new ticket": "Parámetros inválidos para crear un nuevo ticket", - "This item is not available": "Este artículo no está disponible", - "This postcode already exists": "Este código postal ya existe", - "Concept cannot be blank": "El concepto no puede quedar en blanco", - "File doesn't exists": "El archivo no existe", - "You don't have privileges to change the zone": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias", - "This ticket is already on weekly tickets": "Este ticket ya está en tickets programados", - "Ticket id cannot be blank": "El id de ticket no puede quedar en blanco", - "Weekday cannot be blank": "El día de la semana no puede quedar en blanco", - "You can't delete a confirmed order": "No puedes borrar un pedido confirmado", - "The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto", - "Invalid quantity": "Cantidad invalida", - "This postal code is not valid": "Este código postal no es válido", - "is invalid": "es inválido", - "The postcode doesn't exist. Please enter a correct one": "El código postal no existe. Por favor, introduce uno correcto", - "The department name can't be repeated": "El nombre del departamento no puede repetirse", - "This phone already exists": "Este teléfono ya existe", - "You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos", - "You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado", - "You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada", - "You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero", - "You should specify a date": "Debes especificar una fecha", - "You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fin", - "Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fin", - "You should mark at least one week day": "Debes marcar al menos un día de la semana", - "Swift / BIC can't be empty": "Swift / BIC no puede estar vacío", - "Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios", - "Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios", - "Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}", - "Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", - "Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}} {{ticketWeekly}}", - "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}}}) {{ticketWeekly}} ", - "Changed sale quantity": "He cambiado {{changes}} del ticket [{{ticketId}}]({{{ticketUrl}}}) {{ticketWeekly}}", - "Changes in sales": "la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}*", - "State": "Estado", - "regular": "normal", - "reserved": "reservado", - "Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", - "Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})", - "Deny buy request": "Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}", - "MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*", - "Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})", - "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})", - "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", - "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*, con el tipo de recogida *{{claimPickup}}*", - "Claim state has changed to": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *{{newState}}*", - "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", - "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", - "Distance must be lesser than 4000": "La distancia debe ser inferior a 4000", - "This ticket is deleted": "Este ticket está eliminado", - "Unable to clone this travel": "No ha sido posible clonar este travel", - "This thermograph id already exists": "La id del termógrafo ya existe", - "Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante", - "ORDER_ALREADY_CONFIRMED": "ORDEN YA CONFIRMADA", - "Invalid password": "Invalid password", - "Password does not meet requirements": "La contraseña no cumple los requisitos", - "Role already assigned": "Rol ya asignado", - "Invalid role name": "Nombre de rol no válido", - "Role name must be written in camelCase": "El nombre del rol debe escribirse en camelCase", - "Email already exists": "El correo ya existe", - "User already exists": "El/La usuario/a ya existe", - "Absence change notification on the labour calendar": "Notificación de cambio de ausencia en el calendario laboral", - "Record of hours week": "Registro de horas semana {{week}} año {{year}} ", - "Created absence": "El empleado {{author}} ha añadido una ausencia de tipo '{{absenceType}}' a {{employee}} para el día {{dated}}.", - "Deleted absence": "El empleado {{author}} ha eliminado una ausencia de tipo '{{absenceType}}' a {{employee}} del día {{dated}}.", - "I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})", - "I have restored the ticket id": "He restaurado el ticket id [{{id}}]({{{url}}})", - "You can only restore a ticket within the first hour after deletion": "Únicamente puedes restaurar el ticket dentro de la primera hora después de su eliminación", - "Changed this data from the ticket": "He cambiado estos datos del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", - "agencyModeFk": "Agencia", - "clientFk": "Cliente", - "zoneFk": "Zona", - "warehouseFk": "Almacén", - "shipped": "F. envío", - "landed": "F. entrega", - "addressFk": "Consignatario", - "companyFk": "Empresa", - "agency": "Agencia", - "delivery": "Reparto", - "The social name cannot be empty": "La razón social no puede quedar en blanco", - "The nif cannot be empty": "El NIF no puede quedar en blanco", - "You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados", - "ASSIGN_ZONE_FIRST": "Asigna una zona primero", - "Amount cannot be zero": "El importe no puede ser cero", - "Company has to be official": "Empresa inválida", - "You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria", - "Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas", - "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", - "New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*", - "New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*", - "Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío", - "This BIC already exist.": "Este BIC ya existe.", - "That item doesn't exists": "Ese artículo no existe", - "There's a new urgent ticket:": "Hay un nuevo ticket urgente:", - "Invalid account": "Cuenta inválida", - "Compensation account is empty": "La cuenta para compensar está vacia", - "This genus already exist": "Este genus ya existe", - "This specie already exist": "Esta especie ya existe", - "Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})", - "None": "Ninguno", - "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada", - "Cannot add more than one '1/2 day vacation'": "No puedes añadir más de un 'Vacaciones 1/2 dia'", - "This document already exists on this ticket": "Este documento ya existe en el ticket", - "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables", - "You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes", - "nickname": "nickname", - "INACTIVE_PROVIDER": "Proveedor inactivo", - "This client is not invoiceable": "Este cliente no es facturable", - "serial non editable": "Esta serie no permite asignar la referencia", - "Max shipped required": "La fecha límite es requerida", - "Can't invoice to future": "No se puede facturar a futuro", - "Can't invoice to past": "No se puede facturar a pasado", - "This ticket is already invoiced": "Este ticket ya está facturado", - "A ticket with an amount of zero can't be invoiced": "No se puede facturar un ticket con importe cero", - "A ticket with a negative base can't be invoiced": "No se puede facturar un ticket con una base negativa", - "Global invoicing failed": "[Facturación global] No se han podido facturar algunos clientes", - "Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes", - "Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio", - "You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito", - "You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas", - "Amounts do not match": "Las cantidades no coinciden", - "The PDF document does not exist": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", - "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", - "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", - "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", - "The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día", - "You can not modify is pay method checked": "No se puede modificar el campo método de pago validado", - "The account size must be exactly 10 characters": "El tamaño de la cuenta debe ser exactamente de 10 caracteres", - "Can't transfer claimed sales": "No puedes transferir lineas reclamadas", - "You don't have privileges to create refund": "No tienes permisos para crear un abono", - "The item is required": "El artículo es requerido", - "The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo", - "date in the future": "Fecha en el futuro", - "reference duplicated": "Referencia duplicada", - "This ticket is already a refund": "Este ticket ya es un abono", - "isWithoutNegatives": "Sin negativos", - "routeFk": "routeFk", - "Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador", - "No hay un contrato en vigor": "No hay un contrato en vigor", - "No se permite fichar a futuro": "No se permite fichar a futuro", - "No está permitido trabajar": "No está permitido trabajar", - "Fichadas impares": "Fichadas impares", - "Descanso diario 12h.": "Descanso diario 12h.", - "Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.", - "Dirección incorrecta": "Dirección incorrecta", - "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", - "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", - "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", - "This route does not exists": "Esta ruta no existe", - "Claim pickup order sent": "Reclamación Orden de recogida enviada [{{claimId}}]({{{claimUrl}}}) al cliente *{{clientName}}*", - "You don't have grant privilege": "No tienes privilegios para dar privilegios", - "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario", - "Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) fusionado con [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})", - "Already has this status": "Ya tiene este estado", - "There aren't records for this week": "No existen registros para esta semana", - "Empty data source": "Origen de datos vacio", - "App locked": "Aplicación bloqueada por el usuario {{userId}}", - "Email verify": "Correo de verificación", - "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", - "Receipt's bank was not found": "No se encontró el banco del recibo", - "This receipt was not compensated": "Este recibo no ha sido compensado", - "Client's email was not found": "No se encontró el email del cliente", - "Negative basis": "Base negativa", - "This worker code already exists": "Este codigo de trabajador ya existe", - "This personal mail already exists": "Este correo personal ya existe", - "This worker already exists": "Este trabajador ya existe", - "App name does not exist": "El nombre de aplicación no es válido", - "Try again": "Vuelve a intentarlo", - "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9", - "Failed to upload delivery note": "Error al subir albarán {{id}}", - "The DOCUWARE PDF document does not exists": "El documento PDF Docuware no existe", - "It is not possible to modify tracked sales": "No es posible modificar líneas de pedido que se hayan empezado a preparar", - "It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo", - "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", - "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", - "There is no assigned email for this client": "No hay correo asignado para este cliente", - "Exists an invoice with a future date": "Existe una factura con fecha posterior", - "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite", - "Warehouse inventory not set": "El almacén inventario no está establecido", - "This locker has already been assigned": "Esta taquilla ya ha sido asignada", - "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº %s", - "Not exist this branch": "La rama no existe", - "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", - "Collection does not exist": "La colección no existe", - "Cannot obtain exclusive lock": "No se puede obtener un bloqueo exclusivo", - "Insert a date range": "Inserte un rango de fechas", - "Added observation": "{{user}} añadió esta observacion: {{text}} {{defaulterId}} ({{{defaulterUrl}}})", - "Comment added to client": "Observación añadida al cliente {{clientFk}}", - "Invalid auth code": "Código de verificación incorrecto", - "Invalid or expired verification code": "Código de verificación incorrecto o expirado", - "Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen", - "company": "Compañía", - "country": "País", - "clientId": "Id cliente", - "clientSocialName": "Cliente", - "amount": "Importe", - "taxableBase": "Base", - "ticketFk": "Id ticket", - "isActive": "Activo", - "hasToInvoice": "Facturar", - "isTaxDataChecked": "Datos comprobados", - "comercialId": "Id comercial", - "comercialName": "Comercial", - "Pass expired": "La contraseña ha caducado, cambiela desde Salix", - "Invalid NIF for VIES": "Invalid NIF for VIES", - "Ticket does not exist": "Este ticket no existe", - "Ticket is already signed": "Este ticket ya ha sido firmado", - "Authentication failed": "Autenticación fallida", - "You can't use the same password": "No puedes usar la misma contraseña", - "You can only add negative amounts in refund tickets": "Solo se puede añadir cantidades negativas en tickets abono", - "Fecha fuera de rango": "Fecha fuera de rango", - "Error while generating PDF": "Error al generar PDF", - "Error when sending mail to client": "Error al enviar el correo al cliente", - "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", - "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", - "Valid priorities": "Prioridades válidas: %d", - "hasAnyNegativeBase": "Base negativa para los tickets: {{ticketsIds}}", - "hasAnyPositiveBase": "Base positivas para los tickets: {{ticketsIds}}", - "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", - "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", - "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", - "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", - "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", - "You don't have enough privileges.": "No tienes suficientes permisos.", - "This ticket is locked": "Este ticket está bloqueado.", - "This ticket is not editable.": "Este ticket no es editable.", - "The ticket doesn't exist.": "No existe el ticket.", - "Social name should be uppercase": "La razón social debe ir en mayúscula", - "Street should be uppercase": "La dirección fiscal debe ir en mayúscula", - "Ticket without Route": "Ticket sin ruta", - "Select a different client": "Seleccione un cliente distinto", - "Fill all the fields": "Rellene todos los campos", - "The response is not a PDF": "La respuesta no es un PDF", - "Booking completed": "Reserva completada", - "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación", - "The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada", - "User disabled": "Usuario desactivado", - "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima", - "quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima", - "Cannot past travels with entries": "No se pueden pasar envíos con entradas", - "It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}", - "This claim has been updated": "La reclamación con Id: {{claimId}}, ha sido actualizada", - "This user does not have an assigned tablet": "Este usuario no tiene tablet asignada", - "Field are invalid": "El campo '{{tag}}' no es válido", - "Incorrect pin": "Pin incorrecto.", - "You already have the mailAlias": "Ya tienes este alias de correo", - "The alias cant be modified": "Este alias de correo no puede ser modificado", - "No tickets to invoice": "No hay tickets para facturar que cumplan los requisitos de facturación", - "this warehouse has not dms": "El Almacén no acepta documentos", - "This ticket already has a cmr saved": "Este ticket ya tiene un cmr guardado", - "Name should be uppercase": "El nombre debe ir en mayúscula", - "Bank entity must be specified": "La entidad bancaria es obligatoria", - "An email is necessary": "Es necesario un email", - "You cannot update these fields": "No puedes actualizar estos campos", - "CountryFK cannot be empty": "El país no puede estar vacío", - "Cmr file does not exist": "El archivo del cmr no existe", - "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", - "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas", - "No invoice series found for these parameters": "No se encontró una serie para estos parámetros", - "The line could not be marked": "La linea no puede ser marcada", - "Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado", - "They're not your subordinate": "No es tu subordinado/a.", - "No results found": "No se han encontrado resultados", - "InvoiceIn is already booked": "La factura recibida está contabilizada", - "This workCenter is already assigned to this agency": "Este centro de trabajo ya está asignado a esta agencia", - "Select ticket or client": "Elija un ticket o un client", - "It was not able to create the invoice": "No se pudo crear la factura", - "Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE", - "You can not use the same password": "No puedes usar la misma contraseña", - "This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario", - "You can only have one PDA": "Solo puedes tener un PDA", - "The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF", - "It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono", - "Payment method is required": "El método de pago es obligatorio", - "Cannot send mail": "Não é possível enviar o email", - "CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos", - "The sale not exists in the item shelving": "La venta no existe en la estantería del artículo", - "The entry not have stickers": "La entrada no tiene etiquetas", - "Too many records": "Demasiados registros", - "Original invoice not found": "Factura original no encontrada", - "The entry has no lines or does not exist": "La entrada no tiene lineas o no existe", - "Weight already set": "El peso ya está establecido", - "This ticket is not allocated to your department": "Este ticket no está asignado a tu departamento", - "There is already a tray with the same height": "Ya existe una bandeja con la misma altura", - "The height must be greater than 50cm": "La altura debe ser superior a 50cm", - "The maximum height of the wagon is 200cm": "La altura máxima es 200cm", - "The entry does not have stickers": "La entrada no tiene etiquetas", - "This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha", - "No valid travel thermograph found": "No se encontró un termógrafo válido", - "The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea", - "type cannot be blank": "Se debe rellenar el tipo", - "There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero", - "There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén", - "You do not have permission to modify the booked field": "No tienes permisos para modificar el campo contabilizada", - "ticketLostExpedition": "El ticket [{{ticketId}}]({{{ticketUrl}}}) tiene la siguiente expedición perdida:{{ expeditionId }}", - "The web user's email already exists": "El correo del usuario web ya existe", - "Sales already moved": "Ya han sido transferidas", - "The raid information is not correct": "La información de la redada no es correcta", - "An item type with the same code already exists": "Un tipo con el mismo código ya existe", - "Holidays to past days not available": "Las vacaciones a días pasados no están disponibles", - "All tickets have a route order": "Todos los tickets tienen orden de ruta", - "There are tickets to be invoiced": "La zona tiene tickets por facturar", - "Incorrect delivery order alert on route": "Alerta de orden de entrega incorrecta en ruta: {{ route }} zona: {{ zone }}", - "Ticket has been delivered out of order": "El ticket {{ticket}} {{{fullUrl}}} no ha sido entregado en su orden.", - "Price cannot be blank": "El precio no puede estar en blanco", - "clonedFromTicketWeekly": ", que es una linea clonada del ticket {{ticketWeekly}}", - "negativeReplaced": "Sustituido el articulo [#{{oldItemId}}]({{{oldItemUrl}}}) {{oldItem}} por [#{{newItemId}}]({{{newItemUrl}}}) {{newItem}} del ticket [{{ticketId}}]({{{ticketUrl}}})", - "duplicateWarehouse": "El almacén seleccionado ya existe en la zona" -} + "The grade must be similar to the last one": "El grade debe ser similar al último", + "Only manager can change the credit": "Solo el gerente puede cambiar el credito de este cliente", + "Name cannot be blank": "El nombre no puede estar en blanco", + "Phone cannot be blank": "El teléfono no puede estar en blanco", + "Period cannot be blank": "El periodo no puede estar en blanco", + "Choose a company": "Selecciona una empresa", + "Se debe rellenar el campo de texto": "Se debe rellenar el campo de texto", + "Description should have maximum of 45 characters": "La descripción debe tener maximo 45 caracteres", + "Cannot be blank": "El campo no puede estar en blanco", + "The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero", + "Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco", + "Description cannot be blank": "Se debe rellenar el campo de texto", + "The price of the item changed": "El precio del artículo cambió", + "The value should not be greater than 100%": "El valor no debe de ser mayor de 100%", + "The value should be a number": "El valor debe ser un numero", + "This order is not editable": "Esta orden no se puede modificar", + "You can't create an order for a frozen client": "No puedes crear una orden para un cliente congelado", + "You can't create an order for a client that has a debt": "No puedes crear una orden para un cliente con deuda", + "is not a valid date": "No es una fecha valida", + "Barcode must be unique": "El código de barras debe ser único", + "The warehouse can't be repeated": "El almacén no puede repetirse", + "The tag or priority can't be repeated for an item": "El tag o prioridad no puede repetirse para un item", + "The observation type can't be repeated": "El tipo de observación no puede repetirse", + "A claim with that sale already exists": "Ya existe una reclamación para esta línea", + "You don't have enough privileges to change that field": "No tienes permisos para cambiar ese campo", + "Warehouse cannot be blank": "El almacén no puede quedar en blanco", + "Agency cannot be blank": "La agencia no puede quedar en blanco", + "Not enough privileges to edit a client with verified data": "No tienes permisos para hacer cambios en un cliente con datos comprobados", + "This address doesn't exist": "Este consignatario no existe", + "You must delete the claim id %d first": "Antes debes borrar la reclamación %d", + "You don't have enough privileges": "No tienes suficientes permisos", + "Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF", + "You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos básicos de una orden con artículos", + "INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no está permitido el uso de la letra ñ", + "You can't create a ticket for a frozen client": "No puedes crear un ticket para un cliente congelado", + "You can't create a ticket for an inactive client": "No puedes crear un ticket para un cliente inactivo", + "Tag value cannot be blank": "El valor del tag no puede quedar en blanco", + "ORDER_EMPTY": "Cesta vacía", + "You don't have enough privileges to do that": "No tienes permisos para cambiar esto", + "NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT", + "Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido", + "Street cannot be empty": "Dirección no puede estar en blanco", + "City cannot be empty": "Ciudad no puede estar en blanco", + "Code cannot be blank": "Código no puede estar en blanco", + "You cannot remove this department": "No puedes eliminar este departamento", + "The extension must be unique": "La extensión debe ser unica", + "The secret can't be blank": "La contraseña no puede estar en blanco", + "We weren't able to send this SMS": "No hemos podido enviar el SMS", + "This client can't be invoiced": "Este cliente no puede ser facturado", + "You must provide the correction information to generate a corrective invoice": "Debes informar la información de corrección para generar una factura rectificativa", + "This ticket can't be invoiced": "Este ticket no puede ser facturado", + "You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado", + "This ticket can not be modified": "Este ticket no puede ser modificado", + "The introduced hour already exists": "Esta hora ya ha sido introducida", + "INFINITE_LOOP": "Existe una dependencia entre dos Jefes", + "The sales of the receiver ticket can't be modified": "Las lineas del ticket al que envias no pueden ser modificadas", + "NO_AGENCY_AVAILABLE": "No hay una zona de reparto disponible con estos parámetros", + "ERROR_PAST_SHIPMENT": "No puedes seleccionar una fecha de envío en pasado", + "The current ticket can't be modified": "El ticket actual no puede ser modificado", + "The current claim can't be modified": "La reclamación actual no puede ser modificada", + "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", + "The sales do not exists": "La(s) línea(s) seleccionada(s) no existe(n)", + "Please select at least one sale": "Por favor selecciona al menos una linea", + "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", + "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", + "This item doesn't exists": "El artículo no existe", + "NOT_ZONE_WITH_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", + "Extension format is invalid": "El formato de la extensión es inválido", + "Invalid parameters to create a new ticket": "Parámetros inválidos para crear un nuevo ticket", + "This item is not available": "Este artículo no está disponible", + "This postcode already exists": "Este código postal ya existe", + "Concept cannot be blank": "El concepto no puede quedar en blanco", + "File doesn't exists": "El archivo no existe", + "You don't have privileges to change the zone": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias", + "This ticket is already on weekly tickets": "Este ticket ya está en tickets programados", + "Ticket id cannot be blank": "El id de ticket no puede quedar en blanco", + "Weekday cannot be blank": "El día de la semana no puede quedar en blanco", + "You can't delete a confirmed order": "No puedes borrar un pedido confirmado", + "The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto", + "Invalid quantity": "Cantidad invalida", + "This postal code is not valid": "Este código postal no es válido", + "is invalid": "es inválido", + "The postcode doesn't exist. Please enter a correct one": "El código postal no existe. Por favor, introduce uno correcto", + "The department name can't be repeated": "El nombre del departamento no puede repetirse", + "This phone already exists": "Este teléfono ya existe", + "You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos", + "You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado", + "You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada", + "You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero", + "You should specify a date": "Debes especificar una fecha", + "You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fin", + "Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fin", + "You should mark at least one week day": "Debes marcar al menos un día de la semana", + "Swift / BIC can't be empty": "Swift / BIC no puede estar vacío", + "Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios", + "Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios", + "Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}", + "Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}", + "Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}} {{ticketWeekly}}", + "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}}}) {{ticketWeekly}} ", + "Changed sale quantity": "He cambiado {{changes}} del ticket [{{ticketId}}]({{{ticketUrl}}}) {{ticketWeekly}}", + "Changes in sales": "la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}*", + "State": "Estado", + "regular": "normal", + "reserved": "reservado", + "Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})", + "Deny buy request": "Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}", + "MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*", + "Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})", + "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})", + "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", + "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*, con el tipo de recogida *{{claimPickup}}*", + "Claim state has changed to": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *{{newState}}*", + "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", + "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", + "Distance must be lesser than 4000": "La distancia debe ser inferior a 4000", + "This ticket is deleted": "Este ticket está eliminado", + "Unable to clone this travel": "No ha sido posible clonar este travel", + "This thermograph id already exists": "La id del termógrafo ya existe", + "Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante", + "ORDER_ALREADY_CONFIRMED": "ORDEN YA CONFIRMADA", + "Invalid password": "Invalid password", + "Password does not meet requirements": "La contraseña no cumple los requisitos", + "Role already assigned": "Rol ya asignado", + "Invalid role name": "Nombre de rol no válido", + "Role name must be written in camelCase": "El nombre del rol debe escribirse en camelCase", + "Email already exists": "El correo ya existe", + "User already exists": "El/La usuario/a ya existe", + "Absence change notification on the labour calendar": "Notificación de cambio de ausencia en el calendario laboral", + "Record of hours week": "Registro de horas semana {{week}} año {{year}} ", + "Created absence": "El empleado {{author}} ha añadido una ausencia de tipo '{{absenceType}}' a {{employee}} para el día {{dated}}.", + "Deleted absence": "El empleado {{author}} ha eliminado una ausencia de tipo '{{absenceType}}' a {{employee}} del día {{dated}}.", + "I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})", + "I have restored the ticket id": "He restaurado el ticket id [{{id}}]({{{url}}})", + "You can only restore a ticket within the first hour after deletion": "Únicamente puedes restaurar el ticket dentro de la primera hora después de su eliminación", + "Changed this data from the ticket": "He cambiado estos datos del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}", + "agencyModeFk": "Agencia", + "clientFk": "Cliente", + "zoneFk": "Zona", + "warehouseFk": "Almacén", + "shipped": "F. envío", + "landed": "F. entrega", + "addressFk": "Consignatario", + "companyFk": "Empresa", + "agency": "Agencia", + "delivery": "Reparto", + "The social name cannot be empty": "La razón social no puede quedar en blanco", + "The nif cannot be empty": "El NIF no puede quedar en blanco", + "You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados", + "ASSIGN_ZONE_FIRST": "Asigna una zona primero", + "Amount cannot be zero": "El importe no puede ser cero", + "Company has to be official": "Empresa inválida", + "You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria", + "Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas", + "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", + "New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*", + "New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*", + "Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío", + "This BIC already exist.": "Este BIC ya existe.", + "That item doesn't exists": "Ese artículo no existe", + "There's a new urgent ticket:": "Hay un nuevo ticket urgente:", + "Invalid account": "Cuenta inválida", + "Compensation account is empty": "La cuenta para compensar está vacia", + "This genus already exist": "Este genus ya existe", + "This specie already exist": "Esta especie ya existe", + "Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})", + "None": "Ninguno", + "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada", + "Cannot add more than one '1/2 day vacation'": "No puedes añadir más de un 'Vacaciones 1/2 dia'", + "This document already exists on this ticket": "Este documento ya existe en el ticket", + "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables", + "You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes", + "nickname": "nickname", + "INACTIVE_PROVIDER": "Proveedor inactivo", + "This client is not invoiceable": "Este cliente no es facturable", + "serial non editable": "Esta serie no permite asignar la referencia", + "Max shipped required": "La fecha límite es requerida", + "Can't invoice to future": "No se puede facturar a futuro", + "Can't invoice to past": "No se puede facturar a pasado", + "This ticket is already invoiced": "Este ticket ya está facturado", + "A ticket with an amount of zero can't be invoiced": "No se puede facturar un ticket con importe cero", + "A ticket with a negative base can't be invoiced": "No se puede facturar un ticket con una base negativa", + "Global invoicing failed": "[Facturación global] No se han podido facturar algunos clientes", + "Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes", + "Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio", + "You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito", + "You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas", + "Amounts do not match": "Las cantidades no coinciden", + "The PDF document does not exist": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", + "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", + "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", + "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", + "The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día", + "You can not modify is pay method checked": "No se puede modificar el campo método de pago validado", + "The account size must be exactly 10 characters": "El tamaño de la cuenta debe ser exactamente de 10 caracteres", + "Can't transfer claimed sales": "No puedes transferir lineas reclamadas", + "You don't have privileges to create refund": "No tienes permisos para crear un abono", + "The item is required": "El artículo es requerido", + "The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo", + "date in the future": "Fecha en el futuro", + "reference duplicated": "Referencia duplicada", + "This ticket is already a refund": "Este ticket ya es un abono", + "isWithoutNegatives": "Sin negativos", + "routeFk": "routeFk", + "Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador", + "No hay un contrato en vigor": "No hay un contrato en vigor", + "No se permite fichar a futuro": "No se permite fichar a futuro", + "No está permitido trabajar": "No está permitido trabajar", + "Fichadas impares": "Fichadas impares", + "Descanso diario 12h.": "Descanso diario 12h.", + "Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.", + "Dirección incorrecta": "Dirección incorrecta", + "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", + "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", + "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", + "This route does not exists": "Esta ruta no existe", + "Claim pickup order sent": "Reclamación Orden de recogida enviada [{{claimId}}]({{{claimUrl}}}) al cliente *{{clientName}}*", + "You don't have grant privilege": "No tienes privilegios para dar privilegios", + "You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario", + "Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) fusionado con [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})", + "Already has this status": "Ya tiene este estado", + "There aren't records for this week": "No existen registros para esta semana", + "Empty data source": "Origen de datos vacio", + "App locked": "Aplicación bloqueada por el usuario {{userId}}", + "Email verify": "Correo de verificación", + "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", + "Receipt's bank was not found": "No se encontró el banco del recibo", + "This receipt was not compensated": "Este recibo no ha sido compensado", + "Client's email was not found": "No se encontró el email del cliente", + "Negative basis": "Base negativa", + "This worker code already exists": "Este codigo de trabajador ya existe", + "This personal mail already exists": "Este correo personal ya existe", + "This worker already exists": "Este trabajador ya existe", + "App name does not exist": "El nombre de aplicación no es válido", + "Try again": "Vuelve a intentarlo", + "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9", + "Failed to upload delivery note": "Error al subir albarán {{id}}", + "The DOCUWARE PDF document does not exists": "El documento PDF Docuware no existe", + "It is not possible to modify tracked sales": "No es posible modificar líneas de pedido que se hayan empezado a preparar", + "It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo", + "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", + "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", + "There is no assigned email for this client": "No hay correo asignado para este cliente", + "Exists an invoice with a future date": "Existe una factura con fecha posterior", + "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite", + "Warehouse inventory not set": "El almacén inventario no está establecido", + "This locker has already been assigned": "Esta taquilla ya ha sido asignada", + "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº %s", + "Not exist this branch": "La rama no existe", + "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", + "Collection does not exist": "La colección no existe", + "Cannot obtain exclusive lock": "No se puede obtener un bloqueo exclusivo", + "Insert a date range": "Inserte un rango de fechas", + "Added observation": "{{user}} añadió esta observacion: {{text}} {{defaulterId}} ({{{defaulterUrl}}})", + "Comment added to client": "Observación añadida al cliente {{clientFk}}", + "Invalid auth code": "Código de verificación incorrecto", + "Invalid or expired verification code": "Código de verificación incorrecto o expirado", + "Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen", + "company": "Compañía", + "country": "País", + "clientId": "Id cliente", + "clientSocialName": "Cliente", + "amount": "Importe", + "taxableBase": "Base", + "ticketFk": "Id ticket", + "isActive": "Activo", + "hasToInvoice": "Facturar", + "isTaxDataChecked": "Datos comprobados", + "comercialId": "Id comercial", + "comercialName": "Comercial", + "Pass expired": "La contraseña ha caducado, cambiela desde Salix", + "Invalid NIF for VIES": "Invalid NIF for VIES", + "Ticket does not exist": "Este ticket no existe", + "Ticket is already signed": "Este ticket ya ha sido firmado", + "Authentication failed": "Autenticación fallida", + "You can't use the same password": "No puedes usar la misma contraseña", + "You can only add negative amounts in refund tickets": "Solo se puede añadir cantidades negativas en tickets abono", + "Fecha fuera de rango": "Fecha fuera de rango", + "Error while generating PDF": "Error al generar PDF", + "Error when sending mail to client": "Error al enviar el correo al cliente", + "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", + "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", + "Valid priorities": "Prioridades válidas: %d", + "hasAnyNegativeBase": "Base negativa para los tickets: {{ticketsIds}}", + "hasAnyPositiveBase": "Base positivas para los tickets: {{ticketsIds}}", + "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", + "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", + "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", + "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", + "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", + "You don't have enough privileges.": "No tienes suficientes permisos.", + "This ticket is locked": "Este ticket está bloqueado.", + "This ticket is not editable.": "Este ticket no es editable.", + "The ticket doesn't exist.": "No existe el ticket.", + "Social name should be uppercase": "La razón social debe ir en mayúscula", + "Street should be uppercase": "La dirección fiscal debe ir en mayúscula", + "Ticket without Route": "Ticket sin ruta", + "Select a different client": "Seleccione un cliente distinto", + "Fill all the fields": "Rellene todos los campos", + "The response is not a PDF": "La respuesta no es un PDF", + "Booking completed": "Reserva completada", + "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación", + "The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada", + "User disabled": "Usuario desactivado", + "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima", + "quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima", + "Cannot past travels with entries": "No se pueden pasar envíos con entradas", + "It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}", + "This claim has been updated": "La reclamación con Id: {{claimId}}, ha sido actualizada", + "This user does not have an assigned tablet": "Este usuario no tiene tablet asignada", + "Field are invalid": "El campo '{{tag}}' no es válido", + "Incorrect pin": "Pin incorrecto.", + "You already have the mailAlias": "Ya tienes este alias de correo", + "The alias cant be modified": "Este alias de correo no puede ser modificado", + "No tickets to invoice": "No hay tickets para facturar que cumplan los requisitos de facturación", + "this warehouse has not dms": "El Almacén no acepta documentos", + "This ticket already has a cmr saved": "Este ticket ya tiene un cmr guardado", + "Name should be uppercase": "El nombre debe ir en mayúscula", + "Bank entity must be specified": "La entidad bancaria es obligatoria", + "An email is necessary": "Es necesario un email", + "You cannot update these fields": "No puedes actualizar estos campos", + "CountryFK cannot be empty": "El país no puede estar vacío", + "Cmr file does not exist": "El archivo del cmr no existe", + "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", + "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas", + "No invoice series found for these parameters": "No se encontró una serie para estos parámetros", + "The line could not be marked": "La linea no puede ser marcada", + "Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado", + "They're not your subordinate": "No es tu subordinado/a.", + "No results found": "No se han encontrado resultados", + "InvoiceIn is already booked": "La factura recibida está contabilizada", + "This workCenter is already assigned to this agency": "Este centro de trabajo ya está asignado a esta agencia", + "Select ticket or client": "Elija un ticket o un client", + "It was not able to create the invoice": "No se pudo crear la factura", + "Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE", + "You can not use the same password": "No puedes usar la misma contraseña", + "This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario", + "You can only have one PDA": "Solo puedes tener un PDA", + "The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF", + "It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono", + "Payment method is required": "El método de pago es obligatorio", + "Cannot send mail": "Não é possível enviar o email", + "CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos", + "The sale not exists in the item shelving": "La venta no existe en la estantería del artículo", + "The entry not have stickers": "La entrada no tiene etiquetas", + "Too many records": "Demasiados registros", + "Original invoice not found": "Factura original no encontrada", + "The entry has no lines or does not exist": "La entrada no tiene lineas o no existe", + "Weight already set": "El peso ya está establecido", + "This ticket is not allocated to your department": "Este ticket no está asignado a tu departamento", + "There is already a tray with the same height": "Ya existe una bandeja con la misma altura", + "The height must be greater than 50cm": "La altura debe ser superior a 50cm", + "The maximum height of the wagon is 200cm": "La altura máxima es 200cm", + "The entry does not have stickers": "La entrada no tiene etiquetas", + "This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha", + "No valid travel thermograph found": "No se encontró un termógrafo válido", + "The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea", + "type cannot be blank": "Se debe rellenar el tipo", + "There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero", + "There is no company associated with that warehouse": "No hay ninguna empresa asociada a ese almacén", + "You do not have permission to modify the booked field": "No tienes permisos para modificar el campo contabilizada", + "ticketLostExpedition": "El ticket [{{ticketId}}]({{{ticketUrl}}}) tiene la siguiente expedición perdida:{{ expeditionId }}", + "The web user's email already exists": "El correo del usuario web ya existe", + "Sales already moved": "Ya han sido transferidas", + "The raid information is not correct": "La información de la redada no es correcta", + "An item type with the same code already exists": "Un tipo con el mismo código ya existe", + "Holidays to past days not available": "Las vacaciones a días pasados no están disponibles", + "All tickets have a route order": "Todos los tickets tienen orden de ruta", + "There are tickets to be invoiced": "La zona tiene tickets por facturar", + "Incorrect delivery order alert on route": "Alerta de orden de entrega incorrecta en ruta: {{ route }} zona: {{ zone }}", + "Ticket has been delivered out of order": "El ticket {{ticket}} {{{fullUrl}}} no ha sido entregado en su orden.", + "Price cannot be blank": "El precio no puede estar en blanco", + "clonedFromTicketWeekly": ", que es una linea clonada del ticket {{ticketWeekly}}", + "negativeReplaced": "Sustituido el articulo [#{{oldItemId}}]({{{oldItemUrl}}}) {{oldItem}} por [#{{newItemId}}]({{{newItemUrl}}}) {{newItem}} del ticket [{{ticketId}}]({{{ticketUrl}}})", + "The introduced warehouse already exists": "El almacén seleccionado ya existe en la zona", + "The code already exists": "El código ya existe" +} \ No newline at end of file diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index a0dc2248c4..74ae102d0c 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -74,66 +74,77 @@ module.exports = Self => { } try { - const worker = await models.Worker.findOne({ - where: {id: userId} - }, myOptions); - - const obsevationType = await models.ObservationType.findOne({ - where: {code: 'salesPerson'} - }, myOptions); - - const agencyMode = await models.AgencyMode.findOne({ - where: {code: 'refund'} - }, myOptions); - - const state = await models.State.findOne({ - where: {code: 'DELIVERED'} - }, myOptions); - - const zone = await models.Zone.findOne({ - where: {agencyModeFk: agencyMode.id} - }, myOptions); - const claim = await models.Claim.findOne(filter, myOptions); const today = Date.vnNew(); + let agencyModeFk; + let nickname; + let state; + let discountValue = null; + let packages = 0; + const claimConfig = await models.ClaimConfig.findOne(); + const warehouseFk = claimConfig.warehouseFk; + if (claim.pickup === null || claim.pickup == 'agency') { + state = await models.State.findOne({ + where: {code: 'DELIVERED'} + }, myOptions); + const agencyMode = await models.AgencyMode.findOne({ + where: {code: 'refund'} + }, myOptions); + agencyModeFk = agencyMode.id; + nickname = `Abono del: ${claim.ticketFk}`; + } else { + discountValue = 100; + packages = 1; + state = await models.State.findOne({ + where: {code: 'WAITING_FOR_PICKUP'} + }, myOptions); + + nickname = `Recogida pendiente del: ${claim.ticketFk}`; + + agencyModeFk = claimConfig.pickupDeliveryFk; + } + const nextShipped = await models.Agency.getShipped( + ctx, today, claim.ticket().addressFk, agencyModeFk, warehouseFk, myOptions + ); const newRefundTicket = await models.Ticket.create({ clientFk: claim.ticket().clientFk, - shipped: today, - landed: today, - nickname: `Abono del: ${claim.ticketFk}`, - warehouseFk: claim.ticket().warehouseFk, + shipped: nextShipped.shipped, + landed: null, + nickname, + warehouseFk, companyFk: claim.ticket().companyFk, addressFk: claim.ticket().addressFk, - agencyModeFk: agencyMode.id, - zoneFk: zone.id + agencyModeFk, + zoneFk: claim.ticket().zoneFk, + packages }, myOptions); + if (claim.pickup == 'pickup') { + const observationDelivery = + await models.ObservationType.findOne({where: {code: 'delivery'}}, myOptions); + + await saveObservation({ + description: `recoger reclamación: ${claim.id}`, + ticketFk: newRefundTicket.id, + observationTypeFk: observationDelivery.id + }, myOptions); + } await models.TicketRefund.create({ refundTicketFk: newRefundTicket.id, originalTicketFk: claim.ticket().id }, myOptions); - await saveObservation({ - description: `Reclama ticket: ${claim.ticketFk}`, - ticketFk: newRefundTicket.id, - observationTypeFk: obsevationType.id - }, myOptions); + const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions); + const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, discountValue, myOptions); + await insertIntoClaimEnd(createdSales, id, userId, myOptions); await models.Ticket.state(ctx, { ticketFk: newRefundTicket.id, stateFk: state.id, - userFk: worker.id + userFk: userId }, myOptions); - const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions); - const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, myOptions); - await insertIntoClaimEnd(createdSales, id, worker.id, myOptions); - - await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [ - newRefundTicket.id, claim.ticketFk - ], myOptions); - if (tx) await tx.commit(); return newRefundTicket; @@ -143,23 +154,36 @@ module.exports = Self => { } }; - async function addSalesToTicket(salesToRefund, ticketId, options) { - let formatedSales = []; - salesToRefund.forEach(sale => { - let formatedSale = { - itemFk: sale.sale().itemFk, - ticketFk: ticketId, - concept: sale.sale().concept, - quantity: -Math.abs(sale.quantity), - price: sale.sale().price, - discount: sale.sale().discount, - reserved: sale.sale().reserved, - isPicked: sale.sale().isPicked, - created: sale.sale().created + async function addSalesToTicket(salesToRefund, newTicketId, discountValue, options) { + const createdSales = []; + const models = Self.app.models; + for (const saleToRefund of salesToRefund) { + const oldSale = saleToRefund.sale(); + const newSaleData = { + itemFk: oldSale.itemFk, + ticketFk: newTicketId, + concept: oldSale.concept, + quantity: -Math.abs(saleToRefund.quantity), + price: oldSale.price, + discount: discountValue ?? oldSale.discount, + reserved: oldSale.reserved, + isPicked: oldSale.isPicked, + created: oldSale.created }; - formatedSales.push(formatedSale); - }); - return await Self.app.models.Sale.create(formatedSales, options); + const newSale = await models.Sale.create(newSaleData, options); + const oldSaleComponents = await models.SaleComponent.find({ + where: {saleFk: oldSale.id} + }, options); + const newComponents = oldSaleComponents.map(component => { + const data = component.toJSON ? component.toJSON() : {...component}; + delete data.id; + data.saleFk = newSale.id; + return data; + }); + await models.SaleComponent.create(newComponents, options); + createdSales.push(newSale); + } + return createdSales; } async function insertIntoClaimEnd(createdSales, claimId, workerId, options) { diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.spec.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.spec.js deleted file mode 100644 index 156caaeec6..0000000000 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -const app = require('vn-loopback/server/server'); -const LoopBackContext = require('loopback-context'); -const models = app.models; - -describe('claimBeginning', () => { - const claimManagerId = 72; - const activeCtx = { - accessToken: {userId: claimManagerId}, - __: value => value - }; - const ctx = {req: activeCtx}; - - describe('importToNewRefundTicket()', () => { - it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - let claimId = 1; - - const tx = await models.Entry.beginTransaction({}); - try { - const options = {transaction: tx}; - - const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options); - - const refundTicketSales = await models.Sale.find({ - where: {ticketFk: ticket.id} - }, options); - const salesInsertedInClaimEnd = await models.ClaimEnd.find({ - where: {claimFk: claimId} - }, options); - - expect(refundTicketSales.length).toEqual(1); - expect(refundTicketSales[0].quantity).toEqual(-5); - expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - }); -}); diff --git a/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js new file mode 100644 index 0000000000..d305ca1f56 --- /dev/null +++ b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js @@ -0,0 +1,99 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); +const models = app.models; + +describe('importToNewRefundTicket()', () => { + let tx; + const claimManagerId = 72; + const activeCtx = { + accessToken: {userId: claimManagerId}, + }; + let ctx = {req: activeCtx}; + let options; + const claimId = 1; + const expectedDate = Date.vnNew(); + + beforeEach(async() => { + LoopBackContext.getCurrentContext = () => ({ + active: activeCtx, + }); + tx = await models.Entry.beginTransaction({}); + options = {transaction: tx}; + spyOn(models.Agency, 'getShipped').and.returnValue(Promise.resolve({shipped: expectedDate})); + }); + + afterEach(async() => { + await tx.rollback(); + }); + + it('should create a new ticket with negative sales and insert the negative sales into claimEnd', async() => { + const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options); + + const refundTicketSales = await models.Sale.find({ + where: {ticketFk: ticket.id} + }, options); + const salesInsertedInClaimEnd = await models.ClaimEnd.find({ + where: {claimFk: claimId} + }, options); + + expect(refundTicketSales.length).toEqual(1); + expect(refundTicketSales[0].quantity).toEqual(-5); + expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id); + }); + + it('should set DELIVERED state and refund agency mode', async() => { + const state = await models.State.findOne({ + where: {code: 'DELIVERED'} + }, options); + const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options); + const ticketTracking = await models.TicketTracking.findOne({ + where: {ticketFk: ticket.id}, + order: 'id DESC' + }, options); + + const newSales = await models.Sale.find({ + where: {ticketFk: ticket.id} + }, options); + + newSales.forEach(sale => { + expect(sale.discount).toEqual(0); + }); + + expect(ticketTracking.stateFk).toEqual(state.id); + }); + + it('should set WAITING_FOR_PICKUP state for delivery pickups', async() => { + const state = await models.State.findOne({ + where: {code: 'WAITING_FOR_PICKUP'} + }, options); + await models.Claim.updateAll({id: claimId}, {pickup: 'delivery'}, options); + const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options); + const ticketTracking = await models.TicketTracking.findOne({ + where: {ticketFk: ticket.id}, + order: 'id DESC' + }, options); + const newSales = await models.Sale.find({ + where: {ticketFk: ticket.id} + }, options); + + newSales.forEach(sale => { + expect(sale.discount).toEqual(100); + }); + + expect(ticketTracking.stateFk).toEqual(state.id); + }); + + it('should set DELIVERED state for agency pickups', async() => { + const state = await models.State.findOne({ + where: {code: 'DELIVERED'} + }, options); + await models.Claim.updateAll({id: claimId}, {pickup: 'agency'}, options); + const ticket = await models.ClaimBeginning.importToNewRefundTicket(ctx, claimId, options); + const ticketTracking = await models.TicketTracking.findOne({ + where: {ticketFk: ticket.id}, + order: 'id DESC' + }, options); + + expect(ticketTracking.stateFk).toEqual(state.id); + }); +}); diff --git a/modules/claim/back/methods/claim-end/filter.js b/modules/claim/back/methods/claim-end/filter.js index 0dda16a3a7..c4f4879969 100644 --- a/modules/claim/back/methods/claim-end/filter.js +++ b/modules/claim/back/methods/claim-end/filter.js @@ -40,21 +40,24 @@ module.exports = Self => { const stmt = new ParameterizedSQL( `SELECT * FROM ( - SELECT + SELECT ce.id, ce.claimFk, - s.itemFk, - s.ticketFk, - ce.claimDestinationFk, - t.landed, - s.quantity, - s.concept, - s.price, + s.itemFk, + s.ticketFk, + ce.claimDestinationFk, + t.landed, + s.quantity, + s.concept, + s.price, s.discount, - s.quantity * s.price * ((100 - s.discount) / 100) total + s.quantity * s.price * ((100 - s.discount) / 100) total, + ce.shelvingFk, + sh.code shelvingCode FROM vn.claimEnd ce - LEFT JOIN vn.sale s ON s.id = ce.saleFk + LEFT JOIN vn.sale s ON s.id = ce.saleFk LEFT JOIN vn.ticket t ON t.id = s.ticketFk + LEFT JOIN vn.shelving sh ON sh.id = ce.shelvingFk ) ce` ); diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index a51b114ef2..b1c34d2f40 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -3,12 +3,14 @@ module.exports = Self => { description: `Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company`, accessType: 'WRITE', - accepts: [{ - arg: 'id', - type: 'number', - description: 'The claim id', - http: {source: 'path'} - }], + accepts: [ + { + arg: 'id', + type: 'number', + description: 'The claim id', + http: {source: 'path'} + } + ], returns: { type: ['Object'], root: true @@ -22,8 +24,6 @@ module.exports = Self => { Self.regularizeClaim = async(ctx, claimFk, options) => { const models = Self.app.models; const $t = ctx.req.__; // $translate - const resolvedState = 3; - let tx; const myOptions = {}; @@ -37,25 +37,39 @@ module.exports = Self => { try { const claimEnds = await models.ClaimEnd.find({ - include: { - relation: 'claimDestination', - fields: ['addressFk'] - }, - where: {claimFk: claimFk} + where: {claimFk: claimFk}, + include: [ + { + relation: 'claimDestination', + scope: {fields: ['addressFk']} + }, + { + relation: 'shelving', + scope: { + fields: ['code', 'parkingFk'], + include: { + relation: 'parking', + scope: { + fields: ['sectorFk'], + include: { + relation: 'sector', + scope: {fields: ['warehouseFk']} + } + } + } + } + } + ] }, myOptions); for (let claimEnd of claimEnds) { const destination = claimEnd.claimDestination(); const sale = await getSale(claimEnd.saleFk, myOptions); - const addressId = destination && destination.addressFk; - - let address; - if (addressId) - address = await models.Address.findById(addressId, null, myOptions); + const addressId = destination?.addressFk; const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { - const nickname = address && address.nickname || destination.description; + const nickname = destination.description; const url = await Self.app.models.Url.getUrl(); const message = $t('Sent units from ticket', { quantity: sale.quantity, @@ -69,18 +83,21 @@ module.exports = Self => { await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); } - if (!address) continue; + if (!addressId) continue; + + const warehouseFk = claimEnd.shelving().parking().sector().warehouseFk; + const address = await models.Address.findById(addressId, null, myOptions); let ticketFk = await getTicketId({ addressFk: addressId, companyFk: sale.ticket().companyFk, - warehouseFk: sale.ticket().warehouseFk + warehouseFk: warehouseFk }, myOptions); if (!ticketFk) { ctx.args = { clientId: address.clientFk, - warehouseId: sale.ticket().warehouseFk, + warehouseId: warehouseFk, companyId: sale.ticket().companyFk, addressId: addressId }; @@ -90,15 +107,43 @@ module.exports = Self => { ticketFk: ticketFk, itemFk: sale.itemFk, concept: sale.concept, - quantity: -sale.quantity, + quantity: sale.quantity, price: sale.price, discount: 100 }, myOptions); + + const [buyFk] = await Self.rawSql('SELECT vn.buy_getLastWithoutInventory(?, ?) buyFk', + [sale.itemFk, warehouseFk], myOptions + ); + await Self.rawSql('CALL vn.itemShelving_add(?, ?, ?, NULL, NULL, NULL, ?)', + [claimEnd.shelving().code, buyFk.buyFk, -sale.quantity, warehouseFk], + myOptions + ); + const operator = await models.Operator.findById( + ctx.req.accessToken.userId, {fields: ['labelerFk']}, myOptions); + + const params = JSON.stringify({ + copies: 1, + id: buyFk.buyFk, + labelType: 'qr' + }); + + await Self.rawSql(`CALL vn.report_print( ?, ?, ?, ?, ?)`, + ['LabelBuy', + operator?.labelerFk, + ctx.req.accessToken.userId, + params, + 'normal' + ], + myOptions); } + const resolvedState = await models.ClaimState.findOne({ + where: {code: 'resolved'} + }, myOptions); let claim = await Self.findById(claimFk, null, myOptions); claim = await claim.updateAttributes({ - claimStateFk: resolvedState + claimStateFk: resolvedState.id }, myOptions); if (tx) await tx.commit(); @@ -151,7 +196,7 @@ module.exports = Self => { } }, options); - return ticket && ticket.id; + return ticket?.id; } async function createTicket(ctx, options) { diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 55d76ed7a7..f0ad1ce470 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -1,11 +1,8 @@ const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); describe('claim regularizeClaim()', () => { - const userId = 18; - const ctx = beforeAll.mockLoopBackContext(userId); - ctx.req.__ = (value, params) => { - return params.nickname; - }; + const userId = 72; const chatModel = models.Chat; const claimId = 1; const ticketId = 1; @@ -13,9 +10,27 @@ describe('claim regularizeClaim()', () => { const resolvedState = 3; const trashDestination = 2; const okDestination = 1; - const trashAddress = 12; let claimEnds = []; - let trashTicket; + const activeCtx = {accessToken: {userId}}; + let ctx = {req: activeCtx}; + let tx; + let options; + + beforeEach(async() => { + LoopBackContext.getCurrentContext = () => ({ + active: activeCtx, + }); + + ctx.req.__ = (_value, params) => { + return params.nickname; + }; + tx = await models.Claim.beginTransaction({}); + options = {transaction: tx}; + }); + + afterEach(async() => { + await tx.rollback(); + }); async function importTicket(ticketId, claimId, userId, options) { const ticketSales = await models.Sale.find({ @@ -34,84 +49,73 @@ describe('claim regularizeClaim()', () => { } it('should send a chat message with value "Trash" and then change claim state to resolved', async() => { - const tx = await models.Claim.beginTransaction({}); + spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - try { - const options = {transaction: tx}; + claimEnds = await importTicket(ticketId, claimId, userId, options); - spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); + for (const claimEnd of claimEnds) + await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); - claimEnds = await importTicket(ticketId, claimId, userId, options); + let claimBefore = await models.Claim.findById(claimId, null, options); + await models.Claim.regularizeClaim(ctx, claimId, options); + let claimAfter = await models.Claim.findById(claimId, null, options); - for (const claimEnd of claimEnds) - await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); - - let claimBefore = await models.Claim.findById(claimId, null, options); - await models.Claim.regularizeClaim(ctx, claimId, options); - let claimAfter = await models.Claim.findById(claimId, null, options); - - trashTicket = await models.Ticket.findOne({where: {addressFk: 12}}, options); - - expect(trashTicket.addressFk).toEqual(trashAddress); - expect(claimBefore.claimStateFk).toEqual(pendentState); - expect(claimAfter.claimStateFk).toEqual(resolvedState); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Trash'); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(claimBefore.claimStateFk).toEqual(pendentState); + expect(claimAfter.claimStateFk).toEqual(resolvedState); }); - it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { - const tx = await models.Claim.beginTransaction({}); + it('should change claim state to resolved', async() => { + const addressMissingFk = 11; + const shelvingFk = 1; + const warehouseFk = 6; + const minDate = Date.vnNew(); + minDate.setHours(0, 0, 0, 0); - try { - const options = {transaction: tx}; + const maxDate = Date.vnNew(); + maxDate.setHours(23, 59, 59, 59); - spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); + spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - claimEnds = await importTicket(ticketId, claimId, userId, options); + claimEnds = await importTicket(ticketId, claimId, userId, options); - for (claimEnd of claimEnds) - await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); + for (let claimEnd of claimEnds) + await claimEnd.updateAttributes({claimDestinationFk: okDestination, shelvingFk}, options); - await models.Claim.regularizeClaim(ctx, claimId, options); + const sale = await models.Sale.findOne({ + include: [ + { + relation: 'ticket', + scope: { + fields: ['clientFk', 'companyFk'] + } + }], + where: {id: claimEnds[0].saleFk} + }, options); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); + await models.Claim.regularizeClaim(ctx, claimId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); + const ticket = await models.Ticket.findOne({ + where: { + addressFk: addressMissingFk, + companyFk: sale.ticket().companyFk, + warehouseFk: warehouseFk, + shipped: {between: [minDate, maxDate]}, + landed: {between: [minDate, maxDate]} + } + }, options); - it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => { - const tx = await models.Claim.beginTransaction({}); + const missingSale = await models.Sale.findOne({ + where: { + ticketFk: ticket.id + } + }, options); - try { - const options = {transaction: tx}; + const claimBeginning = await models.ClaimBeginning.findOne({ + where: { + claimFk: claimId + } + }, options); - spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - - claimEnds = await importTicket(ticketId, claimId, userId, options); - - for (claimEnd of claimEnds) - await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); - - await models.Claim.regularizeClaim(ctx, claimId, options); - - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(missingSale.quantity).toBe(claimBeginning.quantity); }); }); diff --git a/modules/claim/back/model-config.json b/modules/claim/back/model-config.json index d90ed4c1e8..6183a31423 100644 --- a/modules/claim/back/model-config.json +++ b/modules/claim/back/model-config.json @@ -2,6 +2,9 @@ "Claim": { "dataSource": "vn" }, + "ClaimConfig": { + "dataSource": "vn" + }, "ClaimContainer": { "dataSource": "claimStorage" }, @@ -43,5 +46,5 @@ }, "ClaimObservation": { "dataSource": "vn" - } + } } diff --git a/modules/claim/back/models/claim-config.json b/modules/claim/back/models/claim-config.json new file mode 100644 index 0000000000..6b90909953 --- /dev/null +++ b/modules/claim/back/models/claim-config.json @@ -0,0 +1,42 @@ + +{ + "name": "ClaimConfig", + "base": "VnModel", + "mixins": { + "Loggable": true + }, + "options": { + "mysql": { + "table": "claimConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "maxResponsibility": { + "type": "number" + }, + "monthsToRefund": { + "type": "number" + }, + "minShipped": { + "type": "date" + }, + "pickupdeliveryFk": { + "type": "number" + }, + "warehouseFk": { + "type": "number" + } + }, + "relations": { + "pickupDelivery": { + "type": "belongsTo", + "model": "AgencyMode", + "foreignKey": "pickupdeliveryFk" + } + } +} diff --git a/modules/claim/back/models/claim-end.json b/modules/claim/back/models/claim-end.json index ef5477f50e..aa66824f71 100644 --- a/modules/claim/back/models/claim-end.json +++ b/modules/claim/back/models/claim-end.json @@ -41,6 +41,11 @@ "type": "belongsTo", "model": "ClaimDestination", "foreignKey": "claimDestinationFk" + }, + "shelving": { + "type": "belongsTo", + "model": "Shelving", + "foreignKey": "shelvingFk" } } } diff --git a/modules/client/back/methods/client/createWithUser.js b/modules/client/back/methods/client/createWithUser.js index 1d5e71fcae..ad20171dc9 100644 --- a/modules/client/back/methods/client/createWithUser.js +++ b/modules/client/back/methods/client/createWithUser.js @@ -43,6 +43,17 @@ module.exports = function(Self) { password: String(Math.random() * 100000000000000) }; + const supplier = await models.Supplier.findOne({ + where: {nif: data.fi} + }); + + const role = supplier ? await models.VnRole.findOne({ + where: {name: 'supplier'} + }) : null; + + if (role) + user.roleFk = role.id; + try { const province = await models.Province.findOne({ where: {id: data.provinceFk}, diff --git a/modules/client/back/methods/client/specs/createWithUser.spec.js b/modules/client/back/methods/client/specs/createWithUser.spec.js index 8cff96ac54..fdc0797958 100644 --- a/modules/client/back/methods/client/specs/createWithUser.spec.js +++ b/modules/client/back/methods/client/specs/createWithUser.spec.js @@ -48,11 +48,11 @@ describe('Client Create', () => { expect(error.message).toEqual('An email is necessary'); }); - it('should create a new account with dailyInvoice', async() => { + it('should create a new account with dailyInvoice and role supplier', async() => { const newAccount = { userName: 'deadpool', email: 'deadpool@marvel.com', - fi: '16195279J', + fi: '07972486L', name: 'Wade', socialName: 'DEADPOOL MARVEL', street: 'WALL STREET', @@ -61,33 +61,30 @@ describe('Client Create', () => { provinceFk: 1 }; - try { - const province = await models.Province.findById(newAccount.provinceFk, { - fields: ['id', 'name', 'autonomyFk'], - include: { - relation: 'autonomy' - } - }, options); + const province = await models.Province.findById(newAccount.provinceFk, { + fields: ['id', 'name', 'autonomyFk'], + include: { + relation: 'autonomy' + } + }, options); - const client = await models.Client.createWithUser(newAccount, options); - const account = await models.VnUser.findOne({where: {name: newAccount.userName}}, options); + const client = await models.Client.createWithUser(newAccount, options); + const account = await models.VnUser.findOne({where: {name: newAccount.userName}}, options); + const supplierRole = await models.VnRole.findOne({where: {name: 'supplier'}}, options); - expect(province.autonomy().hasDailyInvoice).toBeTruthy(); - expect(account.name).toEqual(newAccount.userName); - expect(client.id).toEqual(account.id); - expect(client.name).toEqual(newAccount.name); - expect(client.email).toEqual(newAccount.email); - expect(client.fi).toEqual(newAccount.fi); - expect(client.socialName).toEqual(newAccount.socialName); - expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk); - expect(client.hasDailyInvoice).toBeTruthy(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(account.roleFk).toEqual(supplierRole.id); + expect(province.autonomy().hasDailyInvoice).toBeTruthy(); + expect(account.name).toEqual(newAccount.userName); + expect(client.id).toEqual(account.id); + expect(client.name).toEqual(newAccount.name); + expect(client.email).toEqual(newAccount.email); + expect(client.fi).toEqual(newAccount.fi); + expect(client.socialName).toEqual(newAccount.socialName); + expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk); + expect(client.hasDailyInvoice).toBeTruthy(); }); - it('should create a new account without dailyInvoice', async() => { + it('should create a new account without dailyInvoice and role customer', async() => { const newAccount = { userName: 'deadpool', email: 'deadpool@marvel.com', @@ -100,22 +97,20 @@ describe('Client Create', () => { provinceFk: 3 }; - try { - const province = await models.Province.findById(newAccount.provinceFk, { - fields: ['id', 'name', 'autonomyFk'], - include: { - relation: 'autonomy' - } - }, options); + const province = await models.Province.findById(newAccount.provinceFk, { + fields: ['id', 'name', 'autonomyFk'], + include: { + relation: 'autonomy' + } + }, options); - const client = await models.Client.createWithUser(newAccount, options); + const client = await models.Client.createWithUser(newAccount, options); + const vnUser = await models.VnUser.findOne({where: {name: newAccount.userName}}, options); + const customerRole = await models.VnRole.findOne({where: {name: 'customer'}}, options); - expect(province.autonomy.hasDailyInvoice).toBeFalsy(); - expect(client.hasDailyInvoice).toBeFalsy(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(vnUser.roleFk).toEqual(customerRole.id); + expect(province.autonomy.hasDailyInvoice).toBeFalsy(); + expect(client.hasDailyInvoice).toBeFalsy(); }); it('should not be able to create a user if exists', async() => { diff --git a/modules/item/back/methods/item/regularize.js b/modules/item/back/methods/item/regularize.js index dfbcaa69f6..546515093d 100644 --- a/modules/item/back/methods/item/regularize.js +++ b/modules/item/back/methods/item/regularize.js @@ -46,29 +46,24 @@ module.exports = Self => { } try { - const itemDestination = await models.ClaimDestination.findOne({ - include: { - relation: 'address', - scope: { - fields: ['clientFk'] - } - }, - where: {description: 'Corregido'} + const addressWaste = await models.AddressWaste.findOne({ + where: {type: 'fault'}, + include: 'address' }, myOptions); const item = await models.Item.findById(itemFk, null, myOptions); let ticketId = await getTicketId({ - clientFk: itemDestination.address.clientFk, - addressFk: itemDestination.addressFk, + clientFk: addressWaste.address().clientFk, + addressFk: addressWaste.addressFk, warehouseFk: warehouseFk }, myOptions); if (!ticketId) { ctx.args = { - clientId: itemDestination.address().clientFk, + clientId: addressWaste.address().clientFk, warehouseId: warehouseFk, - addressId: itemDestination.addressFk + addressId: addressWaste.addressFk }; ticketId = await createTicket(ctx, myOptions); } @@ -121,7 +116,7 @@ module.exports = Self => { } }, options); - return ticket && ticket.id; + return ticket?.id; } }; }; diff --git a/modules/shelving/back/models/parking.js b/modules/shelving/back/models/parking.js new file mode 100644 index 0000000000..027f1e8ae6 --- /dev/null +++ b/modules/shelving/back/models/parking.js @@ -0,0 +1,5 @@ +module.exports = Self => { + Self.validatesUniquenessOf('code', { + message: `The code already exists` + }); +}; diff --git a/modules/shelving/back/models/shelving.js b/modules/shelving/back/models/shelving.js index bf611d2ba1..207224266e 100644 --- a/modules/shelving/back/models/shelving.js +++ b/modules/shelving/back/models/shelving.js @@ -1,4 +1,8 @@ module.exports = Self => { require('../methods/shelving/getSummary')(Self); require('../methods/shelving/addLog')(Self); + + Self.validatesUniquenessOf('code', { + message: `The code already exists` + }); }; diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index b645c714c9..9559333c87 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -1,5 +1,6 @@ const smtp = require('vn-print/core/smtp'); const config = require('vn-print/core/config'); +const Email = require('vn-print/core/email'); module.exports = Self => { Self.remoteMethodCtx('closeAll', { @@ -44,8 +45,7 @@ module.exports = Self => { LIMIT 1`, [toDate, toDate], myOptions); await Self.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close; - CREATE TEMPORARY TABLE tmp.ticket_close + CREATE OR REPLACE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY WITH wTickets AS( SELECT t.id ticketFk @@ -63,11 +63,12 @@ module.exports = Self => { FROM wTicketsTracking wt JOIN ticketTracking tt ON tt.id = wt.maxTracking ) SELECT tls.ticketFk, - t.clientFk, + t.clientFk clientId, c.name clientName, c.email recipient, + c.isToBeMailed, eu.email salesPersonEmail, - t.addressFk, + t.addressFk addressId, c.hasDailyInvoice, c.hasToInvoiceByAddress, t.totalWithVat, @@ -79,7 +80,7 @@ module.exports = Self => { JOIN agencyMode am ON am.id = t.agencyModeFk JOIN client c ON c.id = t.clientFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk - WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')); + WHERE al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'); CALL ticket_close(); `, [dateFrom, dateTo], myOptions); @@ -98,21 +99,27 @@ module.exports = Self => { AND tob.id IS NULL AND t.routeFk`, [dateFrom, dateTo], myOptions); - const [clients] = await Self.rawSql(` - SELECT clientFk clientId, - clientName, - recipient, - salesPersonEmail, - addressFk addressId, - companyFk, + const clients = await Self.rawSql(` + SELECT *, SUM(totalWithVat) total, 'quick' serialType FROM tmp.ticket_close WHERE hasDailyInvoice - GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk - HAVING total > 0; + GROUP BY IF (hasToInvoiceByAddress, addressId, clientId), companyFk + HAVING total > 0 + `, [], myOptions); + + const [ticketsToMail] = await Self.rawSql(` + SELECT * + FROM tmp.ticket_close + WHERE NOT hasDailyInvoice + AND isToBeMailed + AND salesPersonEmail IS NOT NULL + AND salesPersonEmail <> '' + AND recipient IS NOT NULL + AND recipient <> ''; DROP TEMPORARY TABLE tmp.ticket_close; - `, [], myOptions); + `, [], myOptions); if (tx) await tx.commit(); @@ -130,21 +137,23 @@ module.exports = Self => { if (id) await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id, null, nestedTransaction); } catch (error) { - await Self.rawSql(` - INSERT INTO util.debug (variable, value) - VALUES ('invoicingTicketError', ?) - `, [client.clientId + ' - ' + error]); + await handleInvoicingError(error, client, failedClients); + } + } - if (error.responseCode == 450) { - await invalidEmail(client); - continue; - } + for (const ticket of ticketsToMail) { + const args = { + id: ticket.ticketFk, + recipientId: ticket.clientId, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail, + }; - failedClients.push({ - id: client.clientId, - address: client.addressId, - error - }); + try { + const email = new Email('delivery-note-link', args); + await email.send(); + } catch (error) { + await handleInvoicingError(error, ticket, failedClients); } } @@ -188,4 +197,22 @@ module.exports = Self => { html: body, }).catch(err => console.error(err)); } + + async function handleInvoicingError(error, entity, failedClients) { + await Self.rawSql(` + INSERT INTO util.debug (variable, value) + VALUES ('invoicingTicketError', ?) + `, [entity.clientId + ' - ' + error]); + + if (error.responseCode == 450) { + await invalidEmail(entity); + return; + } + + failedClients.push({ + id: entity.clientId, + address: entity.addressId, + error + }); + } }; diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index 87556810b4..d300b1815d 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -33,6 +33,11 @@ module.exports = Self => { type: 'date', description: `The to date filter` }, + { + arg: 'shipped', + type: 'date', + description: `The shipped date filter` + }, { arg: 'nickname', type: 'string', @@ -201,6 +206,7 @@ module.exports = Self => { case 'clientFk': case 'agencyModeFk': case 'warehouseFk': + case 'shipped': param = `t.${param}`; return {[param]: value}; } diff --git a/modules/worker/back/methods/worker/getWorkerBusiness.js b/modules/worker/back/methods/worker/getWorkerBusiness.js new file mode 100644 index 0000000000..cefde70085 --- /dev/null +++ b/modules/worker/back/methods/worker/getWorkerBusiness.js @@ -0,0 +1,255 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('getWorkerBusiness', { + description: 'Returns an array of business from an specified worker', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + { + arg: 'started', + type: 'date', + description: 'started', + http: {source: 'query'} + }, + { + arg: 'ended', + type: 'date', + description: 'ended', + http: {source: 'query'} + }, + { + arg: 'companyCodeFk', + type: 'string', + description: 'companyCodeFk', + http: {source: 'query'} + }, + { + arg: 'reasonEndFk', + type: 'number', + description: 'reasonEndFk', + http: {source: 'query'} + }, + { + arg: 'reasondEnd', + type: 'string', + description: 'reasondEnd', + http: {source: 'query'} + }, + { + arg: 'departmentFk', + type: 'number', + description: 'departmentFk', + http: {source: 'query'} + }, + { + arg: 'department', + type: 'string', + description: 'department', + http: {source: 'query'} + }, + { + arg: 'workerBusinessProfessionalCategoryFk', + type: 'number', + description: 'workerBusinessProfessionalCategoryFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessProfessionalCategory', + type: 'string', + description: 'workerBusinessProfessionalCategory', + http: {source: 'query'} + }, + { + arg: 'calendarTypeFk', + type: 'number', + description: 'calendarTypeFk', + http: {source: 'query'} + }, + { + arg: 'calendarType', + type: 'string', + description: 'calendarType', + http: {source: 'query'} + }, + { + arg: 'workcenterFk', + type: 'number', + description: 'workcenterFk', + http: {source: 'query'} + }, + { + arg: 'workCenter', + type: 'string', + description: 'workCenter', + http: {source: 'query'} + }, + { + arg: 'workerBusinessCategoryFk', + type: 'number', + description: 'WorkerBusinessCategoryFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessCategory', + type: 'string', + description: 'workerBusinessCategory', + http: {source: 'query'} + }, + { + arg: 'occupationCodeFk', + type: 'number', + description: 'occupationCodeFk', + http: {source: 'query'} + }, + { + arg: 'occupationCode', + type: 'string', + description: 'occupationCode', + http: {source: 'query'} + }, + { + arg: 'rate', + type: 'number', + description: 'rate', + http: {source: 'query'} + }, + { + arg: 'workerBusinessTypeFk', + type: 'number', + description: 'workerBusinessTypeFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessType', + type: 'string', + description: 'workerBusinessType', + http: {source: 'query'} + }, + { + arg: 'amount', + type: 'number', + description: 'amount', + http: {source: 'query'} + }, + { + arg: 'basicSalary', + type: 'number', + description: 'amount', + http: {source: 'query'} + }, + { + arg: 'notes', + type: 'string', + description: 'notes', + http: {source: 'query'} + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/:id/getWorkerBusiness', + verb: 'GET' + } + }); + + Self.getWorkerBusiness = async(ctx, id, filter, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let conn = Self.dataSource.connector; + let where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'started': + case 'ended': + case 'companyCodeFk': + case 'reasonEndFk': + case 'reasondEnd': + case 'departmentFk': + case 'department': + case 'workerBusinessProfessionalCategoryFk': + case 'workerBusinessProfessionalCategory': + case 'calendarTypeFk': + case 'calendarType': + case 'workcenterFk': + case 'workCenter': + case 'workerBusinessCategoryFk': + case 'workerBusinessCategory': + case 'occupationCodeFk': + case 'occupationCode': + case 'rate': + case 'workerBusinessTypeFk': + case 'workerBusinessType': + case 'amount': + case 'basicSalary': + case 'notes': + } + }); + where = {...where, ...{workerFk: id}}; + filter = mergeFilters(filter, {where}); + + let stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `SELECT * FROM( + SELECT b.id, + b.workerFk, + b.started, + b.ended, + b.companyCodeFk, + b.reasonEndFk, + bre.reason, + b.departmentFk, + d.name departmentName, + b.occupationCodeFk, + oc.name occupationName, + b.workerBusinessProfessionalCategoryFk, + pf.description professionalDescription, + b.calendarTypeFk, + ct.description calendarTypeDescription, + b.workcenterFk, + wc.name workCenterName, + b.workerBusinessCategoryFk, + py.description payrollDescription, + b.workerBusinessTypeFk, + wt.name workerBusinessTypeName, + b.amount, + b.basicSalary, + b.notes + FROM business b + LEFT JOIN businessReasonEnd bre ON bre.id = b.reasonEndFk + LEFT JOIN occupationCode oc ON oc.code = b.occupationCodeFk + LEFT JOIN department d ON d.id = b.departmentFk + LEFT JOIN professionalCategory pf ON pf.id = b.workerBusinessProfessionalCategoryFk + JOIN calendarType ct ON ct.id = b.calendarTypeFk + LEFT JOIN workCenter wc ON wc.id = b.workcenterFk + LEFT JOIN payrollCategories py ON py.id = b.workerBusinessCategoryFk + LEFT JOIN workerBusinessType wt ON wt.id = b.workerBusinessTypeFk + ) sub + ` + ); + + stmt.merge(conn.makeSuffix(filter)); + stmts.push(stmt); + + let sql = ParameterizedSQL.join(stmts, ';'); + let result = await conn.executeStmt(sql, myOptions); + return result; + }; +}; diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 4895a6107f..2f8c28ef4b 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -21,6 +21,7 @@ module.exports = Self => { require('../methods/worker/setPassword')(Self); require('../methods/worker/getAvailablePda')(Self); require('../methods/worker/myTeam')(Self); + require('../methods/worker/getWorkerBusiness')(Self); Self.canModifyAbsenceInPast = async(ctx, time) => { const hasPrivs = await Self.app.models.ACL.checkAccessAcl(ctx, 'Worker', 'canModifyAbsenceInPast', 'WRITE'); diff --git a/modules/zone/back/models/zone-warehouse.js b/modules/zone/back/models/zone-warehouse.js index 9cb0e2a1cb..c9713f28c3 100644 --- a/modules/zone/back/models/zone-warehouse.js +++ b/modules/zone/back/models/zone-warehouse.js @@ -3,7 +3,7 @@ let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') - return new UserError(`duplicateWarehouse`); + return new UserError(`The introduced warehouse already exists`); return err; }); }; diff --git a/print/core/components/email-footer/locale/ca.yml b/print/core/components/email-footer/locale/ca.yml new file mode 100644 index 0000000000..b0e3fee865 --- /dev/null +++ b/print/core/components/email-footer/locale/ca.yml @@ -0,0 +1,18 @@ +buttons: + webAcccess: Visita la nostra Web + info: Ajuda'ns a millorar +fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESÍ + · verdnatura.es · clientes@verdnatura.es +disclaimer: '- AVÍS - Aquest missatge és privat i confidencial, i ha de ser utilitzat + exclusivament per la persona destinatària del mateix. Si has rebut aquest missatge + per error, et preguem que ho comuniquis al remitent i esborres aquest missatge + i qualsevol document adjunt que pugui contenir. Verdnatura Levante SL no renuncia + a la confidencialitat ni a cap privilegi per causa de transmissió errònia o mal + funcionament. Igualment, no es fa responsable dels canvis, alteracions, errors + o omissions que es puguin fer al missatge un cop enviat.' +privacy: En compliment del que disposa la Llei Orgànica 15/1999, de Protecció de + Dades de Caràcter Personal, et comuniquem que les dades personals que facilitis + s'inclouran en fitxers automatitzats de VERDNATURA LEVANTE S.L., podent en tot + moment exercir els drets d'accés, rectificació, cancel·lació i oposició, + comunicant-ho per escrit al domicili social de l'entitat. La finalitat del + fitxer és la gestió administrativa, comptabilitat i facturació. diff --git a/print/templates/email/delivery-note-link/locale/ca.yml b/print/templates/email/delivery-note-link/locale/ca.yml new file mode 100644 index 0000000000..41f0c128c7 --- /dev/null +++ b/print/templates/email/delivery-note-link/locale/ca.yml @@ -0,0 +1,11 @@ +subject: El teu albarà +title: El teu albarà +dear: Estimat client +description: Ja està disponible l'albarà corresponent a la comanda {0}.
+ Pots veure'l fent clic en aquest enllaç. +copyLink: 'Com a alternativa, pots copiar el següent enllaç al teu navegador:' +poll: Si ho desitges, pots respondre a la nostra enquesta de satisfacció per + ajudar-nos a oferir un millor servei. La teva opinió és molt important per a nosaltres! +help: Qualsevol dubte que tinguis, no dubtis a consultar-nos, estem aquí + per atendre't! +conclusion: Gràcies per la teva atenció!