From 190731899e5d925ef2ddb6508e44b1961e24c9e6 Mon Sep 17 00:00:00 2001 From: jgallego Date: Thu, 9 Jan 2025 12:33:20 +0100 Subject: [PATCH 01/14] feat: refs #7937 add ClaimConfig model and update refund ticket logic --- .../11401-azureMoss/00-claimConfig.sql | 13 ++++ .../importToNewRefundTicket.js | 69 ++++++++++++++----- modules/claim/back/model-config.json | 5 +- modules/claim/back/models/claim-config.json | 47 +++++++++++++ 4 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 db/versions/11401-azureMoss/00-claimConfig.sql create mode 100644 modules/claim/back/models/claim-config.json diff --git a/db/versions/11401-azureMoss/00-claimConfig.sql b/db/versions/11401-azureMoss/00-claimConfig.sql new file mode 100644 index 0000000000..d50fa363d4 --- /dev/null +++ b/db/versions/11401-azureMoss/00-claimConfig.sql @@ -0,0 +1,13 @@ +ALTER TABLE `vn`.`claimConfig` + ADD COLUMN `pickupAgencyFk` INT(11) DEFAULT 6 + COMMENT 'Agencia utilizada para las recogidas mediante agencia', + ADD COLUMN `pickupDeliveryFk` INT(11) DEFAULT 847 + COMMENT 'Agencia utilizada para las recogidas mediante reparto', + + ADD CONSTRAINT `fk_claimConfig_pickupAgencyFk` + FOREIGN KEY (`pickupAgencyFk`) + REFERENCES `agencyMode` (`id`), + + ADD CONSTRAINT `fk_claimConfig_pickupdeliveryFk` + FOREIGN KEY (`pickupdeliveryFk`) + REFERENCES `agencyMode` (`id`); diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index a0dc2248c4..b220e06c8c 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -82,31 +82,64 @@ module.exports = Self => { 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 shipped; + let landed; + let warehouseFk; + let agencyModeFk; + let zoneFk; + let nickname; + let state; + if (claim.pickup === null) { + state = await models.State.findOne({ + where: {code: 'DELIVERED'} + }, myOptions); + const agencyMode = await models.AgencyMode.findOne({ + where: {code: 'refund'} + }, myOptions); + const zone = await models.Zone.findOne({ + where: {agencyModeFk: agencyMode.id} + }, myOptions); + + shipped = today; + landed = today; + warehouseFk = claim.ticket().warehouseFk; + agencyModeFk = agencyMode.id; + zoneFk = zone.id; + nickname = `Abono del: ${claim.ticketFk}`; + } else { + state = await models.State.findOne({ + where: {code: 'WAITING_FOR_PICKUP'} + }, myOptions); + const warehouse = await models.Warehouse.findOne({ + where: {code: 'rcl'} + }, myOptions); + warehouseFk = warehouse.id; + nickname = `Recogida pendiente del: ${claim.ticketFk}`; + zoneFk = zone.id; + const claimConfig = await models.claimConfig.findOne(); + if (claim.pickup == 'delivery') { + shipped = today; + landed = today; + agencyModeFk = claimConfig.pickupDeliveryFk; + } else { + shipped = null; + landed = null; + agencyModeFk = claimConfig.pickupAgencyFk; + } + } const newRefundTicket = await models.Ticket.create({ clientFk: claim.ticket().clientFk, - shipped: today, - landed: today, - nickname: `Abono del: ${claim.ticketFk}`, - warehouseFk: claim.ticket().warehouseFk, + shipped, + landed, + nickname, + warehouseFk, companyFk: claim.ticket().companyFk, addressFk: claim.ticket().addressFk, - agencyModeFk: agencyMode.id, - zoneFk: zone.id + agencyModeFk, + zoneFk }, myOptions); await models.TicketRefund.create({ 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..7e5aac1ce4 --- /dev/null +++ b/modules/claim/back/models/claim-config.json @@ -0,0 +1,47 @@ + +{ + "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" + }, + "pickupAgencyFk": { + "type": "number" + }, + "pickupdeliveryFk": { + "type": "number" + } + }, + "relations": { + "pickupAgency": { + "type": "belongsTo", + "model": "AgencyMode", + "foreignKey": "pickupAgencyFk" + }, + "pickupDelivery": { + "type": "belongsTo", + "model": "AgencyMode", + "foreignKey": "pickupdeliveryFk" + } + } +} From 0f83549651ede433e4a66274301c484244717b7c Mon Sep 17 00:00:00 2001 From: jgallego Date: Mon, 13 Jan 2025 09:55:46 +0100 Subject: [PATCH 02/14] feat: refs #7937 add warehouse and pickup agency fields to ClaimConfig model and update related logic --- db/dump/fixtures.before.sql | 4 +- .../11401-azureMoss/00-claimConfig.sql | 27 +++++--- loopback/locale/en.json | 2 +- .../importToNewRefundTicket.js | 69 +++++++++++-------- modules/claim/back/models/claim-config.json | 3 + 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index ff896b84d2..5d37c0ac01 100644 --- a/db/dump/fixtures.before.sql +++ b/db/dump/fixtures.before.sql @@ -1940,9 +1940,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`) +INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`, `monthsToRefund`, `minShipped`, `pickupAgencyFk`, `pickupDeliveryFk`, `warehouseFk`) VALUES - (1, 5, 4, '2016-10-01'); + (1, 5, 4, '2016-10-01', 1, 8, 4); INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) VALUES diff --git a/db/versions/11401-azureMoss/00-claimConfig.sql b/db/versions/11401-azureMoss/00-claimConfig.sql index d50fa363d4..ccd9653a92 100644 --- a/db/versions/11401-azureMoss/00-claimConfig.sql +++ b/db/versions/11401-azureMoss/00-claimConfig.sql @@ -1,13 +1,20 @@ ALTER TABLE `vn`.`claimConfig` - ADD COLUMN `pickupAgencyFk` INT(11) DEFAULT 6 - COMMENT 'Agencia utilizada para las recogidas mediante agencia', - ADD COLUMN `pickupDeliveryFk` INT(11) DEFAULT 847 - COMMENT 'Agencia utilizada para las recogidas mediante reparto', + ADD COLUMN `pickupAgencyFk` INT(11) + COMMENT 'Agencia utilizada para las recogidas mediante agencia', + 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_pickupAgencyFk` - FOREIGN KEY (`pickupAgencyFk`) - REFERENCES `agencyMode` (`id`), + ADD CONSTRAINT `fk_claimConfig_pickupAgencyFk` + FOREIGN KEY (`pickupAgencyFk`) + REFERENCES `agencyMode` (`id`), + + ADD CONSTRAINT `fk_claimConfig_pickupdeliveryFk` + FOREIGN KEY (`pickupdeliveryFk`) + REFERENCES `agencyMode` (`id`), + + ADD CONSTRAINT `fk_claimConfig_warehouseFk` + FOREIGN KEY (`warehouseFk`) + REFERENCES `warehouse` (`id`); - ADD CONSTRAINT `fk_claimConfig_pickupdeliveryFk` - FOREIGN KEY (`pickupdeliveryFk`) - REFERENCES `agencyMode` (`id`); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 80da13ae59..2e25408c62 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -251,4 +251,4 @@ "Price cannot be blank": "Price cannot be blank", "There are tickets to be invoiced": "There are tickets to be invoiced", "The address of the customer must have information about Incoterms and Customs Agent": "The address of the customer must have information about Incoterms and Customs Agent" -} \ 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 b220e06c8c..f6cd15607f 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -78,19 +78,17 @@ module.exports = Self => { where: {id: userId} }, myOptions); - const obsevationType = await models.ObservationType.findOne({ + const observationSalesPerson = await models.ObservationType.findOne({ where: {code: 'salesPerson'} }, myOptions); const claim = await models.Claim.findOne(filter, myOptions); const today = Date.vnNew(); - let shipped; - let landed; let warehouseFk; let agencyModeFk; - let zoneFk; let nickname; let state; + let discountValue = null; if (claim.pickup === null) { state = await models.State.findOne({ @@ -99,49 +97,47 @@ module.exports = Self => { const agencyMode = await models.AgencyMode.findOne({ where: {code: 'refund'} }, myOptions); - const zone = await models.Zone.findOne({ - where: {agencyModeFk: agencyMode.id} - }, myOptions); - shipped = today; - landed = today; warehouseFk = claim.ticket().warehouseFk; agencyModeFk = agencyMode.id; - zoneFk = zone.id; nickname = `Abono del: ${claim.ticketFk}`; } else { + const claimConfig = await models.ClaimConfig.findOne(); + + discountValue = 100; state = await models.State.findOne({ where: {code: 'WAITING_FOR_PICKUP'} }, myOptions); - const warehouse = await models.Warehouse.findOne({ - where: {code: 'rcl'} - }, myOptions); - warehouseFk = warehouse.id; + nickname = `Recogida pendiente del: ${claim.ticketFk}`; - zoneFk = zone.id; - const claimConfig = await models.claimConfig.findOne(); - if (claim.pickup == 'delivery') { - shipped = today; - landed = today; + warehouseFk = claimConfig.warehouseFk; + if (claim.pickup == 'delivery') agencyModeFk = claimConfig.pickupDeliveryFk; - } else { - shipped = null; - landed = null; + else agencyModeFk = claimConfig.pickupAgencyFk; - } } const newRefundTicket = await models.Ticket.create({ clientFk: claim.ticket().clientFk, - shipped, - landed, + shipped: await getNextShipped(today, claim.ticket().addressFk, agencyModeFk, warehouseFk, myOptions), + landed: null, nickname, warehouseFk, companyFk: claim.ticket().companyFk, addressFk: claim.ticket().addressFk, agencyModeFk, - zoneFk + zoneFk: null }, 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 @@ -150,7 +146,7 @@ module.exports = Self => { await saveObservation({ description: `Reclama ticket: ${claim.ticketFk}`, ticketFk: newRefundTicket.id, - observationTypeFk: obsevationType.id + observationTypeFk: observationSalesPerson.id }, myOptions); await models.Ticket.state(ctx, { @@ -160,7 +156,7 @@ module.exports = Self => { }, myOptions); const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions); - const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, myOptions); + const createdSales = await addSalesToTicket(salesToRefund, newRefundTicket.id, discountValue, myOptions); await insertIntoClaimEnd(createdSales, id, worker.id, myOptions); await Self.rawSql('CALL vn.ticketCalculateClon(?, ?)', [ @@ -176,7 +172,7 @@ module.exports = Self => { } }; - async function addSalesToTicket(salesToRefund, ticketId, options) { + async function addSalesToTicket(salesToRefund, ticketId, discountValue, options) { let formatedSales = []; salesToRefund.forEach(sale => { let formatedSale = { @@ -185,7 +181,7 @@ module.exports = Self => { concept: sale.sale().concept, quantity: -Math.abs(sale.quantity), price: sale.sale().price, - discount: sale.sale().discount, + discount: discountValue ?? sale.sale().discount, reserved: sale.sale().reserved, isPicked: sale.sale().isPicked, created: sale.sale().created @@ -218,4 +214,17 @@ module.exports = Self => { observation.description ], options); } + + async function getNextShipped(vLanded, vAddressFk, vAgencyModeFk, warehouseFk, options) { + await Self.rawSql( + 'CALL vn.zone_getShipped(?, ?, ?, TRUE)', [vLanded, vAddressFk, vAgencyModeFk], options); + + try { + const [resultSet] = await Self.rawSql( + 'SELECT shipped FROM tmp.zoneGetShipped WHERE warehouseFk = ?', [warehouseFk], options); + return resultSet.shipped; + } catch (error) { + return Date.vnNew(); + } + } }; diff --git a/modules/claim/back/models/claim-config.json b/modules/claim/back/models/claim-config.json index 7e5aac1ce4..858070ae49 100644 --- a/modules/claim/back/models/claim-config.json +++ b/modules/claim/back/models/claim-config.json @@ -30,6 +30,9 @@ }, "pickupdeliveryFk": { "type": "number" + }, + "warehouseFk": { + "type": "number" } }, "relations": { From b97eeea2d2094688b88a836be007d63427d3cbb2 Mon Sep 17 00:00:00 2001 From: jgallego Date: Thu, 16 Jan 2025 08:30:11 +0100 Subject: [PATCH 03/14] refactor: refs #7937 streamline importToNewRefundTicket function and add comprehensive tests --- .../importToNewRefundTicket.js | 68 +++++++------ .../importToNewRefundTicket.spec.js | 44 --------- .../specs/importToNewRefundTicket.spec.js | 97 +++++++++++++++++++ 3 files changed, 135 insertions(+), 74 deletions(-) delete mode 100644 modules/claim/back/methods/claim-beginning/importToNewRefundTicket.spec.js create mode 100644 modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index f6cd15607f..aea649576d 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -74,10 +74,6 @@ module.exports = Self => { } try { - const worker = await models.Worker.findOne({ - where: {id: userId} - }, myOptions); - const observationSalesPerson = await models.ObservationType.findOne({ where: {code: 'salesPerson'} }, myOptions); @@ -89,6 +85,7 @@ module.exports = Self => { let nickname; let state; let discountValue = null; + let packages = 0; if (claim.pickup === null) { state = await models.State.findOne({ @@ -105,6 +102,7 @@ module.exports = Self => { const claimConfig = await models.ClaimConfig.findOne(); discountValue = 100; + packages = 1; state = await models.State.findOne({ where: {code: 'WAITING_FOR_PICKUP'} }, myOptions); @@ -125,7 +123,8 @@ module.exports = Self => { companyFk: claim.ticket().companyFk, addressFk: claim.ticket().addressFk, agencyModeFk, - zoneFk: null + zoneFk: claim.ticket().zoneFk, + packages }, myOptions); if (claim.pickup == 'pickup') { @@ -149,20 +148,16 @@ module.exports = Self => { observationTypeFk: observationSalesPerson.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, discountValue, 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; @@ -172,23 +167,36 @@ module.exports = Self => { } }; - async function addSalesToTicket(salesToRefund, ticketId, discountValue, 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: discountValue ?? 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..f77efbddf0 --- /dev/null +++ b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js @@ -0,0 +1,97 @@ +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; + + beforeEach(async() => { + LoopBackContext.getCurrentContext = () => ({ + active: activeCtx, + }); + tx = await models.Entry.beginTransaction({}); + options = {transaction: tx}; + }); + + 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('debe establecer estado DELIVERED y modo de agencia refund', 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('debe establecer estado WAITING_FOR_PICKUP para las recogidas por reparto', 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); + + expect(ticketTracking.stateFk).toEqual(state.id); + }); + + it('debe establecer estado WAITING_FOR_PICKUP para las recogidas por agencia', async() => { + const state = await models.State.findOne({ + where: {code: 'WAITING_FOR_PICKUP'} + }, 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); + 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); + }); +}); From afef9f40258cde8676054d6bb6304ce7d7a310ae Mon Sep 17 00:00:00 2001 From: jgallego Date: Mon, 10 Feb 2025 13:14:57 +0100 Subject: [PATCH 04/14] feat: refs #7937 add initial state 'Recogido' to state table with relevant attributes --- db/versions/11401-azureMoss/01-createState.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 db/versions/11401-azureMoss/01-createState.sql 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) From 7001076974727bf537b7a033aff0253b6fb35b9b Mon Sep 17 00:00:00 2001 From: jgallego Date: Mon, 17 Feb 2025 09:05:34 +0100 Subject: [PATCH 05/14] refactor: refs #7937 remove pickupAgencyFk and related constraints; update claimConfig and tests --- db/dump/fixtures.before.sql | 4 +- .../11401-azureMoss/00-claimConfig.sql | 6 -- .../importToNewRefundTicket.js | 44 +++-------- .../specs/importToNewRefundTicket.spec.js | 30 ++++---- .../back/methods/claim/regularizeClaim.js | 77 ++----------------- .../claim/specs/regularizeClaim.spec.js | 13 +--- modules/claim/back/models/claim-config.json | 8 -- 7 files changed, 37 insertions(+), 145 deletions(-) diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index 8629365d00..776ddd6cbf 100644 --- a/db/dump/fixtures.before.sql +++ b/db/dump/fixtures.before.sql @@ -1946,9 +1946,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`, `pickupAgencyFk`, `pickupDeliveryFk`, `warehouseFk`) +INSERT INTO `vn`.`claimConfig`(`id`, `maxResponsibility`, `monthsToRefund`, `minShipped`,`daysToClaim`, `pickupDeliveryFk`, `warehouseFk`) VALUES - (1, 5, 4, '2016-10-01', 7, 1, 8, 4); + (1, 5, 4, '2016-10-01', 7, 8, 4); INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`) VALUES diff --git a/db/versions/11401-azureMoss/00-claimConfig.sql b/db/versions/11401-azureMoss/00-claimConfig.sql index ccd9653a92..8942de33b4 100644 --- a/db/versions/11401-azureMoss/00-claimConfig.sql +++ b/db/versions/11401-azureMoss/00-claimConfig.sql @@ -1,15 +1,9 @@ ALTER TABLE `vn`.`claimConfig` - ADD COLUMN `pickupAgencyFk` INT(11) - COMMENT 'Agencia utilizada para las recogidas mediante agencia', 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_pickupAgencyFk` - FOREIGN KEY (`pickupAgencyFk`) - REFERENCES `agencyMode` (`id`), - ADD CONSTRAINT `fk_claimConfig_pickupdeliveryFk` FOREIGN KEY (`pickupdeliveryFk`) REFERENCES `agencyMode` (`id`), diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index aea649576d..5a08140e7f 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -74,20 +74,16 @@ module.exports = Self => { } try { - const observationSalesPerson = await models.ObservationType.findOne({ - where: {code: 'salesPerson'} - }, myOptions); - const claim = await models.Claim.findOne(filter, myOptions); const today = Date.vnNew(); - let warehouseFk; let agencyModeFk; let nickname; let state; let discountValue = null; let packages = 0; - - if (claim.pickup === null) { + 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); @@ -95,12 +91,9 @@ module.exports = Self => { where: {code: 'refund'} }, myOptions); - warehouseFk = claim.ticket().warehouseFk; agencyModeFk = agencyMode.id; nickname = `Abono del: ${claim.ticketFk}`; } else { - const claimConfig = await models.ClaimConfig.findOne(); - discountValue = 100; packages = 1; state = await models.State.findOne({ @@ -108,15 +101,15 @@ module.exports = Self => { }, myOptions); nickname = `Recogida pendiente del: ${claim.ticketFk}`; - warehouseFk = claimConfig.warehouseFk; - if (claim.pickup == 'delivery') - agencyModeFk = claimConfig.pickupDeliveryFk; - else - agencyModeFk = claimConfig.pickupAgencyFk; + + 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: await getNextShipped(today, claim.ticket().addressFk, agencyModeFk, warehouseFk, myOptions), + shipped: nextShipped, landed: null, nickname, warehouseFk, @@ -142,12 +135,6 @@ module.exports = Self => { originalTicketFk: claim.ticket().id }, myOptions); - await saveObservation({ - description: `Reclama ticket: ${claim.ticketFk}`, - ticketFk: newRefundTicket.id, - observationTypeFk: observationSalesPerson.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); @@ -222,17 +209,4 @@ module.exports = Self => { observation.description ], options); } - - async function getNextShipped(vLanded, vAddressFk, vAgencyModeFk, warehouseFk, options) { - await Self.rawSql( - 'CALL vn.zone_getShipped(?, ?, ?, TRUE)', [vLanded, vAddressFk, vAgencyModeFk], options); - - try { - const [resultSet] = await Self.rawSql( - 'SELECT shipped FROM tmp.zoneGetShipped WHERE warehouseFk = ?', [warehouseFk], options); - return resultSet.shipped; - } catch (error) { - return Date.vnNew(); - } - } }; diff --git a/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js index f77efbddf0..64a1a175cf 100644 --- a/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js +++ b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js @@ -11,6 +11,7 @@ describe('importToNewRefundTicket()', () => { let ctx = {req: activeCtx}; let options; const claimId = 1; + const expectedDate = Date.vnNew(); beforeEach(async() => { LoopBackContext.getCurrentContext = () => ({ @@ -18,6 +19,7 @@ describe('importToNewRefundTicket()', () => { }); tx = await models.Entry.beginTransaction({}); options = {transaction: tx}; + spyOn(models.Agency, 'getShipped').and.returnValue(Promise.resolve(expectedDate)); }); afterEach(async() => { @@ -70,20 +72,6 @@ describe('importToNewRefundTicket()', () => { where: {ticketFk: ticket.id}, order: 'id DESC' }, options); - - expect(ticketTracking.stateFk).toEqual(state.id); - }); - - it('debe establecer estado WAITING_FOR_PICKUP para las recogidas por agencia', async() => { - const state = await models.State.findOne({ - where: {code: 'WAITING_FOR_PICKUP'} - }, 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); const newSales = await models.Sale.find({ where: {ticketFk: ticket.id} }, options); @@ -94,4 +82,18 @@ describe('importToNewRefundTicket()', () => { expect(ticketTracking.stateFk).toEqual(state.id); }); + + it('debe establecer estado DELIVERED para las recogidas por agencia', 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/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index a51b114ef2..14c1fb1dfe 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -22,8 +22,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 +35,17 @@ module.exports = Self => { try { const claimEnds = await models.ClaimEnd.find({ - include: { - relation: 'claimDestination', - fields: ['addressFk'] - }, + include: {relation: 'claimDestination'}, where: {claimFk: claimFk} }, 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 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, @@ -68,37 +58,13 @@ module.exports = Self => { }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); } - - if (!address) continue; - - let ticketFk = await getTicketId({ - addressFk: addressId, - companyFk: sale.ticket().companyFk, - warehouseFk: sale.ticket().warehouseFk - }, myOptions); - - if (!ticketFk) { - ctx.args = { - clientId: address.clientFk, - warehouseId: sale.ticket().warehouseFk, - companyId: sale.ticket().companyFk, - addressId: addressId - }; - ticketFk = await createTicket(ctx, myOptions); - } - await models.Sale.create({ - ticketFk: ticketFk, - itemFk: sale.itemFk, - concept: sale.concept, - quantity: -sale.quantity, - price: sale.price, - discount: 100 - }, 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(); @@ -133,35 +99,4 @@ module.exports = Self => { where: {id: saleFk} }, options); } - - async function getTicketId(params, options) { - const minDate = Date.vnNew(); - minDate.setHours(0, 0, 0, 0); - - const maxDate = Date.vnNew(); - maxDate.setHours(23, 59, 59, 59); - - let ticket = await Self.app.models.Ticket.findOne({ - where: { - addressFk: params.addressFk, - companyFk: params.companyFk, - warehouseFk: params.warehouseFk, - shipped: {between: [minDate, maxDate]}, - landed: {between: [minDate, maxDate]} - } - }, options); - - return ticket && ticket.id; - } - - async function createTicket(ctx, options) { - ctx.args.shipped = Date.vnNew(); - ctx.args.landed = Date.vnNew(); - ctx.args.agencyModeId = null; - ctx.args.routeId = null; - - const ticket = await Self.app.models.Ticket.new(ctx, options); - - return ticket.id; - } }; diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 55d76ed7a7..827279fb41 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -3,7 +3,7 @@ const models = require('vn-loopback/server/server').models; describe('claim regularizeClaim()', () => { const userId = 18; const ctx = beforeAll.mockLoopBackContext(userId); - ctx.req.__ = (value, params) => { + ctx.req.__ = (_value, params) => { return params.nickname; }; const chatModel = models.Chat; @@ -13,9 +13,7 @@ describe('claim regularizeClaim()', () => { const resolvedState = 3; const trashDestination = 2; const okDestination = 1; - const trashAddress = 12; let claimEnds = []; - let trashTicket; async function importTicket(ticketId, claimId, userId, options) { const ticketSales = await models.Sale.find({ @@ -50,12 +48,9 @@ describe('claim regularizeClaim()', () => { 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).toHaveBeenCalledWith(ctx, 18, 'Basura/Perd.'); expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); await tx.rollback(); @@ -75,7 +70,7 @@ describe('claim regularizeClaim()', () => { claimEnds = await importTicket(ticketId, claimId, userId, options); - for (claimEnd of claimEnds) + for (let claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); await models.Claim.regularizeClaim(ctx, claimId, options); @@ -100,7 +95,7 @@ describe('claim regularizeClaim()', () => { claimEnds = await importTicket(ticketId, claimId, userId, options); - for (claimEnd of claimEnds) + for (let claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); await models.Claim.regularizeClaim(ctx, claimId, options); diff --git a/modules/claim/back/models/claim-config.json b/modules/claim/back/models/claim-config.json index 858070ae49..6b90909953 100644 --- a/modules/claim/back/models/claim-config.json +++ b/modules/claim/back/models/claim-config.json @@ -25,9 +25,6 @@ "minShipped": { "type": "date" }, - "pickupAgencyFk": { - "type": "number" - }, "pickupdeliveryFk": { "type": "number" }, @@ -36,11 +33,6 @@ } }, "relations": { - "pickupAgency": { - "type": "belongsTo", - "model": "AgencyMode", - "foreignKey": "pickupAgencyFk" - }, "pickupDelivery": { "type": "belongsTo", "model": "AgencyMode", From fd53142bff3f3e46b09d11451be77f30c0467316 Mon Sep 17 00:00:00 2001 From: jgallego Date: Tue, 18 Feb 2025 12:53:43 +0100 Subject: [PATCH 06/14] fix: refs #7937 update claimDestination and regularizeClaim methods for address handling --- db/dump/fixtures.before.sql | 8 +- .../back/methods/claim/regularizeClaim.js | 68 ++++++++- .../claim/specs/regularizeClaim.spec.js | 135 ++++++++++-------- modules/item/back/methods/item/regularize.js | 18 +-- 4 files changed, 154 insertions(+), 75 deletions(-) diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index a874bc9224..562812660b 100644 --- a/db/dump/fixtures.before.sql +++ b/db/dump/fixtures.before.sql @@ -1957,11 +1957,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 diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index 14c1fb1dfe..9f2eaed325 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -35,13 +35,21 @@ module.exports = Self => { try { const claimEnds = await models.ClaimEnd.find({ - include: {relation: 'claimDestination'}, + include: { + relation: 'claimDestination', + fields: ['addressFk'] + }, where: {claimFk: claimFk} }, myOptions); for (let claimEnd of claimEnds) { const destination = claimEnd.claimDestination(); const sale = await getSale(claimEnd.saleFk, myOptions); + const addressId = destination?.addressFk; + + let address; + if (addressId) + address = await models.Address.findById(addressId, null, myOptions); const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { @@ -58,10 +66,37 @@ module.exports = Self => { }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); } + + if (!address) continue; + + let ticketFk = await getTicketId({ + addressFk: addressId, + companyFk: sale.ticket().companyFk, + warehouseFk: sale.ticket().warehouseFk + }, myOptions); + + if (!ticketFk) { + ctx.args = { + clientId: address.clientFk, + warehouseId: sale.ticket().warehouseFk, + companyId: sale.ticket().companyFk, + addressId: addressId + }; + ticketFk = await createTicket(ctx, myOptions); + } + await models.Sale.create({ + ticketFk: ticketFk, + itemFk: sale.itemFk, + concept: sale.concept, + quantity: sale.quantity, + price: sale.price, + discount: 100 + }, 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.id @@ -99,4 +134,35 @@ module.exports = Self => { where: {id: saleFk} }, options); } + + async function getTicketId(params, options) { + const minDate = Date.vnNew(); + minDate.setHours(0, 0, 0, 0); + + const maxDate = Date.vnNew(); + maxDate.setHours(23, 59, 59, 59); + + let ticket = await Self.app.models.Ticket.findOne({ + where: { + addressFk: params.addressFk, + companyFk: params.companyFk, + warehouseFk: params.warehouseFk, + shipped: {between: [minDate, maxDate]}, + landed: {between: [minDate, maxDate]} + } + }, options); + + return ticket?.id; + } + + async function createTicket(ctx, options) { + ctx.args.shipped = Date.vnNew(); + ctx.args.landed = Date.vnNew(); + ctx.args.agencyModeId = null; + ctx.args.routeId = null; + + const ticket = await Self.app.models.Ticket.new(ctx, options); + + return ticket.id; + } }; diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 827279fb41..5cb300ac04 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -3,9 +3,6 @@ const models = require('vn-loopback/server/server').models; describe('claim regularizeClaim()', () => { const userId = 18; const ctx = beforeAll.mockLoopBackContext(userId); - ctx.req.__ = (_value, params) => { - return params.nickname; - }; const chatModel = models.Chat; const claimId = 1; const ticketId = 1; @@ -14,6 +11,20 @@ describe('claim regularizeClaim()', () => { const trashDestination = 2; const okDestination = 1; let claimEnds = []; + let tx; + let options; + + beforeEach(async() => { + 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({ @@ -32,81 +43,89 @@ 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); - - expect(claimBefore.claimStateFk).toEqual(pendentState); - expect(claimAfter.claimStateFk).toEqual(resolvedState); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Basura/Perd.'); - 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); + expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Basura/Perd.'); + expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); }); it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { - const tx = await models.Claim.beginTransaction({}); + const addressMissingFk = 11; + 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 (let claimEnd of claimEnds) - await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); + for (let claimEnd of claimEnds) + await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); - await models.Claim.regularizeClaim(ctx, claimId, options); + const sale = await models.Sale.findOne({ + include: [ + { + relation: 'ticket', + scope: { + fields: ['clientFk', 'warehouseFk', '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: sale.ticket().warehouseFk, + shipped: {between: [minDate, maxDate]}, + landed: {between: [minDate, maxDate]} + } + }, options); + + const missingSale = await models.Sale.findOne({ + where: { + ticketFk: ticket.id + } + }, options); + + const claimBeginning = await models.ClaimBeginning.findOne({ + where: { + claimFk: claimId + } + }, options); + + expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); + expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); + expect(missingSale.quantity).toBe(claimBeginning.quantity); }); it('should send a chat message to the salesPerson when claim isPickUp is enabled', 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 (let claimEnd of claimEnds) + await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); - claimEnds = await importTicket(ticketId, claimId, userId, options); + await models.Claim.regularizeClaim(ctx, claimId, options); - for (let 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(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); + expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); }); }); diff --git a/modules/item/back/methods/item/regularize.js b/modules/item/back/methods/item/regularize.js index dfbcaa69f6..f03400157a 100644 --- a/modules/item/back/methods/item/regularize.js +++ b/modules/item/back/methods/item/regularize.js @@ -46,29 +46,23 @@ 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'} }, myOptions); const item = await models.Item.findById(itemFk, null, myOptions); let ticketId = await getTicketId({ - clientFk: itemDestination.address.clientFk, + clientFk: addressWaste.address.clientFk, addressFk: itemDestination.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 +115,7 @@ module.exports = Self => { } }, options); - return ticket && ticket.id; + return ticket?.id; } }; }; From ed61d6488d7e59818b084550751c56978bce9df4 Mon Sep 17 00:00:00 2001 From: jgallego Date: Wed, 19 Feb 2025 12:52:06 +0100 Subject: [PATCH 07/14] feat: refs #7937 add shelvingFk to claimEnd table and update related models and queries --- db/dump/fixtures.before.sql | 21 +++-- db/versions/11401-azureMoss/02-claimEnd.sql | 5 ++ .../claim/back/methods/claim-end/filter.js | 21 ++--- .../back/methods/claim/regularizeClaim.js | 80 ++++++++++++++----- .../claim/specs/regularizeClaim.spec.js | 38 ++++----- modules/claim/back/models/claim-end.json | 5 ++ modules/item/back/methods/item/regularize.js | 7 +- 7 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 db/versions/11401-azureMoss/02-claimEnd.sql diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql index 562812660b..08c8407fbd 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 @@ -1302,9 +1303,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 +1617,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`) @@ -3063,9 +3066,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 @@ -4142,3 +4146,6 @@ INSERT IGNORE INTO vn.vehicleType (id, name) (2, 'furgoneta'), (3, 'cabeza tractora'), (4, 'remolque'); + +INSERT INTO vn.addressWaste (addressFk, type) + VALUES (11, 'fault'); 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/modules/claim/back/methods/claim-end/filter.js b/modules/claim/back/methods/claim-end/filter.js index 0dda16a3a7..644e10605a 100644 --- a/modules/claim/back/methods/claim-end/filter.js +++ b/modules/claim/back/methods/claim-end/filter.js @@ -40,20 +40,21 @@ 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 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 ) ce` ); diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index 9f2eaed325..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 @@ -35,11 +37,29 @@ 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) { @@ -47,10 +67,6 @@ module.exports = Self => { const sale = await getSale(claimEnd.saleFk, myOptions); const addressId = destination?.addressFk; - let address; - if (addressId) - address = await models.Address.findById(addressId, null, myOptions); - const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { const nickname = destination.description; @@ -67,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 }; @@ -92,6 +111,31 @@ module.exports = Self => { 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'} diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 5cb300ac04..f0ad1ce470 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -1,8 +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); + const userId = 72; const chatModel = models.Chat; const claimId = 1; const ticketId = 1; @@ -11,10 +11,16 @@ describe('claim regularizeClaim()', () => { const trashDestination = 2; const okDestination = 1; let claimEnds = []; + 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; }; @@ -56,12 +62,12 @@ describe('claim regularizeClaim()', () => { expect(claimBefore.claimStateFk).toEqual(pendentState); expect(claimAfter.claimStateFk).toEqual(resolvedState); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Basura/Perd.'); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); }); - it('should send a chat message with value "Bueno" and then change claim state to resolved', async() => { + 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); @@ -73,14 +79,14 @@ describe('claim regularizeClaim()', () => { claimEnds = await importTicket(ticketId, claimId, userId, options); for (let claimEnd of claimEnds) - await claimEnd.updateAttributes({claimDestinationFk: okDestination}, options); + await claimEnd.updateAttributes({claimDestinationFk: okDestination, shelvingFk}, options); const sale = await models.Sale.findOne({ include: [ { relation: 'ticket', scope: { - fields: ['clientFk', 'warehouseFk', 'companyFk'] + fields: ['clientFk', 'companyFk'] } }], where: {id: claimEnds[0].saleFk} @@ -92,7 +98,7 @@ describe('claim regularizeClaim()', () => { where: { addressFk: addressMissingFk, companyFk: sale.ticket().companyFk, - warehouseFk: sale.ticket().warehouseFk, + warehouseFk: warehouseFk, shipped: {between: [minDate, maxDate]}, landed: {between: [minDate, maxDate]} } @@ -110,22 +116,6 @@ describe('claim regularizeClaim()', () => { } }, options); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno'); - expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4); expect(missingSale.quantity).toBe(claimBeginning.quantity); }); - - it('should send a chat message to the salesPerson when claim isPickUp is enabled', async() => { - spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - - claimEnds = await importTicket(ticketId, claimId, userId, options); - - for (let 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); - }); }); 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/item/back/methods/item/regularize.js b/modules/item/back/methods/item/regularize.js index f03400157a..546515093d 100644 --- a/modules/item/back/methods/item/regularize.js +++ b/modules/item/back/methods/item/regularize.js @@ -47,14 +47,15 @@ module.exports = Self => { try { const addressWaste = await models.AddressWaste.findOne({ - where: {type: 'fault'} + where: {type: 'fault'}, + include: 'address' }, myOptions); const item = await models.Item.findById(itemFk, null, myOptions); let ticketId = await getTicketId({ - clientFk: addressWaste.address.clientFk, - addressFk: itemDestination.addressFk, + clientFk: addressWaste.address().clientFk, + addressFk: addressWaste.addressFk, warehouseFk: warehouseFk }, myOptions); From ff3268b6e94fd6bb457f91e4c889f28b4adb1783 Mon Sep 17 00:00:00 2001 From: provira Date: Wed, 19 Feb 2025 12:53:29 +0100 Subject: [PATCH 08/14] fix: refs #8612 handled duplicate code error in shelving & parking --- loopback/locale/en.json | 3 +- loopback/locale/es.json | 803 ++++++++++++----------- modules/shelving/back/models/parking.js | 5 + modules/shelving/back/models/shelving.js | 4 + 4 files changed, 413 insertions(+), 402 deletions(-) create mode 100644 modules/shelving/back/models/parking.js diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 07a82d214d..c1748fe8c4 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" + "duplicateWarehouse": "The introduced warehouse already exists", + "duplicateCode": "The code already exists" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 6463608818..fbbc67c19f 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}}})", + "duplicateWarehouse": "El almacén seleccionado ya existe en la zona", + "duplicateCode": "El código ya existe" +} \ No newline at end of file diff --git a/modules/shelving/back/models/parking.js b/modules/shelving/back/models/parking.js new file mode 100644 index 0000000000..9b82439dcb --- /dev/null +++ b/modules/shelving/back/models/parking.js @@ -0,0 +1,5 @@ +module.exports = Self => { + Self.validatesUniquenessOf('code', { + message: `duplicateCode` + }); +}; diff --git a/modules/shelving/back/models/shelving.js b/modules/shelving/back/models/shelving.js index bf611d2ba1..e61cda790b 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: `duplicateCode` + }); }; From e18e51aa92db6d70705c15fdf6c34b45c98cb1ab Mon Sep 17 00:00:00 2001 From: jgallego Date: Thu, 20 Feb 2025 11:20:47 +0100 Subject: [PATCH 09/14] refactor: refs #7937 improve error handling and transaction management in report_print procedure --- db/routines/vn/procedures/report_print.sql | 144 +++++++++++---------- 1 file changed, 77 insertions(+), 67 deletions(-) diff --git a/db/routines/vn/procedures/report_print.sql b/db/routines/vn/procedures/report_print.sql index a5e08538eb..c57b7d9469 100644 --- a/db/routines/vn/procedures/report_print.sql +++ b/db/routines/vn/procedures/report_print.sql @@ -1,83 +1,93 @@ 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 + /** + * 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 @@in_transaction; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + IF NOT vTx THEN ROLLBACK; - RESIGNAL; - END; + END IF; + RESIGNAL; + END; - SELECT id, paperSizeFk INTO vReportFk, vReportSize - FROM report - WHERE name = vReportName; - SELECT id, paperSizeFk INTO vIsThePrinterReal, vPrinteSize - FROM printer - WHERE id = vPrinterFk; + SELECT id, paperSizeFk INTO vReportFk, vReportSize + FROM report + WHERE name = vReportName; - SELECT id INTO vPriorityFk - FROM queuePriority - WHERE code = vPriorityName; + SELECT id, paperSizeFk INTO vIsThePrinterReal, vPrinterSize + FROM printer + WHERE id = vPrinterFk; - IF vIsThePrinterReal IS NULL THEN - CALL util.throw('printerNotExists'); - END IF; + SELECT id INTO vPriorityFk + FROM queuePriority + WHERE code = vPriorityName; - IF vReportFk IS NULL THEN - CALL util.throw('reportNotExists'); - END IF; + IF vIsThePrinterReal IS NULL THEN + CALL util.throw('printerNotExists'); + END IF; - IF vReportSize <> vPrinteSize THEN - CALL util.throw('incorrectSize'); - END IF; + IF vReportFk IS NULL THEN + CALL util.throw('reportNotExists'); + END IF; - START TRANSACTION; - INSERT INTO printQueue - SET printerFk = vPrinterFk, - priorityFk = vPriorityFk, - reportFk = vReportFk, - workerFk = vUserFk; - - SET vPrintQueueFk = LAST_INSERT_ID(); + IF vReportSize <> vPrinterSize THEN + CALL util.throw('incorrectSize'); + END IF; - WHILE vI < vLength DO - SET vKey = JSON_VALUE(vKeys, CONCAT('$[', vI ,']')); - SET vVal = JSON_VALUE(vParams, CONCAT('$.', vKey)); + IF NOT vTx THEN + START TRANSACTION; + END IF; + 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; + + IF NOT vTx THEN + COMMIT; + END IF; END$$ DELIMITER ; From 9f391bba27d7a7c45d8f62fa40846c3981bb79de Mon Sep 17 00:00:00 2001 From: provira Date: Thu, 20 Feb 2025 13:00:00 +0100 Subject: [PATCH 10/14] fix: refs #8612 changed translations --- loopback/locale/en.json | 4 ++-- loopback/locale/es.json | 4 ++-- modules/shelving/back/models/parking.js | 2 +- modules/shelving/back/models/shelving.js | 2 +- modules/zone/back/models/zone-warehouse.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index c1748fe8c4..d9cb3ed476 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -258,6 +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", - "duplicateCode": "The code 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 fbbc67c19f..7783182c01 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -399,6 +399,6 @@ "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", - "duplicateCode": "El código ya existe" + "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/shelving/back/models/parking.js b/modules/shelving/back/models/parking.js index 9b82439dcb..027f1e8ae6 100644 --- a/modules/shelving/back/models/parking.js +++ b/modules/shelving/back/models/parking.js @@ -1,5 +1,5 @@ module.exports = Self => { Self.validatesUniquenessOf('code', { - message: `duplicateCode` + message: `The code already exists` }); }; diff --git a/modules/shelving/back/models/shelving.js b/modules/shelving/back/models/shelving.js index e61cda790b..207224266e 100644 --- a/modules/shelving/back/models/shelving.js +++ b/modules/shelving/back/models/shelving.js @@ -3,6 +3,6 @@ module.exports = Self => { require('../methods/shelving/addLog')(Self); Self.validatesUniquenessOf('code', { - message: `duplicateCode` + message: `The code already exists` }); }; 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; }); }; From 5eb981165e273548044b6bf1dd14b115c519b064 Mon Sep 17 00:00:00 2001 From: jgallego Date: Thu, 20 Feb 2025 15:02:19 +0100 Subject: [PATCH 11/14] refactor: refs #7937 improve transaction handling in report_print procedure and enhance claimEnd filter --- db/routines/vn/procedures/report_print.sql | 15 ++++----------- .../11401-azureMoss/03-claimDestination.sql | 8 ++++++++ modules/claim/back/methods/claim-end/filter.js | 4 +++- 3 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 db/versions/11401-azureMoss/03-claimDestination.sql diff --git a/db/routines/vn/procedures/report_print.sql b/db/routines/vn/procedures/report_print.sql index c57b7d9469..17c0c921bb 100644 --- a/db/routines/vn/procedures/report_print.sql +++ b/db/routines/vn/procedures/report_print.sql @@ -28,13 +28,11 @@ BEGIN DECLARE vPrinterSize VARCHAR(255); DECLARE vPriorityFk INT; DECLARE vReportFk INT; - DECLARE vTx BOOLEAN DEFAULT @@in_transaction; + DECLARE vTx BOOLEAN DEFAULT NOT @@in_transaction; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN - IF NOT vTx THEN - ROLLBACK; - END IF; + CALL util.tx_rollback(vTx); RESIGNAL; END; @@ -62,10 +60,7 @@ BEGIN IF vReportSize <> vPrinterSize THEN CALL util.throw('incorrectSize'); END IF; - - IF NOT vTx THEN - START TRANSACTION; - END IF; + CALL util.tx_start(vTx); INSERT INTO printQueue SET printerFk = vPrinterFk, priorityFk = vPriorityFk, @@ -86,8 +81,6 @@ BEGIN SET vI = vI + 1; END WHILE; - IF NOT vTx THEN - COMMIT; - END IF; + CALL util.tx_commit(vTx); END$$ DELIMITER ; 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/modules/claim/back/methods/claim-end/filter.js b/modules/claim/back/methods/claim-end/filter.js index 644e10605a..c4f4879969 100644 --- a/modules/claim/back/methods/claim-end/filter.js +++ b/modules/claim/back/methods/claim-end/filter.js @@ -52,10 +52,12 @@ module.exports = Self => { s.price, s.discount, s.quantity * s.price * ((100 - s.discount) / 100) total, - ce.shelvingFk + ce.shelvingFk, + sh.code shelvingCode FROM vn.claimEnd ce 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` ); From 6f5a966c550463365479625ef1037bda78ce365f Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 21 Feb 2025 10:25:41 +0100 Subject: [PATCH 12/14] refactor: refs #7764 add role assignment for supplier during user creation --- .../back/methods/client/createWithUser.js | 11 +++ .../client/specs/createWithUser.spec.js | 73 +++++++++---------- 2 files changed, 45 insertions(+), 39 deletions(-) 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..cf68f089ec 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'}}); - 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'}}); - 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() => { From 63a36139c2ba1b3cb02d4aa7591975e155bc6f49 Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 21 Feb 2025 11:12:13 +0100 Subject: [PATCH 13/14] refactor: refs #7937 update test descriptions to English for clarity in importToNewRefundTicket specs --- .../claim-beginning/specs/importToNewRefundTicket.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js index 64a1a175cf..7771310674 100644 --- a/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js +++ b/modules/claim/back/methods/claim-beginning/specs/importToNewRefundTicket.spec.js @@ -41,7 +41,7 @@ describe('importToNewRefundTicket()', () => { expect(salesInsertedInClaimEnd[0].saleFk).toEqual(refundTicketSales[0].id); }); - it('debe establecer estado DELIVERED y modo de agencia refund', async() => { + it('should set DELIVERED state and refund agency mode', async() => { const state = await models.State.findOne({ where: {code: 'DELIVERED'} }, options); @@ -62,7 +62,7 @@ describe('importToNewRefundTicket()', () => { expect(ticketTracking.stateFk).toEqual(state.id); }); - it('debe establecer estado WAITING_FOR_PICKUP para las recogidas por reparto', async() => { + it('should set WAITING_FOR_PICKUP state for delivery pickups', async() => { const state = await models.State.findOne({ where: {code: 'WAITING_FOR_PICKUP'} }, options); @@ -83,7 +83,7 @@ describe('importToNewRefundTicket()', () => { expect(ticketTracking.stateFk).toEqual(state.id); }); - it('debe establecer estado DELIVERED para las recogidas por agencia', async() => { + it('should set DELIVERED state for agency pickups', async() => { const state = await models.State.findOne({ where: {code: 'DELIVERED'} }, options); From a15cf04a4c3d16a08c6e797fd5cd06c43ef482ad Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 21 Feb 2025 12:53:08 +0100 Subject: [PATCH 14/14] refactor: refs #7764 include options parameter in role retrieval for user creation tests --- .../client/back/methods/client/specs/createWithUser.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/client/back/methods/client/specs/createWithUser.spec.js b/modules/client/back/methods/client/specs/createWithUser.spec.js index cf68f089ec..fdc0797958 100644 --- a/modules/client/back/methods/client/specs/createWithUser.spec.js +++ b/modules/client/back/methods/client/specs/createWithUser.spec.js @@ -70,7 +70,7 @@ describe('Client Create', () => { 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'}}); + const supplierRole = await models.VnRole.findOne({where: {name: 'supplier'}}, options); expect(account.roleFk).toEqual(supplierRole.id); expect(province.autonomy().hasDailyInvoice).toBeTruthy(); @@ -106,7 +106,7 @@ describe('Client Create', () => { 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'}}); + const customerRole = await models.VnRole.findOne({where: {name: 'customer'}}, options); expect(vnUser.roleFk).toEqual(customerRole.id); expect(province.autonomy.hasDailyInvoice).toBeFalsy();