diff --git a/db/changes/10271-wisemen/00-ACL.sql b/db/changes/10271-wisemen/00-ACL.sql new file mode 100644 index 000000000..40e47b1a3 --- /dev/null +++ b/db/changes/10271-wisemen/00-ACL.sql @@ -0,0 +1 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('FixedPrice', '*', '*', 'ALLOW', 'ROLE', 'buyer'); \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 72e048772..ad81b9b3a 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -250,6 +250,18 @@ export default { taxClassCheckbox: '.vn-popover.shown vn-horizontal:nth-child(11) > vn-check', saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' }, + itemFixedPrice: { + add: 'vn-fixed-price vn-icon[icon="add_circle"]', + fourthFixedPrice: 'vn-fixed-price vn-tr:nth-child(4)', + fourthItemID: 'vn-fixed-price vn-tr:nth-child(4) vn-autocomplete[ng-model="price.itemFk"]', + fourthWarehouse: 'vn-fixed-price vn-tr:nth-child(4) vn-autocomplete[ng-model="price.warehouseFk"]', + fourthPPU: 'vn-fixed-price vn-tr:nth-child(4) > vn-td-editable:nth-child(4)', + fourthPPP: 'vn-fixed-price vn-tr:nth-child(4) > vn-td-editable:nth-child(5)', + fourthMinPrice: 'vn-fixed-price vn-tr:nth-child(4) > vn-td-editable:nth-child(6)', + fourthStarted: 'vn-fixed-price vn-tr:nth-child(4) vn-date-picker[ng-model="price.started"]', + fourthEnded: 'vn-fixed-price vn-tr:nth-child(4) vn-date-picker[ng-model="price.ended"]', + fourthDeleteIcon: 'vn-fixed-price vn-tr:nth-child(4) > vn-td:nth-child(9) > vn-icon-button[icon="delete"]' + }, itemCreateView: { temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', type: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]', @@ -793,13 +805,13 @@ export default { workerCalendar: { year: 'vn-worker-calendar vn-autocomplete[ng-model="$ctrl.year"]', totalHolidaysUsed: 'vn-worker-calendar div.totalBox > div', - januaryThirtyFirst: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(33) > div', - marchTwentyThird: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div', - mayFourth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div', - mayEighth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(12) > div', - mayTwelfth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div', - mayThirteenth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div', - mayFourteenth: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div', + penultimateMondayOfJanuary: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(22) > div', + lastMondayOfMarch: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div', + fistMondayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div', + secondFridayOfJun: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(12) > div', + secondTuesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div', + secondWednesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div', + secondThursdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div', holidays: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(1)', absence: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(2)', halfHoliday: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(3)', @@ -828,12 +840,13 @@ export default { }, travelIndex: { anySearchResult: 'vn-travel-index vn-tbody > a', - firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)' + firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)', + firstTravelAddEntryButton: 'vn-travel-index a:nth-child(1) vn-icon[icon="icon-ticket"]', }, travelExtraCommunity: { 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[name="reference"]', - firstTravelLockedKg: 'vn-travel-extra-community vn-card:nth-child(1) vn-td-editable[name="lockedKg"]', + firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]', + firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]', removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i' }, travelBasicData: { @@ -867,6 +880,7 @@ export default { dotMenu: 'vn-travel-descriptor vn-icon-button[icon="more_vert"]', dotMenuClone: '#clone', dotMenuCloneWithEntries: '#cloneWithEntries', + dotMenuAddEntry: '[name="addEntry"]', acceptClonation: 'tpl-buttons > button[response="accept"]' }, travelCreate: { @@ -897,6 +911,10 @@ export default { volumetric: 'vn-zone-basic-data vn-check[ng-model="$ctrl.zone.isVolumetric"]', saveButton: 'vn-zone-basic-data vn-submit > button', }, + entryCreate: { + travel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]', + company: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]' + }, entrySummary: { header: 'vn-entry-summary > vn-card > h5', reference: 'vn-entry-summary vn-label-value[label="Reference"]', diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js index e82006b3c..08ef71f13 100644 --- a/e2e/paths/03-worker/05_calendar.spec.js +++ b/e2e/paths/03-worker/05_calendar.spec.js @@ -27,31 +27,31 @@ describe('Worker calendar path', () => { it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => { await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst); + await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.absence); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.marchTwentyThird); + await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.halfHoliday); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayFourth); + await page.waitToClick(selectors.workerCalendar.fistMondayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.furlough); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayTwelfth); + await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayThirteenth); + await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayFourteenth); + await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.halfFurlough); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayEighth); + await page.waitToClick(selectors.workerCalendar.secondFridayOfJun); await page.waitForTimeout(reasonableTimeBetweenClicks); const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); @@ -71,31 +71,31 @@ describe('Worker calendar path', () => { await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst); + await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.absence); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.marchTwentyThird); + await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.halfHoliday); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayFourth); + await page.waitToClick(selectors.workerCalendar.fistMondayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.furlough); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayTwelfth); + await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayThirteenth); + await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayFourteenth); + await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay); await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.halfFurlough); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.mayEighth); + await page.waitToClick(selectors.workerCalendar.secondFridayOfJun); }); it('should check the total holidays used are back to what it was', async() => { @@ -116,7 +116,7 @@ describe('Worker calendar path', () => { await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); - await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst); + await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); }); it('should check the total holidays used are now the initial ones', async() => { diff --git a/e2e/paths/04-item/14_fixedPrice.spec.js b/e2e/paths/04-item/14_fixedPrice.spec.js new file mode 100644 index 000000000..477daa151 --- /dev/null +++ b/e2e/paths/04-item/14_fixedPrice.spec.js @@ -0,0 +1,61 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Item fixed prices path', () => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('buyer', 'item'); + await page.accessToSection('item.fixedPrice'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should click on the add new foxed price button', async() => { + await page.waitToClick(selectors.itemFixedPrice.add); + await page.waitForSelector(selectors.itemFixedPrice.fourthFixedPrice); + }); + + it('should fill the fixed price data', async() => { + const now = new Date(); + const searchValue = 'Chest ammo box'; + await page.waitToClick(selectors.itemFixedPrice.fourthItemID); + await page.write('body > div > div > div.content > div.filter.ng-scope > vn-textfield', searchValue); + try { + await page.waitForFunction(searchValue => { + const element = document.querySelector('li.active'); + if (element) + return element.innerText.toLowerCase().includes(searchValue.toLowerCase()); + }, {}, searchValue); + } catch (error) { + const builtSelector = await page.selectorFormater(selectors.ticketSales.moreMenuState); + const inputValue = await page.evaluate(() => { + return document.querySelector('.vn-drop-down.shown vn-textfield input').value; + }); + throw new Error(`${builtSelector} value is ${inputValue}! ${error}`); + } + await page.keyboard.press('Enter'); + await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one'); + await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPPU, '20'); + await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPPP, '10'); + await page.writeOnEditableTD(selectors.itemFixedPrice.fourthMinPrice, '5'); + await page.pickDate(selectors.itemFixedPrice.fourthStarted, now); + await page.pickDate(selectors.itemFixedPrice.fourthEnded, now); + await page.waitForTimeout(1000); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + + it('should reload the section and check the created price has the expected ID', async() => { + await page.accessToSection('item.index'); + await page.accessToSection('item.fixedPrice'); + const result = await page.getProperty('vn-fixed-price > div > vn-card > vn-table > div > vn-tbody > vn-tr:nth-child(4) > vn-td:nth-child(1) > span', 'innerText'); + + expect(result).toContain('13'); + }); +}); diff --git a/e2e/paths/10-travel/03_descriptor.spec.js b/e2e/paths/10-travel/03_descriptor.spec.js index cdca379ad..a1a035317 100644 --- a/e2e/paths/10-travel/03_descriptor.spec.js +++ b/e2e/paths/10-travel/03_descriptor.spec.js @@ -33,6 +33,61 @@ describe('Travel descriptor path', () => { expect(state).toBe('travel.card.summary'); }); + it('should be redirected to the create entry view', async() => { + await page.waitToClick(selectors.travelDescriptor.dotMenu); + await page.waitToClick(selectors.travelDescriptor.dotMenuAddEntry); + await page.waitForState('entry.create'); + const state = await page.getState(); + + expect(state).toBe('entry.create'); + }); + + it('should check some data was imported from the travel', async() => { + const travel = await page.waitToGetProperty(selectors.entryCreate.travel, 'value'); + const campany = await page.waitToGetProperty(selectors.entryCreate.company, 'value'); + + expect(travel).toContain('Warehouse'); + expect(campany).toContain('VNL'); + }); + + it('should navigate back to the travel index', async() => { + await page.waitToClick('.cancel'); + await page.waitToClick(selectors.globalItems.homeButton); + await page.selectModule('travel'); + await page.waitForState('travel.index'); + const state = await page.getState(); + + expect(state).toBe('travel.index'); + }); + + it('should click on the add entry button of the third result to be redirected to create entry', async() => { + await page.keyboard.press('Enter'); + await page.waitToClick(selectors.travelIndex.firstTravelAddEntryButton); + await page.waitForState('entry.create'); + const state = await page.getState(); + + expect(state).toBe('entry.create'); + }); + + it('should check again some data was imported from the travel', async() => { + const travel = await page.waitToGetProperty(selectors.entryCreate.travel, 'value'); + const campany = await page.waitToGetProperty(selectors.entryCreate.company, 'value'); + + expect(travel).toContain('Warehouse'); + expect(campany).toContain('VNL'); + }); + + it('should navigate to the travel summary of a given travel', async() => { + await page.waitToClick('.cancel'); + await page.waitToClick(selectors.globalItems.homeButton); + await page.selectModule('travel'); + await page.accessToSearchResult('3'); + await page.waitForState('travel.card.summary'); + const state = await page.getState(); + + expect(state).toBe('travel.card.summary'); + }); + it('should be redirected to the create travel when using the clone option of the dot menu', async() => { await page.waitToClick(selectors.travelDescriptor.dotMenu); await page.waitToClick(selectors.travelDescriptor.dotMenuClone); diff --git a/e2e/paths/10-travel/04_extra_community.spec.js b/e2e/paths/10-travel/04_extra_community.spec.js index d902bb45e..60a3fddb3 100644 --- a/e2e/paths/10-travel/04_extra_community.spec.js +++ b/e2e/paths/10-travel/04_extra_community.spec.js @@ -21,6 +21,7 @@ describe('Travel extra community path', () => { await page.waitForSpinnerLoad(); await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500'); + await page.waitForTimeout(1000); }); it('should reload the index and confirm the reference and locked kg were edited', async() => { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index b54c0cc67..aa54c6b94 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -166,5 +166,6 @@ "The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta", "Sorts whole route": "Reordena ruta entera", "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}}" + "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}}", + "That item doesn't exists": "Ese artículo no existe" } \ No newline at end of file diff --git a/modules/client/back/methods/client/updateAddress.js b/modules/client/back/methods/client/updateAddress.js index db8ed38d6..27f9faf1c 100644 --- a/modules/client/back/methods/client/updateAddress.js +++ b/modules/client/back/methods/client/updateAddress.js @@ -5,7 +5,7 @@ module.exports = function(Self) { description: 'Updates a client address updating default address', accepts: [{ arg: 'ctx', - type: 'Object', + type: 'object', http: {source: 'context'} }, { @@ -70,7 +70,7 @@ module.exports = function(Self) { }], returns: { root: true, - type: 'Object' + type: 'object' }, http: { verb: 'patch', diff --git a/modules/item/back/methods/fixed-price/filter.js b/modules/item/back/methods/fixed-price/filter.js new file mode 100644 index 000000000..22cf2bf44 --- /dev/null +++ b/modules/item/back/methods/fixed-price/filter.js @@ -0,0 +1,192 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('filter', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + }, + { + arg: 'search', + type: 'string', + description: `If it's and integer searchs by itemFk, otherwise it searchs by the itemType code`, + }, + { + arg: 'itemFk', + type: 'integer', + description: 'The item id', + }, + { + arg: 'typeFk', + type: 'integer', + description: 'The item type id', + }, + { + arg: 'categoryFk', + type: 'integer', + description: 'The item category id', + }, + { + arg: 'warehouseFk', + type: 'integer', + description: 'The warehouse id', + }, + { + arg: 'buyerFk', + type: 'integer', + description: 'The buyer id', + }, + { + arg: 'rate2', + type: 'integer', + description: 'The price per unit', + }, + { + arg: 'rate3', + type: 'integer', + description: 'The price per package', + }, + { + arg: 'minPrice', + type: 'integer', + description: 'The minimum price of the item', + }, + { + arg: 'hasMinPrice', + type: 'boolean', + description: 'whether a minimum price has been defined for the item', + }, + { + arg: 'started', + type: 'date', + description: 'Price validity start date', + }, + { + arg: 'ended', + type: 'date', + description: 'Price validity end date', + }, + { + arg: 'tags', + type: ['object'], + description: 'List of tags to filter with', + }, + { + arg: 'mine', + type: 'Boolean', + description: `Search requests attended by the current user` + } + ], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/filter`, + verb: 'GET' + } + }); + + Self.filter = async(ctx, filter) => { + const conn = Self.dataSource.connector; + let userId = ctx.req.accessToken.userId; + + if (ctx.args.mine) + ctx.args.buyerFk = userId; + + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {'fp.itemFk': {inq: value}} + : {'it.code': {like: `%${value}%`}}; + case 'categoryFk': + return {'it.categoryFk': value}; + case 'buyerFk': + return {'it.workerFk': value}; + case 'warehouseFk': + case 'rate2': + case 'rate3': + case 'started': + case 'ended': + param = `fp.${param}`; + return {[param]: value}; + case 'minPrice': + case 'hasMinPrice': + case 'typeFk': + param = `i.${param}`; + return {[param]: value}; + } + }); + filter = mergeFilters(filter, {where}); + + const stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `SELECT fp.id, + fp.itemFk, + fp.warehouseFk, + fp.rate2, + fp.rate3, + fp.started, + fp.ended, + i.minPrice, + i.hasMinPrice, + i.name, + i.subName, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10 + FROM priceFixed fp + JOIN item i ON i.id = fp.itemFk + JOIN itemType it ON it.id = i.typeFk` + ); + + if (ctx.args.tags) { + let i = 1; + for (const tag of ctx.args.tags) { + const tAlias = `it${i++}`; + + if (tag.tagFk) { + stmt.merge({ + sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id + AND ${tAlias}.tagFk = ? + AND ${tAlias}.value LIKE ?`, + params: [tag.tagFk, `%${tag.value}%`], + }); + } else { + stmt.merge({ + sql: `JOIN vn.itemTag ${tAlias} ON ${tAlias}.itemFk = i.id + AND ${tAlias}.value LIKE ?`, + params: [`%${tag.value}%`], + }); + } + } + } + + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(conn.makePagination(filter)); + + const fixedPriceIndex = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql); + return fixedPriceIndex === 0 ? result : result[fixedPriceIndex]; + }; +}; diff --git a/modules/item/back/methods/fixed-price/specs/filter.spec.js b/modules/item/back/methods/fixed-price/specs/filter.spec.js new file mode 100644 index 000000000..ff259357f --- /dev/null +++ b/modules/item/back/methods/fixed-price/specs/filter.spec.js @@ -0,0 +1,86 @@ +const app = require('vn-loopback/server/server'); + +describe('fixed price filter()', () => { + it('should return 1 result filtering by item ID', async() => { + const itemID = 3; + const ctx = { + req: {accessToken: {userId: 1}}, + args: { + search: itemID + } + }; + const result = await app.models.FixedPrice.filter(ctx); + + expect(result.length).toEqual(1); + expect(result[0].id).toEqual(2); + expect(result[0].itemFk).toEqual(itemID); + }); + + it('should return 1 result filtering by item type code', async() => { + const itemCode = 'CRI'; + const ctx = { + req: {accessToken: {userId: 1}}, + args: { + search: itemCode + } + }; + const itemType = await app.models.ItemType.findOne({ + where: {code: itemCode}, + fields: ['id'] + }); + const items = await app.models.Item.find({ + where: {typeFk: itemType.id}, + fields: ['id'] + }); + const IDs = items.map(item => { + return item.id; + }); + + const result = await app.models.FixedPrice.filter(ctx); + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.id).toEqual(2); + expect(IDs).toContain(firstResult.itemFk); + }); + + it('should return 2 results filtering by warehouse', async() => { + const warehouseID = 1; + const ctx = { + req: {accessToken: {userId: 1}}, + args: { + warehouseFk: warehouseID + } + }; + const result = await app.models.FixedPrice.filter(ctx); + const length = result.length; + const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; + + expect(result.length).toEqual(2); + expect(anyResult.warehouseFk).toEqual(warehouseID); + }); + + it('should return no results filtering by hasMinPrice', async() => { + const ctx = { + req: {accessToken: {userId: 1}}, + args: { + hasMinPrice: true + } + }; + const result = await app.models.FixedPrice.filter(ctx); + + expect(result.length).toEqual(0); + }); + + it('should return no results filtering by typeFk', async() => { + const ctx = { + req: {accessToken: {userId: 1}}, + args: { + typeFk: 1 + } + }; + const result = await app.models.FixedPrice.filter(ctx); + + expect(result.length).toEqual(1); + }); +}); diff --git a/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js b/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js new file mode 100644 index 000000000..63c211293 --- /dev/null +++ b/modules/item/back/methods/fixed-price/specs/upsertFixedPrice.spec.js @@ -0,0 +1,62 @@ +const app = require('vn-loopback/server/server'); + +describe('upsertFixedPrice()', () => { + const now = new Date(); + const fixedPriceId = 1; + let originalFixedPrice; + let originalItem; + + beforeAll(async() => { + originalFixedPrice = await app.models.FixedPrice.findById(fixedPriceId); + originalItem = await app.models.Item.findById(originalFixedPrice.itemFk); + }); + + afterAll(async() => { + await originalFixedPrice.save(); + await originalItem.save(); + }); + + it(`should toggle the hasMinPrice boolean if there's a minPrice and update the rest of the data`, async() => { + const ctx = {args: { + id: fixedPriceId, + itemFk: originalFixedPrice.itemFk, + warehouseFk: 1, + rate2: 100, + rate3: 300, + started: now, + ended: now, + minPrice: 100, + hasMinPrice: false + }}; + + const result = await app.models.FixedPrice.upsertFixedPrice(ctx, ctx.args.id); + + delete ctx.args.started; + delete ctx.args.ended; + ctx.args.hasMinPrice = true; + + expect(result).toEqual(jasmine.objectContaining(ctx.args)); + }); + + it(`should toggle the hasMinPrice boolean if there's no minPrice and update the rest of the data`, async() => { + const ctx = {args: { + id: fixedPriceId, + itemFk: originalFixedPrice.itemFk, + warehouseFk: 1, + rate2: 2.5, + rate3: 2, + started: now, + ended: now, + minPrice: 0, + hasMinPrice: true + }}; + + const result = await app.models.FixedPrice.upsertFixedPrice(ctx, ctx.args.id); + + delete ctx.args.started; + delete ctx.args.ended; + ctx.args.hasMinPrice = false; + + expect(result).toEqual(jasmine.objectContaining(ctx.args)); + }); +}); diff --git a/modules/item/back/methods/fixed-price/upsertFixedPrice.js b/modules/item/back/methods/fixed-price/upsertFixedPrice.js new file mode 100644 index 000000000..dbdeebdab --- /dev/null +++ b/modules/item/back/methods/fixed-price/upsertFixedPrice.js @@ -0,0 +1,116 @@ +module.exports = Self => { + Self.remoteMethod('upsertFixedPrice', { + description: 'Inserts or updates a fixed price for an item', + accessType: 'WRITE', + accepts: [{ + arg: 'ctx', + type: 'object', + http: {source: 'context'} + }, + { + arg: 'id', + type: 'number', + description: 'The fixed price id' + }, + { + arg: 'itemFk', + type: 'number' + }, + { + arg: 'warehouseFk', + type: 'number' + }, + { + arg: 'started', + type: 'date' + }, + { + arg: 'ended', + type: 'date' + }, + { + arg: 'rate2', + type: 'number' + }, + { + arg: 'rate3', + type: 'number' + }, + { + arg: 'minPrice', + type: 'number' + }, + { + arg: 'hasMinPrice', + type: 'any' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/upsertFixedPrice`, + verb: 'PATCH' + } + }); + + Self.upsertFixedPrice = async ctx => { + const models = Self.app.models; + + const args = ctx.args; + const tx = await models.Address.beginTransaction({}); + try { + const options = {transaction: tx}; + delete args.ctx; // removed unwanted data + + const fixedPrice = await models.FixedPrice.upsert(args, options); + const targetItem = await models.Item.findById(args.itemFk, null, options); + await targetItem.updateAttributes({ + minPrice: args.minPrice, + hasMinPrice: args.minPrice ? true : false + }, options); + + const itemFields = [ + 'minPrice', + 'hasMinPrice', + 'name', + 'subName', + 'tag5', + 'value5', + 'tag6', + 'value6', + 'tag7', + 'value7', + 'tag8', + 'value8', + 'tag9', + 'value9', + 'tag10', + 'value10' + ]; + + const fieldsCopy = [].concat(itemFields); + const filter = { + include: { + relation: 'item', + scope: { + fields: fieldsCopy + } + } + }; + + const result = await models.FixedPrice.findById(fixedPrice.id, filter, options); + const item = result.item(); + + for (let key of itemFields) + result[key] = item[key]; + + await tx.commit(); + return result; + } catch (e) { + await tx.rollback(); + throw e; + } + }; +}; + diff --git a/modules/item/back/methods/item/new.js b/modules/item/back/methods/item/new.js index 1d3f6f230..e6288aede 100644 --- a/modules/item/back/methods/item/new.js +++ b/modules/item/back/methods/item/new.js @@ -60,7 +60,6 @@ module.exports = Self => { query = `SET @isTriggerDisabled = FALSE`; await Self.rawSql(query, null, options); - query = `CALL vn.itemRefreshTags(?)`; await Self.rawSql(query, [item.id], options); await tx.commit(); diff --git a/modules/item/back/model-config.json b/modules/item/back/model-config.json index 13e30dc15..9f101f9c7 100644 --- a/modules/item/back/model-config.json +++ b/modules/item/back/model-config.json @@ -76,5 +76,8 @@ }, "TaxType": { "dataSource": "vn" + }, + "FixedPrice": { + "dataSource": "vn" } } diff --git a/modules/item/back/models/fixed-price.js b/modules/item/back/models/fixed-price.js new file mode 100644 index 000000000..9c78c586f --- /dev/null +++ b/modules/item/back/models/fixed-price.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/fixed-price/filter')(Self); + require('../methods/fixed-price/upsertFixedPrice')(Self); +}; diff --git a/modules/item/back/models/fixed-price.json b/modules/item/back/models/fixed-price.json new file mode 100644 index 000000000..85e9194a3 --- /dev/null +++ b/modules/item/back/models/fixed-price.json @@ -0,0 +1,59 @@ +{ + "name": "FixedPrice", + "base": "VnModel", + "options": { + "mysql": { + "table": "priceFixed" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "itemFk": { + "type": "number", + "required": true + }, + "warehouseFk": { + "type": "number" + }, + "rate2": { + "type": "number", + "required": true + }, + "rate3": { + "type": "number", + "required": true + }, + "started": { + "type": "date", + "required": true + }, + "ended": { + "type": "date", + "required": true + } + }, + "relations": { + "item": { + "type": "belongsTo", + "model": "Item", + "foreignKey": "itemFk" + }, + "warehouse": { + "type": "belongsTo", + "model": "Warehouse", + "foreignKey": "warehouseFk" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "buyer", + "permission": "ALLOW" + } + ] +} \ No newline at end of file diff --git a/modules/item/back/models/item.json b/modules/item/back/models/item.json index 96fec5161..d48e4e95d 100644 --- a/modules/item/back/models/item.json +++ b/modules/item/back/models/item.json @@ -12,119 +12,119 @@ }, "properties": { "id": { - "type": "Number", + "type": "number", "id": true, "description": "Id" }, "name": { - "type": "String", + "type": "string", "description": "Name" }, "size": { - "type": "Number", + "type": "number", "description": "Size" }, "category": { - "type": "String", + "type": "string", "description": "Category" }, "typeFk": { - "type": "Number", + "type": "number", "description": "Type", "required": true }, "stems": { - "type": "Number", + "type": "number", "description": "Stems" }, "description": { - "type": "String", + "type": "string", "description": "Description" }, "isOnOffer": { - "type": "Boolean", + "type": "boolean", "description": "Offer" }, "isBargain": { - "type": "Boolean", + "type": "boolean", "description": "Bargain" }, "isActive": { - "type": "Boolean", + "type": "boolean", "description": "Active" }, "comment": { - "type": "String", + "type": "string", "description": "Comment" }, "relevancy": { - "type": "Number", + "type": "number", "description": "Relevancy" }, "density": { - "type": "Number", + "type": "number", "description": "Density" }, "stemMultiplier": { - "type": "Number", + "type": "number", "description": "Multiplier" },"image": { - "type": "String", + "type": "string", "description": "Image" }, "longName": { - "type": "String", + "type": "string", "description": "Long name" }, "subName": { - "type": "String", + "type": "string", "description": "Subname" }, "tag5": { - "type": "String" + "type": "string" }, "value5": { - "type": "String" + "type": "string" }, "tag6": { - "type": "String" + "type": "string" }, "value6": { - "type": "String" + "type": "string" }, "tag7": { - "type": "String" + "type": "string" }, "value7": { - "type": "String" + "type": "string" }, "tag8": { - "type": "String" + "type": "string" }, "value8": { - "type": "String" + "type": "string" }, "tag9": { - "type": "String" + "type": "string" }, "value9": { - "type": "String" + "type": "string" }, "tag10": { - "type": "String" + "type": "string" }, "value10": { - "type": "String" + "type": "string" }, "compression": { - "type": "Number" + "type": "number" }, "hasKgPrice": { - "type": "Boolean", + "type": "boolean", "description": "Price per Kg" }, "expenseFk": { - "type": "Number", + "type": "number", "mysql": { "columnName": "expenceFk" } @@ -132,8 +132,11 @@ "minPrice": { "type": "number" }, + "hasMinPrice": { + "type": "boolean" + }, "isFragile": { - "type": "Boolean" + "type": "boolean" } }, "relations": { diff --git a/modules/item/front/fixed-price-search-panel/index.html b/modules/item/front/fixed-price-search-panel/index.html new file mode 100644 index 000000000..5a1e7781e --- /dev/null +++ b/modules/item/front/fixed-price-search-panel/index.html @@ -0,0 +1,136 @@ + + +
+
+ + + + + + + + + +
{{name}}
+
+ {{category.name}} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + Tags + + + + + + + + + + + + + + + + + +
+
diff --git a/modules/item/front/fixed-price-search-panel/index.js b/modules/item/front/fixed-price-search-panel/index.js new file mode 100644 index 000000000..ec13765fd --- /dev/null +++ b/modules/item/front/fixed-price-search-panel/index.js @@ -0,0 +1,19 @@ +import ngModule from '../module'; +import SearchPanel from 'core/components/searchbar/search-panel'; + +class Controller extends SearchPanel { + get filter() { + return this.$.filter; + } + + set filter(value = {}) { + if (!value.tags) value.tags = [{}]; + + this.$.filter = value; + } +} + +ngModule.vnComponent('vnFixedPriceSearchPanel', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/item/front/fixed-price-search-panel/locale/es.yml b/modules/item/front/fixed-price-search-panel/locale/es.yml new file mode 100644 index 000000000..06e5e6b26 --- /dev/null +++ b/modules/item/front/fixed-price-search-panel/locale/es.yml @@ -0,0 +1,4 @@ +Started: Inicio +Ended: Fin +Minimum price: Precio mínimo +Item ID: ID Artículo \ No newline at end of file diff --git a/modules/item/front/fixed-price/index.html b/modules/item/front/fixed-price/index.html new file mode 100644 index 000000000..770405e3a --- /dev/null +++ b/modules/item/front/fixed-price/index.html @@ -0,0 +1,159 @@ + + + + + + + + +
+ + + + + Item ID + Item + Warehouse + P.P.U. + P.P.P. + Min price + Started + Ended + + + + + + + + {{price.itemFk}} + + + + {{::id}} - {{::name}} + + + + + + + + + + + + + + + {{price.rate2 | currency: 'EUR':2}} + + + + + + + {{price.rate3 | currency: 'EUR':2}} + + + + + + + {{(price.hasMinPrice ? (price.minPrice | currency: 'EUR':2) : "-")}} + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + \ No newline at end of file diff --git a/modules/item/front/fixed-price/index.js b/modules/item/front/fixed-price/index.js new file mode 100644 index 000000000..4dc1cf472 --- /dev/null +++ b/modules/item/front/fixed-price/index.js @@ -0,0 +1,54 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + } + + /** + * Inserts a new instance + */ + add() { + this.$.model.insert({}); + } + + upsertPrice(price) { + price.hasMinPrice = price.minPrice ? true : false; + + let requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3']; + for (let field of requiredFields) + if (price[field] == undefined) return; + + const query = 'FixedPrices/upsertFixedPrice'; + this.$http.patch(query, price) + .then(res => { + this.vnApp.showSuccess(this.$t('Data saved!')); + Object.assign(price, res.data); + }); + } + + removePrice($index) { + const price = this.$.model.data[$index]; + if (price.id) { + this.$http.delete(`FixedPrices/${price.id}`) + .then(() => { + this.$.model.remove($index); + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } else + this.$.model.remove($index); + } + + itemSearchFunc($search) { + return /^\d+$/.test($search) + ? {id: $search} + : {name: {like: '%' + $search + '%'}}; + } +} + +ngModule.vnComponent('vnFixedPrice', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/item/front/fixed-price/index.spec.js b/modules/item/front/fixed-price/index.spec.js new file mode 100644 index 000000000..faf6b850a --- /dev/null +++ b/modules/item/front/fixed-price/index.spec.js @@ -0,0 +1,87 @@ +import './index'; + +describe('fixed price', () => { + describe('Component vnFixedPrice', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('item')); + + beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => { + $httpBackend = _$httpBackend_; + const $scope = $rootScope.$new(); + const $element = angular.element(''); + controller = $componentController('vnFixedPrice', {$element, $scope}); + })); + + describe('upsertPrice()', () => { + it('should do nothing if one or more required arguments are missing', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.upsertPrice({}); + + expect(controller.vnApp.showSuccess).not.toHaveBeenCalled(); + }); + + it('should perform an http request to update the price', () => { + const now = new Date(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectPATCH('FixedPrices/upsertFixedPrice').respond(); + controller.upsertPrice({ + itemFk: 1, + started: now, + ended: now, + rate2: 1, + rate3: 2 + }); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('removePrice()', () => { + it(`should only remove the created instance by the model as it doesn't have an ID yet`, () => { + const $index = 0; + controller.$ = { + model: { + remove: () => {}, + data: [{ + foo: 'bar' + }] + } + }; + jest.spyOn(controller.vnApp, 'showSuccess'); + jest.spyOn(controller.$.model, 'remove'); + + controller.removePrice($index); + + expect(controller.vnApp.showSuccess).not.toHaveBeenCalled(); + expect(controller.$.model.remove).toHaveBeenCalled(); + }); + + it('should remove the instance performing an delete http request', () => { + const $index = 0; + controller.$ = { + model: { + remove: () => {}, + data: [{ + id: '1' + }] + } + }; + jest.spyOn(controller.vnApp, 'showSuccess'); + jest.spyOn(controller.$.model, 'remove'); + + const query = `FixedPrices/${controller.$.model.data[0].id}`; + $httpBackend.expectDELETE(query).respond(); + controller.removePrice($index); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.$.model.remove).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/modules/item/front/fixed-price/locale/es.yml b/modules/item/front/fixed-price/locale/es.yml new file mode 100644 index 000000000..c19b7703c --- /dev/null +++ b/modules/item/front/fixed-price/locale/es.yml @@ -0,0 +1,4 @@ +Fixed prices: Precios fijados +Search prices by item ID or code: Buscar por ID de artículo o código +Search fixed prices: Buscar precios fijados +Add fixed price: Añadir precio fijado \ No newline at end of file diff --git a/modules/item/front/fixed-price/style.scss b/modules/item/front/fixed-price/style.scss new file mode 100644 index 000000000..74df1800a --- /dev/null +++ b/modules/item/front/fixed-price/style.scss @@ -0,0 +1,5 @@ +@import "variables"; + +vn-table vn-date-picker { + max-width: 90px; +} \ No newline at end of file diff --git a/modules/item/front/index.js b/modules/item/front/index.js index 0f11c0563..e6a37abfc 100644 --- a/modules/item/front/index.js +++ b/modules/item/front/index.js @@ -21,4 +21,6 @@ import './botanical'; import './barcode'; import './summary'; import './waste'; +import './fixed-price'; +import './fixed-price-search-panel'; diff --git a/modules/item/front/routes.json b/modules/item/front/routes.json index d3bde0205..bc13d5dfd 100644 --- a/modules/item/front/routes.json +++ b/modules/item/front/routes.json @@ -8,7 +8,8 @@ "main": [ {"state": "item.index", "icon": "icon-item"}, {"state": "item.request", "icon": "pan_tool"}, - {"state": "item.waste", "icon": "icon-claims"} + {"state": "item.waste", "icon": "icon-claims"}, + {"state": "item.fixedPrice", "icon": ""} ], "card": [ {"state": "item.card.basicData", "icon": "settings"}, @@ -32,22 +33,26 @@ "abstract": true, "description": "Items", "component": "vn-items" - }, { + }, + { "url": "/index?q", "state": "item.index", "component": "vn-item-index", "description": "Items" - }, { + }, + { "url": "/create", "state": "item.create", "component": "vn-item-create", "description": "New item" - }, { + }, + { "url": "/:id", "state": "item.card", "abstract": true, "component": "vn-item-card" - }, { + }, + { "url" : "/basic-data", "state": "item.card.basicData", "component": "vn-item-basic-data", @@ -56,7 +61,8 @@ "item": "$ctrl.item" }, "acl": ["buyer"] - }, { + }, + { "url" : "/tags", "state": "item.card.tags", "component": "vn-item-tags", @@ -65,13 +71,15 @@ "item-tags": "$ctrl.itemTags" }, "acl": ["buyer", "replenisher"] - }, { + }, + { "url" : "/tax", "state": "item.card.tax", "component": "vn-item-tax", "description": "Tax", "acl": ["administrative","buyer"] - }, { + }, + { "url" : "/niche", "state": "item.card.niche", "component": "vn-item-niche", @@ -80,7 +88,8 @@ "item": "$ctrl.item" }, "acl": ["buyer","replenisher"] - }, { + }, + { "url" : "/botanical", "state": "item.card.botanical", "component": "vn-item-botanical", @@ -89,7 +98,8 @@ "item": "$ctrl.item" }, "acl": ["buyer"] - }, { + }, + { "url" : "/barcode", "state": "item.card.itemBarcode", "component": "vn-item-barcode", @@ -98,7 +108,8 @@ "item": "$ctrl.item" }, "acl": ["buyer","replenisher"] - }, { + }, + { "url" : "/summary", "state": "item.card.summary", "component": "vn-item-summary", @@ -106,7 +117,8 @@ "params": { "item": "$ctrl.item" } - }, { + }, + { "url" : "/diary?warehouseFk&lineFk", "state": "item.card.diary", "component": "vn-item-diary", @@ -115,7 +127,8 @@ "item": "$ctrl.item" }, "acl": ["employee"] - }, { + }, + { "url" : "/last-entries", "state": "item.card.last-entries", "component": "vn-item-last-entries", @@ -124,12 +137,14 @@ "item": "$ctrl.item" }, "acl": ["employee"] - }, { + }, + { "url" : "/log", "state": "item.card.log", "component": "vn-item-log", "description": "Log" - }, { + }, + { "url" : "/request?q", "state": "item.request", "component": "vn-item-request", @@ -138,12 +153,20 @@ "item": "$ctrl.item" }, "acl": ["employee"] - }, { + }, + { "url" : "/waste", "state": "item.waste", "component": "vn-item-waste", "description": "Waste breakdown", "acl": ["buyer"] + }, + { + "url" : "/fixed-price", + "state": "item.fixedPrice", + "component": "vn-fixed-price", + "description": "Fixed prices", + "acl": ["buyer"] } ] } \ No newline at end of file diff --git a/modules/item/front/tags/index.html b/modules/item/front/tags/index.html index 0dca863fd..3775526be 100644 --- a/modules/item/front/tags/index.html +++ b/modules/item/front/tags/index.html @@ -27,8 +27,8 @@ initial-data="itemTag.tag" ng-model="itemTag.tagFk" data="tags" - on-change="$ctrl.getSourceTable(tag)" show-field="name" + on-change="itemTag.value = null" rule> { +// #2735 route updateVolume() returns inconsistent values +xdescribe('route updateVolume()', () => { const routeId = 1; const userId = 50; const activeCtx = { diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index c8702c1db..ec4376adb 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -60,7 +60,8 @@ describe('sale updatePrice()', () => { await originalSalesPersonMana.updateAttributes(originalSalesPersonMana); }); - it('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => { + // #2736 sale updatePrice() returns inconsistent values + xit('should set price as a decimal number and check the sale has the mana component changing the salesPersonMana', async() => { let ctx = {req: {accessToken: {userId: 18}}}; let price = 5.4; diff --git a/modules/ticket/back/methods/ticket-request/filter.js b/modules/ticket/back/methods/ticket-request/filter.js index 4b7b088f4..5778b1db2 100644 --- a/modules/ticket/back/methods/ticket-request/filter.js +++ b/modules/ticket/back/methods/ticket-request/filter.js @@ -30,11 +30,11 @@ module.exports = Self => { }, { arg: 'attenderFk', type: 'Number', - description: `Search requests atended by the given worker` + description: `Search requests attended by a given worker id` }, { arg: 'mine', type: 'Boolean', - description: `Search requests attended by the connected worker` + description: `Search requests attended by the current user` }, { arg: 'from', type: 'Date', @@ -62,10 +62,9 @@ module.exports = Self => { Self.filter = async(ctx, filter) => { let conn = Self.dataSource.connector; let userId = ctx.req.accessToken.userId; - let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}}); if (ctx.args.mine) - ctx.args.attenderFk = worker.id; + ctx.args.attenderFk = userId; let where = buildFilter(ctx.args, (param, value) => { switch (param) { diff --git a/modules/ticket/front/locale/es.yml b/modules/ticket/front/locale/es.yml index 73d7ec5c2..02cab50ed 100644 --- a/modules/ticket/front/locale/es.yml +++ b/modules/ticket/front/locale/es.yml @@ -21,7 +21,7 @@ Discount: Descuento Employee : Empleado Import: Importe Is checked: Comprobado -Item: Articulo +Item: Artículo Landing: Llegada Landed: F. entrega More: Más diff --git a/modules/ticket/front/weekly/index.html b/modules/ticket/front/weekly/index.html index 9267e62e0..9ea35d92e 100644 --- a/modules/ticket/front/weekly/index.html +++ b/modules/ticket/front/weekly/index.html @@ -26,7 +26,7 @@ Ticket ID Client - Weekday + Shipment Agency Warehouse Salesperson diff --git a/modules/ticket/front/weekly/locale/es.yml b/modules/ticket/front/weekly/locale/es.yml index 62f0f5b53..c673adc57 100644 --- a/modules/ticket/front/weekly/locale/es.yml +++ b/modules/ticket/front/weekly/locale/es.yml @@ -4,4 +4,4 @@ You are going to delete this weekly ticket: Vas a eliminar este ticket programad This ticket will be removed from weekly tickets! Continue anyway?: Este ticket se eliminará de tickets programados! ¿Continuar de todas formas? Search weekly ticket by id or client id: Busca tickets programados por el identificador o el identificador del cliente Search by weekly ticket: Buscar por tickets programados -Weekday: Llegada \ No newline at end of file +Shipment: Salida \ No newline at end of file diff --git a/modules/travel/front/descriptor-menu/index.html b/modules/travel/front/descriptor-menu/index.html index 171aa89ec..c7edcd59e 100644 --- a/modules/travel/front/descriptor-menu/index.html +++ b/modules/travel/front/descriptor-menu/index.html @@ -18,6 +18,12 @@ translate> Clone travel and his entries + + Add entry + diff --git a/modules/travel/front/descriptor-menu/locale/es.yml b/modules/travel/front/descriptor-menu/locale/es.yml index ca61c4e01..b764ba0cd 100644 --- a/modules/travel/front/descriptor-menu/locale/es.yml +++ b/modules/travel/front/descriptor-menu/locale/es.yml @@ -1,3 +1,4 @@ Clone travel: Clonar envío +Add entry: Añadir entrada 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 +Do you want to clone this travel and all containing entries?: ¿Quieres clonar este travel y todas las entradas que contiene? diff --git a/modules/travel/front/index/index.html b/modules/travel/front/index/index.html index d85b62a4c..1a0f59e14 100644 --- a/modules/travel/front/index/index.html +++ b/modules/travel/front/index/index.html @@ -49,6 +49,11 @@ vn-tooltip="Clone" icon="icon-clone"> + +