diff --git a/db/changes/10340-summer/00-buy_importReference.sql b/db/changes/10340-summer/00-buy_importReference.sql new file mode 100644 index 0000000000..f6bdc059c8 --- /dev/null +++ b/db/changes/10340-summer/00-buy_importReference.sql @@ -0,0 +1,14 @@ +create table `vn`.`itemMatchProperties` +( + itemFk int not null, + name varchar(80) not null, + producer varchar(80) not null, + size int not null, + constraint itemMatchProperties_pk + primary key (itemFk, name, producer, size), + constraint itemFk___fk + foreign key (itemFk) references item (id) + on update cascade on delete cascade +) +comment 'Propiedades para encontrar articulos equivalentes en verdnatura'; + diff --git a/e2e/paths/12-entry/07_buys.spec.js b/e2e/paths/12-entry/07_buys.spec.js index e5617b8bd4..4042c99b6c 100644 --- a/e2e/paths/12-entry/07_buys.spec.js +++ b/e2e/paths/12-entry/07_buys.spec.js @@ -29,9 +29,6 @@ describe('Entry import, create and edit buys path', () => { }); it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => { - await page.write(selectors.entryBuys.ref, 'a reference'); - await page.write(selectors.entryBuys.observation, 'an observation'); - let currentDir = process.cwd(); let filePath = `${currentDir}/e2e/assets/07_import_buys.json`; @@ -41,6 +38,9 @@ describe('Entry import, create and edit buys path', () => { ]); await fileChooser.accept([filePath]); + await page.waitForTextInField(selectors.entryBuys.ref, '200573095, 200573106, 200573117, 200573506'); + await page.waitForTextInField(selectors.entryBuys.observation, '729-6340 2846'); + await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm'); await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m'); await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m'); diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js index 325fe4d22d..3ed8ac1c7d 100644 --- a/modules/entry/back/methods/entry/importBuys.js +++ b/modules/entry/back/methods/entry/importBuys.js @@ -11,11 +11,6 @@ module.exports = Self => { description: 'The entry id', http: {source: 'path'} }, - { - arg: 'options', - type: 'object', - description: 'Callback options', - }, { arg: 'ref', type: 'string', @@ -28,11 +23,11 @@ module.exports = Self => { }, { arg: 'buys', - type: ['Object'], + type: ['object'], description: 'The buys', }], returns: { - type: ['Object'], + type: ['object'], root: true }, http: { @@ -41,23 +36,27 @@ module.exports = Self => { } }); - Self.importBuys = async(ctx, id, options = {}) => { + Self.importBuys = async(ctx, id, options) => { const conn = Self.dataSource.connector; const args = ctx.args; const models = Self.app.models; let tx; + const myOptions = {}; - if (!options.transaction) { + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { tx = await Self.beginTransaction({}); - options.transaction = tx; + myOptions.transaction = tx; } try { - const entry = await models.Entry.findById(id, null, options); + const entry = await models.Entry.findById(id, null, myOptions); await entry.updateAttributes({ observation: args.observation, ref: args.ref - }, options); + }, myOptions); const buys = []; for (let buy of args.buys) { @@ -71,9 +70,16 @@ module.exports = Self => { buyingValue: buy.buyingValue, packageFk: buy.packageFk }); + + await models.ItemMatchProperties.upsert({ + itemFk: buy.itemFk, + name: buy.description, + producer: buy.companyName, + size: buy.size + }, myOptions); } - const createdBuys = await models.Buy.create(buys, options); + const createdBuys = await models.Buy.create(buys, myOptions); const buyIds = createdBuys.map(buy => buy.id); const stmts = []; @@ -90,7 +96,7 @@ module.exports = Self => { stmts.push('CALL buy_recalcPrices()'); const sql = ParameterizedSQL.join(stmts, ';'); - await conn.executeStmt(sql, options); + await conn.executeStmt(sql, myOptions); if (tx) await tx.commit(); } catch (e) { if (tx) await tx.rollback(); diff --git a/modules/entry/back/methods/entry/importBuysPreview.js b/modules/entry/back/methods/entry/importBuysPreview.js index 9ba2b58ed8..790d33364c 100644 --- a/modules/entry/back/methods/entry/importBuysPreview.js +++ b/modules/entry/back/methods/entry/importBuysPreview.js @@ -37,7 +37,21 @@ module.exports = Self => { where: {volume: {gte: buy.volume}}, order: 'volume ASC' }, myOptions); - buy.packageFk = packaging.id; + + if (packaging) + buy.packageFk = packaging.id; + + const reference = await models.ItemMatchProperties.findOne({ + fields: ['itemFk'], + where: { + name: buy.description, + producer: buy.companyName, + size: buy.size + } + }, myOptions); + + if (reference) + buy.itemFk = reference.itemFk; } return buys; diff --git a/modules/entry/back/model-config.json b/modules/entry/back/model-config.json index eddef9c410..ad5a9063ec 100644 --- a/modules/entry/back/model-config.json +++ b/modules/entry/back/model-config.json @@ -5,6 +5,9 @@ "Buy": { "dataSource": "vn" }, + "ItemMatchProperties": { + "dataSource": "vn" + }, "EntryLog": { "dataSource": "vn" }, diff --git a/modules/entry/back/models/buy-import-reference.json b/modules/entry/back/models/buy-import-reference.json new file mode 100644 index 0000000000..ab64dad73e --- /dev/null +++ b/modules/entry/back/models/buy-import-reference.json @@ -0,0 +1,32 @@ +{ + "name": "ItemMatchProperties", + "base": "VnModel", + "options": { + "mysql": { + "table": "itemMatchProperties" + } + }, + "properties": { + "itemFk": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "name": { + "type": "string" + }, + "producer": { + "type": "string" + }, + "size": { + "type": "string" + } + }, + "relations": { + "item": { + "type": "belongsTo", + "model": "Item", + "foreignKey": "itemFk" + } + } +} \ No newline at end of file diff --git a/modules/entry/front/buy/import/index.html b/modules/entry/front/buy/import/index.html index 74b6c708ad..179657dae5 100644 --- a/modules/entry/front/buy/import/index.html +++ b/modules/entry/front/buy/import/index.html @@ -9,20 +9,6 @@ class="vn-ma-md"> <div class="vn-w-lg"> <vn-card class="vn-pa-lg"> - <vn-horizontal> - <vn-textfield vn-focus - vn-one - label="Reference" - ng-model="$ctrl.import.ref"> - </vn-textfield> - </vn-horizontal> - <vn-horizontal> - <vn-textarea - vn-one - label="Observation" - ng-model="$ctrl.import.observation"> - </vn-textarea> - </vn-horizontal> <vn-horizontal> <vn-input-file vn-one @@ -40,6 +26,20 @@ </append> </vn-input-file> </vn-horizontal> + <vn-horizontal ng-show="$ctrl.import.ref"> + <vn-textfield vn-focus + vn-one + label="Reference" + ng-model="$ctrl.import.ref"> + </vn-textfield> + </vn-horizontal> + <vn-horizontal ng-show="$ctrl.import.observation"> + <vn-textarea + vn-one + label="Observation" + ng-model="$ctrl.import.observation"> + </vn-textarea> + </vn-horizontal> <vn-horizontal ng-show="$ctrl.import.buys.length > 0"> <table class="vn-table"> <thead> @@ -51,7 +51,6 @@ <th translate center>Grouping</th> <th translate center>Buying value</th> <th translate center>Box</th> - <th translate center>Volume</th> </tr> </thead> <tbody ng-repeat="buy in $ctrl.import.buys"> @@ -70,20 +69,19 @@ <tpl-item> {{::id}} - {{::name}} </tpl-item> + <append> + <vn-icon-button + icon="filter_alt" + vn-click-stop="$ctrl.showFilterDialog(buy)" + vn-tooltip="Filter..."> + </vn-icon-button> + </append> </vn-autocomplete> </td> <td title="{{::buy.description}}" expand>{{::buy.description | dashIfEmpty}}</td> <td center title="{{::buy.size}}">{{::buy.size | dashIfEmpty}}</td> - <td center> - <vn-chip> - <span>{{::buy.packing | dashIfEmpty}}</span> - </vn-chip> - </td> - <td center> - <vn-chip> - <span>{{::buy.grouping | dashIfEmpty}}</span> - </vn-chip> - </vn-td> + <td center>{{::buy.packing | dashIfEmpty}}</td> + <td center>{{::buy.grouping | dashIfEmpty}}</td> <td>{{::buy.buyingValue | currency: 'EUR':2}}</td> <td center title="{{::buy.packageFk | dashIfEmpty}}"> <vn-autocomplete @@ -95,7 +93,6 @@ ng-model="buy.packageFk"> </vn-autocomplete> </td> - <td center title="{{::buy.volume}}">{{::buy.volume | number}}</td> </tr> </tbody> </table> @@ -110,7 +107,95 @@ label="Cancel" ui-sref="entry.card.buy.index"> </vn-button> - </vn-button> </vn-button-bar> </div> </form> + +<vn-dialog + vn-id="filterDialog" + on-accept="$ctrl.addTime()" + message="Filter item"> + <tpl-body class="itemFilter"> + <vn-horizontal> + <vn-textfield + label="Name" + ng-model="$ctrl.itemFilterParams.name" + vn-focus> + </vn-textfield> + <vn-textfield + label="Size" + ng-model="$ctrl.itemFilterParams.size"> + </vn-textfield> + <vn-autocomplete + label="Producer" + ng-model="$ctrl.itemFilterParams.producerFk" + url="Producers" + show-field="name" + value-field="id"> + </vn-autocomplete> + <vn-autocomplete + label="Type" + ng-model="$ctrl.itemFilterParams.typeFk" + url="ItemTypes" + show-field="name" + value-field="id"> + </vn-autocomplete> + <vn-autocomplete + label="Color" + ng-model="$ctrl.itemFilterParams.inkFk" + url="Inks" + show-field="name" + value-field="id"> + </vn-autocomplete> + </vn-horizontal> + <vn-horizontal class="vn-mb-md"> + <vn-button vn-none + label="Search" + ng-click="$ctrl.filter()"> + </vn-button> + </vn-horizontal> + <vn-crud-model + vn-id="itemsModel" + url="Items" + filter="$ctrl.itemFilter" + data="items" + limit="10"> + </vn-crud-model> + <vn-data-viewer + model="itemsModel" + class="vn-w-lg"> + <vn-table class="scrollable"> + <vn-thead> + <vn-tr> + <vn-th shrink>ID</vn-th> + <vn-th expand>Item</vn-th> + <vn-th number>Size</vn-th> + <vn-th expand>Producer</vn-th> + <vn-th>Color</vn-th> + </vn-tr> + </vn-thead> + <vn-tbody> + <a ng-repeat="item in items" + class="clickable vn-tr search-result" + ng-click="$ctrl.selectItem(item.id)"> + <vn-td shrink> + <span + ng-click="itemDescriptor.show($event, item.id)" + class="link"> + {{::item.id}} + </span> + </vn-td> + <vn-td expand>{{::item.name}}</vn-td> + <vn-td number>{{::item.size}}</vn-td> + <vn-td expand>{{::item.producer.name}}</vn-td> + <vn-td>{{::item.ink.name}}</vn-td> + </a> + </vn-tbody> + </vn-table> + </vn-data-viewer> + <vn-item-descriptor-popover + vn-id="item-descriptor" + warehouse-fk="$ctrl.vnConfig.warehouseFk"> + </vn-item-descriptor-popover> + </tpl-body> +</vn-dialog> \ No newline at end of file diff --git a/modules/entry/front/buy/import/index.js b/modules/entry/front/buy/import/index.js index b5ff92a893..2f13b27460 100644 --- a/modules/entry/front/buy/import/index.js +++ b/modules/entry/front/buy/import/index.js @@ -29,6 +29,7 @@ class Controller extends Section { this.$.$applyAsync(() => { this.import.observation = invoice.tx_awb; + const companyName = invoice.tx_company; const boxes = invoice.boxes; const buys = []; for (let box of boxes) { @@ -37,11 +38,12 @@ class Controller extends Section { const packing = product.nu_stems_bunch * product.nu_bunches; buys.push({ description: product.nm_product, + companyName: companyName, size: product.nu_length, packing: packing, grouping: product.nu_stems_bunch, buyingValue: parseFloat(product.mny_rate_stem), - volume: boxVolume + volume: boxVolume, }); } } @@ -86,6 +88,59 @@ class Controller extends Section { ? {id: $search} : {name: {like: '%' + $search + '%'}}; } + + showFilterDialog(buy) { + this.activeBuy = buy; + this.itemFilterParams = {}; + this.itemFilter = { + include: [ + { + relation: 'producer', + scope: { + fields: ['name'] + } + }, + { + relation: 'ink', + scope: { + fields: ['name'] + } + } + ] + }; + + this.$.filterDialog.show(); + } + + selectItem(id) { + this.activeBuy['itemFk'] = id; + this.$.filterDialog.hide(); + } + + filter() { + const filter = this.itemFilter; + const params = this.itemFilterParams; + const where = {}; + + for (let key in params) { + const value = params[key]; + if (!value) continue; + + switch (key) { + case 'name': + where[key] = {like: `%${value}%`}; + break; + case 'producerFk': + case 'typeFk': + case 'size': + case 'ink': + where[key] = value; + } + } + + filter.where = where; + this.$.itemsModel.applyFilter(filter); + } } Controller.$inject = ['$element', '$scope']; diff --git a/modules/entry/front/buy/import/style.scss b/modules/entry/front/buy/import/style.scss index dba0696164..8426d41699 100644 --- a/modules/entry/front/buy/import/style.scss +++ b/modules/entry/front/buy/import/style.scss @@ -2,4 +2,10 @@ vn-entry-buy-import { .vn-table > tbody td:nth-child(1) { width: 250px } +} + +.itemFilter { + vn-table.scrollable { + height: 500px + } } \ No newline at end of file diff --git a/modules/entry/front/buy/locale/es.yml b/modules/entry/front/buy/locale/es.yml index c775877581..55828a3c6a 100644 --- a/modules/entry/front/buy/locale/es.yml +++ b/modules/entry/front/buy/locale/es.yml @@ -3,4 +3,6 @@ Observation: Observación Box: Embalaje Import buys: Importar compras Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo -JSON files only: Solo ficheros JSON \ No newline at end of file +JSON files only: Solo ficheros JSON +Filter item: Filtrar artículo +Filter...: Filtrar... \ No newline at end of file diff --git a/modules/item/front/search-panel/locale/es.yml b/modules/item/front/search-panel/locale/es.yml index 197da0695c..67a5200d7e 100644 --- a/modules/item/front/search-panel/locale/es.yml +++ b/modules/item/front/search-panel/locale/es.yml @@ -1,6 +1,6 @@ Ink: Tinta Origin: Origen -Producer: Productor. +Producer: Productor With visible: Con visible Field: Campo More fields: Más campos