diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 5e85c78a1..d38bc89d8 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2178,6 +2178,15 @@ INSERT INTO `hedera`.`imageCollectionSize`(`id`, `collectionFk`,`width`, `height VALUES (1, 4, 160, 160); +INSERT INTO `vn`.`rateConfig`(`rate0`, `rate1`, `rate2`, `rate3`) + VALUES + (36, 31, 25, 21); + +INSERT INTO `vn`.`rate`(`dated`, `warehouseFk`, `rate0`, `rate1`, `rate2`, `rate3`) + VALUES + (DATE_ADD(CURDATE(), INTERVAL -1 YEAR), 1, 10, 15, 20, 25), + (CURDATE(), 1, 12, 17, 22, 27); + INSERT INTO `vn`.`awb` (id, code, package, weight, created, amount, transitoryFk, taxFk) VALUES (1, '07546501420', 67, 671, CURDATE(), 1761, 1, 1), @@ -2247,4 +2256,4 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`) INSERT INTO `vn`.`ticketRecalc`(`ticketFk`) SELECT `id` FROM `vn`.`ticket`; -CALL `vn`.`ticket_doRecalc`(); \ No newline at end of file +CALL `vn`.`ticket_doRecalc`(); diff --git a/e2e/assets/07_import_buys.json b/e2e/assets/07_import_buys.json new file mode 100644 index 000000000..5f0f74342 --- /dev/null +++ b/e2e/assets/07_import_buys.json @@ -0,0 +1,127 @@ +{ + "invoices": [ + { + "tx_company": "TESSAROSES S.A.", + "id_invoice": "20062926", + "id_purchaseorder": "20106319", + "tx_customer_ref": "", + "id_customer": "56116", + "id_customer_floricode": "", + "nm_bill": "VERDNATURA LEVANTE SL", + "nm_ship": "VERDNATURA LEVANTE SL", + "nm_cargo": "OYAMBARILLO", + "dt_purchaseorder": "06/19/2020", + "dt_fly": "06/20/2020", + "dt_invoice": "06/19/2020", + "nm_incoterm": "FOB UIO", + "tx_awb": "729-6340 2846", + "tx_hawb": "LA0061832844", + "tx_oe": "05520204000335992", + "nu_totalstemsPO": "850", + "mny_flower": "272.5000", + "mny_freight": "0.0000", + "mny_total": "272.5000", + "nu_boxes": "4", + "nu_fulls": "1.75", + "dt_posted": "2020-06-19T13:31:41", + "boxes": [ + { + "id_box": "200573095", + "nm_box": "HB", + "tp_box": "HB", + "tx_label": "", + "nu_length": "96", + "nu_width": "32", + "nu_height": "30.5", + "products": [ + { + "id_floricode": "27887", + "id_migros_variety": "", + "nm_product": "FREEDOM 60CM 25ST", + "nm_species": "ROSES", + "nm_variety": "FREEDOM", + "nu_length": "60", + "nu_stems_bunch": "25", + "nu_bunches": "10", + "mny_rate_stem": "0.3500", + "mny_freight_unit": "0.0000", + "barcodes": "202727621,202725344,202725345,202725571,202725730,202725731,202725732,202725925,202726131,202726685" + } + ] + }, + { + "id_box": "200573106", + "nm_box": "HB", + "tp_box": "HB", + "tx_label": "", + "nu_length": "96", + "nu_width": "32", + "nu_height": "30.5", + "products": [ + { + "id_floricode": "27887", + "id_migros_variety": "", + "nm_product": "FREEDOM 70CM 25ST", + "nm_species": "ROSES", + "nm_variety": "FREEDOM", + "nu_length": "70", + "nu_stems_bunch": "25", + "nu_bunches": "8", + "mny_rate_stem": "0.4000", + "mny_freight_unit": "0.0000", + "barcodes": "202727077,202727078,202727079,202727080,202727650,202727654,202727656,202727657" + } + ] + }, + { + "id_box": "200573117", + "nm_box": "HB", + "tp_box": "HB", + "tx_label": "", + "nu_length": "96", + "nu_width": "32", + "nu_height": "30.5", + "products": [ + { + "id_floricode": "28409", + "id_migros_variety": "", + "nm_product": "TIBET 40CM 25ST", + "nm_species": "ROSES", + "nm_variety": "TIBET", + "nu_length": "40", + "nu_stems_bunch": "25", + "nu_bunches": "12", + "mny_rate_stem": "0.2500", + "mny_freight_unit": "0.0000", + "barcodes": "202723350,202723351,202723352,202723353,202723354,202723355,202723356,202723357,202726690,202726745,202726813,202726814" + } + ] + }, + { + "id_box": "200573506", + "nm_box": "QB 2", + "tp_box": "QB", + "tx_label": "", + "nu_length": "80", + "nu_width": "30", + "nu_height": "17.5", + "products": [ + { + "id_floricode": "27887", + "id_migros_variety": "", + "nm_product": "FREEDOM 50CM 25ST", + "nm_species": "ROSES", + "nm_variety": "FREEDOM", + "nu_length": "50", + "nu_stems_bunch": "25", + "nu_bunches": "4", + "mny_rate_stem": "0.3000", + "mny_freight_unit": "0.0000", + "barcodes": "202727837,202727839,202727842,202726682" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/storage/dms/ecc/3.jpeg b/e2e/assets/thermograph.jpeg similarity index 100% rename from storage/dms/ecc/3.jpeg rename to e2e/assets/thermograph.jpeg diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 75ae95293..d06e9c75d 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1015,6 +1015,17 @@ export default { travelsQuicklink: 'vn-entry-descriptor vn-quick-link[icon="local_airport"] > a', entriesQuicklink: 'vn-entry-descriptor vn-quick-link[icon="icon-entry"] > a' }, + entryBuys: { + importButton: 'vn-entry-buy-index vn-icon[icon="publish"]', + ref: 'vn-entry-buy-import vn-textfield[ng-model="$ctrl.import.ref"]', + observation: 'vn-entry-buy-import vn-textarea[ng-model="$ctrl.import.observation"]', + file: 'vn-entry-buy-import vn-input-file[ng-model="$ctrl.import.file"]', + firstImportedItem: 'vn-entry-buy-import tbody:nth-child(2) vn-autocomplete[ng-model="buy.itemFk"]', + secondImportedItem: 'vn-entry-buy-import tbody:nth-child(3) vn-autocomplete[ng-model="buy.itemFk"]', + thirdImportedItem: 'vn-entry-buy-import tbody:nth-child(4) vn-autocomplete[ng-model="buy.itemFk"]', + fourthImportedItem: 'vn-entry-buy-import tbody:nth-child(5) vn-autocomplete[ng-model="buy.itemFk"]', + importBuysButton: 'vn-entry-buy-import button[type="submit"]' + }, entryLatestBuys: { firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)', allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check', diff --git a/e2e/paths/10-travel/05_thermograph.spec.js b/e2e/paths/10-travel/05_thermograph.spec.js index 44fc783f0..97077554f 100644 --- a/e2e/paths/10-travel/05_thermograph.spec.js +++ b/e2e/paths/10-travel/05_thermograph.spec.js @@ -38,7 +38,7 @@ describe('Travel thermograph path', () => { it('should select the file to upload', async() => { let currentDir = process.cwd(); - let filePath = `${currentDir}/storage/dms/ecc/3.jpeg`; + let filePath = `${currentDir}/e2e/assets/thermograph.jpeg`; const [fileChooser] = await Promise.all([ page.waitForFileChooser(), diff --git a/e2e/paths/12-entry/03_latestBuys.spec.js b/e2e/paths/12-entry/03_latestBuys.spec.js index 8e9de8158..f2d64e3b4 100644 --- a/e2e/paths/12-entry/03_latestBuys.spec.js +++ b/e2e/paths/12-entry/03_latestBuys.spec.js @@ -41,6 +41,6 @@ describe('Entry lastest buys path', () => { it('should navigate to the entry.buy section by clicking one of the buys', async() => { await page.waitToClick(selectors.entryLatestBuys.firstBuy); - await page.waitForState('entry.card.buy'); + await page.waitForState('entry.card.buy.index'); }); }); diff --git a/e2e/paths/12-entry/07_import_buys.spec.js b/e2e/paths/12-entry/07_import_buys.spec.js new file mode 100644 index 000000000..02db0ded5 --- /dev/null +++ b/e2e/paths/12-entry/07_import_buys.spec.js @@ -0,0 +1,62 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Entry import buys path', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('buyer', 'entry'); + await page.accessToSearchResult('1'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should count the summary buys and find there only one at this point', async() => { + const buysCount = await page.countElement(selectors.entrySummary.anyBuyLine); + + expect(buysCount).toEqual(1); + }); + + it('should navigate to the buy section and then click the import button opening the import form', async() => { + await page.accessToSection('entry.card.buy.index'); + await page.waitToClick(selectors.entryBuys.importButton); + await page.waitForState('entry.card.buy.import'); + }); + + 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`; + + const [fileChooser] = await Promise.all([ + page.waitForFileChooser(), + page.waitToClick(selectors.entryBuys.file) + ]); + await fileChooser.accept([filePath]); + + 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'); + await page.autocompleteSearch(selectors.entryBuys.fourthImportedItem, 'Container ammo box 1m'); + + await page.waitToClick(selectors.entryBuys.importBuysButton); + + const message = await page.waitForSnackbar(); + const state = await page.getState(); + + expect(message.text).toContain('Data saved!'); + expect(state).toBe('entry.card.buy.index'); + }); + + it('should navigate to the entry summary and count the buys to find 4 buys have been added', async() => { + await page.waitToClick('vn-icon[icon="preview"]'); + await page.waitForNumberOfElements(selectors.entrySummary.anyBuyLine, 5); + }); +}); diff --git a/front/core/components/field/index.js b/front/core/components/field/index.js index d18973bbe..e10ef6383 100644 --- a/front/core/components/field/index.js +++ b/front/core/components/field/index.js @@ -20,7 +20,8 @@ export default class Field extends FormInput { super.$onInit(); if (this.info) this.classList.add('has-icons'); - this.input.addEventListener('change', () => this.onChange()); + this.input.addEventListener('change', event => + this.onChange(event)); } set field(value) { @@ -82,6 +83,9 @@ export default class Field extends FormInput { this._required = value; let required = this.element.querySelector('.required'); display(required, this._required); + + this.$.$applyAsync(() => + this.input.setAttribute('required', value)); } get required() { @@ -186,10 +190,13 @@ export default class Field extends FormInput { this.refreshHint(); } - onChange() { + onChange($event) { // Changes doesn't reflect until appling async this.$.$applyAsync(() => { - this.emit('change', {value: this.field}); + this.emit('change', { + value: this.field, + $event: $event + }); }); } } diff --git a/front/core/components/input-file/index.js b/front/core/components/input-file/index.js index 8bdb1a4fe..962f38b73 100644 --- a/front/core/components/input-file/index.js +++ b/front/core/components/input-file/index.js @@ -71,12 +71,23 @@ export default class InputFile extends Field { this.input.click(); } - onChange() { + onChange($event) { this.emit('change', { value: this.field, - $files: this.files + $files: this.files, + $event: $event }); } + + get accept() { + return this._accept; + } + + set accept(value) { + this._accept = value; + this.$.$applyAsync(() => + this.input.setAttribute('accept', value)); + } } ngModule.vnComponent('vnInputFile', { diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js new file mode 100644 index 000000000..10871f4ad --- /dev/null +++ b/modules/entry/back/methods/entry/importBuys.js @@ -0,0 +1,100 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +module.exports = Self => { + Self.remoteMethodCtx('importBuys', { + description: 'Imports the buys from a list', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The entry id', + http: {source: 'path'} + }, + { + arg: 'options', + type: 'object', + description: 'Callback options', + }, + { + arg: 'ref', + type: 'string', + description: 'The buyed boxes ids', + }, + { + arg: 'observation', + type: 'string', + description: 'The observation', + }, + { + arg: 'buys', + type: ['Object'], + description: 'The buys', + }], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/:id/importBuys`, + verb: 'POST' + } + }); + + Self.importBuys = async(ctx, id, options = {}) => { + const conn = Self.dataSource.connector; + const args = ctx.args; + const models = Self.app.models; + + let tx; + if (!options.transaction) { + tx = await Self.beginTransaction({}); + options.transaction = tx; + } + + try { + const entry = await models.Entry.findById(id, null, options); + await entry.updateAttributes({ + observation: args.observation, + ref: args.ref + }, options); + + const buys = []; + for (let buy of args.buys) { + buys.push({ + entryFk: entry.id, + itemFk: buy.itemFk, + stickers: 1, + quantity: 1, + packing: buy.packing, + grouping: buy.grouping, + buyingValue: buy.buyingValue, + packageFk: buy.packageFk + }); + } + + const createdBuys = await models.Buy.create(buys, options); + const buyIds = createdBuys.map(buy => buy.id); + + let stmts = []; + let stmt; + + stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc'); + stmt = new ParameterizedSQL( + `CREATE TEMPORARY TABLE tmp.buyRecalc + (INDEX (id)) + ENGINE = MEMORY + SELECT ? AS id`, [buyIds]); + + stmts.push(stmt); + stmts.push('CALL buy_recalcPrices()'); + + const sql = ParameterizedSQL.join(stmts, ';'); + await conn.executeStmt(sql, options); + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/entry/back/methods/entry/importBuysPreview.js b/modules/entry/back/methods/entry/importBuysPreview.js new file mode 100644 index 000000000..9d6662327 --- /dev/null +++ b/modules/entry/back/methods/entry/importBuysPreview.js @@ -0,0 +1,40 @@ +module.exports = Self => { + Self.remoteMethod('importBuysPreview', { + description: 'Calculates the preview buys for an entry import', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The entry id', + http: {source: 'path'} + }, + { + arg: 'buys', + type: ['Object'], + description: 'The buys', + }], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/:id/importBuysPreview`, + verb: 'GET' + } + }); + + Self.importBuysPreview = async(id, buys) => { + const models = Self.app.models; + for (let buy of buys) { + const packaging = await models.Packaging.findOne({ + fields: ['id'], + where: {volume: {gte: buy.volume}}, + order: 'volume ASC' + }); + buy.packageFk = packaging.id; + } + + return buys; + }; +}; diff --git a/modules/entry/back/methods/entry/specs/importBuys.spec.js b/modules/entry/back/methods/entry/specs/importBuys.spec.js new file mode 100644 index 000000000..d0793a2f6 --- /dev/null +++ b/modules/entry/back/methods/entry/specs/importBuys.spec.js @@ -0,0 +1,80 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('entry import()', () => { + let newEntry; + const buyerId = 35; + const companyId = 442; + const travelId = 1; + const supplierId = 1; + const activeCtx = { + accessToken: {userId: buyerId}, + }; + + beforeAll(async done => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + + done(); + }); + + it('should import the buy rows', async() => { + const expectedRef = '1, 2'; + const expectedObservation = '123456'; + const ctx = { + req: activeCtx, + args: { + observation: expectedObservation, + ref: expectedRef, + buys: [ + { + itemFk: 1, + buyingValue: 5.77, + description: 'Bow', + grouping: 1, + packing: 1, + size: 1, + volume: 1200, + packageFk: '94' + }, + { + itemFk: 4, + buyingValue: 2.16, + description: 'Arrow', + grouping: 1, + packing: 1, + size: 25, + volume: 1125, + packageFk: '94' + } + ] + } + }; + const tx = await app.models.Entry.beginTransaction({}); + const options = {transaction: tx}; + + newEntry = await app.models.Entry.create({ + dated: new Date(), + supplierFk: supplierId, + travelFk: travelId, + companyFk: companyId, + observation: 'The entry', + ref: 'Entry ref' + }, options); + + await app.models.Entry.importBuys(ctx, newEntry.id, options); + + const updatedEntry = await app.models.Entry.findById(newEntry.id, null, options); + const entryBuys = await app.models.Buy.find({ + where: {entryFk: newEntry.id} + }, options); + + expect(updatedEntry.observation).toEqual(expectedObservation); + expect(updatedEntry.ref).toEqual(expectedRef); + expect(entryBuys.length).toEqual(2); + + // Restores + await tx.rollback(); + }); +}); diff --git a/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js new file mode 100644 index 000000000..d286993ad --- /dev/null +++ b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js @@ -0,0 +1,41 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('entry importBuysPreview()', () => { + const entryId = 1; + beforeAll(async done => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + + done(); + }); + + it('should return the buys with the calculated packageFk', async() => { + const expectedPackageFk = '3'; + const buys = [ + { + itemFk: 1, + buyingValue: 5.77, + description: 'Bow', + grouping: 1, + size: 1, + volume: 1200 + }, + { + itemFk: 4, + buyingValue: 2.16, + description: 'Arrow', + grouping: 1, + size: 25, + volume: 1125 + } + ]; + + const result = await app.models.Entry.importBuysPreview(entryId, buys); + const randomIndex = Math.floor(Math.random() * result.length); + const buy = result[randomIndex]; + + expect(buy.packageFk).toEqual(expectedPackageFk); + }); +}); diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index 94dbe787d..f1a22fddd 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -2,4 +2,6 @@ module.exports = Self => { require('../methods/entry/filter')(Self); require('../methods/entry/getEntry')(Self); require('../methods/entry/getBuys')(Self); + require('../methods/entry/importBuys')(Self); + require('../methods/entry/importBuysPreview')(Self); }; diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json index 40d6d29dd..78d3c5e4f 100644 --- a/modules/entry/back/models/entry.json +++ b/modules/entry/back/models/entry.json @@ -28,7 +28,7 @@ "type": "boolean" }, "notes": { - "type": "String" + "type": "string" }, "isConfirmed": { "type": "boolean" diff --git a/modules/entry/front/buy/import/index.html b/modules/entry/front/buy/import/index.html new file mode 100644 index 000000000..74b6c708a --- /dev/null +++ b/modules/entry/front/buy/import/index.html @@ -0,0 +1,116 @@ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ItemDescriptionSizePackingGroupingBuying valueBoxVolume
+ + + {{::id}} - {{::name}} + + + {{::buy.description | dashIfEmpty}}{{::buy.size | dashIfEmpty}} + + {{::buy.packing | dashIfEmpty}} + + + + {{::buy.grouping | dashIfEmpty}} + + + {{::buy.buyingValue | currency: 'EUR':2}} + + + {{::buy.volume | number}}
+
+
+ + + + + + + +
+
diff --git a/modules/entry/front/buy/import/index.js b/modules/entry/front/buy/import/index.js new file mode 100644 index 000000000..9dc3418f0 --- /dev/null +++ b/modules/entry/front/buy/import/index.js @@ -0,0 +1,99 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + this.import = { + file: '', + invoice: null, + buys: [] + }; + } + + onFileChange($event) { + const input = $event.target; + const file = input.files[0]; + + const reader = new FileReader(); + reader.onload = event => + this.fillData(event.target.result); + reader.readAsText(file, 'UTF-8'); + } + + fillData(raw) { + const data = JSON.parse(raw); + const [invoice] = data.invoices; + + this.$.$applyAsync(() => { + this.import.observation = invoice.tx_awb; + + const boxes = invoice.boxes; + const buys = []; + for (let box of boxes) { + const boxVolume = box.nu_length * box.nu_width * box.nu_height; + for (let product of box.products) { + const packing = product.nu_stems_bunch * product.nu_bunches; + buys.push({ + description: product.nm_product, + size: product.nu_length, + packing: packing, + grouping: product.nu_stems_bunch, + buyingValue: parseFloat(product.mny_rate_stem), + volume: boxVolume + }); + } + } + + const boxesId = boxes.map(box => box.id_box); + this.import.ref = boxesId.join(', '); + + this.fetchBuys(buys); + }); + } + + fetchBuys(buys) { + const params = {buys}; + const query = `Entries/${this.entry.id}/importBuysPreview`; + this.$http.get(query, {params}).then(res => { + this.import.buys = res.data; + }); + } + + onSubmit() { + try { + const params = this.import; + const hasAnyEmptyRow = params.buys.some(buy => { + return buy.itemFk == null; + }); + + if (hasAnyEmptyRow) + throw new Error(`Some of the imported buys doesn't have an item`); + + const query = `Entries/${this.entry.id}/importBuys`; + return this.$http.post(query, params) + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))) + .then(() => this.$state.go('entry.card.buy.index')); + } catch (e) { + this.vnApp.showError(this.$t(e.message)); + return false; + } + } + + itemSearchFunc($search) { + return /^\d+$/.test($search) + ? {id: $search} + : {name: {like: '%' + $search + '%'}}; + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnEntryBuyImport', { + template: require('./index.html'), + controller: Controller, + bindings: { + worker: '<' + } +}); diff --git a/modules/entry/front/buy/import/index.spec.js b/modules/entry/front/buy/import/index.spec.js new file mode 100644 index 000000000..126c7375f --- /dev/null +++ b/modules/entry/front/buy/import/index.spec.js @@ -0,0 +1,145 @@ +import './index.js'; + +describe('Entry', () => { + describe('Component vnEntryBuyImport', () => { + let controller; + let $httpParamSerializer; + let $httpBackend; + + beforeEach(ngModule('entry')); + + beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpParamSerializer_, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; + let $element = $compile(' { + it(`should call to the fillData() method`, () => { + controller.fetchBuys = jest.fn(); + + const rawData = `{ + "invoices": [ + { + "tx_awb": "123456", + "boxes": [ + { + "id_box": 1, + "nu_length": 1, + "nu_width": 15, + "nu_height": 80, + "products": [ + { + "nm_product": "Bow", + "nu_length": 1, + "nu_stems_bunch": 1, + "nu_bunches": 1, + "mny_rate_stem": 5.77 + } + + ] + }, + { + "id_box": 2, + "nu_length": 25, + "nu_width": 1, + "nu_height": 45, + "products": [ + { + "nm_product": "Arrow", + "nu_length": 25, + "nu_stems_bunch": 1, + "nu_bunches": 1, + "mny_rate_stem": 2.16 + } + ] + } + ] + } + ]}`; + const expectedBuys = [ + {'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200}, + {'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125} + ]; + controller.fillData(rawData); + controller.$.$apply(); + + const importData = controller.import; + + expect(importData.observation).toEqual('123456'); + expect(importData.ref).toEqual('1, 2'); + + expect(controller.fetchBuys).toHaveBeenCalledWith(expectedBuys); + }); + }); + + describe('fetchBuys()', () => { + it(`should perform a query to fetch the buys data`, () => { + const buys = [ + {'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200}, + {'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125} + ]; + + const serializedParams = $httpParamSerializer({buys}); + const query = `Entries/1/importBuysPreview?${serializedParams}`; + $httpBackend.expectGET(query).respond(200, buys); + controller.fetchBuys(buys); + $httpBackend.flush(); + + const importData = controller.import; + + expect(importData.buys.length).toEqual(2); + }); + }); + + describe('onSubmit()', () => { + it(`should throw an error when some of the rows doesn't have an item`, () => { + jest.spyOn(controller.vnApp, 'showError'); + + controller.import = { + observation: '123456', + ref: '1, 2', + buys: [ + {'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200}, + {'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125} + ] + }; + + controller.onSubmit(); + + expect(controller.vnApp.showError).toHaveBeenCalledWith(`Some of the imported buys doesn't have an item`); + }); + + it(`should perform a query to update columns`, () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + controller.$state.go = jest.fn(); + + controller.import = { + observation: '123456', + ref: '1, 2', + buys: [ + {'itemFk': 10, 'buyingValue': 5.77, 'description': 'Bow', 'grouping': 1, 'packing': 1, 'size': 1, 'volume': 1200}, + {'itemFk': 11, 'buyingValue': 2.16, 'description': 'Arrow', 'grouping': 1, 'packing': 1, 'size': 25, 'volume': 1125} + ] + }; + const params = controller.import; + + const query = `Entries/1/importBuys`; + $httpBackend.expectPOST(query, params).respond(200, params.buys); + controller.onSubmit(); + $httpBackend.flush(); + + const importData = controller.import; + + expect(importData.buys.length).toEqual(2); + + expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); + expect(controller.$state.go).toHaveBeenCalledWith('entry.card.buy.index'); + }); + }); + }); +}); diff --git a/modules/entry/front/buy/import/style.scss b/modules/entry/front/buy/import/style.scss new file mode 100644 index 000000000..dba069616 --- /dev/null +++ b/modules/entry/front/buy/import/style.scss @@ -0,0 +1,5 @@ +vn-entry-buy-import { + .vn-table > tbody td:nth-child(1) { + width: 250px + } +} \ No newline at end of file diff --git a/modules/entry/front/buy/index.html b/modules/entry/front/buy/index.html deleted file mode 100644 index 8b1378917..000000000 --- a/modules/entry/front/buy/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/entry/front/buy/index/index.html b/modules/entry/front/buy/index/index.html new file mode 100644 index 000000000..0ff11c8a6 --- /dev/null +++ b/modules/entry/front/buy/index/index.html @@ -0,0 +1,14 @@ +
+ + + + + + +
\ No newline at end of file diff --git a/modules/entry/front/buy/index.js b/modules/entry/front/buy/index/index.js similarity index 66% rename from modules/entry/front/buy/index.js rename to modules/entry/front/buy/index/index.js index 00a8421fb..6cb27e022 100644 --- a/modules/entry/front/buy/index.js +++ b/modules/entry/front/buy/index/index.js @@ -1,7 +1,7 @@ -import ngModule from '../module'; +import ngModule from '../../module'; import Section from 'salix/components/section'; -ngModule.vnComponent('vnEntryBuy', { +ngModule.vnComponent('vnEntryBuyIndex', { template: require('./index.html'), controller: Section, bindings: { diff --git a/modules/entry/front/buy/index/locale/es.yml b/modules/entry/front/buy/index/locale/es.yml new file mode 100644 index 000000000..8f2be1e44 --- /dev/null +++ b/modules/entry/front/buy/index/locale/es.yml @@ -0,0 +1 @@ +Buy: Lineas de entrada \ 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 8f2be1e44..b5a82948b 100644 --- a/modules/entry/front/buy/locale/es.yml +++ b/modules/entry/front/buy/locale/es.yml @@ -1 +1,5 @@ -Buy: Lineas de entrada \ No newline at end of file +reference: Referencia +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 \ No newline at end of file diff --git a/modules/entry/front/index.js b/modules/entry/front/index.js index 14aecc8db..88700b166 100644 --- a/modules/entry/front/index.js +++ b/modules/entry/front/index.js @@ -13,4 +13,6 @@ import './card'; import './note'; import './summary'; import './log'; -import './buy'; +import './buy/index'; +import './buy/import'; + diff --git a/modules/entry/front/latest-buys/index.html b/modules/entry/front/latest-buys/index.html index 34f6464fc..3b6143e76 100644 --- a/modules/entry/front/latest-buys/index.html +++ b/modules/entry/front/latest-buys/index.html @@ -61,7 +61,7 @@ + ui-sref="entry.card.buy.index({id: {{::buy.entryFk}}})">