diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js index 9986a16c9..4c3fc0cd3 100644 --- a/back/methods/postcode/filter.js +++ b/back/methods/postcode/filter.js @@ -1,16 +1,11 @@ const {ParameterizedSQL} = require('loopback-connector'); -const {buildFilter, mergeFilters} = require('vn-loopback/util/filter'); -// const {models} = require('vn-loopback/server/server'); +const {buildFilter} = require('vn-loopback/util/filter'); module.exports = Self => { Self.remoteMethod('filter', { description: 'Find all postcodes of the model matched by postcode, town, province or country.', accessType: 'READ', - returns: { - type: ['object'], - root: true, - }, accepts: [ { arg: 'filter', @@ -25,6 +20,10 @@ module.exports = Self => { http: {source: 'query'} }, ], + returns: { + type: ['object'], + root: true, + }, http: { path: `/filter`, verb: 'GET', @@ -32,30 +31,34 @@ module.exports = Self => { }); Self.filter = async(ctx, filter, options) => { const myOptions = {}; - if (typeof options == 'object') Object.assign(myOptions, options); + filter = ctx?.filter ?? {}; + const conn = Self.dataSource.connector; - const where = buildFilter(ctx.args, (param, value) => { + const where = buildFilter(filter?.where, (param, value) => { switch (param) { case 'search': - return {or: [ - {'pc.code': {like: `%${value}%`}}, - {'t.name': {like: `%${value}%`}}, - {'p.name': {like: `%${value}%`}}, - {'c.country': {like: `%${value}%`}} - ] + return { + or: [ + {'pc.code': {like: `%${value}%`}}, + {'t.name': {like: `%${value}%`}}, + {'p.name': {like: `%${value}%`}}, + {'c.country': {like: `%${value}%`}} + ] }; } }) ?? {}; - - filter = mergeFilters(ctx.args?.filter ?? {}, {where}); + delete ctx.filter.where; const stmts = []; let stmt; stmt = new ParameterizedSQL(` SELECT + pc.townFk, + t.provinceFk, + p.countryFk, pc.code, t.name as town, p.name as province, @@ -67,7 +70,7 @@ module.exports = Self => { JOIN country c on c.id = p.countryFk `); - stmt.merge(conn.makeSuffix(filter)); + stmt.merge(conn.makeSuffix({where, ...ctx})); const itemsIndex = stmts.push(stmt) - 1; const sql = ParameterizedSQL.join(stmts, ';'); diff --git a/back/methods/postcode/specs/filter.spec.js b/back/methods/postcode/specs/filter.spec.js index c393b629a..60ac24809 100644 --- a/back/methods/postcode/specs/filter.spec.js +++ b/back/methods/postcode/specs/filter.spec.js @@ -7,13 +7,13 @@ describe('Postcode filter()', () => { try { const ctx = { - args: { - + filter: { }, + limit: 1 }; const results = await models.Postcode.filter(ctx, options); - expect(results.length).toBeGreaterThan(0); + expect(results.length).toEqual(1); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -27,8 +27,10 @@ describe('Postcode filter()', () => { try { const ctx = { - args: { - search: 46, + filter: { + where: { + search: 46, + } }, }; const results = await models.Postcode.filter(ctx, options); @@ -47,8 +49,10 @@ describe('Postcode filter()', () => { try { const ctx = { - args: { - search: 'Alz', + filter: { + where: { + search: 'Alz', + } }, }; const results = await models.Postcode.filter(ctx, options); @@ -67,8 +71,10 @@ describe('Postcode filter()', () => { try { const ctx = { - args: { - search: 'one', + filter: { + where: { + search: 'one', + } }, }; const results = await models.Postcode.filter(ctx, options); @@ -87,8 +93,10 @@ describe('Postcode filter()', () => { try { const ctx = { - args: { - search: 'Ec', + filter: { + where: { + search: 'Ec', + } }, }; const results = await models.Postcode.filter(ctx, options); diff --git a/db/changes/240601/00-grant_privileges_util_tx.sql b/db/changes/240601/00-grant_privileges_util_tx.sql new file mode 100644 index 000000000..bd49f6cb2 --- /dev/null +++ b/db/changes/240601/00-grant_privileges_util_tx.sql @@ -0,0 +1,3 @@ +GRANT EXECUTE ON PROCEDURE util.tx_commit TO guest; +GRANT EXECUTE ON PROCEDURE util.tx_rollback TO guest; +GRANT EXECUTE ON PROCEDURE util.tx_start TO guest; diff --git a/db/changes/240601/00-update_procedure_TravelCloneWithEntries.sql b/db/changes/240601/00-update_procedure_TravelCloneWithEntries.sql new file mode 100644 index 000000000..b0c177b9a --- /dev/null +++ b/db/changes/240601/00-update_procedure_TravelCloneWithEntries.sql @@ -0,0 +1,85 @@ +DROP PROCEDURE IF EXISTS vn.travel_cloneWithEntries; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`travel_cloneWithEntries`( + IN vTravelFk INT, + IN vDateStart DATE, + IN vDateEnd DATE, + IN vWarehouseOutFk INT, + IN vWarehouseInFk INT, + IN vRef VARCHAR(255), + IN vAgencyModeFk INT, + OUT vNewTravelFk INT) +BEGIN +/** + * Clona un travel junto con sus entradas y compras + * @param vTravelFk travel plantilla a clonar + * @param vDateStart fecha del shipment del nuevo travel + * @param vDateEnd fecha del landing del nuevo travel + * @param vWarehouseOutFk warehouse del salida del nuevo travel + * @param vWarehouseInFk warehouse de landing del nuevo travel + * @param vRef referencia del nuevo travel + * @param vAgencyModeFk del nuevo travel + * @param vNewTravelFk id del nuevo travel + */ + DECLARE vNewEntryFk INT; + DECLARE vEvaNotes VARCHAR(255); + DECLARE vDone BOOL; + DECLARE vAuxEntryFk INT; + DECLARE vTx BOOLEAN DEFAULT !@@in_transaction; + DECLARE vRsEntry CURSOR FOR + SELECT e.id + FROM entry e + JOIN travel t ON t.id = e.travelFk + WHERE e.travelFk = vTravelFk; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + CALL util.tx_rollback(vTx); + RESIGNAL; + END; + + CALL util.tx_start(vTx); + + INSERT INTO travel (shipped, landed, warehouseInFk, warehouseOutFk, agencyModeFk, `ref`, isDelivered, isReceived, m3, cargoSupplierFk, kg,clonedFrom) + SELECT vDateStart, vDateEnd, vWarehouseInFk, vWarehouseOutFk, vAgencyModeFk, vRef, isDelivered, isReceived, m3,cargoSupplierFk, kg,vTravelFk + FROM travel + WHERE id = vTravelFk; + + SET vNewTravelFk = LAST_INSERT_ID(); + + SET vDone = FALSE; + SET @isModeInventory = TRUE; + + OPEN vRsEntry; + + l: LOOP + SET vDone = FALSE; + FETCH vRsEntry INTO vAuxEntryFk; + + IF vDone THEN + LEAVE l; + END IF; + + CALL entry_cloneHeader(vAuxEntryFk, vNewEntryFk, vNewTravelFk); + CALL entry_copyBuys(vAuxEntryFk, vNewEntryFk); + + SELECT evaNotes INTO vEvaNotes + FROM entry + WHERE id = vAuxEntryFk; + + UPDATE entry + SET evaNotes = vEvaNotes + WHERE id = vNewEntryFk; + + END LOOP; + + SET @isModeInventory = FALSE; + CLOSE vRsEntry; + + CALL util.tx_commit(vTx); +END$$ +DELIMITER ; diff --git a/db/changes/240601/00-util_tx_commit.sql b/db/changes/240601/00-util_tx_commit.sql new file mode 100644 index 000000000..11be92c2e --- /dev/null +++ b/db/changes/240601/00-util_tx_commit.sql @@ -0,0 +1,15 @@ +DELIMITER $$ +$$ + +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `util`.`tx_commit`(IN tx BOOL) +BEGIN +/** + * Procedimiento para confirmar los cambios asociados a una transacción + * + * @param tx BOOL es true si existe transacción asociada +*/ + IF tx THEN + COMMIT; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/240601/00-util_tx_rollback.sql b/db/changes/240601/00-util_tx_rollback.sql new file mode 100644 index 000000000..b6ac2cd12 --- /dev/null +++ b/db/changes/240601/00-util_tx_rollback.sql @@ -0,0 +1,15 @@ +DELIMITER $$ +$$ + +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `util`.`tx_rollback`(tx BOOL) +BEGIN +/** + * Procedimiento para deshacer los cambios asociados a una transacción + * + * @param tx BOOL es true si existe transacción asociada +*/ + IF tx THEN + ROLLBACK; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/240601/00-util_tx_start.sql b/db/changes/240601/00-util_tx_start.sql new file mode 100644 index 000000000..6e513790b --- /dev/null +++ b/db/changes/240601/00-util_tx_start.sql @@ -0,0 +1,17 @@ + + +DELIMITER $$ +$$ + +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `util`.`tx_start`(tx BOOL) +BEGIN +/** + * Procedimiento para iniciar una transacción + * + * @param tx BOOL es true si existe transacción asociada +*/ + IF tx THEN + START TRANSACTION; + END IF; +END$$ +DELIMITER ; diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 3589eac4b..dd092573d 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -28,7 +28,6 @@ module.exports = Self => { const loopBackContext = LoopBackContext.getCurrentContext(); ctx.req = loopBackContext.active; - if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'canForceQuantity', 'WRITE')) return; const ticketId = changes?.ticketFk || instance?.ticketFk; const itemId = changes?.itemFk || instance?.itemFk; diff --git a/modules/travel/back/methods/travel/cloneWithEntries.js b/modules/travel/back/methods/travel/cloneWithEntries.js index 45ebef283..e5eb0b06c 100644 --- a/modules/travel/back/methods/travel/cloneWithEntries.js +++ b/modules/travel/back/methods/travel/cloneWithEntries.js @@ -1,6 +1,5 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const UserError = require('vn-loopback/util/user-error'); -const loggable = require('vn-loopback/util/log'); module.exports = Self => { Self.remoteMethodCtx('cloneWithEntries', { @@ -11,8 +10,9 @@ module.exports = Self => { type: 'number', required: true, description: 'The original travel id', - http: {source: 'path'} - }], + http: {source: 'path'}, + }, + ], returns: { type: 'object', description: 'The new cloned travel id', @@ -24,61 +24,75 @@ module.exports = Self => { } }); - Self.cloneWithEntries = async(ctx, id) => { + Self.cloneWithEntries = async(ctx, id, options) => { const conn = Self.dataSource.connector; - const travel = await Self.findById(id, { - fields: [ - 'id', - 'shipped', - 'landed', - 'warehouseInFk', - 'warehouseOutFk', - 'agencyModeFk', - 'ref' - ] - }); - const started = Date.vnNew(); - const ended = Date.vnNew(); + const myOptions = {}; + let tx = options?.transaction; - if (!travel) - throw new UserError('Travel not found'); + try { + if (typeof options == 'object') + Object.assign(myOptions, options); - let stmts = []; - let stmt; + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } - stmt = new ParameterizedSQL( - `CALL travel_cloneWithEntries(?, ?, ?, ?, ?, ?, ?, @vTravelFk)`, [ - id, - started, - ended, - travel.warehouseOutFk, - travel.warehouseInFk, - travel.ref, - travel.agencyModeFk - ] - ); - stmts.push(stmt); - const newTravelIndex = stmts.push('SELECT @vTravelFk AS id') - 1; + const travel = await Self.findById(id, { + fields: [ + 'id', + 'shipped', + 'landed', + 'warehouseInFk', + 'warehouseOutFk', + 'agencyModeFk', + 'ref' + ] + }); + const started = Date.vnNew(); + const ended = Date.vnNew(); - const sql = ParameterizedSQL.join(stmts, ';'); - const result = await conn.executeStmt(sql); - const [lastInsert] = result[newTravelIndex]; + if (!travel) + throw new UserError('Travel not found'); - if (!lastInsert.id) - throw new UserError('Unable to clone this travel'); + let stmts = []; + let stmt; + stmt = new ParameterizedSQL( + `CALL travel_cloneWithEntries(?, ?, ?, ?, ?, ?, ?, @vTravelFk)`, [ + id, + started, + ended, + travel.warehouseOutFk, + travel.warehouseInFk, + travel.ref, + travel.agencyModeFk + ] + ); + stmts.push(stmt); + const newTravelIndex = stmts.push('SELECT @vTravelFk AS id') - 1; - const newTravel = await Self.findById(lastInsert.id, { - fields: [ - 'id', - 'shipped', - 'landed', - 'warehouseInFk', - 'warehouseOutFk', - 'agencyModeFk', - 'ref' - ] - }); + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + const [lastInsert] = result[newTravelIndex]; - return newTravel.id; + if (!lastInsert.id) + throw new UserError('Unable to clone this travel'); + + const newTravel = await Self.findById(lastInsert.id, { + fields: [ + 'id', + 'shipped', + 'landed', + 'warehouseInFk', + 'warehouseOutFk', + 'agencyModeFk', + 'ref' + ] + }, myOptions); + return newTravel.id; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; }; diff --git a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js index 0e434e048..74ec10155 100644 --- a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js +++ b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js @@ -5,73 +5,36 @@ describe('Travel cloneWithEntries()', () => { const travelId = 5; const currentUserId = 1102; const ctx = {req: {accessToken: {userId: currentUserId}}}; - let travelBefore; let newTravelId; - - // afterAll(async() => { - // try { - // const entries = await models.Entry.find({ - // where: { - // travelFk: newTravelId - // } - // }); - // const entriesId = entries.map(entry => entry.id); - - // // Destroy all entries buys - // await models.Buy.destroyAll({ - // where: { - // entryFk: {inq: entriesId} - // } - // }); - - // // Destroy travel entries - // await models.Entry.destroyAll({ - // where: { - // travelFk: newTravelId - // } - // }); - - // // Destroy new travel - // await models.Travel.destroyById(newTravelId); - - // // Restore original travel shipped & landed - // const travel = await models.Travel.findById(travelId); - // await travel.updateAttributes({ - // shipped: travelBefore.shipped, - // landed: travelBefore.landed - // }); - // } catch (error) { - // console.error(error); - // } - // }); - it(`should clone the travel and the containing entries`, async() => { - pending('#2687 - Cannot make a data rollback because of the triggers'); + const tx = await models.Travel.beginTransaction({ + }); const warehouseThree = 3; const agencyModeOne = 1; - const yesterday = Date.vnNew(); - yesterday.setDate(yesterday.getDate() - 1); + try { + const options = {transaction: tx}; + newTravelId = await models.Travel.cloneWithEntries(ctx, travelId, options); + const travelEntries = await models.Entry.find({ + where: { + travelFk: newTravelId + } + }, options); + const newTravel = await models.Travel.findById(travelId); - travelBefore = await models.Travel.findById(travelId); - await travelBefore.updateAttributes({ - shipped: yesterday, - landed: yesterday - }); + expect(newTravelId).not.toEqual(travelId); + expect(newTravel.ref).toEqual('fifth travel'); + expect(newTravel.warehouseInFk).toEqual(warehouseThree); + expect(newTravel.warehouseOutFk).toEqual(warehouseThree); + expect(newTravel.agencyModeFk).toEqual(agencyModeOne); + expect(travelEntries.length).toBeGreaterThan(0); - newTravelId = await models.Travel.cloneWithEntries(ctx, travelId); - const travelEntries = await models.Entry.find({ - where: { - travelFk: newTravelId - } - }); + await tx.rollback(); + const travelRemoved = await models.Travel.findById(newTravelId, options); - const newTravel = await models.Travel.findById(travelId); - - expect(newTravelId).not.toEqual(travelId); - expect(newTravel.ref).toEqual('fifth travel'); - expect(newTravel.warehouseInFk).toEqual(warehouseThree); - expect(newTravel.warehouseOutFk).toEqual(warehouseThree); - expect(newTravel.agencyModeFk).toEqual(agencyModeOne); - expect(travelEntries.length).toBeGreaterThan(0); + expect(travelRemoved).toBeNull(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }); });