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/db/changes/10260-holidays/00-zoneLog.sql b/db/changes/10260-holidays/00-zoneLog.sql new file mode 100644 index 000000000..13d81bc92 --- /dev/null +++ b/db/changes/10260-holidays/00-zoneLog.sql @@ -0,0 +1,18 @@ +CREATE TABLE `vn`.`zoneLog` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `originFk` int(10) NOT NULL, + `userFk` int(10) unsigned DEFAULT NULL, + `action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL, + `creationDate` timestamp NULL DEFAULT current_timestamp(), + `description` text CHARACTER SET utf8 DEFAULT NULL, + `changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL, + `oldInstance` text COLLATE utf8_unicode_ci DEFAULT NULL, + `newInstance` text COLLATE utf8_unicode_ci DEFAULT NULL, + `changedModelId` int(11) DEFAULT NULL, + `changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `originFk` (`originFk`), + KEY `userFk` (`userFk`), + CONSTRAINT `zoneLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `vn`.`zone` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `zoneLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 0ed3607ad..066d432c8 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -831,7 +831,8 @@ export default { firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)' }, travelExtraCommunity: { - firstTravelReference: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr > vn-td-editable', + anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr', + firstTravelReference: 'vn-travel-extra-community vn-card:nth-child(1) vn-td-editable', removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i' }, travelBasicData: { @@ -863,7 +864,18 @@ export default { travelDescriptor: { filterByAgencyButton: 'vn-descriptor-content .quicklinks > div:nth-child(1) > vn-quick-link > a[vn-tooltip="All travels with current agency"]', dotMenu: 'vn-travel-descriptor vn-icon-button[icon="more_vert"]', - dotMenuClone: '#clone' + dotMenuClone: '#clone', + dotMenuCloneWithEntries: '#cloneWithEntries', + acceptClonation: 'tpl-buttons > button[response="accept"]' + }, + travelCreate: { + reference: 'vn-travel-create vn-textfield[ng-model="$ctrl.travel.ref"]', + agency: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]', + shipped: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.shipped"]', + landed: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.landed"]', + warehouseOut: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]', + warehouseIn: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]', + saveButton: 'vn-travel-create vn-submit[label="Save"]' }, zoneIndex: { searchResult: 'vn-zone-index a.vn-tr', diff --git a/e2e/paths/02-client/03_edit_fiscal_data.spec.js b/e2e/paths/02-client/03_edit_fiscal_data.spec.js index da04c813a..614ede0f0 100644 --- a/e2e/paths/02-client/03_edit_fiscal_data.spec.js +++ b/e2e/paths/02-client/03_edit_fiscal_data.spec.js @@ -200,7 +200,7 @@ describe('Client Edit fiscalData path', () => { it('should confirm the sageTransaction have been edited', async() => { const result = await page.waitToGetProperty(selectors.clientFiscalData.sageTransaction, 'value'); - expect(result).toEqual('Regularización de inversiones'); + expect(result).toEqual('36: Regularización de inversiones'); }); it('should confirm the transferor have been edited', async() => { diff --git a/e2e/paths/06-claim/05_summary.spec.js b/e2e/paths/06-claim/05_summary.spec.js index c63e686cb..cea5edb55 100644 --- a/e2e/paths/06-claim/05_summary.spec.js +++ b/e2e/paths/06-claim/05_summary.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('claim Summary path', () => { +describe('Claim summary path', () => { let browser; let page; const claimId = '4'; diff --git a/e2e/paths/06-claim/06_descriptor.spec.js b/e2e/paths/06-claim/06_descriptor.spec.js index c0affb3cc..0826bad63 100644 --- a/e2e/paths/06-claim/06_descriptor.spec.js +++ b/e2e/paths/06-claim/06_descriptor.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('claim Descriptor path', () => { +describe('Claim descriptor path', () => { let browser; let page; const claimId = '1'; diff --git a/e2e/paths/08-route/03_create_and_clone.spec.js b/e2e/paths/08-route/03_create_and_clone.spec.js index be758f788..c0132362f 100644 --- a/e2e/paths/08-route/03_create_and_clone.spec.js +++ b/e2e/paths/08-route/03_create_and_clone.spec.js @@ -74,6 +74,7 @@ describe('Route create path', () => { }); it(`should clone the first route`, async() => { + await page.waitForTimeout(1000); // needs time for the index to show all items await page.waitToClick(selectors.routeIndex.firstRouteCheckbox); await page.waitToClick(selectors.routeIndex.cloneButton); await page.waitToClick(selectors.routeIndex.submitClonationButton); diff --git a/e2e/paths/10-travel/03_descriptor.spec.js b/e2e/paths/10-travel/03_descriptor.spec.js index 3f79bea06..cdca379ad 100644 --- a/e2e/paths/10-travel/03_descriptor.spec.js +++ b/e2e/paths/10-travel/03_descriptor.spec.js @@ -42,4 +42,48 @@ describe('Travel descriptor path', () => { expect(state).toBe('travel.create'); }); + + it('should edit the data to clone and then get redirected to the cloned travel basic data', async() => { + await page.clearInput(selectors.travelCreate.reference); + await page.write(selectors.travelCreate.reference, 'reference'); + await page.autocompleteSearch(selectors.travelCreate.agency, 'entanglement'); + await page.pickDate(selectors.travelCreate.shipped); + await page.pickDate(selectors.travelCreate.landed); + await page.autocompleteSearch(selectors.travelCreate.warehouseOut, 'warehouse one'); + await page.autocompleteSearch(selectors.travelCreate.warehouseIn, 'warehouse two'); + await page.waitToClick(selectors.travelCreate.saveButton); + await page.waitForState('travel.card.basicData'); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + + it('should atempt to clone the travel and its entries using the descriptor menu but receive an error', async() => { + await page.waitToClick(selectors.travelDescriptor.dotMenu); + await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries); + await page.waitToClick(selectors.travelDescriptor.acceptClonation); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('A travel with this data already exists'); + }); + + it('should update the landed date to a future date to enable cloneWithEntries', async() => { + const nextMonth = new Date(); + nextMonth.setMonth(nextMonth.getMonth() + 1); + await page.pickDate(selectors.travelBasicData.deliveryDate, nextMonth); + await page.waitToClick(selectors.travelBasicData.save); + await page.waitForState('travel.card.basicData'); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + + it('should navigate to the summary and then clone the travel and its entries using the descriptor menu to get redirected to the cloned travel basic data', async() => { + await page.waitToClick('vn-icon[icon="preview"]'); // summary icon + await page.waitForState('travel.card.summary'); + await page.waitToClick(selectors.travelDescriptor.dotMenu); + await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries); + await page.waitToClick(selectors.travelDescriptor.acceptClonation); + await page.waitForState('travel.card.basicData'); + }); }); diff --git a/e2e/paths/10-travel/04_extra_community.spec.js b/e2e/paths/10-travel/04_extra_community.spec.js index bc81c086c..7a37b89e4 100644 --- a/e2e/paths/10-travel/04_extra_community.spec.js +++ b/e2e/paths/10-travel/04_extra_community.spec.js @@ -18,6 +18,7 @@ describe('Travel extra community path', () => { it('should edit the travel reference', async() => { await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter); + await page.waitForSpinnerLoad(); await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); }); diff --git a/e2e/paths/13-supplier/03_fiscal_data.spec.js b/e2e/paths/13-supplier/03_fiscal_data.spec.js index d929288d4..0238c8704 100644 --- a/e2e/paths/13-supplier/03_fiscal_data.spec.js +++ b/e2e/paths/13-supplier/03_fiscal_data.spec.js @@ -23,10 +23,13 @@ describe('Supplier fiscal data path', () => { await page.clearInput(selectors.supplierFiscalData.country); await page.clearInput(selectors.supplierFiscalData.postCode); await page.write(selectors.supplierFiscalData.city, 'Valencia'); + await page.waitForTimeout(1000); // must repeat this action twice or fails. also #2699 may be a cool solution to this. + await page.clearInput(selectors.supplierFiscalData.city); + await page.write(selectors.supplierFiscalData.city, 'Valencia'); await page.clearInput(selectors.supplierFiscalData.socialName); await page.write(selectors.supplierFiscalData.socialName, 'Farmer King SL'); await page.clearInput(selectors.supplierFiscalData.taxNumber); - await page.write(selectors.supplierFiscalData.taxNumber, 'invalid tax number'); + await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number'); await page.clearInput(selectors.supplierFiscalData.account); await page.write(selectors.supplierFiscalData.account, 'edited account number'); await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva'); diff --git a/loopback/common/models/vn-model.js b/loopback/common/models/vn-model.js index f56183df2..cc3eede8e 100644 --- a/loopback/common/models/vn-model.js +++ b/loopback/common/models/vn-model.js @@ -145,9 +145,15 @@ module.exports = function(Self) { rewriteDbError(replaceErrFunc) { function replaceErr(err, replaceErrFunc) { if (Array.isArray(err)) { + const errors = err.filter(error => { + return error != undefined && error != null; + }); let errs = []; - for (let e of err) - errs.push(replaceErrFunc(e)); + for (let e of errors) { + if (!(e instanceof UserError)) + errs.push(replaceErrFunc(e)); + else errs.push(e); + } return errs; } return replaceErrFunc(err); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 0081af429..2e22f828b 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -84,5 +84,6 @@ "companyFk": "Company", "You need to fill sage information before you check verified data": "You need to fill sage information before you check verified data", "The social name cannot be empty": "The social name cannot be empty", - "The nif cannot be empty": "The nif cannot be empty" + "The nif cannot be empty": "The nif cannot be empty", + "A travel with this data already exists": "A travel with this data already exists" } \ No newline at end of file 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/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html index eb3c3b9e3..064a9d4f5 100644 --- a/modules/claim/front/basic-data/index.html +++ b/modules/claim/front/basic-data/index.html @@ -1,8 +1,9 @@ + + form="form" + save="patch"> { +describe('client sendSms()', () => { let createdLog; afterAll(async done => { diff --git a/modules/client/back/methods/sms/send.spec.js b/modules/client/back/methods/sms/send.spec.js index 06288ffb5..612a16cf1 100644 --- a/modules/client/back/methods/sms/send.spec.js +++ b/modules/client/back/methods/sms/send.spec.js @@ -1,8 +1,7 @@ const app = require('vn-loopback/server/server'); const soap = require('soap'); -// Issue #2471 -xdescribe('sms send()', () => { +describe('sms send()', () => { it('should return the expected message and status code', async() => { const code = 200; const smsConfig = await app.models.SmsConfig.findOne(); diff --git a/modules/client/front/address/edit/index.js b/modules/client/front/address/edit/index.js index 30201b880..b8d2e28a4 100644 --- a/modules/client/front/address/edit/index.js +++ b/modules/client/front/address/edit/index.js @@ -42,9 +42,10 @@ export default class Controller extends Section { // Town auto complete set town(selection) { + const oldValue = this._town; this._town = selection; - if (!selection) return; + if (!selection || !oldValue) return; const province = selection.province; const postcodes = selection.postcodes; @@ -62,9 +63,10 @@ export default class Controller extends Section { // Postcode auto complete set postcode(selection) { + const oldValue = this._postcode; this._postcode = selection; - if (!selection) return; + if (!selection || !oldValue) return; const town = selection.town; const province = town.province; diff --git a/modules/client/front/fiscal-data/index.html b/modules/client/front/fiscal-data/index.html index 2b671c69a..2ae9e0475 100644 --- a/modules/client/front/fiscal-data/index.html +++ b/modules/client/front/fiscal-data/index.html @@ -24,12 +24,6 @@ data="sageTaxTypes" order="vat"> - -
@@ -69,12 +63,15 @@ + {{id}}: {{transaction}} { }, { arg: 'account', - type: 'string' + type: 'any' }, { arg: 'sageTaxTypeFk', - type: 'number' + type: 'any' }, { arg: 'sageWithholdingFk', - type: 'number' + type: 'any' }, { arg: 'sageTransactionTypeFk', - type: 'number' + type: 'any' }, { arg: 'postCode', - type: 'string' + type: 'any' + }, + { + arg: 'street', + type: 'any' }, { arg: 'city', @@ -47,11 +51,11 @@ module.exports = Self => { }, { arg: 'provinceFk', - type: 'number' + type: 'any' }, { arg: 'countryFk', - type: 'number' + type: 'any' }], returns: { arg: 'res', diff --git a/modules/supplier/front/fiscal-data/index.html b/modules/supplier/front/fiscal-data/index.html index 804be9784..fc44468f4 100644 --- a/modules/supplier/front/fiscal-data/index.html +++ b/modules/supplier/front/fiscal-data/index.html @@ -24,12 +24,6 @@ data="sageTaxTypes" order="vat"> - - + {{id}}: {{transaction}} diff --git a/modules/supplier/front/fiscal-data/index.js b/modules/supplier/front/fiscal-data/index.js index f2929c91f..8a6a51249 100644 --- a/modules/supplier/front/fiscal-data/index.js +++ b/modules/supplier/front/fiscal-data/index.js @@ -8,9 +8,10 @@ export default class Controller extends Section { // Province auto complete set province(selection) { + const oldValue = this._province; this._province = selection; - if (!selection) return; + if (!selection || !oldValue) return; const country = selection.country; @@ -24,9 +25,10 @@ export default class Controller extends Section { // Town auto complete set town(selection) { + const oldValue = this._town; this._town = selection; - if (!selection) return; + if (!selection || !oldValue) return; const province = selection.province; const country = province.country; 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/ticket/back/methods/ticket/specs/sendSms.spec.js b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js index a08e7555a..20066a5ba 100644 --- a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js +++ b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js @@ -1,7 +1,6 @@ const app = require('vn-loopback/server/server'); -// Issue #2471 -xdescribe('ticket sendSms()', () => { +describe('ticket sendSms()', () => { let logId; afterAll(async done => { diff --git a/modules/travel/back/methods/travel/cloneWithEntries.js b/modules/travel/back/methods/travel/cloneWithEntries.js new file mode 100644 index 000000000..ef385f1e0 --- /dev/null +++ b/modules/travel/back/methods/travel/cloneWithEntries.js @@ -0,0 +1,93 @@ +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; + + try { + 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; + } catch (error) { + if (error.code === 'ER_DUP_ENTRY') + throw new UserError('A travel with this data already exists'); + throw error; + } + }; +}; 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.html b/modules/travel/front/create/index.html index 1400ef6d7..0931c322e 100644 --- a/modules/travel/front/create/index.html +++ b/modules/travel/front/create/index.html @@ -43,7 +43,6 @@ 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..171aa89ec 100644 --- a/modules/travel/front/descriptor-menu/index.html +++ b/modules/travel/front/descriptor-menu/index.html @@ -7,9 +7,17 @@ Clone travel + + Clone travel and his entries + @@ -20,3 +28,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..d34c5495b 100644 --- a/modules/travel/front/descriptor-menu/index.js +++ b/modules/travel/front/descriptor-menu/index.js @@ -48,6 +48,10 @@ class Controller extends Section { .then(res => this.travel = res.data); } + get isBuyer() { + return this.aclService.hasAny(['buyer']); + } + onCloneAccept() { const params = JSON.stringify({ ref: this.travel.ref, @@ -59,6 +63,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}} + diff --git a/modules/zone/back/model-config.json b/modules/zone/back/model-config.json index f353be088..05da8b2c3 100644 --- a/modules/zone/back/model-config.json +++ b/modules/zone/back/model-config.json @@ -31,5 +31,8 @@ }, "ZoneEstimatedDelivery": { "dataSource": "vn" + }, + "ZoneLog": { + "dataSource": "vn" } } diff --git a/modules/zone/back/models/zone-event.json b/modules/zone/back/models/zone-event.json index 9ac1007b5..54c9c4a7d 100644 --- a/modules/zone/back/models/zone-event.json +++ b/modules/zone/back/models/zone-event.json @@ -1,6 +1,10 @@ { "name": "ZoneEvent", - "base": "VnModel", + "base": "Loggable", + "log": { + "model":"ZoneLog", + "relation": "zone" + }, "options": { "mysql": { "table": "zoneEvent" diff --git a/modules/zone/back/models/zone-included.json b/modules/zone/back/models/zone-included.json index 9f9508824..e462b7a65 100644 --- a/modules/zone/back/models/zone-included.json +++ b/modules/zone/back/models/zone-included.json @@ -1,6 +1,11 @@ { "name": "ZoneIncluded", - "base": "VnModel", + "base": "Loggable", + "log": { + "model": "ZoneLog", + "relation": "zone", + "showField": "isIncluded" + }, "options": { "mysql": { "table": "zoneIncluded" diff --git a/modules/zone/back/models/zone-log.json b/modules/zone/back/models/zone-log.json new file mode 100644 index 000000000..ddca9261b --- /dev/null +++ b/modules/zone/back/models/zone-log.json @@ -0,0 +1,58 @@ +{ + "name": "ZoneLog", + "base": "VnModel", + "options": { + "mysql": { + "table": "zoneLog" + } + }, + "properties": { + "id": { + "id": true, + "type": "Number", + "forceId": false + }, + "originFk": { + "type": "Number", + "required": true + }, + "userFk": { + "type": "Number" + }, + "action": { + "type": "String", + "required": true + }, + "changedModel": { + "type": "String" + }, + "oldInstance": { + "type": "Object" + }, + "newInstance": { + "type": "Object" + }, + "creationDate": { + "type": "Date" + }, + "changedModelId": { + "type": "String" + }, + "changedModelValue": { + "type": "String" + }, + "description": { + "type": "String" + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userFk" + } + }, + "scope": { + "order": ["creationDate DESC", "id DESC"] + } +} diff --git a/modules/zone/back/models/zone-warehouse.json b/modules/zone/back/models/zone-warehouse.json index 14c4e84a4..0f0e43f4a 100644 --- a/modules/zone/back/models/zone-warehouse.json +++ b/modules/zone/back/models/zone-warehouse.json @@ -1,6 +1,10 @@ { "name": "ZoneWarehouse", - "base": "VnModel", + "base": "Loggable", + "log": { + "model":"ZoneLog", + "relation": "zone" + }, "options": { "mysql": { "table": "zoneWarehouse" diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json index e94146946..bafef0a95 100644 --- a/modules/zone/back/models/zone.json +++ b/modules/zone/back/models/zone.json @@ -1,6 +1,10 @@ { "name": "Zone", - "base": "VnModel", + "base": "Loggable", + "log": { + "model":"ZoneLog", + "showField": "name" + }, "options": { "mysql": { "table": "zone" @@ -39,11 +43,6 @@ } }, "relations": { - "geolocations": { - "type": "hasMany", - "model": "ZoneGeo", - "foreignKey": "zoneFk" - }, "agencyMode": { "type": "belongsTo", "model": "AgencyMode", diff --git a/modules/zone/front/index.js b/modules/zone/front/index.js index 26c491709..dc20eea47 100644 --- a/modules/zone/front/index.js +++ b/modules/zone/front/index.js @@ -16,3 +16,4 @@ import './calendar'; import './location'; import './calendar'; import './upcoming-deliveries'; +import './log'; diff --git a/modules/zone/front/log/index.html b/modules/zone/front/log/index.html new file mode 100644 index 000000000..539afda82 --- /dev/null +++ b/modules/zone/front/log/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/zone/front/log/index.js b/modules/zone/front/log/index.js new file mode 100644 index 000000000..8c3be2423 --- /dev/null +++ b/modules/zone/front/log/index.js @@ -0,0 +1,7 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +ngModule.vnComponent('vnZoneLog', { + template: require('./index.html'), + controller: Section, +}); diff --git a/modules/zone/front/routes.json b/modules/zone/front/routes.json index 6c799dcc8..0134c3b78 100644 --- a/modules/zone/front/routes.json +++ b/modules/zone/front/routes.json @@ -14,6 +14,7 @@ {"state": "zone.card.basicData", "icon": "settings"}, {"state": "zone.card.location", "icon": "my_location"}, {"state": "zone.card.warehouses", "icon": "home"}, + {"state": "zone.card.log", "icon": "history"}, {"state": "zone.card.events", "icon": "today"} ] }, @@ -84,6 +85,11 @@ "params": { "zone": "$ctrl.zone" } + }, { + "url" : "/log", + "state": "zone.card.log", + "component": "vn-zone-log", + "description": "Log" } ] } \ No newline at end of file