diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index bb33487a3..2f3a9378d 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1103,11 +1103,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`) (104, 500), (105, 5000); -INSERT INTO `vn`.`supplier`(`id`, `name`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`) +INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`) VALUES - (1, 'Plants SL', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1), - (2, 'Flower King', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2), - (442, 'Verdnatura Levante SL', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2); + (1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1), + (2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2), + (442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2); INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`) VALUES diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 1f1ab4b26..563b8d89a 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -799,5 +799,10 @@ export default { inflation: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.inflation"]', volumetric: 'vn-zone-basic-data vn-check[ng-model="$ctrl.zone.isVolumetric"]', saveButton: 'vn-zone-basic-data vn-submit > button', + }, + entrySummary: { + header: 'vn-entry-summary > vn-card > h5', + reference: 'vn-entry-summary vn-label-value[label="Reference"]', + confirmed: 'vn-entry-summary vn-check[label="Confirmed"]', } }; diff --git a/e2e/paths/12-entry/01_summary.spec.js b/e2e/paths/12-entry/01_summary.spec.js new file mode 100644 index 000000000..39b12b840 --- /dev/null +++ b/e2e/paths/12-entry/01_summary.spec.js @@ -0,0 +1,43 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Entry summary path', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('buyer', 'entry'); + await page.waitToClick('vn-entry-index vn-tbody > a:nth-child(2)'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should reach the second entry summary section', async() => { + let url = await page.expectURL('#!/entry/2/summary'); + + expect(url).toBe(true); + }); + + it(`should display details from the entry on the header`, async() => { + await page.waitForTextInElement(selectors.entrySummary.header, 'The king'); + const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText'); + + expect(result).toContain('The king'); + }); + + it('should display some entry details like the reference', async() => { + const result = await page.waitToGetProperty(selectors.entrySummary.reference, 'innerText'); + + expect(result).toContain('Movement 2'); + }); + + it('should display other entry details like the confirmed', async() => { + const result = await page.checkboxState(selectors.entrySummary.confirmed, 'innerText'); + + expect(result).toContain('unchecked'); + }); +}); diff --git a/front/core/styles/icons/salixfont.css b/front/core/styles/icons/salixfont.css index 64f2776ea..505fb8520 100644 --- a/front/core/styles/icons/salixfont.css +++ b/front/core/styles/icons/salixfont.css @@ -23,6 +23,12 @@ -moz-osx-font-smoothing: grayscale; } +.icon-net:before { + content: "\e95b"; +} +.icon-anonymous:before { + content: "\e95c"; +} .icon-buyrequest:before { content: "\e914"; } diff --git a/front/core/styles/icons/salixfont.svg b/front/core/styles/icons/salixfont.svg index 2c13f601b..7cd1af528 100644 --- a/front/core/styles/icons/salixfont.svg +++ b/front/core/styles/icons/salixfont.svg @@ -98,4 +98,6 @@ + + \ No newline at end of file diff --git a/front/core/styles/icons/salixfont.ttf b/front/core/styles/icons/salixfont.ttf index 7a6ad2721..08a36a48b 100644 Binary files a/front/core/styles/icons/salixfont.ttf and b/front/core/styles/icons/salixfont.ttf differ diff --git a/front/core/styles/icons/salixfont.woff b/front/core/styles/icons/salixfont.woff index 0e6d9a21a..325a1bc2d 100644 Binary files a/front/core/styles/icons/salixfont.woff and b/front/core/styles/icons/salixfont.woff differ diff --git a/modules/client/front/balance/index/index.spec.js b/modules/client/front/balance/index/index.spec.js index e10f903d0..e044b7979 100644 --- a/modules/client/front/balance/index/index.spec.js +++ b/modules/client/front/balance/index/index.spec.js @@ -44,12 +44,6 @@ describe('Client', () => { }); describe('company setter/getter', () => { - it('should return the company', () => { - controller.companyId = null; - - expect(controller._companyId).toEqual(jasmine.any(Object)); - }); - it('should return the company and then call getData()', () => { spyOn(controller, 'getData'); controller.companyId = 442; diff --git a/modules/client/front/descriptor/index.html b/modules/client/front/descriptor/index.html index 30777bcda..d8e035942 100644 --- a/modules/client/front/descriptor/index.html +++ b/modules/client/front/descriptor/index.html @@ -77,18 +77,15 @@
- From date + Send consumer report
-
- To date -
{ type: 'Number', description: 'The currency id to filter', http: {source: 'query'} + }, { + arg: 'from', + type: 'Date', + description: `The from date filter` + }, { + arg: 'to', + type: 'Date', + description: `The to date filter` } ], returns: { @@ -91,6 +99,10 @@ module.exports = Self => { return {[param]: {like: `%${value}%`}}; case 'created': return {'e.created': {gte: value}}; + case 'from': + return {'t.landed': {gte: value}}; + case 'to': + return {'t.landed': {lte: value}}; case 'id': case 'isBooked': case 'isConfirmed': diff --git a/modules/entry/back/methods/entry/getEntry.js b/modules/entry/back/methods/entry/getEntry.js new file mode 100644 index 000000000..008ee8148 --- /dev/null +++ b/modules/entry/back/methods/entry/getEntry.js @@ -0,0 +1,76 @@ +module.exports = Self => { + Self.remoteMethod('getEntry', { + description: 'Returns an entry', + accessType: 'READ', + accepts: { + arg: 'id', + type: 'number', + required: true, + description: 'The entry id', + http: {source: 'path'} + }, + returns: { + type: 'object', + root: true + }, + http: { + path: `/:id/getEntry`, + verb: 'GET' + } + }); + + Self.getEntry = async id => { + let filter = { + where: {id: id}, + include: [ + { + relation: 'supplier', + scope: { + fields: ['id', 'nickname'] + } + }, + { + relation: 'travel', + scope: { + fields: ['id', 'name', 'shipped', 'landed', 'agencyFk', 'warehouseOutFk', 'warehouseInFk'], + include: [ + { + relation: 'agency', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseOut', + scope: { + fields: ['name'] + } + }, + { + relation: 'warehouseIn', + scope: { + fields: ['name'] + } + } + ] + } + }, + { + relation: 'currency', + scope: { + fields: ['id', 'name'] + } + }, + { + relation: 'company', + scope: { + fields: ['id', 'code'] + } + } + ], + }; + + let entry = await Self.app.models.Entry.findOne(filter); + return entry; + }; +}; diff --git a/modules/entry/back/methods/entry/specs/getEntry.spec.js b/modules/entry/back/methods/entry/specs/getEntry.spec.js new file mode 100644 index 000000000..3791a3703 --- /dev/null +++ b/modules/entry/back/methods/entry/specs/getEntry.spec.js @@ -0,0 +1,31 @@ +const app = require('vn-loopback/server/server'); + +describe('travel getEntry()', () => { + const entryId = 1; + it('should check the entry contains the id', async() => { + const entry = await app.models.Entry.getEntry(entryId); + + expect(entry.id).toEqual(entryId); + }); + + it('should check the entry contains the supplier name', async() => { + const entry = await app.models.Entry.getEntry(entryId); + const supplierName = entry.supplier().nickname; + + expect(supplierName).toEqual('Plants nick'); + }); + + it('should check the entry contains the receiver warehouse name', async() => { + const entry = await app.models.Entry.getEntry(entryId); + const receiverWarehouseName = entry.travel().warehouseIn().name; + + expect(receiverWarehouseName).toEqual('Warehouse One'); + }); + + it('should check the entry contains the company code', async() => { + const entry = await app.models.Entry.getEntry(entryId); + const companyCode = entry.company().code; + + expect(companyCode).toEqual('VNL'); + }); +}); diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index 4034b7e0a..b1f71b4bd 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -1,4 +1,5 @@ module.exports = Self => { require('../methods/entry/filter')(Self); + require('../methods/entry/getEntry')(Self); }; diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json index a2eef4cd2..c2a7d7c42 100644 --- a/modules/entry/back/models/entry.json +++ b/modules/entry/back/models/entry.json @@ -39,6 +39,9 @@ "commission": { "type": "Number" }, + "isOrdered": { + "type": "Boolean" + }, "created": { "type": "date" }, diff --git a/modules/entry/front/card/index.js b/modules/entry/front/card/index.js index 62fed7db0..83f47c83d 100644 --- a/modules/entry/front/card/index.js +++ b/modules/entry/front/card/index.js @@ -11,11 +11,34 @@ class Controller extends ModuleCard { fields: ['id', 'code'] } }, { - relation: 'travel' + 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', 'name'] + fields: ['id', 'nickname'] } }, { relation: 'currency' @@ -27,7 +50,7 @@ class Controller extends ModuleCard { } } -ngModule.component('vnEntry Card', { +ngModule.component('vnEntryCard', { template: require('./index.html'), controller: Controller }); diff --git a/modules/entry/front/descriptor/index.html b/modules/entry/front/descriptor/index.html index 372479c79..cd4057c43 100644 --- a/modules/entry/front/descriptor/index.html +++ b/modules/entry/front/descriptor/index.html @@ -6,16 +6,37 @@ - + +
- + + + + + + +
+ +
diff --git a/modules/entry/front/descriptor/index.js b/modules/entry/front/descriptor/index.js index a9f5cd679..8f51308f2 100644 --- a/modules/entry/front/descriptor/index.js +++ b/modules/entry/front/descriptor/index.js @@ -1,17 +1,80 @@ import ngModule from '../module'; +import Component from 'core/lib/component'; -class Controller { - constructor($scope) { - this.$ = $scope; +class Controller extends Component { + constructor($element, $, $httpParamSerializer, vnConfig) { + super($element, $); + this.vnConfig = vnConfig; + this.$httpParamSerializer = $httpParamSerializer; + this.moreOptions = [ + {name: 'Show entry report', callback: this.showEntryReport} + ]; + } + + onMoreChange(callback) { + callback.call(this); + } + + get entry() { + return this._entry; + } + + set entry(value) { + this._entry = value; + if (!value) return; + + const date = value.travel.landed; + let to = new Date(date); + let from = new Date(date); + to.setDate(to.getDate() + 10); + + to.setHours(0, 0, 0, 0); + + from.setDate(from.getDate() - 10); + from.setHours(0, 0, 0, 0); + + let links = { + btnOne: { + icon: 'local_airport', + state: `travel.index({q: '{"agencyFk": ${value.travel.agencyFk}}'})`, + tooltip: 'All travels with current agency' + }}; + + links.btnTwo = { + icon: 'icon-entry', + state: `entry.index({q: '{"supplierFk": ${value.supplierFk}, "to": "${to}", "from": "${from}"}'})`, + tooltip: 'All entries with current supplier' + }; + + this._quicklinks = links; + } + + get quicklinks() { + return this._quicklinks; + } + + set quicklinks(value = {}) { + this._quicklinks = Object.assign(value, this._quicklinks); + } + + showEntryReport() { + const params = { + clientId: this.vnConfig.storage.currentUserWorkerId, + entryId: this.entry.id + }; + const serializedParams = this.$httpParamSerializer(params); + let url = `api/report/entry-order?${serializedParams}`; + window.open(url); } } -Controller.$inject = ['$scope']; +Controller.$inject = ['$element', '$scope', '$httpParamSerializer', 'vnConfig']; ngModule.component('vnEntryDescriptor', { template: require('./index.html'), bindings: { - entry: '<' + entry: '<', + quicklinks: '<' }, require: { card: '^?vnEntryCard' diff --git a/modules/entry/front/descriptor/index.spec.js b/modules/entry/front/descriptor/index.spec.js new file mode 100644 index 000000000..a63abc0f1 --- /dev/null +++ b/modules/entry/front/descriptor/index.spec.js @@ -0,0 +1,35 @@ +import './index.js'; + +describe('Entry Component vnEntryDescriptor', () => { + let $httpParamSerializer; + let controller; + let $element; + + beforeEach(ngModule('entry')); + + beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$httpParamSerializer_) => { + $httpParamSerializer = _$httpParamSerializer_; + $element = angular.element(``); + controller = $componentController('vnEntryDescriptor', {$element}); + controller._entry = {id: 2}; + controller.vnConfig.storage = {currentUserWorkerId: 9}; + controller.cardReload = ()=> { + return true; + }; + })); + + describe('showEntryReport()', () => { + it('should open a new window showing a delivery note PDF document', () => { + const params = { + clientId: controller.vnConfig.storage.currentUserWorkerId, + entryId: controller.entry.id + }; + const serializedParams = $httpParamSerializer(params); + let expectedPath = `api/report/entry-order?${serializedParams}`; + spyOn(window, 'open'); + controller.showEntryReport(); + + expect(window.open).toHaveBeenCalledWith(expectedPath); + }); + }); +}); diff --git a/modules/entry/front/descriptor/locale/es.yml b/modules/entry/front/descriptor/locale/es.yml index e5b926f1d..5e1eb25c2 100644 --- a/modules/entry/front/descriptor/locale/es.yml +++ b/modules/entry/front/descriptor/locale/es.yml @@ -1 +1,4 @@ Reference: Referencia +All travels with current agency: Todos los envios con la agencia actual +All entries with current supplier: Todas las entradas con el proveedor actual +Show entry report: Ver informe del pedido \ No newline at end of file diff --git a/modules/entry/front/index.js b/modules/entry/front/index.js index a0272fccf..25e054a71 100644 --- a/modules/entry/front/index.js +++ b/modules/entry/front/index.js @@ -5,7 +5,4 @@ import './index/'; import './search-panel'; import './descriptor'; import './card'; -// import './summary'; -// import './basic-data'; -// import './log'; -// import './create'; +import './summary'; diff --git a/modules/entry/front/index/index.html b/modules/entry/front/index/index.html index f0f540489..60bbe46d5 100644 --- a/modules/entry/front/index/index.html +++ b/modules/entry/front/index/index.html @@ -16,7 +16,7 @@ - + Id Landed Reference @@ -33,18 +33,18 @@ - + + icon="icon-anonymous"> + icon="icon-net"> {{::entry.id}} diff --git a/modules/entry/front/routes.json b/modules/entry/front/routes.json index bd1ace3f2..612edc157 100644 --- a/modules/entry/front/routes.json +++ b/modules/entry/front/routes.json @@ -28,6 +28,14 @@ "state": "entry.card", "abstract": true, "component": "vn-entry-card" + }, { + "url": "/summary", + "state": "entry.card.summary", + "component": "vn-entry-summary", + "description": "Summary", + "params": { + "entry": "$ctrl.entry" + } } ] } \ No newline at end of file diff --git a/modules/entry/front/search-panel/index.html b/modules/entry/front/search-panel/index.html index d648edcdd..a3daa8f0f 100644 --- a/modules/entry/front/search-panel/index.html +++ b/modules/entry/front/search-panel/index.html @@ -54,6 +54,18 @@ ng-model="filter.created"> + + + + + + +
Entry #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/entry/front/summary/index.js b/modules/entry/front/summary/index.js new file mode 100644 index 000000000..3b26907d7 --- /dev/null +++ b/modules/entry/front/summary/index.js @@ -0,0 +1,37 @@ +import ngModule from '../module'; +import './style.scss'; +import Component from 'core/lib/component'; + +class Controller extends Component { + constructor($element, $, $httpParamSerializer) { + super($element, $); + this.$httpParamSerializer = $httpParamSerializer; + } + + get entry() { + return this._entry; + } + + set entry(value) { + this._entry = value; + + if (value && value.id) + this.getEntryData(); + } + + getEntryData() { + return this.$http.get(`/api/Entries/${this.entry.id}/getEntry`).then(response => { + this.entryData = response.data; + }); + } +} + +Controller.$inject = ['$element', '$scope', '$httpParamSerializer']; + +ngModule.component('vnEntrySummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + entry: '<' + } +}); diff --git a/modules/entry/front/summary/index.spec.js b/modules/entry/front/summary/index.spec.js new file mode 100644 index 000000000..ea4a5a7c1 --- /dev/null +++ b/modules/entry/front/summary/index.spec.js @@ -0,0 +1,50 @@ +import './index'; + +describe('component vnEntrySummary', () => { + let controller; + let $httpBackend; + let $scope; + let $element; + + beforeEach(angular.mock.module('entry', $translateProvider => { + $translateProvider.translations('en', {}); + })); + + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { + $httpBackend = _$httpBackend_; + $scope = $rootScope.$new(); + $element = angular.element(``); + controller = $componentController('vnEntrySummary', {$element, $scope}); + })); + + describe('entry setter/getter', () => { + it('should check if value.id is defined', () => { + spyOn(controller, 'getEntryData'); + + controller.entry = {id: 1}; + + expect(controller.getEntryData).toHaveBeenCalledWith(); + }); + + it('should return the entry and then call getEntryData()', () => { + spyOn(controller, 'getEntryData'); + controller.entry = {id: 99}; + + expect(controller._entry.id).toEqual(99); + expect(controller.getEntryData).toHaveBeenCalledWith(); + }); + }); + + describe('getEntryData()', () => { + it('should perform a get and then store data on the controller', () => { + controller._entry = {id: 999}; + + const query = `/api/Entries/${controller._entry.id}/getEntry`; + $httpBackend.expectGET(query).respond('I am the entryData'); + controller.getEntryData(); + $httpBackend.flush(); + + expect(controller.entryData).toEqual('I am the entryData'); + }); + }); +}); diff --git a/modules/entry/front/summary/locale/es.yml b/modules/entry/front/summary/locale/es.yml new file mode 100644 index 000000000..1673d6a17 --- /dev/null +++ b/modules/entry/front/summary/locale/es.yml @@ -0,0 +1,3 @@ +Inventory: Inventario +Virtual: Redada +Entry: Entrada \ No newline at end of file diff --git a/modules/entry/front/summary/style.scss b/modules/entry/front/summary/style.scss new file mode 100644 index 000000000..8887e15b9 --- /dev/null +++ b/modules/entry/front/summary/style.scss @@ -0,0 +1,10 @@ +@import "variables"; + + +vn-entry-summary .summary { + max-width: $width-lg; + + vn-icon[icon=insert_drive_file]{ + color: $color-font-secondary; + } +} \ No newline at end of file diff --git a/modules/travel/front/summary/index.spec.js b/modules/travel/front/summary/index.spec.js index 5411d8a0d..593d2cfcc 100644 --- a/modules/travel/front/summary/index.spec.js +++ b/modules/travel/front/summary/index.spec.js @@ -20,12 +20,6 @@ describe('component vnTravelSummary', () => { })); describe('travel setter/getter', () => { - it('should return the travel', () => { - controller.travel = {id: null}; - - expect(controller.travel).toEqual(jasmine.any(Object)); - }); - it('should return the travel and then call both getTravel() and getEntries()', () => { spyOn(controller, 'getTravel'); spyOn(controller, 'getEntries');