From 3a82ef0e06739f4af46726fbad84c81a1091141d Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Wed, 26 Aug 2020 16:33:47 +0200 Subject: [PATCH] 2375 entry lastBuy seccion --- db/changes/10210-summer/00-ACL.sql | 2 + front/core/directives/smart-table.js | 14 +- .../back/methods/entry/editLatestBuys.js | 78 ++++++++ .../back/methods/entry/latestBuysFilter.js | 135 +++++++++---- modules/entry/back/model-config.json | 3 + modules/entry/back/models/buy.js | 4 + modules/entry/back/models/buy.json | 64 +++++++ modules/entry/back/models/entry.js | 1 - modules/entry/front/card/index.js | 9 +- .../entry/front/descriptor-popover/index.html | 3 + .../entry/front/descriptor-popover/index.js | 9 + modules/entry/front/descriptor/index.js | 60 +++++- modules/entry/front/index.js | 1 + .../front/latest-buys-search-panel/index.html | 21 ++- .../front/latest-buys-search-panel/index.js | 2 +- modules/entry/front/latest-buys/index.html | 178 +++++++++++------- modules/entry/front/latest-buys/index.js | 53 ++++++ modules/entry/front/routes.json | 8 +- modules/item/back/methods/item/filter.js | 12 +- modules/item/front/index/index.html | 28 +-- modules/route/front/index/index.html | 12 +- 21 files changed, 539 insertions(+), 158 deletions(-) create mode 100644 db/changes/10210-summer/00-ACL.sql create mode 100644 modules/entry/back/methods/entry/editLatestBuys.js create mode 100644 modules/entry/back/models/buy.js create mode 100644 modules/entry/back/models/buy.json create mode 100644 modules/entry/front/descriptor-popover/index.html create mode 100644 modules/entry/front/descriptor-popover/index.js diff --git a/db/changes/10210-summer/00-ACL.sql b/db/changes/10210-summer/00-ACL.sql new file mode 100644 index 0000000000..755b148d74 --- /dev/null +++ b/db/changes/10210-summer/00-ACL.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES ('Buy', '*', '*', 'ALLOW', 'ROLE', 'buyer'); diff --git a/front/core/directives/smart-table.js b/front/core/directives/smart-table.js index 2d5a24ade4..c7072c09e0 100644 --- a/front/core/directives/smart-table.js +++ b/front/core/directives/smart-table.js @@ -2,22 +2,26 @@ import ngModule from '../module'; import template from './smart-table.html'; import './smart-table.scss'; +/** + * Directive to hide/show selected columns of a table, don't use with rowspan. + */ directive.$inject = ['$http', '$compile', 'vnApp', '$translate']; export function directive($http, $compile, vnApp, $translate) { function getHeaderList($element, $scope) { - let allHeaders = $element[0].querySelectorAll(`vn-th[field], vn-th[th-id]`); - let headerList = Array.from(allHeaders); + let filtrableHeaders = $element[0].querySelectorAll('vn-th[field]'); + let headerList = Array.from(filtrableHeaders); let ids = []; let titles = {}; headerList.forEach(header => { - let id = header.getAttribute('th-id') || header.getAttribute('field'); + let id = header.getAttribute('field'); ids.push(id); titles[id] = header.innerText || id.charAt(0).toUpperCase() + id.slice(1); }); $scope.fields = ids; $scope.titles = titles; + $scope.allHeaders = Array.from($element[0].querySelectorAll('vn-th')); return headerList; } @@ -38,8 +42,8 @@ export function directive($http, $compile, vnApp, $translate) { Object.keys(userConfig.configuration).forEach(key => { let index; if (userConfig.configuration[key] === false) { - index = headerList.findIndex(el => { - return (el.getAttribute('th-id') == key || el.getAttribute('field') == key); + index = $scope.allHeaders.findIndex(el => { + return el.getAttribute('field') == key; }); let baseSelector = `vn-table[vn-smart-table=${userConfig.tableCode}] > div`; diff --git a/modules/entry/back/methods/entry/editLatestBuys.js b/modules/entry/back/methods/entry/editLatestBuys.js new file mode 100644 index 0000000000..acda6ed1cc --- /dev/null +++ b/modules/entry/back/methods/entry/editLatestBuys.js @@ -0,0 +1,78 @@ +module.exports = Self => { + Self.remoteMethod('editLatestBuys', { + description: 'Updates a column for one of more buys', + accessType: 'WRITE', + accepts: [{ + arg: 'column', + type: 'Object', + required: true, + description: `the column to edit and it's new value` + }, + { + arg: 'buys', + type: ['Object'], + required: true, + description: `the buys which will be modified` + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/editLatestBuys`, + verb: 'POST' + } + }); + + Self.editLatestBuys = async(column, buys) => { + let modelName; + let identifier; + switch (column.field) { + case 'size': + case 'density': + modelName = 'Item'; + identifier = 'itemFk'; + break; + case 'quantity': + case 'buyingValue': + case 'freightValue': + case 'packing': + case 'grouping': + case 'groupingMode': + case 'comissionValue': + case 'packageValue': + case 'price2': + case 'price3': + case 'minPrice': + case 'weight': + modelName = 'Buy'; + identifier = 'id'; + } + + const models = Self.app.models; + const model = models[modelName]; + + let tx = await model.beginTransaction({}); + + try { + let promises = []; + let options = {transaction: tx}; + + let targets = buys.map(buy => { + return buy[identifier]; + }); + + let value = {}; + value[column.field] = column.newValue; + + for (let target of targets) + promises.push(model.upsertWithWhere({id: target}, value, options)); + + await Promise.all(promises); + await tx.commit(); + } catch (error) { + await tx.rollback(); + throw error; + } + }; +}; diff --git a/modules/entry/back/methods/entry/latestBuysFilter.js b/modules/entry/back/methods/entry/latestBuysFilter.js index edfc46e3b2..50cd8cf3c0 100644 --- a/modules/entry/back/methods/entry/latestBuysFilter.js +++ b/modules/entry/back/methods/entry/latestBuysFilter.js @@ -12,43 +12,44 @@ module.exports = Self => { arg: 'filter', type: 'Object', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', - http: {source: 'query'} }, { + arg: 'search', + type: 'String', + description: `If it's and integer searchs by id, otherwise it searchs by name`, + }, { + arg: 'id', + type: 'Integer', + description: 'Item id', + }, { arg: 'tags', type: ['Object'], description: 'List of tags to filter with', http: {source: 'query'} - }, { - arg: 'search', - type: 'String', - description: `If it's and integer searchs by id, otherwise it searchs by name`, - http: {source: 'query'} - }, { - arg: 'id', - type: 'Integer', - description: 'Item id', - http: {source: 'query'} }, { arg: 'categoryFk', type: 'Integer', description: 'Category id', - http: {source: 'query'} }, { arg: 'typeFk', type: 'Integer', description: 'Type id', - http: {source: 'query'} }, { - arg: 'isActive', + arg: 'active', type: 'Boolean', description: 'Whether the the item is or not active', - http: {source: 'query'} + }, { + arg: 'visible', + type: 'Boolean', + description: 'Whether the the item is or not visible', }, { arg: 'salesPersonFk', type: 'Integer', description: 'The buyer of the item', - http: {source: 'query'} + }, { + arg: 'description', + type: 'String', + description: 'The item description', } ], returns: { @@ -64,42 +65,100 @@ module.exports = Self => { Self.latestBuysFilter = async(ctx, filter) => { let conn = Self.dataSource.connector; let where = buildFilter(ctx.args, (param, value) => { - // switch (param) { - // case 'search': - // return /^\d+$/.test(value) - // ? {or: [{'i.id': value}]} - // : {or: [{'i.name': {like: `%${value}%`}}]}; - // case 'id': - // return {'i.id': value}; - // case 'description': - // return {'i.description': {like: `%${value}%`}}; - // case 'categoryFk': - // return {'ic.id': value}; - // case 'salesPersonFk': - // return {'t.workerFk': value}; - // case 'typeFk': - // return {'i.typeFk': value}; - // case 'isActive': - // return {'i.isActive': value}; - // } + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {'i.id': value} + : {'i.name': {like: `%${value}%`}}; + case 'id': + return {'i.id': value}; + case 'description': + return {'i.description': {like: `%${value}%`}}; + case 'categoryFk': + return {'ic.id': value}; + case 'salesPersonFk': + return {'it.workerFk': value}; + case 'typeFk': + return {'i.typeFk': value}; + case 'active': + return {'i.isActive': value}; + case 'visible': + if (value) + return {'v.visible': {gt: 0}}; + else if (!value) + return {'v.visible': {lte: 0}}; + } }); filter = mergeFilters(ctx.args.filter, {where}); let stmts = []; let stmt; - stmt = new ParameterizedSQL('CALL cache.last_buy_refresh(FALSE)'); - stmts.push(stmt); + stmts.push('CALL cache.last_buy_refresh(FALSE)'); + stmts.push('CALL cache.visible_refresh(@calc_id, FALSE, 1)'); stmt = new ParameterizedSQL(` SELECT - size + i.image, + i.id AS itemFk, + i.size, + i.density, + i.typeFk, + t.name AS type, + i.family, + intr.description AS intrastat, + ori.code AS origin, + i.isActive, + b.entryFk, + b.id, + b.quantity, + b.buyingValue, + b.freightValue, + b.isIgnored, + b.packing, + b.grouping, + b.groupingMode, + b.comissionValue, + b.packageValue, + b.price2, + b.price3, + b.minPrice, + b.ektFk, + b.weight FROM cache.last_buy lb + LEFT JOIN cache.visible v ON v.item_id = lb.item_id + AND v.calc_id = @calc_id JOIN item i ON i.id = lb.item_id JOIN itemType it ON it.id = i.typeFk AND lb.warehouse_id = it.warehouseFk - JOIN buy b ON b.id = lb.buy_id` + JOIN buy b ON b.id = lb.buy_id + LEFT JOIN itemCategory ic ON ic.id = it.categoryFk + LEFT JOIN itemType t ON t.id = i.typeFk + LEFT JOIN intrastat intr ON intr.id = i.intrastatFk + LEFT JOIN origin ori ON ori.id = i.originFk` ); + 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.makeSuffix(filter)); let buysIndex = stmts.push(stmt) - 1; diff --git a/modules/entry/back/model-config.json b/modules/entry/back/model-config.json index c8c8babadc..0e37b947ff 100644 --- a/modules/entry/back/model-config.json +++ b/modules/entry/back/model-config.json @@ -2,6 +2,9 @@ "Entry": { "dataSource": "vn" }, + "Buy": { + "dataSource": "vn" + }, "EntryLog": { "dataSource": "vn" } diff --git a/modules/entry/back/models/buy.js b/modules/entry/back/models/buy.js new file mode 100644 index 0000000000..e110164e82 --- /dev/null +++ b/modules/entry/back/models/buy.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/entry/editLatestBuys')(Self); + require('../methods/entry/latestBuysFilter')(Self); +}; diff --git a/modules/entry/back/models/buy.json b/modules/entry/back/models/buy.json new file mode 100644 index 0000000000..14f2490d8f --- /dev/null +++ b/modules/entry/back/models/buy.json @@ -0,0 +1,64 @@ +{ + "name": "Buy", + "base": "Loggable", + "log": { + "model": "EntryLog", + "relation": "entry" + }, + "options": { + "mysql": { + "table": "buy" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "quantity": { + "type": "number" + }, + "buyingValue": { + "type": "number" + }, + "freightValue": { + "type": "number" + }, + "packing": { + "type": "number" + }, + "grouping": { + "type": "number" + }, + "groupingMode": { + "type": "number" + }, + "comissionValue": { + "type": "number" + }, + "packageValue": { + "type": "number" + }, + "price2": { + "type": "number" + }, + "price3": { + "type": "number" + }, + "minPrice": { + "type": "number" + }, + "weight": { + "type": "number" + } + }, + "relations": { + "entry": { + "type": "belongsTo", + "model": "Entry", + "foreignKey": "entryFk", + "required": true + } + } +} \ No newline at end of file diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index 40f89ebf09..713154d1b0 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -1,5 +1,4 @@ module.exports = Self => { - require('../methods/entry/latestBuysFilter')(Self); require('../methods/entry/filter')(Self); require('../methods/entry/getEntry')(Self); }; diff --git a/modules/entry/front/card/index.js b/modules/entry/front/card/index.js index f9ab6187c1..eafed171bb 100644 --- a/modules/entry/front/card/index.js +++ b/modules/entry/front/card/index.js @@ -10,7 +10,8 @@ class Controller extends ModuleCard { scope: { fields: ['id', 'code'] } - }, { + }, + { relation: 'travel', scope: { fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'], @@ -35,12 +36,14 @@ class Controller extends ModuleCard { } ] } - }, { + }, + { relation: 'supplier', scope: { fields: ['id', 'nickname'] } - }, { + }, + { relation: 'currency' } ] diff --git a/modules/entry/front/descriptor-popover/index.html b/modules/entry/front/descriptor-popover/index.html new file mode 100644 index 0000000000..465a9bf51e --- /dev/null +++ b/modules/entry/front/descriptor-popover/index.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/entry/front/descriptor-popover/index.js b/modules/entry/front/descriptor-popover/index.js new file mode 100644 index 0000000000..d79aed03e8 --- /dev/null +++ b/modules/entry/front/descriptor-popover/index.js @@ -0,0 +1,9 @@ +import ngModule from '../module'; +import DescriptorPopover from 'salix/components/descriptor-popover'; + +class Controller extends DescriptorPopover {} + +ngModule.vnComponent('vnEntryDescriptorPopover', { + slotTemplate: require('./index.html'), + controller: Controller +}); diff --git a/modules/entry/front/descriptor/index.js b/modules/entry/front/descriptor/index.js index dd417a8422..fed3787d49 100644 --- a/modules/entry/front/descriptor/index.js +++ b/modules/entry/front/descriptor/index.js @@ -11,15 +11,23 @@ class Controller extends Descriptor { } get travelFilter() { - return this.entry && JSON.stringify({ - agencyFk: this.entry.travel.agencyFk - }); + let travelFilter; + const entryTravel = this.entry && this.entry.travel; + + if (entryTravel && entryTravel.agencyFk) { + travelFilter = this.entry && JSON.stringify({ + agencyFk: entryTravel.agencyFk + }); + } + return travelFilter; } get entryFilter() { - if (!this.entry) return null; + let entryTravel = this.entry && this.entry.travel; - const date = new Date(this.entry.travel.landed); + if (!entryTravel || !entryTravel.landed) return null; + + const date = new Date(entryTravel.landed); date.setHours(0, 0, 0, 0); const from = new Date(date.getTime()); @@ -35,6 +43,48 @@ class Controller extends Descriptor { }); } + loadData() { + const filter = { + include: [ + { + relation: 'travel', + scope: { + fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'], + include: [ + { + relation: 'agency', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseOut', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseIn', + scope: { + fields: ['name'] + } + } + ] + } + }, + { + relation: 'supplier', + scope: { + fields: ['id', 'nickname'] + } + } + ] + }; + + return this.getData(`Entries/${this.id}`, {filter}) + .then(res => this.entity = res.data); + } + showEntryReport() { this.vnReport.show('entry-order', { entryId: this.entry.id diff --git a/modules/entry/front/index.js b/modules/entry/front/index.js index eb2c823c40..fc24e3efb7 100644 --- a/modules/entry/front/index.js +++ b/modules/entry/front/index.js @@ -6,6 +6,7 @@ import './latest-buys'; import './search-panel'; import './latest-buys-search-panel'; import './descriptor'; +import './descriptor-popover'; import './card'; import './summary'; import './log'; diff --git a/modules/entry/front/latest-buys-search-panel/index.html b/modules/entry/front/latest-buys-search-panel/index.html index f30442ec61..67fa7f0c22 100644 --- a/modules/entry/front/latest-buys-search-panel/index.html +++ b/modules/entry/front/latest-buys-search-panel/index.html @@ -3,7 +3,6 @@
- + + + + + + Tags @@ -65,7 +74,6 @@ + data="$ctrl.buys"> - @@ -33,72 +24,98 @@ vn-smart-table="latestBuys"> - - + - - Id - Landed - Reference - Supplier - Currency - Company - Booked - Confirmed - Ordered - Notes + Picture + Id + Grouping + Packing + Size + Type + Intrastat + Origin + Density + Active + Family + Entry + Quantity + Buying value + Freight value + Comission value + Package value + Is ignored + Grouping mode + price2 + price3 + Min price + Ekt + Weight + ui-sref="entry.card.buy({id: {{::buy.entryFk}}})"> - - - - - + + - {{::buy.size}} - - - {{::buy.landed | date:'dd/MM/yyyy'}} + + + {{::buy.itemFk | zeroFill:6}} - {{::buy.ref}} - {{::buy.supplierName}} - {{::buy.currencyCode}} - {{::buy.companyCode}} - - - - - - + {{::buy.grouping}} + {{::buy.packing}} + {{::buy.size}} + + {{::buy.type}} + + {{::buy.intrastat}} + + {{::buy.origin}} + {{::buy.density}} + + + + + {{::buy.family}} + + + {{::buy.entryFk}} + + + {{::buy.quantity}} + {{::buy.buyingValue | currency: 'EUR':2}} + {{::buy.freightValue | currency: 'EUR':2}} + {{::buy.comissionValue | currency: 'EUR':2}} + {{::buy.packageValue | currency: 'EUR':2}} + {{::buy.isIgnored}} + {{::buy.groupingMode}} + {{::buy.price2 | currency: 'EUR':2}} + {{::buy.price3 | currency: 'EUR':2}} + {{::buy.minPrice | currency: 'EUR':2}} + {{::buy.ektFk | dashIfEmpty}} + {{::buy.weight}} @@ -109,12 +126,41 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/modules/entry/front/latest-buys/index.js b/modules/entry/front/latest-buys/index.js index 7f2de41853..b3b120af8c 100644 --- a/modules/entry/front/latest-buys/index.js +++ b/modules/entry/front/latest-buys/index.js @@ -8,7 +8,33 @@ export default class Controller extends Section { id: false, actions: false }; + this.editedColumn; } + + get columns() { + if (this._columns) return this._columns; + + this._columns = [ + {field: 'quantity', displayName: 'quantity'}, + {field: 'buyingValue', displayName: 'buyingValue'}, + {field: 'freightValue', displayName: 'freightValue'}, + {field: 'packing', displayName: 'packing'}, + {field: 'grouping', displayName: 'grouping'}, + {field: 'groupingMode', displayName: 'groupingMode'}, + {field: 'comissionValue', displayName: 'comissionValue'}, + {field: 'packageValue', displayName: 'packageValue'}, + {field: 'price2', displayName: 'price2'}, + {field: 'price3', displayName: 'price3'}, + {field: 'minPrice', displayName: 'minPrice'}, + {field: 'weight', displayName: 'weight'}, + {field: 'size', displayName: 'size'}, + {field: 'density', displayName: 'density'}, + {field: 'description', displayName: 'description'} + ]; + + return this._columns; + } + get checked() { const buys = this.$.model.data || []; const checkedBuys = []; @@ -20,9 +46,36 @@ export default class Controller extends Section { return checkedBuys; } + uncheck() { + console.log('clicked!'); + const lines = this.checked; + for (let line of lines) { + if (line.checked) + line.checked = false; + } + } + get totalChecked() { return this.checked.length; } + + onEditAccept() { + let data = { + column: this.editedColumn, + buys: this.checked + }; + + this.$http.post('Buys/editLatestBuys', data) + .then(() => { + this.$.edit.hide(); + this.uncheck(); + this.$.model.refresh(); + }); + + this.editedColumn = null; + + return false; + } } ngModule.component('vnEntryLatestBuys', { diff --git a/modules/entry/front/routes.json b/modules/entry/front/routes.json index 32c046a3d6..cdaaebc7db 100644 --- a/modules/entry/front/routes.json +++ b/modules/entry/front/routes.json @@ -2,7 +2,7 @@ "module": "entry", "name": "Entries", "icon": "icon-entry", - "dependencies": ["travel"], + "dependencies": ["travel", "item"], "validations": true, "menus": { "main": [ @@ -25,12 +25,14 @@ "url": "/index?q", "state": "entry.index", "component": "vn-entry-index", - "description": "Entries" + "description": "Entries", + "acl": ["buyer"] }, { "url": "/latest-buys?q", "state": "entry.latestBuys", "component": "vn-entry-latest-buys", - "description": "Latest buys" + "description": "Latest buys", + "acl": ["buyer"] }, { "url": "/:id", "state": "entry.card", diff --git a/modules/item/back/methods/item/filter.js b/modules/item/back/methods/item/filter.js index 5d9ae9e0fb..a38a067132 100644 --- a/modules/item/back/methods/item/filter.js +++ b/modules/item/back/methods/item/filter.js @@ -12,42 +12,38 @@ module.exports = Self => { arg: 'filter', type: 'Object', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', - http: {source: 'query'} }, { arg: 'tags', type: ['Object'], description: 'List of tags to filter with', - http: {source: 'query'} }, { arg: 'search', type: 'String', description: `If it's and integer searchs by id, otherwise it searchs by name`, - http: {source: 'query'} }, { arg: 'id', type: 'Integer', description: 'Item id', - http: {source: 'query'} }, { arg: 'categoryFk', type: 'Integer', description: 'Category id', - http: {source: 'query'} }, { arg: 'typeFk', type: 'Integer', description: 'Type id', - http: {source: 'query'} }, { arg: 'isActive', type: 'Boolean', description: 'Whether the the item is or not active', - http: {source: 'query'} }, { arg: 'salesPersonFk', type: 'Integer', description: 'The buyer of the item', - http: {source: 'query'} + }, { + arg: 'description', + type: 'String', + description: 'The item description', } ], returns: { diff --git a/modules/item/front/index/index.html b/modules/item/front/index/index.html index 296ac97473..b34445d367 100644 --- a/modules/item/front/index/index.html +++ b/modules/item/front/index/index.html @@ -11,21 +11,21 @@ vn-smart-table="itemIndex"> - + Id - Grouping - Packing - Description - Stems - Size - Niche - Type - Category - Intrastat - Origin - Buyer - Density - Active + Grouping + Packing + Description + Stems + Size + Niche + Type + Category + Intrastat + Origin + Buyer + Density + Active diff --git a/modules/route/front/index/index.html b/modules/route/front/index/index.html index 7258018f10..909d782490 100644 --- a/modules/route/front/index/index.html +++ b/modules/route/front/index/index.html @@ -14,12 +14,12 @@ Id - Worker - Agency - Vehicle - Date - - Description + Worker + Agency + Vehicle + Date + + Description