Merge pull request '2879 - Autocomplete item field from imported buys' (#690) from 2879-import_buys into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #690 Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
commit
2b866cd38f
|
@ -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';
|
||||
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"Buy": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ItemMatchProperties": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"EntryLog": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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'];
|
||||
|
|
|
@ -2,4 +2,10 @@ vn-entry-buy-import {
|
|||
.vn-table > tbody td:nth-child(1) {
|
||||
width: 250px
|
||||
}
|
||||
}
|
||||
|
||||
.itemFilter {
|
||||
vn-table.scrollable {
|
||||
height: 500px
|
||||
}
|
||||
}
|
|
@ -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
|
||||
JSON files only: Solo ficheros JSON
|
||||
Filter item: Filtrar artículo
|
||||
Filter...: Filtrar...
|
|
@ -1,6 +1,6 @@
|
|||
Ink: Tinta
|
||||
Origin: Origen
|
||||
Producer: Productor.
|
||||
Producer: Productor
|
||||
With visible: Con visible
|
||||
Field: Campo
|
||||
More fields: Más campos
|
||||
|
|
Loading…
Reference in New Issue