diff --git a/db/changes/10260-holidays/00-travel_cloneWithEntries.sql b/db/changes/10260-holidays/00-travel_cloneWithEntries.sql new file mode 100644 index 000000000..1302a78ca --- /dev/null +++ b/db/changes/10260-holidays/00-travel_cloneWithEntries.sql @@ -0,0 +1,135 @@ +-- DROP PROCEDURE `vn`.`clonTravelComplete`; + +DELIMITER $$ +USE `vn`$$ +CREATE + DEFINER = root@`%` PROCEDURE `vn`.`travel_cloneWithEntries`(IN vTravelFk INT, IN vDateStart DATE, IN vDateEnd DATE, + IN vRef VARCHAR(255), OUT vNewTravelFk INT) +BEGIN + DECLARE vEntryNew INT; + DECLARE vDone BOOLEAN DEFAULT FALSE; + DECLARE vAuxEntryFk INT; + DECLARE vRsEntry CURSOR FOR + SELECT e.id + FROM entry e + JOIN travel t + ON t.id = e.travelFk + WHERE e.travelFk = vTravelFk; + + DECLARE vRsBuy CURSOR FOR + SELECT b.* + FROM buy b + JOIN entry e + ON b.entryFk = e.id + WHERE e.travelFk = vNewTravelFk and b.entryFk=vNewTravelFk + ORDER BY e.id; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + START TRANSACTION; + + INSERT INTO travel (shipped,landed, warehouseInFk, warehouseOutFk, agencyFk, ref, isDelivered, isReceived, m3, kg) + SELECT vDateStart, vDateEnd,warehouseInFk, warehouseOutFk, agencyFk, vRef, isDelivered, isReceived, m3, kg + FROM travel + WHERE id = vTravelFk; + + SET vNewTravelFk = LAST_INSERT_ID(); + SET vDone = FALSE; + OPEN vRsEntry ; + FETCH vRsEntry INTO vAuxEntryFk; + + WHILE NOT vDone DO + INSERT INTO entry (supplierFk, + ref, + isInventory, + isConfirmed, + isOrdered, + isRaid, + commission, + created, + evaNotes, + travelFk, + currencyFk, + companyFk, + gestDocFk, + invoiceInFk) + SELECT supplierFk, + ref, + isInventory, + isConfirmed, + isOrdered, + isRaid, + commission, + created, + evaNotes, + vNewTravelFk, + currencyFk, + companyFk, + gestDocFk, + invoiceInFk + FROM entry + WHERE id = vAuxEntryFk; + + SET vEntryNew = LAST_INSERT_ID(); + + + INSERT INTO buy (entryFk, + itemFk, + quantity, + buyingValue, + packageFk, + stickers, + freightValue, + packageValue, + comissionValue, + packing, + `grouping`, + groupingMode, + location, + price1, + price2, + price3, + minPrice, + producer, + printedStickers, + isChecked, + weight) + SELECT vEntryNew, + itemFk, + quantity, + buyingValue, + packageFk, + stickers, + freightValue, + packageValue, + comissionValue, + packing, + `grouping`, + groupingMode, + location, + price1, + price2, + price3, + minPrice, + producer, + printedStickers, + isChecked, + weight + FROM buy + WHERE entryFk = vAuxEntryFk; + + + FETCH vRsEntry INTO vAuxEntryFk; + END WHILE; + CLOSE vRsEntry; + COMMIT; +END;$$ +DELIMITER ; + + diff --git a/loopback/util/log.js b/loopback/util/log.js index baba3e827..d81fc39a0 100644 --- a/loopback/util/log.js +++ b/loopback/util/log.js @@ -33,6 +33,9 @@ exports.translateValues = async(instance, changes) => { }).format(date); } + if (changes instanceof instance) + changes = changes.__data; + const properties = Object.assign({}, changes); for (let property in properties) { const relation = getRelation(instance, property); @@ -41,13 +44,14 @@ exports.translateValues = async(instance, changes) => { if (relation) { let fieldsToShow = ['alias', 'name', 'code', 'description']; - const log = instance.definition.settings.log; + const modelName = relation.model; + const model = models[modelName]; + const log = model.definition.settings.log; if (log && log.showField) - fieldsToShow = log.showField; + fieldsToShow = [log.showField]; - const model = relation.model; - const row = await models[model].findById(value, { + const row = await model.findById(value, { fields: fieldsToShow }); const newValue = getValue(row); diff --git a/modules/ticket/back/methods/ticket/new.js b/modules/ticket/back/methods/ticket/new.js index 2763f1bd0..8bafe5403 100644 --- a/modules/ticket/back/methods/ticket/new.js +++ b/modules/ticket/back/methods/ticket/new.js @@ -130,7 +130,7 @@ module.exports = Self => { let logRecord = { originFk: cleanInstance.id, userFk: myUserId, - action: 'create', + action: 'insert', changedModel: 'Ticket', changedModelId: cleanInstance.id, oldInstance: {}, diff --git a/modules/travel/back/methods/travel/cloneWithEntries.js b/modules/travel/back/methods/travel/cloneWithEntries.js new file mode 100644 index 000000000..82d6b40e2 --- /dev/null +++ b/modules/travel/back/methods/travel/cloneWithEntries.js @@ -0,0 +1,87 @@ +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', { + description: 'Clone travel', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The original travel id', + http: {source: 'path'} + }], + returns: { + type: 'Object', + description: 'The new cloned travel id', + root: true, + }, + http: { + path: `/:id/cloneWithEntries`, + verb: 'post' + } + }); + + Self.cloneWithEntries = async(ctx, id) => { + const userId = ctx.req.accessToken.userId; + const conn = Self.dataSource.connector; + const models = Self.app.models; + const travel = await Self.findById(id, { + fields: [ + 'id', + 'shipped', + 'landed', + 'warehouseInFk', + 'warehouseOutFk', + 'agencyFk', + 'ref' + ] + }); + const started = new Date(); + const ended = new Date(); + + if (!travel) + throw new UserError('Travel not found'); + + let stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `CALL travel_cloneWithEntries(?, ?, ?, ?, @vTravelFk)`, [ + id, started, ended, travel.ref]); + + stmts.push(stmt); + const index = stmts.push('SELECT @vTravelFk AS id') - 1; + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql); + const [lastInsert] = result[index]; + const newTravel = await Self.findById(lastInsert.id, { + fields: [ + 'id', + 'shipped', + 'landed', + 'warehouseInFk', + 'warehouseOutFk', + 'agencyFk', + 'ref' + ] + }); + + const oldProperties = await loggable.translateValues(Self, travel); + const newProperties = await loggable.translateValues(Self, newTravel); + await models.TravelLog.create({ + originFk: newTravel.id, + userFk: userId, + action: 'insert', + changedModel: 'Travel', + changedModelId: newTravel.id, + oldInstance: oldProperties, + newInstance: newProperties + }); + + return newTravel.id; + }; +}; diff --git a/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js new file mode 100644 index 000000000..ab7d3aa1d --- /dev/null +++ b/modules/travel/back/methods/travel/specs/cloneWithEntries.spec.js @@ -0,0 +1,79 @@ +const app = require('vn-loopback/server/server'); + +// #2687 - Cannot make a data rollback because of the triggers +xdescribe('Travel cloneWithEntries()', () => { + const models = app.models; + const travelId = 5; + const currentUserId = 102; + const ctx = {req: {accessToken: {userId: currentUserId}}}; + let travelBefore; + let newTravelId; + + afterAll(async done => { + 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); + } + + done(); + }); + + it(`should clone the travel and the containing entries`, async() => { + const warehouseThree = 3; + const agencyModeOne = 1; + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + + travelBefore = await models.Travel.findById(travelId); + await travelBefore.updateAttributes({ + shipped: yesterday, + landed: yesterday + }); + + newTravelId = await models.Travel.cloneWithEntries(ctx, travelId); + const travelEntries = await models.Entry.find({ + where: { + travelFk: newTravelId + } + }); + + 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.agencyFk).toEqual(agencyModeOne); + expect(travelEntries.length).toBeGreaterThan(0); + }); +}); diff --git a/modules/travel/back/models/travel.js b/modules/travel/back/models/travel.js index b8a1a24b3..46d33b305 100644 --- a/modules/travel/back/models/travel.js +++ b/modules/travel/back/models/travel.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/travel/deleteThermograph')(Self); require('../methods/travel/updateThermograph')(Self); require('../methods/travel/extraCommunityFilter')(Self); + require('../methods/travel/cloneWithEntries')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/travel/front/create/index.js b/modules/travel/front/create/index.js index 7d0020034..9a9c5ce9d 100644 --- a/modules/travel/front/create/index.js +++ b/modules/travel/front/create/index.js @@ -9,7 +9,7 @@ class Controller extends Section { onSubmit() { return this.$.watcher.submit().then( - res => this.$state.go('travel.card.summary', {id: res.data.id}) + res => this.$state.go('travel.card.basicData', {id: res.data.id}) ); } } diff --git a/modules/travel/front/create/index.spec.js b/modules/travel/front/create/index.spec.js index 4bde7747e..99f52b322 100644 --- a/modules/travel/front/create/index.spec.js +++ b/modules/travel/front/create/index.spec.js @@ -22,7 +22,7 @@ describe('Travel Component vnTravelCreate', () => { controller.onSubmit(); - expect(controller.$state.go).toHaveBeenCalledWith('travel.card.summary', {id: 1234}); + expect(controller.$state.go).toHaveBeenCalledWith('travel.card.basicData', {id: 1234}); }); }); @@ -39,4 +39,3 @@ describe('Travel Component vnTravelCreate', () => { }); }); }); - diff --git a/modules/travel/front/descriptor-menu/index.html b/modules/travel/front/descriptor-menu/index.html index 1eb558008..714d3ce3f 100644 --- a/modules/travel/front/descriptor-menu/index.html +++ b/modules/travel/front/descriptor-menu/index.html @@ -10,6 +10,12 @@ translate> Clone travel + + Clone travel and his entries + @@ -20,3 +26,11 @@ question="Do you want to clone this travel?" message="All it's properties will be copied"> + + + + diff --git a/modules/travel/front/descriptor-menu/index.js b/modules/travel/front/descriptor-menu/index.js index 975cd9134..e83a90b97 100644 --- a/modules/travel/front/descriptor-menu/index.js +++ b/modules/travel/front/descriptor-menu/index.js @@ -59,6 +59,11 @@ class Controller extends Section { }); this.$state.go('travel.create', {q: params}); } + + onCloneWithEntriesAccept() { + this.$http.post(`Travels/${this.travelId}/cloneWithEntries`) + .then(res => this.$state.go('travel.card.basicData', {id: res.data})); + } } Controller.$inject = ['$element', '$scope']; diff --git a/modules/travel/front/descriptor-menu/index.spec.js b/modules/travel/front/descriptor-menu/index.spec.js index d66f3a435..3d94a0963 100644 --- a/modules/travel/front/descriptor-menu/index.spec.js +++ b/modules/travel/front/descriptor-menu/index.spec.js @@ -2,11 +2,14 @@ import './index.js'; describe('Travel Component vnTravelDescriptorMenu', () => { let controller; + let $httpBackend; beforeEach(ngModule('travel')); - beforeEach(inject(($componentController, $state,) => { + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; const $element = angular.element(''); controller = $componentController('vnTravelDescriptorMenu', {$element}); + controller._travelId = 5; })); describe('onCloneAccept()', () => { @@ -36,4 +39,18 @@ describe('Travel Component vnTravelDescriptorMenu', () => { expect(controller.$state.go).toHaveBeenCalledWith('travel.create', {'q': params}); }); }); + + describe('onCloneWithEntriesAccept()', () => { + it('should make an HTTP query and then call to the $state.go method with the returned id', () => { + jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); + + $httpBackend.expect('POST', `Travels/${controller.travelId}/cloneWithEntries`).respond(200, 9); + controller.onCloneWithEntriesAccept(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('travel.card.basicData', { + id: jasmine.any(Number) + }); + }); + }); }); diff --git a/modules/travel/front/descriptor-menu/locale/es.yml b/modules/travel/front/descriptor-menu/locale/es.yml index 117611660..ca61c4e01 100644 --- a/modules/travel/front/descriptor-menu/locale/es.yml +++ b/modules/travel/front/descriptor-menu/locale/es.yml @@ -1 +1,3 @@ -Clone travel: Clonar envío \ No newline at end of file +Clone travel: Clonar envío +Clone travel and his entries: Clonar travel y sus entradas +Do you want to clone this travel and all containing entries?: ¿Quieres clonar este travel y todas las entradas que contiene? \ No newline at end of file diff --git a/modules/travel/front/locale/es.yml b/modules/travel/front/locale/es.yml index 06fc80601..7231d37cd 100644 --- a/modules/travel/front/locale/es.yml +++ b/modules/travel/front/locale/es.yml @@ -13,7 +13,7 @@ Received: Recibido Travel id: Id envío Search travels by id: Buscar envíos por identificador New travel: Nuevo envío -travel: envio +travel: envío # Sections Travels: Envíos diff --git a/modules/travel/front/summary/index.html b/modules/travel/front/summary/index.html index 8815c09e2..de6f6e979 100644 --- a/modules/travel/front/summary/index.html +++ b/modules/travel/front/summary/index.html @@ -7,6 +7,7 @@ {{$ctrl.travelData.id}} - {{$ctrl.travelData.ref}} +