From 30aea3d08625c481367b007fb65b293808f6ca1d Mon Sep 17 00:00:00 2001 From: Juan Date: Fri, 25 May 2018 17:25:35 +0200 Subject: [PATCH 1/6] Tests fixed, watcher refactor, global error handling, angular 1.7 --- client/auth/src/login/style.scss | 2 +- .../src/address/edit/address-edit.spec.js | 1 + .../index/credit-insurance-index.spec.js | 1 + .../index/credit-insurance-list.spec.js | 1 + .../src/credit/create/credit-create.spec.js | 1 + .../client/src/descriptor/descriptor.spec.js | 1 + .../src/fiscal-data/fiscal-data.spec.js | 1 + .../autocomplete/autocomplete.spec.js | 1 + .../components/icon-menu/icon-menu.spec.js | 85 ----------------- client/core/src/components/tooltip/tooltip.js | 12 ++- client/core/src/components/watcher/watcher.js | 92 +++++++++---------- .../src/components/watcher/watcher.spec.js | 78 ++++------------ .../src/directives/specs/zoom-image.spec.js | 3 +- client/core/src/lib/app.js | 9 +- client/core/src/lib/date.js | 10 +- client/core/src/lib/http-error.js | 12 +++ client/core/src/lib/interceptor.js | 39 ++------ client/item/src/barcode/barcode.spec.js | 1 + client/item/src/botanical/botanical.spec.js | 1 + client/item/src/botanical/index.html | 1 - client/item/src/botanical/index.js | 2 +- client/item/src/card/card.spec.js | 1 + client/item/src/niche/niche.spec.js | 1 + client/item/src/tags/tags.spec.js | 1 + client/salix/src/components/app/app.html | 2 +- client/salix/src/components/app/style.scss | 2 +- client/salix/src/module.js | 75 ++++++++++----- client/ticket/src/data/step-one/index.js | 3 +- .../ticket/src/data/step-one/step-one.spec.js | 21 ++--- .../src/note/ticket-observation.spec.js | 1 + client/ticket/src/package/package.spec.js | 6 +- client/ticket/src/sale/sale.spec.js | 1 + .../ticket/src/volume/ticket-volume.spec.js | 1 + .../client-module/01_create_client.spec.js | 2 +- package-lock.json | 46 +++++----- package.json | 10 +- .../methods/client/specs/getMana.spec.js | 2 +- .../common/methods/ticket/componentUpdate.js | 12 +-- .../methods/ticket/specs/get-volume.spec.js | 2 +- 39 files changed, 221 insertions(+), 322 deletions(-) delete mode 100644 client/core/src/components/icon-menu/icon-menu.spec.js create mode 100644 client/core/src/lib/http-error.js diff --git a/client/auth/src/login/style.scss b/client/auth/src/login/style.scss index e959e15b24..61e5a3e02f 100644 --- a/client/auth/src/login/style.scss +++ b/client/auth/src/login/style.scss @@ -45,7 +45,7 @@ vn-login > div { position: absolute; width: 0; top: .3em; - right: 4em; + right: 3em; overflow: visible; } } diff --git a/client/client/src/address/edit/address-edit.spec.js b/client/client/src/address/edit/address-edit.spec.js index c48108b99e..14c771a1f4 100644 --- a/client/client/src/address/edit/address-edit.spec.js +++ b/client/client/src/address/edit/address-edit.spec.js @@ -15,6 +15,7 @@ describe('Client', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $state.params.addressId = '1'; controller = $componentController('vnClientAddressEdit', {$state: $state}); })); diff --git a/client/client/src/credit-insurance/index/credit-insurance-index.spec.js b/client/client/src/credit-insurance/index/credit-insurance-index.spec.js index 2a87132bed..29aad18801 100644 --- a/client/client/src/credit-insurance/index/credit-insurance-index.spec.js +++ b/client/client/src/credit-insurance/index/credit-insurance-index.spec.js @@ -13,6 +13,7 @@ describe('Client', () => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnClientCreditInsuranceIndex'); })); diff --git a/client/client/src/credit-insurance/insurance/index/credit-insurance-list.spec.js b/client/client/src/credit-insurance/insurance/index/credit-insurance-list.spec.js index a6723d3b69..f994829a85 100644 --- a/client/client/src/credit-insurance/insurance/index/credit-insurance-list.spec.js +++ b/client/client/src/credit-insurance/insurance/index/credit-insurance-list.spec.js @@ -14,6 +14,7 @@ describe('Client', () => { $componentController = _$componentController_; let $state = {params: {classificationId: 1}}; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnClientCreditInsuranceInsuranceIndex', {$state: $state}); })); diff --git a/client/client/src/credit/create/credit-create.spec.js b/client/client/src/credit/create/credit-create.spec.js index cff48be7da..7dea16d101 100644 --- a/client/client/src/credit/create/credit-create.spec.js +++ b/client/client/src/credit/create/credit-create.spec.js @@ -34,6 +34,7 @@ describe('Client', () => { $state = _$state_; $state.params.id = 101; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnClientCreditCreate', {$scope: $scope}, {$state: $state}); })); describe('onSubmit()', () => { diff --git a/client/client/src/descriptor/descriptor.spec.js b/client/client/src/descriptor/descriptor.spec.js index f59ff9bb29..58d348f439 100644 --- a/client/client/src/descriptor/descriptor.spec.js +++ b/client/client/src/descriptor/descriptor.spec.js @@ -13,6 +13,7 @@ describe('Descriptor', () => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnClientDescriptor'); })); diff --git a/client/client/src/fiscal-data/fiscal-data.spec.js b/client/client/src/fiscal-data/fiscal-data.spec.js index a16593ddb8..f6dbd4b6a2 100644 --- a/client/client/src/fiscal-data/fiscal-data.spec.js +++ b/client/client/src/fiscal-data/fiscal-data.spec.js @@ -14,6 +14,7 @@ describe('Client', () => { beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $scope = $rootScope.$new(); controller = $componentController('vnClientFiscalData', {$scope: $scope}); })); diff --git a/client/core/src/components/autocomplete/autocomplete.spec.js b/client/core/src/components/autocomplete/autocomplete.spec.js index 602112b68b..6ed409af21 100644 --- a/client/core/src/components/autocomplete/autocomplete.spec.js +++ b/client/core/src/components/autocomplete/autocomplete.spec.js @@ -66,6 +66,7 @@ describe('Component vnAutocomplete', () => { }; let json = encodeURIComponent(JSON.stringify(filter)); + $httpBackend.whenGET(`localhost?filter=${json}`).respond({}); $httpBackend.expectGET(`localhost?filter=${json}`); controller.field = data.id; $httpBackend.flush(); diff --git a/client/core/src/components/icon-menu/icon-menu.spec.js b/client/core/src/components/icon-menu/icon-menu.spec.js deleted file mode 100644 index 11be1ff626..0000000000 --- a/client/core/src/components/icon-menu/icon-menu.spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import './icon-menu.js'; - -describe('Component vnIconMenu', () => { - let $componentController; - let $element; - let $httpBackend; - let $timeout; - let $scope; - let controller; - - beforeEach(() => { - angular.mock.module('client'); - }); - - beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => { - $componentController = _$componentController_; - $httpBackend = _$httpBackend_; - $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); - $timeout = _$timeout_; - $scope = $rootScope.$new(); - $element = angular.element('
'); - controller = $componentController('vnIconMenu', {$scope, $element, $httpBackend, $timeout}, {url: 'test.com'}); - })); - - describe('component vnIconMenu', () => { - describe('findItem()', () => { - it(`should return items empty array if the controller does not provide a url and have no items defined`, () => { - controller.url = undefined; - controller.items = undefined; - let result = controller.findItems('some search value'); - - expect(result).toEqual([]); - }); - - it(`should return items array if the controller does not provide a url`, () => { - controller.url = undefined; - controller.items = ['Batman', 'Bruce Wayne']; - controller.findItems('some search value'); - - expect(controller.items.length).toEqual(2); - }); - - it(`should perform a search and store the result in controller items`, () => { - let search = 'The Joker'; - let json = JSON.stringify({where: {name: {regexp: search}}}); - $httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]); - $httpBackend.expectGET(`${controller.url}?filter=${json}`); - controller.findItems(search); - $httpBackend.flush(); - - expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'}); - }); - - it(`should call getItems function if there's no search value`, () => { - spyOn(controller, 'getItems'); - controller.findItems(); - - expect(controller.getItems).toHaveBeenCalledWith(); - }); - }); - - describe('getItems()', () => { - it(`should perform a query and then push elements found into controller.items`, () => { - controller.items = []; - $httpBackend.whenGET(`${controller.url}?filter={}`).respond([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]); - $httpBackend.expectGET(`${controller.url}?filter={}`); - controller.getItems(); - $httpBackend.flush(); - - expect(controller.items).toEqual([{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}]); - }); - - it(`should perform a query and then set controller.maxRow to false if there are no items in the controller`, () => { - controller.items = []; - controller.maxRow = true; - $httpBackend.whenGET(`${controller.url}?filter={"skip":0,"limit":true,"order":"name ASC"}`).respond(controller.items); - $httpBackend.expectGET(`${controller.url}?filter={"skip":0,"limit":true,"order":"name ASC"}`); - controller.getItems(); - $httpBackend.flush(); - - expect(controller.maxRow).toBeFalsy(); - }); - }); - }); -}); diff --git a/client/core/src/components/tooltip/tooltip.js b/client/core/src/components/tooltip/tooltip.js index a907f03e70..3f221c1073 100644 --- a/client/core/src/components/tooltip/tooltip.js +++ b/client/core/src/components/tooltip/tooltip.js @@ -26,7 +26,8 @@ export default class Tooltip extends Component { this.parent = parent; this.$element.addClass('show'); this.relocate(); - this.relocateTimeout = this.$timeout(() => this.relocate(), 200); + this.cancelTimeout(); + this.relocateTimeout = this.$timeout(() => this.relocate(), 50); } /** @@ -34,7 +35,10 @@ export default class Tooltip extends Component { */ hide() { this.$element.removeClass('show'); + this.cancelTimeout(); + } + cancelTimeout() { if (this.relocateTimeout) { this.$timeout.cancel(this.relocateTimeout); this.relocateTimeout = null; @@ -102,6 +106,8 @@ export default class Tooltip extends Component { } calcCoords(); + // Overflow + let axisOverflow = axis == 'x' && (left < min || left > maxLeft) || axis == 'y' && (top < min || top > maxTop); @@ -124,8 +130,6 @@ export default class Tooltip extends Component { calcCoords(); } - // Overflow - function range(coord, min, max) { return Math.min(Math.max(coord, min), max); } @@ -186,7 +190,7 @@ export default class Tooltip extends Component { this.arrow = arrow; } - $destroy() { + $onDestroy() { this.hide(); } } diff --git a/client/core/src/components/watcher/watcher.js b/client/core/src/components/watcher/watcher.js index c48d0ab168..bb15e926ba 100644 --- a/client/core/src/components/watcher/watcher.js +++ b/client/core/src/components/watcher/watcher.js @@ -12,7 +12,7 @@ import isFullEmpty from '../../lib/full-empty'; * properties are provided. */ export default class Watcher extends Component { - constructor($element, $scope, $state, $transitions, $http, vnApp, $translate, $attrs) { + constructor($element, $scope, $state, $transitions, $http, vnApp, $translate, $attrs, $q) { super($element); this.$scope = $scope; this.$state = $state; @@ -20,6 +20,7 @@ export default class Watcher extends Component { this.$translate = $translate; this.$attrs = $attrs; this.vnApp = vnApp; + this.$q = $q; this.state = null; this.deregisterCallback = $transitions.onStart({}, @@ -64,9 +65,10 @@ export default class Watcher extends Component { * @return {Promise} The request promise */ submitBack() { - return this.submit().then( - () => this.window.history.back() - ); + return this.submit().then(res => { + this.window.history.back(); + return res; + }); } /** @@ -77,9 +79,10 @@ export default class Watcher extends Component { * @return {Promise} The request promise */ submitGo(state, params) { - return this.submit().then( - () => this.$state.go(state, params || {}) - ); + return this.submit().then(res => { + this.$state.go(state, params || {}); + return res; + }); } /** @@ -88,37 +91,35 @@ export default class Watcher extends Component { * @return {Promise} The http request promise */ submit() { + return this.realSubmit().then(res => { + this.vnApp.showMessage(this.$translate.instant('Data saved!')); + return res; + }); + } + + errorHandler(err) { + this.vnApp.showError(err.message); + return err; + } + + realSubmit() { if (this.form) { this.form.$setSubmitted(); if (!this.form.$valid) - return new Promise( - (resolve, reject) => this.invalidForm(reject) - ); - } - if (!this.dataChanged()) { - return new Promise( - (resolve, reject) => this.noChanges(reject) - ); + return this.invalidForm(); } + if (!this.dataChanged()) + return this.noChanges(); + let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post'); let changedData = isPost ? this.data : getModifiedData(this.data, this.orgData); - if (this.requiredField && !changedData[this.requiredField]) { - let required = this.data[this.requiredField] || this.orgData[this.requiredField]; - if (required === undefined) { - return new Promise( - (resolve, reject) => this.invalidForm(reject) - ); - } - changedData[this.requiredField] = required; - } - if (this.save && this.save.accept) { - this.save.model = changedData; // this.copyInNewObject(changedData); - return new Promise((resolve, reject) => { + this.save.model = changedData; + return this.$q((resolve, reject) => { this.save.accept().then( json => this.writeData({data: json}, resolve), json => reject(json) @@ -131,42 +132,40 @@ export default class Watcher extends Component { let id = this.idField ? this.orgData[this.idField] : null; if (id) { - return new Promise((resolve, reject) => { + return this.$q((resolve, reject) => { this.$http.patch(`${this.url}/${id}`, changedData).then( json => this.writeData(json, resolve), - json => reject(json) + reject ); }); } - return new Promise((resolve, reject) => { + return this.$q((resolve, reject) => { this.$http.post(this.url, changedData).then( json => this.writeData(json, resolve), - json => reject(json) + reject ); }); } + noChanges() { + let message = this.$translate.instant('No changes to save'); + let p = this.$q.reject(new Error(message)); + console.log(p); + return p; + } + + invalidForm() { + let message = this.$translate.instant('Some fields are invalid'); + return this.$q.reject(new Error(message)); + } + writeData(json, resolve) { Object.assign(this.data, json.data); this.updateOriginalData(); resolve(json); } - noChanges(reject) { - this.vnApp.showMessage( - this.$translate.instant('No changes to save') - ); - reject(new Error('No changes to save')); - } - - invalidForm(reject) { - this.vnApp.showMessage( - this.$translate.instant('Some fields are invalid') - ); - reject(new Error('Some fields are invalid')); - } - updateOriginalData() { this.orgData = this.copyInNewObject(this.data); if (this.form && this.form.$dirty) @@ -218,14 +217,13 @@ export default class Watcher extends Component { } } } -Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnApp', '$translate', '$attrs']; +Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q']; ngModule.component('vnWatcher', { template: require('./watcher.html'), bindings: { url: '@?', idField: '@?', - requiredField: '@?', data: '<', form: '<', save: '<', diff --git a/client/core/src/components/watcher/watcher.spec.js b/client/core/src/components/watcher/watcher.spec.js index 88305c819c..45f34a63f4 100644 --- a/client/core/src/components/watcher/watcher.spec.js +++ b/client/core/src/components/watcher/watcher.spec.js @@ -3,6 +3,7 @@ import getModifiedData from '../../lib/modified'; describe('Component vnWatcher', () => { let $componentController; + let $rootScope; let $scope; let $element; let $state; @@ -12,25 +13,28 @@ describe('Component vnWatcher', () => { let $translate; let controller; let $attrs; + let $q; beforeEach(() => { angular.mock.module('client'); }); - beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_, _$transitions_, _$httpBackend_, _vnApp_, _$translate_) => { + beforeEach(angular.mock.inject((_$componentController_, _$rootScope_, _$state_, _$transitions_, _$httpBackend_, _vnApp_, _$translate_, _$q_) => { $componentController = _$componentController_; + $rootScope = _$rootScope_; $scope = $rootScope.$new(); $element = angular.element('
'); $state = _$state_; vnApp = _vnApp_; $transitions = _$transitions_; $httpBackend = _$httpBackend_; - $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $translate = _$translate_; + $q = _$q_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $attrs = { save: "patch" }; - controller = $componentController('vnWatcher', {$scope, $element, $state, vnApp, $transitions, $httpBackend, $translate, $attrs}); + controller = $componentController('vnWatcher', {$scope, $element, $state, vnApp, $transitions, $httpBackend, $translate, $attrs, $q}); })); describe('$onInit()', () => { @@ -52,25 +56,6 @@ describe('Component vnWatcher', () => { }); }); - describe('$onChanges()', () => { - it(`should call updateOriginalData() if controllers data is defined`, () => { - controller.data = []; - spyOn(controller, 'updateOriginalData'); - controller.$onChanges(); - - expect(controller.updateOriginalData).toHaveBeenCalledWith(); - }); - }); - - describe('$onDestroy()', () => { - it(`should call deregisterCallback()`, () => { - spyOn(controller, 'deregisterCallback'); - controller.$onDestroy(); - - expect(controller.deregisterCallback).toHaveBeenCalledWith(); - }); - }); - describe('fetchData()', () => { it(`should perform a query then store the received data into controller.data and call updateOriginalData()`, () => { spyOn(controller, 'updateOriginalData'); @@ -115,12 +100,12 @@ describe('Component vnWatcher', () => { }); }); - describe('submit()', () => { + describe('realSubmit()', () => { describe('when controller.form', () => { - it(`should call controller.form.setSubminted if controller.form is defined`, () => { + it(`should call controller.form.setSubmited if controller.form is defined`, () => { controller.form = {$setSubmitted: () => {}}; spyOn(controller.form, '$setSubmitted'); - controller.submit(); + controller.realSubmit(); expect(controller.form.$setSubmitted).toHaveBeenCalledWith(); }); @@ -128,48 +113,34 @@ describe('Component vnWatcher', () => { it(`should call controller.invalidForm if controller.form.$valid is not defined`, () => { controller.form = {$setSubmitted: () => {}}; spyOn(controller, 'invalidForm'); - controller.submit(); + controller.realSubmit(); - expect(controller.invalidForm).toHaveBeenCalledWith(jasmine.any(Function)); + expect(controller.invalidForm).toHaveBeenCalledWith(); }); }); describe('when !controller.dataChanged()', () => { it(`should call controller.noChanges()`, () => { spyOn(controller, 'noChanges'); - controller.submit(); + controller.realSubmit(); - expect(controller.noChanges).toHaveBeenCalledWith(jasmine.any(Function)); + expect(controller.noChanges).toHaveBeenCalledWith(); }); }); describe('when controller.save()', () => { it(`should set controller.save.model property`, () => { - controller.save = {accept: () => {}}; + controller.save = {accept: () => $q.resolve()}; controller.data = {originalInfo: 'original data', info: 'new data'}; controller.orgData = {originalInfo: 'original data'}; - controller.submit(); + controller.realSubmit(); expect(controller.save.model).toEqual({info: 'new data'}); }); - - it(`should call controller.save.accept() then controller.writeData`, done => { - controller.save = {accept: () => {}}; - controller.data = {originalInfo: 'original data', info: 'new data'}; - controller.orgData = {originalInfo: 'original data'}; - spyOn(controller.save, 'accept').and.returnValue(Promise.resolve()); - spyOn(controller, 'writeData').and.callThrough(); - controller.submit() - .then(() => { - expect(controller.save.accept).toHaveBeenCalledWith(); - expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); - done(); - }); - }); }); describe('when id is defined', () => { - it(`should perform a query then call controller.writeData()`, () => { + it(`should perform a query then call controller.writeData()`, done => { controller.dataChanged = () => { return true; }; @@ -182,7 +153,7 @@ describe('Component vnWatcher', () => { spyOn(controller, 'writeData').and.callThrough(); $httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json); $httpBackend.expectPATCH(`${controller.url}/1`); - controller.submit() + controller.realSubmit() .then(() => { expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); done(); @@ -191,7 +162,7 @@ describe('Component vnWatcher', () => { }); }); - it(`should perform a POST query then call controller.writeData()`, () => { + it(`should perform a POST query then call controller.writeData()`, done => { controller.dataChanged = () => { return true; }; @@ -202,7 +173,7 @@ describe('Component vnWatcher', () => { spyOn(controller, 'writeData').and.callThrough(); $httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json); $httpBackend.expectPOST(`${controller.url}`, controller.data); - controller.submit() + controller.realSubmit() .then(() => { expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); done(); @@ -224,15 +195,6 @@ describe('Component vnWatcher', () => { }); }); - describe('copyInNewObject()', () => { - it(`should return newCopy object if data was an object`, () => { - let data = {id: 1, Heroname: 'Batman', name: 'Bruce Wayne'}; - let result = controller.copyInNewObject(data); - - expect(result).toEqual(data); - }); - }); - describe('callback()', () => { describe(`when dataChanged() returns true and there's no state in the controller`, () => { it(`should define controller.state, call controller.$scope.confirm.show() and return false`, () => { diff --git a/client/core/src/directives/specs/zoom-image.spec.js b/client/core/src/directives/specs/zoom-image.spec.js index 1891f738ad..bf1bc252e0 100644 --- a/client/core/src/directives/specs/zoom-image.spec.js +++ b/client/core/src/directives/specs/zoom-image.spec.js @@ -9,9 +9,10 @@ describe('Directive zoomImage', () => { angular.mock.module('client'); }); - beforeEach(angular.mock.inject(($compile, $rootScope) => { + beforeEach(angular.mock.inject(($compile, $rootScope, $httpBackend) => { compile = $compile; scope = $rootScope.$new(); + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); })); afterEach(() => { diff --git a/client/core/src/lib/app.js b/client/core/src/lib/app.js index 5a41d209cf..5b3d0b3e68 100644 --- a/client/core/src/lib/app.js +++ b/client/core/src/lib/app.js @@ -7,9 +7,9 @@ import ngModule from '../module'; * @property {Snackbar} snackbar The main object to show messages. */ export default class App { - constructor($rootScope) { + constructor() { this.loaderStatus = 0; - this.$rootScope = $rootScope; + this.loading = false; } showMessage(message) { if (this.snackbar) @@ -22,14 +22,13 @@ export default class App { pushLoader() { this.loaderStatus++; if (this.loaderStatus === 1) - this.$rootScope.loading = true; + this.loading = true; } popLoader() { this.loaderStatus--; if (this.loaderStatus === 0) - this.$rootScope.loading = false; + this.loading = false; } } -App.$inject = ['$rootScope']; ngModule.service('vnApp', App); diff --git a/client/core/src/lib/date.js b/client/core/src/lib/date.js index 1fd9847caa..2acfd6e552 100644 --- a/client/core/src/lib/date.js +++ b/client/core/src/lib/date.js @@ -1,6 +1,6 @@ /** * Transforms a UTC date to JSON date without datetime. - * + * * @param {date} date Date to format * @return {String} Formatted date string */ @@ -12,10 +12,10 @@ export function toJsonDate(date) { let year = date.getFullYear(); if (day < 10) - day = `0${day}` + day = `0${day}`; - if (month < 10) - month = `0${month}` + if (month < 10) + month = `0${month}`; return new Date(`${year}-${month}-${day}`); -} \ No newline at end of file +} diff --git a/client/core/src/lib/http-error.js b/client/core/src/lib/http-error.js new file mode 100644 index 0000000000..91ccc89059 --- /dev/null +++ b/client/core/src/lib/http-error.js @@ -0,0 +1,12 @@ +/** + * Wraps $http error responses. This class is mainly used to + * avoid the default AngularJS behaviour, that is, stringifying all + * unhandled rejections that aren't @Error objects. More info at: + * - https://github.com/angular/angular.js/issues/14631 + */ +export default class HttpError extends Error { + constructor(message, fileName, lineNumber) { + super(message, fileName, lineNumber); + this.name = 'HttpError'; + } +} diff --git a/client/core/src/lib/interceptor.js b/client/core/src/lib/interceptor.js index 5eac56e69d..8d8cd46c02 100644 --- a/client/core/src/lib/interceptor.js +++ b/client/core/src/lib/interceptor.js @@ -1,7 +1,8 @@ import ngModule from '../module'; +import HttpError from './http-error'; -interceptor.$inject = ['$q', '$window', 'vnApp', '$translate', '$cookies']; -function interceptor($q, $window, vnApp, $translate, $cookies) { +interceptor.$inject = ['$q', 'vnApp', '$cookies']; +function interceptor($q, vnApp, $cookies) { return { request: function(config) { vnApp.pushLoader(); @@ -16,42 +17,14 @@ function interceptor($q, $window, vnApp, $translate, $cookies) { return $q.reject(rejection); }, response: function(response) { - switch (response.config.method) { - case 'PUT': - case 'POST': - case 'PATCH': - vnApp.showMessage($translate.instant('Data saved!')); - } vnApp.popLoader(); return response; }, responseError: function(rejection) { vnApp.popLoader(); - let data = rejection.data; - let error; - - switch (rejection.xhrStatus) { - case 'timeout': - case 'abort': - return $q.reject(rejection); - } - - if (data && data.error instanceof Object) - error = data.error.message; - else if (rejection.status === -1) - error = $translate.instant(`Can't contact with server`); - else - error = `${rejection.status}: ${rejection.statusText}`; - - if (rejection.status === 401) { - let location = $window.location; - let continueUrl = location.pathname + location.search + location.hash; - continueUrl = encodeURIComponent(continueUrl); - $window.location = `/auth/?apiKey=${vnApp.name}&continue=${continueUrl}`; - } - - vnApp.showError(error); - return $q.reject(rejection); + let err = new HttpError(rejection.statusText); + Object.assign(err, rejection); + return $q.reject(err); } }; } diff --git a/client/item/src/barcode/barcode.spec.js b/client/item/src/barcode/barcode.spec.js index b000caec07..06fa796f15 100644 --- a/client/item/src/barcode/barcode.spec.js +++ b/client/item/src/barcode/barcode.spec.js @@ -15,6 +15,7 @@ describe('Item', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $state.params.id = '1'; controller = $componentController('vnItemBarcode', {$state: $state}); })); diff --git a/client/item/src/botanical/botanical.spec.js b/client/item/src/botanical/botanical.spec.js index 000f12164f..ae111f52a0 100644 --- a/client/item/src/botanical/botanical.spec.js +++ b/client/item/src/botanical/botanical.spec.js @@ -14,6 +14,7 @@ describe('ItemBotanical', () => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $state = { params: { id: 123 diff --git a/client/item/src/botanical/index.html b/client/item/src/botanical/index.html index c95511be81..57352c7ca4 100644 --- a/client/item/src/botanical/index.html +++ b/client/item/src/botanical/index.html @@ -6,7 +6,6 @@ vn-id="watcher" data="$ctrl.botanical" id-field="itemFk" - required-field="itemFk" form="form" save="patch"> diff --git a/client/item/src/botanical/index.js b/client/item/src/botanical/index.js index 2c1ec61b2a..651777fa5a 100644 --- a/client/item/src/botanical/index.js +++ b/client/item/src/botanical/index.js @@ -5,7 +5,7 @@ class Controller { this.$http = $http; this.$state = $state; } - + _getBotanical() { let filter = { where: {itemFk: this.$state.params.id}, diff --git a/client/item/src/card/card.spec.js b/client/item/src/card/card.spec.js index f441b27d32..acb6d39dfa 100644 --- a/client/item/src/card/card.spec.js +++ b/client/item/src/card/card.spec.js @@ -14,6 +14,7 @@ describe('Item', () => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $state = { params: { id: 123 diff --git a/client/item/src/niche/niche.spec.js b/client/item/src/niche/niche.spec.js index c42fb781ca..11930afd6a 100644 --- a/client/item/src/niche/niche.spec.js +++ b/client/item/src/niche/niche.spec.js @@ -15,6 +15,7 @@ describe('Item', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnItemNiche', {$state: $state}); })); diff --git a/client/item/src/tags/tags.spec.js b/client/item/src/tags/tags.spec.js index a694f4a1c7..6be96a46ad 100644 --- a/client/item/src/tags/tags.spec.js +++ b/client/item/src/tags/tags.spec.js @@ -15,6 +15,7 @@ describe('Item', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnItemTags', {$state: $state}); })); diff --git a/client/salix/src/components/app/app.html b/client/salix/src/components/app/app.html index adbc23575a..b021b49874 100644 --- a/client/salix/src/components/app/app.html +++ b/client/salix/src/components/app/app.html @@ -3,7 +3,7 @@ - + diff --git a/client/salix/src/components/app/style.scss b/client/salix/src/components/app/style.scss index fabe3dcca7..6c06006912 100644 --- a/client/salix/src/components/app/style.scss +++ b/client/salix/src/components/app/style.scss @@ -14,7 +14,7 @@ vn-app { } vn-spinner { float: left; - padding: .4em; + padding: 1em .4em; } } .main-view { diff --git a/client/salix/src/module.js b/client/salix/src/module.js index 3431a62c3f..28dc0db93a 100644 --- a/client/salix/src/module.js +++ b/client/salix/src/module.js @@ -6,29 +6,6 @@ export const appName = 'salix'; const ngModule = ng.module('salix', ['vnCore']); export default ngModule; -config.$inject = ['$translatePartialLoaderProvider', '$httpProvider', '$qProvider']; -export function config($translatePartialLoaderProvider, $httpProvider, $qProvider) { - $translatePartialLoaderProvider.addPart(appName); - $httpProvider.interceptors.push('vnInterceptor'); - - // TODO: Handle or remove unhandled rejections - // $qProvider.errorOnUnhandledRejections(false); -} -ngModule.config(config); - -/* -// FIXME: Handle unhandled exceptions -exceptionHandler.$inject = ['vnApp']; -function exceptionHandler(vnApp) { - return function(exception, cause) { - console.error(exception); - }; -} -ngModule.factory('$exceptionHandler', exceptionHandler); -*/ - -const HOOK_ABORTED_TRANSITION = 3; - run.$inject = ['$window', '$rootScope', 'vnApp', '$state']; export function run($window, $rootScope, vnApp, $state) { $window.validations = {}; @@ -37,8 +14,58 @@ export function run($window, $rootScope, vnApp, $state) { $rootScope.$on('$viewContentLoaded', () => {}); window.myAppErrorLog = []; $state.defaultErrorHandler(function(error) { - if (error.type === HOOK_ABORTED_TRANSITION) + if (error.type === 3) // ABORTED_TRANSITION window.myAppErrorLog.push(error); }); } ngModule.run(run); + +config.$inject = ['$translatePartialLoaderProvider', '$httpProvider']; +export function config($translatePartialLoaderProvider, $httpProvider) { + $translatePartialLoaderProvider.addPart(appName); + $httpProvider.interceptors.push('vnInterceptor'); +} +ngModule.config(config); + +// Unhandled exceptions + +$exceptionHandler.$inject = ['vnApp', '$window']; +function $exceptionHandler(vnApp, $window) { + return function(exception, cause) { + let message; + + if (exception.name == 'HttpError') { + switch (exception.xhrStatus) { + case 'timeout': + case 'abort': + return; + } + + let data = exception.data; + + if (data && data.error instanceof Object) + message = data.error.message; + else if (exception.status === -1) + message = `Can't contact with server`; + else + message = `${exception.status}: ${exception.statusText}`; + + if (exception.status === 401) { + let location = $window.location; + let continueUrl = location.pathname + location.search + location.hash; + continueUrl = encodeURIComponent(continueUrl); + $window.location = `/auth/?apiKey=${vnApp.name}&continue=${continueUrl}`; + } + } else if (exception.message) { + message = exception.message; + } else if (typeof exception == 'string') { + message = exception; + } else { + message = `Unknown error`; + console.error(exception); + } + + vnApp.showError(message); + }; +} +ngModule.factory('$exceptionHandler', $exceptionHandler); diff --git a/client/ticket/src/data/step-one/index.js b/client/ticket/src/data/step-one/index.js index f74a64c54d..612e8f38c8 100644 --- a/client/ticket/src/data/step-one/index.js +++ b/client/ticket/src/data/step-one/index.js @@ -35,14 +35,13 @@ class Controller { addressFk: this.ticket.addressFk, agencyModeFk: this.ticket.agencyModeFk }; - + return this.$http.post(query, data).then(res => { if (res.data) this.ticket.sale = res.data; return true; }, res => { - console.log(res); if (res.data.error.message === 'NO_AGENCY_AVAILABLE') this.vnApp.showError( this.$translate.instant(`There's no available agency for this landing date`) diff --git a/client/ticket/src/data/step-one/step-one.spec.js b/client/ticket/src/data/step-one/step-one.spec.js index f00198308c..7054f78cb8 100644 --- a/client/ticket/src/data/step-one/step-one.spec.js +++ b/client/ticket/src/data/step-one/step-one.spec.js @@ -15,6 +15,7 @@ describe('ticket', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnTicketDataStepOne', {$state: $state}); })); @@ -36,6 +37,9 @@ describe('ticket', () => { describe('onStepChange()', () => { it('should call onStepChange method and return a NO_AGENCY_AVAILABLE signal error', async () => { + let landed = new Date(); + landed.setHours(0, 0, 0, 0); + controller.ticket = { id: 1, clientFk: 1, @@ -43,21 +47,16 @@ describe('ticket', () => { agencyModeFk: 1, companyFk: 442, shipped: new Date(), - landed: new Date() + landed: landed }; - let data = { - addressFk: 121, - agencyModeFk: 1, - landed: new Date() - }; - let response = {data: {error: new Error('NO_AGENCY_AVAILABLE')}}; + let response = {error: new Error('NO_AGENCY_AVAILABLE')}; - $httpBackend.whenPOST(`/ticket/api/sales/1/priceDifference`, data).respond(400, response); - $httpBackend.expectPOST(`/ticket/api/sales/1/priceDifference`, data); - await controller.onStepChange(); + $httpBackend.whenPOST(`/ticket/api/sales/1/priceDifference`).respond(400, response); + $httpBackend.expectPOST(`/ticket/api/sales/1/priceDifference`); + controller.onStepChange(); $httpBackend.flush(); }); }); }); -}); \ No newline at end of file +}); diff --git a/client/ticket/src/note/ticket-observation.spec.js b/client/ticket/src/note/ticket-observation.spec.js index 7d58abd813..6cb7041a5c 100644 --- a/client/ticket/src/note/ticket-observation.spec.js +++ b/client/ticket/src/note/ticket-observation.spec.js @@ -15,6 +15,7 @@ describe('ticket', () => { $componentController = _$componentController_; $state = _$state_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnTicketObservation', {$state: $state}); })); diff --git a/client/ticket/src/package/package.spec.js b/client/ticket/src/package/package.spec.js index 81bddc9f64..f9d0ec6069 100644 --- a/client/ticket/src/package/package.spec.js +++ b/client/ticket/src/package/package.spec.js @@ -14,6 +14,7 @@ describe('Ticket', () => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_, $rootScope) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $scope = { index: { accept: function() {} @@ -35,11 +36,6 @@ describe('Ticket', () => { describe('submit()', () => { it('should perform a post', () => { spyOn(controller.$.index, 'accept'); - let packagesObj = { - delete: controller.removedPackages, - create: [], - update: [] - }; let query = '/ticket/api/TicketPackagings/crudTicketPackaging'; controller.removedPackages = []; controller.oldPackages = []; diff --git a/client/ticket/src/sale/sale.spec.js b/client/ticket/src/sale/sale.spec.js index 9ce3cef2e0..729a9c4b77 100644 --- a/client/ticket/src/sale/sale.spec.js +++ b/client/ticket/src/sale/sale.spec.js @@ -15,6 +15,7 @@ describe('Ticket', () => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $scope = $rootScope.$new(); $scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => { return { diff --git a/client/ticket/src/volume/ticket-volume.spec.js b/client/ticket/src/volume/ticket-volume.spec.js index d630bad0ae..7ebd668e0e 100644 --- a/client/ticket/src/volume/ticket-volume.spec.js +++ b/client/ticket/src/volume/ticket-volume.spec.js @@ -15,6 +15,7 @@ describe('ticket', () => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => { $componentController = _$componentController_; $httpBackend = _$httpBackend_; + $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $scope = $rootScope.$new(); $scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => { return { diff --git a/e2e/paths/client-module/01_create_client.spec.js b/e2e/paths/client-module/01_create_client.spec.js index 7b3497ee16..971d5842c3 100644 --- a/e2e/paths/client-module/01_create_client.spec.js +++ b/e2e/paths/client-module/01_create_client.spec.js @@ -67,7 +67,7 @@ describe('Client', () => { .click(selectors.createClientView.createButton) .waitForSnackbar() .then(result => { - expect(result).toEqual('Some fields are invalid'); + expect(result).toContain('Some fields are invalid'); }); }); diff --git a/package-lock.json b/package-lock.json index 94775b2810..fc16c84174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,19 +103,19 @@ "dev": true }, "angular": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.8.tgz", - "integrity": "sha512-9WErZIOw1Cu1V5Yxdvxz/6YpND8ntdP71fdPpufPFJvZodZXqCjQBYrHqEoMZreO5i84O3D/Jw/vepoFt68Azw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.0.tgz", + "integrity": "sha512-3LboCLjrOuC7dWh953O0+dI3dJ7PexYRSCIrfqoN5qoHyja/wak3eWoxPKb2Sl2qwiPbrUV5KJXwgpUQ48McBQ==" }, "angular-cookies": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.6.4.tgz", - "integrity": "sha1-wo8/aqx6mCbB5F8daAckADblsm0=" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.0.tgz", + "integrity": "sha512-bxY7SAl7M+P+DazcDq4OVSFhmR0QET6KWw7bsxh4V22Ky+NcGbdyFySRNqu0TtWB5LkiGvo0wCFLd/vDyuMQOQ==" }, "angular-mocks": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.6.6.tgz", - "integrity": "sha1-yTAY54OMbcXOrxprz5vhPIMOpRU=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.0.tgz", + "integrity": "sha512-tBlj9jIEpbgiYY1VpV6XAi+5JSAO0AXFziVW4TSIFETB23fautoREI7XbOeRgy/QmOhZA4P320gs2XgpbvLd0w==", "dev": true }, "angular-paging": { @@ -124,19 +124,19 @@ "integrity": "sha1-cC9XTW0UBpADXqxkOV/jEfeYf7s=" }, "angular-translate": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.17.0.tgz", - "integrity": "sha512-SudfI0R0Hhtvngc0X3wFChXQGmw90o95i+QPZ11LhJJryneTq8LR3+3E4E7jgHA4fu6TcswgcfZ9+cp5ckbUHw==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", + "integrity": "sha512-Mw0kFBqsv5j8ItL9IhRZunIlVmIRW6iFsiTmRs9wGr2QTt8z4rehYlWyHos8qnXc/kyOYJiW50iH50CSNHGB9A==", "requires": { - "angular": "1.6.8" + "angular": "1.7.0" } }, "angular-translate-loader-partial": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.17.0.tgz", - "integrity": "sha512-pyRJcRc93iwiUnRnh9ZfehbQE/yxO5T6jmEqIvLEVz8gKLjDqDLKcaQFgPef9wCIN2n3e531YbStkkbSH3LYmQ==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.1.tgz", + "integrity": "sha512-+bPzY3+F2I1tb+X5bscvZq0OGoVEVkHwPGZvaY4nhbktpshArYpvIEV+RQFUa/QNj8vQc3iQ/pruJDb8w3zIdw==", "requires": { - "angular-translate": "2.17.0" + "angular-translate": "2.18.1" } }, "ansi-align": { @@ -1383,7 +1383,7 @@ "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", "dev": true }, "bn.js": { @@ -10785,7 +10785,7 @@ "jasmine-spec-reporter": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", - "integrity": "sha1-HWMq7ANBZwrTJPkrqEtLMrNeniI=", + "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==", "dev": true, "requires": { "colors": "1.1.2" @@ -10933,7 +10933,7 @@ "karma": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", - "integrity": "sha1-hcwI6eCiLXzpzKN8ShvoJPaisa4=", + "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", "dev": true, "requires": { "bluebird": "3.5.1", @@ -10985,7 +10985,7 @@ "karma-chrome-launcher": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha1-zxudBxNswY/iOTJ9JGVMPbw2is8=", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, "requires": { "fs-access": "1.0.1", @@ -11822,7 +11822,7 @@ "resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz", "integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=", "requires": { - "angular": "1.6.8" + "angular": "1.7.0" } }, "micromatch": { @@ -19992,7 +19992,7 @@ "useragent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha1-IX+UOtVAyyEoZYqyP8lg9qiMmXI=", + "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "dev": true, "requires": { "lru-cache": "4.1.1", diff --git a/package.json b/package.json index 35ade2ee72..b1e2d67815 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ }, "dependencies": { "@uirouter/angularjs": "^1.0.3", - "angular": "^1.6.8", - "angular-cookies": "^1.6.4", + "angular": "^1.7.0", + "angular-cookies": "^1.7.0", "angular-paging": "^2.2.2", - "angular-translate": "^2.17.0", - "angular-translate-loader-partial": "^2.17.0", + "angular-translate": "^2.18.1", + "angular-translate-loader-partial": "^2.18.1", "flatpickr": "^4.4.6", "fs-extra": "^5.0.0", "material-design-lite": "^1.3.0", @@ -25,7 +25,7 @@ "validator": "^6.2.1" }, "devDependencies": { - "angular-mocks": "^1.6.6", + "angular-mocks": "^1.7.0", "assets-webpack-plugin": "^3.5.1", "babel": "^6.23.0", "babel-core": "^6.26.0", diff --git a/services/loopback/common/methods/client/specs/getMana.spec.js b/services/loopback/common/methods/client/specs/getMana.spec.js index f4e317bf3c..a10dd657fb 100644 --- a/services/loopback/common/methods/client/specs/getMana.spec.js +++ b/services/loopback/common/methods/client/specs/getMana.spec.js @@ -4,7 +4,7 @@ describe('client getMana()', () => { it('should call the getMana method', done => { app.models.Client.getMana(101) .then(response => { - expect(response.mana).toEqual(30.02); + expect(response.mana).toEqual(50); done(); }); }); diff --git a/services/loopback/common/methods/ticket/componentUpdate.js b/services/loopback/common/methods/ticket/componentUpdate.js index 284b5baf3c..1bb2a96f43 100644 --- a/services/loopback/common/methods/ticket/componentUpdate.js +++ b/services/loopback/common/methods/ticket/componentUpdate.js @@ -25,14 +25,14 @@ module.exports = Self => { } }); - Self.componentUpdate = async (ticketFk, data) => { + Self.componentUpdate = async(ticketFk, data) => { let query = 'CALL vn.ticketComponentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?)'; let res = await Self.rawSql(query, [ - ticketFk, - data.agencyModeFk, - data.addressFk, - data.warehouseFk, - data.shipped, + ticketFk, + data.agencyModeFk, + data.addressFk, + data.warehouseFk, + data.shipped, data.landed, data.isDeleted, data.option diff --git a/services/loopback/common/methods/ticket/specs/get-volume.spec.js b/services/loopback/common/methods/ticket/specs/get-volume.spec.js index dbe97684d8..95eaf536f9 100644 --- a/services/loopback/common/methods/ticket/specs/get-volume.spec.js +++ b/services/loopback/common/methods/ticket/specs/get-volume.spec.js @@ -5,7 +5,7 @@ describe('ticket getVolume()', () => { let ticketFk = 1; app.models.Ticket.getVolume(ticketFk) .then(response => { - expect(response[0][0].m3_total).toEqual(0.008); + expect(response[0][0].m3).toEqual(0.008); done(); }); }); From a78bb24e095c946decbf02d7c9ab9a464983a129 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 31 May 2018 11:52:39 +0200 Subject: [PATCH 2/6] Tests fixed, bugs solved, vnCrudModel, vnRestModel, UserError --- .../src/address/edit/address-edit.spec.js | 33 --- client/client/src/address/edit/index.html | 64 ++--- client/client/src/address/edit/index.js | 147 +--------- .../components/autocomplete/autocomplete.js | 13 +- .../src/components/drop-down/drop-down.html | 4 +- .../src/components/drop-down/drop-down.js | 11 +- .../components/drop-down/drop-down.spec.js | 2 +- client/core/src/components/index.js | 5 +- .../src/components/model-proxy/model-proxy.js | 125 +++++++++ .../src/components/rest-model/crud-model.js | 103 +++++++ .../model.js => rest-model/rest-model.js} | 8 +- .../rest-model.spec.js} | 6 +- client/core/src/components/watcher/watcher.js | 114 ++++---- .../src/components/watcher/watcher.spec.js | 46 +-- client/core/src/lib/index.js | 2 + client/core/src/lib/user-error.js | 10 + client/item/src/barcode/barcode.spec.js | 3 + client/item/src/barcode/index.html | 4 + client/item/src/barcode/index.js | 1 + client/item/src/botanical/index.js | 13 +- client/item/src/niche/index.js | 11 +- client/item/src/niche/niche.spec.js | 3 + client/item/src/tags/index.html | 8 +- client/item/src/tags/index.js | 174 ++++-------- client/item/src/tags/tags.spec.js | 105 +++---- client/item/src/tax/index.html | 8 +- client/item/src/tax/index.js | 19 +- client/salix/src/module.js | 6 +- client/ticket/src/note/index.js | 1 + .../src/note/ticket-observation.spec.js | 3 + client/ticket/src/tracking/edit/index.html | 12 +- client/ticket/src/tracking/edit/index.js | 3 +- e2e/helpers/components_selectors.js | 12 +- e2e/helpers/selectors.js | 264 +++++++++--------- .../06_add_address_notes.spec.js | 2 +- .../05_create_new_tracking_state.spec.js | 14 +- .../address/crudAddressObservations.js | 3 - .../common/models/address-observation.js | 24 +- services/loopback/common/locale/en.json | 3 +- services/loopback/common/locale/es.json | 5 +- .../common/methods/item-tag/crudItemTags.js | 3 - .../{vnModel => vn-model}/installMethod.js | 0 .../methods/{vnModel => vn-model}/rawSql.js | 0 .../specs/installCrudModel.spec.js | 0 .../{vnModel => vn-model}/validateBinded.js | 0 .../methods/vnModel/installCrudModel.js | 46 --- services/loopback/common/models/item-tag.js | 1 - services/loopback/common/models/vn-model.js | 59 +++- 48 files changed, 801 insertions(+), 702 deletions(-) create mode 100644 client/core/src/components/model-proxy/model-proxy.js create mode 100644 client/core/src/components/rest-model/crud-model.js rename client/core/src/components/{drop-down/model.js => rest-model/rest-model.js} (94%) rename client/core/src/components/{drop-down/model.spec.js => rest-model/rest-model.spec.js} (86%) create mode 100644 client/core/src/lib/user-error.js delete mode 100644 services/client/common/methods/address/crudAddressObservations.js delete mode 100644 services/loopback/common/methods/item-tag/crudItemTags.js rename services/loopback/common/methods/{vnModel => vn-model}/installMethod.js (100%) rename services/loopback/common/methods/{vnModel => vn-model}/rawSql.js (100%) rename services/loopback/common/methods/{vnModel => vn-model}/specs/installCrudModel.spec.js (100%) rename services/loopback/common/methods/{vnModel => vn-model}/validateBinded.js (100%) delete mode 100644 services/loopback/common/methods/vnModel/installCrudModel.js diff --git a/client/client/src/address/edit/address-edit.spec.js b/client/client/src/address/edit/address-edit.spec.js index 14c771a1f4..3b9ae5385f 100644 --- a/client/client/src/address/edit/address-edit.spec.js +++ b/client/client/src/address/edit/address-edit.spec.js @@ -19,38 +19,5 @@ describe('Client', () => { $state.params.addressId = '1'; controller = $componentController('vnClientAddressEdit', {$state: $state}); })); - - it('should define and set address property', () => { - expect(controller.address.id).toEqual(1); - }); - - describe('_observationsEquals', () => { - it('should return true if two observations are equals independent of control attributes', () => { - let ob1 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: true}; - let ob2 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: false}; - let equals = controller.equalObservations(ob2, ob1); - - expect(equals).toBeTruthy(); - }); - - it('should return false if two observations are not equals independent of control attributes', () => { - let ob1 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: true}; - let ob2 = {id: 1, observationTypeFk: 1, description: 'Spiderman sucks', showAddIcon: true}; - let equals = controller.equalObservations(ob2, ob1); - - expect(equals).toBeFalsy(); - }); - }); - - describe('$onInit()', () => { - it('should perform a GET query to receive the address observations', () => { - let filter = {where: {addressFk: 1}, include: {relation: 'observationType'}}; - let res = ['some notes']; - $httpBackend.whenGET(`/client/api/AddressObservations?filter=${JSON.stringify(filter)}`).respond(res); - $httpBackend.expectGET(`/client/api/AddressObservations?filter=${JSON.stringify(filter)}`); - controller.$onInit(); - $httpBackend.flush(); - }); - }); }); }); diff --git a/client/client/src/address/edit/index.html b/client/client/src/address/edit/index.html index dc7218e8e4..8ef86e11df 100644 --- a/client/client/src/address/edit/index.html +++ b/client/client/src/address/edit/index.html @@ -1,16 +1,29 @@ + + form="form"> -
+ + + + + Address @@ -49,32 +62,17 @@ - - -
- -
- - Notes - - + Notes +
+ - - - - - - - +
+ +
diff --git a/client/client/src/address/edit/index.js b/client/client/src/address/edit/index.js index 117560e066..5a55d78d8e 100644 --- a/client/client/src/address/edit/index.js +++ b/client/client/src/address/edit/index.js @@ -1,151 +1,30 @@ import ngModule from '../../module'; export default class Controller { - constructor($state, $scope, $http, $q, $translate, vnApp) { + constructor($scope, $state) { + this.$ = $scope; this.$state = $state; - this.$scope = $scope; - this.$http = $http; - this.$q = $q; - this.$translate = $translate; - this.vnApp = vnApp; - - this.address = { - id: parseInt($state.params.addressId) - }; - this.observations = []; - this.oldObservations = {}; - this.removedObservations = []; - } - - _setDirtyForm() { - if (this.$scope.form) { - this.$scope.form.$setDirty(); - } - } - - _unsetDirtyForm() { - if (this.$scope.form) { - this.$scope.form.$setPristine(); - } - } - - addObservation() { - this.observations.push({observationTypeFk: null, addressFk: this.address.id, description: null}); + this.$stateParams = $state.params; } removeObservation(index) { - let item = this.observations[index]; - if (item) { - this.observations.splice(index, 1); - if (item.id) { - this.removedObservations.push(item.id); - this._setDirtyForm(); - } - } - if (this.observations.length === 0 && Object.keys(this.oldObservations).length === 0) { - this._unsetDirtyForm(); - } - } - - _submitObservations(observationsObject) { - return this.$http.post(`/client/api/AddressObservations/crudAddressObservations`, observationsObject); - } - - equalObservations(oldObj, newObj) { - return oldObj.id === newObj.id && oldObj.observationTypeFk === newObj.observationTypeFk && oldObj.description === newObj.description; + this.$.watcher.setDirty(); + this.$.model.remove(index); } submit() { - if (this.$scope.addressForm.$invalid || this.$scope.notesForm.$invalid) { - this.vnApp.showMessage( - this.$translate.instant('Some fields are invalid') - ); - return false; - } - - let canSubmitWatcher = this.$scope.watcher.dataChanged(); - let canSubmitObservations; - let repeatedTypes = false; - let emptyFields = false; - let types = []; - let observationsObj = { - delete: this.removedObservations, - create: [], - update: [] - }; - - this.observations.forEach(observation => { - let isNewObservation = observation.id === undefined; - - if (observation.observationTypeFk && types.indexOf(observation.observationTypeFk) !== -1) { - repeatedTypes = true; - return; - } - - if (observation.observationTypeFk === null || observation.description === null) { - emptyFields = true; - return; - } - - if (observation.observationTypeFk) - types.push(observation.observationTypeFk); - - if (isNewObservation && observation.observationTypeFk && observation.description) { - observationsObj.create.push(observation); - } else if (!isNewObservation && !this.equalObservations(this.oldObservations[observation.id], observation)) { - observationsObj.update.push(observation); - } - }); - - canSubmitObservations = observationsObj.update.length > 0 || observationsObj.create.length > 0 || observationsObj.delete.length > 0; - - if (repeatedTypes) { - this.vnApp.showMessage( - this.$translate.instant('The observation type must be unique') - ); - } else if (emptyFields) { - this.vnApp.showMessage( - this.$translate.instant('No field can be blank') - ); - } else if (canSubmitWatcher && !canSubmitObservations) { - this.$scope.watcher.submit().then(() => { - this.$state.go('client.card.address.index', {id: this.$state.params.id}); + this.$.watcher.check(); + this.$.watcher.realSubmit() + .then(() => this.$.model.save(true)) + .then(() => { + this.$.watcher.setPristine(); + this.$.watcher.notifySaved(); this.card.reload(); + this.$state.go('client.card.address.index', {id: this.$stateParams.id}); }); - } else if (!canSubmitWatcher && canSubmitObservations) { - this._submitObservations(observationsObj).then(() => { - this.$state.go('client.card.address.index', {id: this.$state.params.id}); - }); - } else if (canSubmitWatcher && canSubmitObservations) { - this.$q.all([this.$scope.watcher.submit(), this._submitObservations(observationsObj)]).then(() => { - this.$state.go('client.card.address.index', {id: this.$state.params.id}); - }); - } else { - this.vnApp.showMessage( - this.$translate.instant('No changes to save') - ); - } - this._unsetDirtyForm(); - } - - _getAddressNotes() { - let filter = { - where: {addressFk: this.address.id}, - include: {relation: 'observationType'} - }; - this.$http.get(`/client/api/AddressObservations?filter=${JSON.stringify(filter)}`).then(res => { - this.observations = res.data; - res.data.forEach(item => { - this.oldObservations[item.id] = Object.assign({}, item); - }); - }); - } - - $onInit() { - this._getAddressNotes(); } } -Controller.$inject = ['$state', '$scope', '$http', '$q', '$translate', 'vnApp']; +Controller.$inject = ['$scope', '$state']; ngModule.component('vnClientAddressEdit', { template: require('./index.html'), diff --git a/client/core/src/components/autocomplete/autocomplete.js b/client/core/src/components/autocomplete/autocomplete.js index 6162faee43..e8dfe906ff 100755 --- a/client/core/src/components/autocomplete/autocomplete.js +++ b/client/core/src/components/autocomplete/autocomplete.js @@ -62,6 +62,15 @@ export default class Autocomplete extends Input { this.refreshDisplayed(); } + set data(value) { + this._data = value; + this.refreshSelection(); + } + + get data() { + return this._data; + } + selectionIsValid(selection) { return selection && selection[this.valueField] == this._field @@ -212,7 +221,7 @@ export default class Autocomplete extends Input { showDropDown(search) { Object.assign(this.$.dropDown.$.model, { url: this.url, - staticData: this.data + staticData: this._data }); asignProps(this, this.$.dropDown, [ @@ -220,9 +229,9 @@ export default class Autocomplete extends Input { 'showField', 'where', 'order', + 'limit', 'showFilter', 'multiple', - 'limit', '$transclude' ]); diff --git a/client/core/src/components/drop-down/drop-down.html b/client/core/src/components/drop-down/drop-down.html index 560ef77fa2..9c216be385 100755 --- a/client/core/src/components/drop-down/drop-down.html +++ b/client/core/src/components/drop-down/drop-down.html @@ -1,7 +1,7 @@ - - + { $scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions}); $scope.popover.$postLink(); - $scope.model = $componentController('vnModel', {$httpBackend, $q, $filter}); + $scope.model = $componentController('vnRestModel', {$httpBackend, $q, $filter}); controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null}); controller.$postLink(); controller.parent = angular.element('')[0]; diff --git a/client/core/src/components/index.js b/client/core/src/components/index.js index 44a41911c1..fb18d0eaac 100644 --- a/client/core/src/components/index.js +++ b/client/core/src/components/index.js @@ -1,5 +1,8 @@ -import './textfield/textfield'; +import './model-proxy/model-proxy'; +import './rest-model/crud-model'; +import './rest-model/rest-model'; import './watcher/watcher'; +import './textfield/textfield'; import './paging/paging'; import './icon/icon'; import './dialog/dialog'; diff --git a/client/core/src/components/model-proxy/model-proxy.js b/client/core/src/components/model-proxy/model-proxy.js new file mode 100644 index 0000000000..ab010d6510 --- /dev/null +++ b/client/core/src/components/model-proxy/model-proxy.js @@ -0,0 +1,125 @@ +import ngModule from '../../module'; + +export default class ModelProxy { + constructor() { + this._data = []; + this.resetChanges(); + } + + get orgData() { + return this._orgData; + } + + set orgData(value) { + this._orgData = value; + this._data = []; + // this._data.splice(0, this._data.length); + + for (let i = 0; i < value.length; i++) { + let row = new this.Row(value[i], i); + this._data.push(row); + } + + this.resetChanges(); + } + + get data() { + return this._data; + } + + set data(value) {} + + get fields() { + return this._fields; + } + + set fields(value) { + this._fields = value; + + let Row = function(data, index) { + this.$data = data; + this.$index = index; + this.$oldData = null; + this.$isNew = false; + }; + + for (let prop of value) { + Object.defineProperty(Row.prototype, prop, { + enumerable: true, + configurable: false, + set: function(value) { + if (!this.$isNew) { + if (!this.$oldData) + this.$oldData = {}; + if (!this.$oldData[prop]) + this.$oldData[prop] = this.$data[prop]; + } + + this.$data[prop] = value; + }, + get: function() { + return this.$data[prop]; + } + }); + } + + this.Row = Row; + } + + remove(index) { + let data = this._data; + + let item; + [item] = data.splice(index, 1); + + for (let i = index; i < data.length; i++) + data[i].$index = i; + + if (!item.$isNew) + this.removed.push(item); + } + + insert(data) { + data = Object.assign(data || {}, this.link); + let newRow = new this.Row(data, this._data.length); + newRow.$isNew = true; + return this._data.push(newRow); + } + + resetChanges() { + this.removed = []; + + for (let row of this._data) { + row.$oldData = null; + row.$isNew = false; + } + } + + undoChanges() { + let data = this._data; + + for (let i = 0; i < data.length; i++) { + let row = data[i]; + + if (row.$oldData) + Object.assign(row.$data, row.$oldData); + if (row.$isNew) + data.splice(i--, 1); + } + + for (let row of this.removed) + data.splice(row.$index, 0, row); + + this.resetChanges(); + } +} + +ngModule.component('vnModelProxy', { + controller: ModelProxy, + bindings: { + orgData: ' { + this.orgData = res.data; + }); + } + + getChanges() { + let create = []; + let update = []; + let remove = []; + + for (let row of this.removed) + remove.push(row.$data[this.primaryKey]); + + for (let row of this._data) { + if (row.$isNew) + create.push(row.$data); + else if (row.$oldData) + update.push(row.$data); + } + + let isChanged = + create.length > 0 || + update.length > 0 || + remove.length > 0; + + if (!isChanged) + return null; + + let changes = { + create: create, + update: update, + delete: remove + }; + + return changes; + } + + save(ignoreChanges) { + let changes = this.getChanges(); + + if (!changes) + return this.$q.resolve(); + + let url = this.saveUrl ? this.saveUrl : `${this.url}/crud`; + return this.$http.post(url, changes) + .then(() => this.resetChanges); + } +} +CrudModel.$inject = ['$http', '$q']; + +ngModule.component('vnCrudModel', { + controller: CrudModel, + bindings: { + orgData: ' { +describe('Component vnRestModel', () => { let $componentController; let $httpBackend; let controller; @@ -11,7 +11,7 @@ describe('Component vnModel', () => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { $componentController = _$componentController_; - controller = $componentController('vnModel', {$httpBackend}); + controller = $componentController('vnRestModel', {$httpBackend}); })); describe('set url', () => { diff --git a/client/core/src/components/watcher/watcher.js b/client/core/src/components/watcher/watcher.js index bb15e926ba..790f76870f 100644 --- a/client/core/src/components/watcher/watcher.js +++ b/client/core/src/components/watcher/watcher.js @@ -4,6 +4,7 @@ import getModifiedData from '../../lib/modified'; import copyObject from '../../lib/copy'; import isEqual from '../../lib/equals'; import isFullEmpty from '../../lib/full-empty'; +import UserError from '../../lib/user-error'; /** * Component that checks for changes on a specific model property and @@ -14,10 +15,10 @@ import isFullEmpty from '../../lib/full-empty'; export default class Watcher extends Component { constructor($element, $scope, $state, $transitions, $http, vnApp, $translate, $attrs, $q) { super($element); - this.$scope = $scope; + this.$ = $scope; this.$state = $state; this.$http = $http; - this.$translate = $translate; + this._ = $translate; this.$attrs = $attrs; this.vnApp = vnApp; this.$q = $q; @@ -29,34 +30,38 @@ export default class Watcher extends Component { } $onInit() { - if (this.get && this.url) { + if (this.get && this.url) this.fetchData(); - } else if (this.get && !this.url) { - throw new Error('Error: Parameter url ommitted'); - } + else if (this.get && !this.url) + throw new Error('URL parameter ommitted'); } $onChanges(changes) { - if (this.data) { + if (this.data) this.updateOriginalData(); - } } $onDestroy() { this.deregisterCallback(); } + get dirty() { + return this.form && this.form.$dirty || this.dataChanged(); + } + + dataChanged() { + let data = this.copyInNewObject(this.data); + return !isEqual(data, this.orgData); + } + fetchData() { let id = this.data[this.idField]; - // return new Promise((resolve, reject) => { - this.$http.get(`${this.url}/${id}`).then( + return this.$http.get(`${this.url}/${id}`).then( json => { this.data = copyObject(json.data); this.updateOriginalData(); } - // json => reject(json) ); - // }); } /** @@ -91,45 +96,55 @@ export default class Watcher extends Component { * @return {Promise} The http request promise */ submit() { + try { + this.check(); + } catch (err) { + return this.$q.reject(err); + } + return this.realSubmit().then(res => { - this.vnApp.showMessage(this.$translate.instant('Data saved!')); + this.notifySaved(); return res; }); } - errorHandler(err) { - this.vnApp.showError(err.message); - return err; - } - + /** + * Submits the data without checking data validity or changes. + * + * @return {Promise} The http request promise + */ realSubmit() { - if (this.form) { + if (this.form) this.form.$setSubmitted(); - if (!this.form.$valid) - return this.invalidForm(); + if (!this.dataChanged()) { + this.updateOriginalData(); + return this.$q.resolve(); } - if (!this.dataChanged()) - return this.noChanges(); let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post'); let changedData = isPost ? this.data : getModifiedData(this.data, this.orgData); + let id = this.idField ? this.orgData[this.idField] : null; + + // If watcher is associated to mgCrud + if (this.save && this.save.accept) { + if (id) + changedData[this.idField] = id; + this.save.model = changedData; return this.$q((resolve, reject) => { this.save.accept().then( json => this.writeData({data: json}, resolve), - json => reject(json) + reject ); }); } - // XXX: Alternative when mgCrud is not used - - let id = this.idField ? this.orgData[this.idField] : null; + // When mgCrud is not used if (id) { return this.$q((resolve, reject) => { @@ -148,16 +163,21 @@ export default class Watcher extends Component { }); } - noChanges() { - let message = this.$translate.instant('No changes to save'); - let p = this.$q.reject(new Error(message)); - console.log(p); - return p; + /** + * Checks if data is ready to send. + */ + check() { + if (this.form && this.form.$invalid) + throw new UserError(this._.instant('Some fields are invalid')); + if (!this.dirty) + throw new UserError(this._.instant('No changes to save')); } - invalidForm() { - let message = this.$translate.instant('Some fields are invalid'); - return this.$q.reject(new Error(message)); + /** + * Notifies the user that the data has been saved. + */ + notifySaved() { + this.vnApp.showMessage(this._.instant('Data saved!')); } writeData(json, resolve) { @@ -168,8 +188,15 @@ export default class Watcher extends Component { updateOriginalData() { this.orgData = this.copyInNewObject(this.data); - if (this.form && this.form.$dirty) - this.form.$setPristine(); + this.setPristine(); + } + + setPristine() { + if (this.form) this.form.$setPristine(); + } + + setDirty() { + if (this.form) this.form.$setDirty(); } copyInNewObject(data) { @@ -191,22 +218,15 @@ export default class Watcher extends Component { } callback(transition) { - let dataChanged = this.dataChanged(); - if (!this.state && dataChanged) { + if (!this.state && this.dirty) { this.state = transition.to().name; - this.$scope.confirm.show(); + this.$.confirm.show(); return false; } return true; } - dataChanged() { - let newData = this.copyInNewObject(this.data); - if (this.form && this.form.$dirty) return !isEqual(newData, this.orgData); - return !isEqual(newData, this.orgData); - } - onConfirmResponse(response) { if (response === 'ACCEPT') { if (this.data) @@ -227,7 +247,7 @@ ngModule.component('vnWatcher', { data: '<', form: '<', save: '<', - get: '=?' + get: ' { expect(function() { controller.$onInit(); - }).toThrow(new Error('Error: Parameter url ommitted')); + }).toThrowError(/parameter/); }); }); @@ -100,32 +100,36 @@ describe('Component vnWatcher', () => { }); }); + describe('check()', () => { + it(`should throw error if controller.form is invalid`, () => { + controller.form = {$invalid: true}; + + expect(function() { + controller.check(); + }).toThrowError(); + }); + + it(`should throw error if controller.dirty is true`, () => { + controller.form = {$invalid: true}; + + expect(function() { + controller.check(); + }).toThrowError(); + }); + }); + describe('realSubmit()', () => { describe('when controller.form', () => { it(`should call controller.form.setSubmited if controller.form is defined`, () => { - controller.form = {$setSubmitted: () => {}}; + controller.form = { + $setSubmitted: () => {}, + $setPristine: () => {} + }; spyOn(controller.form, '$setSubmitted'); controller.realSubmit(); expect(controller.form.$setSubmitted).toHaveBeenCalledWith(); }); - - it(`should call controller.invalidForm if controller.form.$valid is not defined`, () => { - controller.form = {$setSubmitted: () => {}}; - spyOn(controller, 'invalidForm'); - controller.realSubmit(); - - expect(controller.invalidForm).toHaveBeenCalledWith(); - }); - }); - - describe('when !controller.dataChanged()', () => { - it(`should call controller.noChanges()`, () => { - spyOn(controller, 'noChanges'); - controller.realSubmit(); - - expect(controller.noChanges).toHaveBeenCalledWith(); - }); }); describe('when controller.save()', () => { @@ -197,7 +201,7 @@ describe('Component vnWatcher', () => { describe('callback()', () => { describe(`when dataChanged() returns true and there's no state in the controller`, () => { - it(`should define controller.state, call controller.$scope.confirm.show() and return false`, () => { + it(`should define controller.state, call controller.$.confirm.show() and return false`, () => { $scope.confirm = {show: jasmine.createSpy('show')}; controller.dataChanged = () => { return true; @@ -209,7 +213,7 @@ describe('Component vnWatcher', () => { let result = controller.callback(transition); expect(controller.state).toEqual('Batman'); - expect(controller.$scope.confirm.show).toHaveBeenCalledWith(); + expect(controller.$.confirm.show).toHaveBeenCalledWith(); expect(result).toBeFalsy(); }); }); diff --git a/client/core/src/lib/index.js b/client/core/src/lib/index.js index 77904844ce..5a43a69887 100644 --- a/client/core/src/lib/index.js +++ b/client/core/src/lib/index.js @@ -13,3 +13,5 @@ import './equals'; import './modified'; import './key-codes'; import './get-watchers'; +import './http-error'; +import './user-error'; diff --git a/client/core/src/lib/user-error.js b/client/core/src/lib/user-error.js new file mode 100644 index 0000000000..597656c746 --- /dev/null +++ b/client/core/src/lib/user-error.js @@ -0,0 +1,10 @@ +/** + * Errors that can be visible and understood by the end user. + * Used mainly in the global error handler. + */ +export default class UserError extends Error { + constructor(message, fileName, lineNumber) { + super(message, fileName, lineNumber); + this.name = 'UserError'; + } +} diff --git a/client/item/src/barcode/barcode.spec.js b/client/item/src/barcode/barcode.spec.js index 06fa796f15..585af2d713 100644 --- a/client/item/src/barcode/barcode.spec.js +++ b/client/item/src/barcode/barcode.spec.js @@ -18,6 +18,9 @@ describe('Item', () => { $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $state.params.id = '1'; controller = $componentController('vnItemBarcode', {$state: $state}); + controller.$scope.watcher = { + notifySaved: () => {} + }; })); describe('add / remove barcode()', () => { diff --git a/client/item/src/barcode/index.html b/client/item/src/barcode/index.html index ad9ec7f4cb..b7187a8089 100644 --- a/client/item/src/barcode/index.html +++ b/client/item/src/barcode/index.html @@ -1,3 +1,7 @@ + + Item barcode diff --git a/client/item/src/barcode/index.js b/client/item/src/barcode/index.js index 2b1309fcdd..3e31cab81f 100644 --- a/client/item/src/barcode/index.js +++ b/client/item/src/barcode/index.js @@ -95,6 +95,7 @@ export default class Controller { return this.$http.post(`/item/api/ItemBarcodes/crudItemBarcodes`, barcodesObj).then(() => { this.getBarcodes(); this._unsetDirtyForm(); + this.$scope.watcher.notifySaved(); }); } this.vnApp.showMessage(this.$translate.instant('No changes to save')); diff --git a/client/item/src/botanical/index.js b/client/item/src/botanical/index.js index 651777fa5a..92cea41aaf 100644 --- a/client/item/src/botanical/index.js +++ b/client/item/src/botanical/index.js @@ -4,6 +4,9 @@ class Controller { constructor($http, $state) { this.$http = $http; this.$state = $state; + this.botanical = { + itemFk: this.$state.params.id + }; } _getBotanical() { @@ -13,16 +16,8 @@ class Controller { }; this.$http.get(`/item/api/ItemBotanicals?filter=${JSON.stringify(filter)}`) .then(res => { - if (res.data.length) { + if (res.data.length) this.botanical = res.data[0]; - } else { - this.botanical = { - itemFk: this.$state.params.id, - botanical: null, - genusFk: null, - specieFk: null - }; - } }); } diff --git a/client/item/src/niche/index.js b/client/item/src/niche/index.js index a01d3e1394..9fec4ba91e 100644 --- a/client/item/src/niche/index.js +++ b/client/item/src/niche/index.js @@ -14,6 +14,11 @@ export default class Controller { this.oldNiches = {}; } + $onInit() { + this.getNiches(); + this.getWarehouses(); + } + _setIconAdd() { if (this.niches.length) { this.niches.map(element => { @@ -130,15 +135,11 @@ export default class Controller { return this.$http.post(`/item/api/ItemNiches/crudItemNiches`, nichesObj).then(() => { this.getNiches(); this._unsetDirtyForm(); + this.$scope.watcher.notifySaved(); }); } this.vnApp.showMessage(this.$translate.instant('No changes to save')); } - - $onInit() { - this.getNiches(); - this.getWarehouses(); - } } Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; diff --git a/client/item/src/niche/niche.spec.js b/client/item/src/niche/niche.spec.js index 11930afd6a..d3b91be9e5 100644 --- a/client/item/src/niche/niche.spec.js +++ b/client/item/src/niche/niche.spec.js @@ -17,6 +17,9 @@ describe('Item', () => { $httpBackend = _$httpBackend_; $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnItemNiche', {$state: $state}); + controller.$scope.watcher = { + notifySaved: () => {} + }; })); describe('add / remove niche', () => { diff --git a/client/item/src/tags/index.html b/client/item/src/tags/index.html index f8fe5fa509..18eaa613c8 100644 --- a/client/item/src/tags/index.html +++ b/client/item/src/tags/index.html @@ -6,7 +6,7 @@ Item tags - + @@ -54,7 +54,7 @@ medium-grey vn-tooltip="Remove tag" icon="remove_circle_outline" - ng-click="$ctrl.removeItemTag($index)"> + ng-click="$ctrl.removeTag($index)"> @@ -62,7 +62,7 @@ vn-bind="+" vn-tooltip="Add tag" icon="add_circle" - ng-click="$ctrl.addItemTag()"> + ng-click="$ctrl.addTag()"> diff --git a/client/item/src/tags/index.js b/client/item/src/tags/index.js index 66997ae5e6..e633938981 100644 --- a/client/item/src/tags/index.js +++ b/client/item/src/tags/index.js @@ -1,71 +1,63 @@ import ngModule from '../module'; class Controller { - constructor($stateParams, $scope, $http, $translate, vnApp) { + constructor($stateParams, $scope, $http) { this.params = $stateParams; - this.$scope = $scope; + this.$ = $scope; this.$http = $http; - this.$translate = $translate; - this.vnApp = vnApp; - - this.itemTagTypes = []; - this.removedItemTags = []; - this.oldItemTags = {}; + this.tags = []; + this.removedTags = []; } - _setIconAdd() { - if (this.instancedItemTags && this.instancedItemTags.length) { - this.instancedItemTags.map(element => { - element.showAddIcon = false; - return true; + $onInit() { + this.getTags(); + } + + getTags() { + let filter = { + where: {itemFk: this.params.id}, + order: "priority ASC", + include: {relation: "tag"} + }; + this.$http.get(`/item/api/ItemTags?filter=${JSON.stringify(filter)}`).then(response => { + this.removedTags = []; + + this.itemTags = JSON.parse(JSON.stringify(this.tags)); + this.tags = response.data; + this.orgTags = {}; + this.tags.forEach(tag => { + this.orgTags[tag.id] = Object.assign({}, tag); }); - this.instancedItemTags[this.instancedItemTags.length - 1].showAddIcon = true; - } + + this.$.form.$setPristine(); + }); } - _setDirtyForm() { - if (this.$scope.form) { - this.$scope.form.$setDirty(); - } - } - _unsetDirtyForm() { - if (this.$scope.form) { - this.$scope.form.$setPristine(); - } + addTag() { + this.tags.push({ + itemFk: this.params.id, + priority: this.getHighestPriority(this.tags) + }); } - checkAutocompleteChanges(itemTag) { - itemTag.value = null; - } - - addItemTag() { - if (this.instancedItemTags) { - this.instancedItemTags.push({value: null, itemFk: this.params.id, tagFk: null, priority: this.getHighestPriority(this.instancedItemTags), showAddIcon: true}); - this._setIconAdd(); - } - } - - removeItemTag(index) { - let item = this.instancedItemTags[index]; - if (item) { - this.instancedItemTags.splice(index, 1); - this._setIconAdd(); - if (item.id) { - this.removedItemTags.push(item.id); - this._setDirtyForm(); - } - } - } - - getHighestPriority(instancedItemTags) { + getHighestPriority(tags) { let max = 0; - instancedItemTags.forEach(tag => { + tags.forEach(tag => { if (tag.priority > max) max = tag.priority; }); return max + 1; } + removeTag(index) { + let item = this.tags[index]; + this.tags.splice(index, 1); + if (item.id) { + this.removedTags.push(item.id); + this.$.form.$setDirty(); + } + } + getSourceTable(selection) { if (!selection || selection.isFree === true) return null; @@ -78,86 +70,38 @@ class Controller { } } - _equalItemTags(oldTag, newTag) { - return oldTag.tagFk === newTag.tagFk && oldTag.value === newTag.value && oldTag.priority === newTag.priority; - } - - _setOlTags(instancedItemTags) { - this._setIconAdd(); - instancedItemTags.forEach(tag => { - this.oldItemTags[tag.id] = Object.assign({}, tag); - }); - } - - _getItemTags() { - let filter = { - where: {itemFk: this.params.id}, - order: "priority ASC", - include: {relation: "tag"} - }; - this.$http.get(`/item/api/ItemTags?filter=${JSON.stringify(filter)}`).then(response => { - this.instancedItemTags = response.data; - this.itemTags = JSON.parse(JSON.stringify(this.instancedItemTags)); - this._setOlTags(response.data); - }); + tagIsEqual(oldTag, newTag) { + return oldTag.tagFk === newTag.tagFk && + oldTag.value === newTag.value && + oldTag.priority === newTag.priority; } submit() { - let itemTagsDefined = []; - let repeatedItemTags = false; - let canSubmit; - let tagsObj = { - delete: this.removedItemTags, + this.$.watcher.check(); + + let changes = { + delete: this.removedTags, create: [], update: [] }; - this.instancedItemTags.forEach(tag => { - let isNewTag = !tag.id; - - if (itemTagsDefined.indexOf(tag.tagFk) !== -1) { - repeatedItemTags = true; - return; - } - itemTagsDefined.push(tag.tagFk); - - if (isNewTag) { - tagsObj.create.push(tag); - } - - if (!isNewTag && !this._equalItemTags(this.oldItemTags[tag.id], tag)) { + this.tags.forEach(tag => { + if (!tag.id) + changes.create.push(tag); + else if (!this.tagIsEqual(this.orgTags[tag.id], tag)) { let tagToUpdate = Object.assign({}, tag); delete tagToUpdate.tag; - delete tagToUpdate.showAddIcon; - tagsObj.update.push(tagToUpdate); + changes.update.push(tagToUpdate); } }); - if (this.$scope.form.$invalid) { - return this.vnApp.showMessage(this.$translate.instant('Some fields are invalid')); - } - - if (repeatedItemTags) { - return this.vnApp.showMessage(this.$translate.instant('The tag must be unique')); - } - - canSubmit = tagsObj.update.length > 0 || tagsObj.create.length > 0 || tagsObj.delete.length > 0; - - if (canSubmit) { - return this.$http.post(`/item/api/ItemTags/crudItemTags`, tagsObj).then(() => { - // this.itemTags = JSON.parse(JSON.stringify(this.instancedItemTags)); - this._getItemTags(); - this._unsetDirtyForm(); - }); - } - this.vnApp.showMessage(this.$translate.instant('No changes to save')); - } - - $onInit() { - this._getItemTags(); + this.$http.post(`/item/api/ItemTags/crud`, changes).then(() => { + this.getTags(); + this.$.watcher.notifySaved(); + }); } } -Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; +Controller.$inject = ['$stateParams', '$scope', '$http']; ngModule.component('vnItemTags', { template: require('./index.html'), diff --git a/client/item/src/tags/tags.spec.js b/client/item/src/tags/tags.spec.js index 6be96a46ad..41e2380a61 100644 --- a/client/item/src/tags/tags.spec.js +++ b/client/item/src/tags/tags.spec.js @@ -17,37 +17,41 @@ describe('Item', () => { $httpBackend = _$httpBackend_; $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnItemTags', {$state: $state}); + controller.$.form = { + $setPristine: () => {}, + $dirty: true + }; + controller.$.watcher = { + notifySaved: () => {}, + check: () => {} + }; })); describe('add / remove tags', () => { - it('should add one empty tag into controller tags collection and call _setIconAdd()', () => { - controller.instancedItemTags = []; - spyOn(controller, '_setIconAdd').and.callThrough(); - controller.addItemTag(); + it('should add one empty tag into controller tags collection', () => { + controller.tags = []; + controller.addTag(); - expect(controller._setIconAdd).toHaveBeenCalledWith(); - expect(controller.instancedItemTags.length).toEqual(1); - expect(controller.instancedItemTags[0].id).toBe(undefined); - expect(controller.instancedItemTags[0].showAddIcon).toBeTruthy(); + expect(controller.tags.length).toEqual(1); + expect(controller.tags[0].id).toBe(undefined); }); - it('should remove a tag that occupies the position in the index given and call _setIconAdd()', () => { + it('should remove a tag that occupies the position in the index', () => { + controller.$.form = {$setDirty: () => {}}; + spyOn(controller.$.form, '$setDirty'); + let index = 2; - controller.instancedItemTags = [ + controller.tags = [ {id: 1, typeFk: 1, value: '1111', showAddIcon: false}, {id: 2, typeFk: 2, value: '2222', showAddIcon: false}, {id: 3, typeFk: 3, value: '3333', showAddIcon: true} ]; - spyOn(controller, '_setIconAdd').and.callThrough(); + controller.removeTag(index); - controller.removeItemTag(index); - - expect(controller._setIconAdd).toHaveBeenCalledWith(); - expect(controller.instancedItemTags.length).toEqual(2); - expect(controller.instancedItemTags[0].showAddIcon).toBeFalsy(); - expect(controller.instancedItemTags[1].showAddIcon).toBeTruthy(); - expect(controller.instancedItemTags[index]).toBe(undefined); + expect(controller.tags.length).toEqual(2); + expect(controller.tags[index]).toBe(undefined); + expect(controller.$.form.$setDirty).toHaveBeenCalledWith(); }); }); @@ -90,11 +94,11 @@ describe('Item', () => { }); }); - describe('_equalItemTags()', () => { + describe('tagIsEqual()', () => { it('should return true if two tags are equals independent of control attributes', () => { let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true}; let tag2 = {id: 1, typeFk: 1, value: '1111', showAddIcon: false}; - let equals = controller._equalItemTags(tag2, tag1); + let equals = controller.tagIsEqual(tag2, tag1); expect(equals).toBeTruthy(); }); @@ -102,7 +106,7 @@ describe('Item', () => { it('should return false if two tags aint equal independent of control attributes', () => { let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true}; let tag2 = {id: 1, typeFk: 1, value: '2222', showAddIcon: true}; - let equals = controller._equalItemTags(tag2, tag1); + let equals = controller.tagIsEqual(tag2, tag1); expect(equals).toBeFalsy(); }); @@ -110,73 +114,78 @@ describe('Item', () => { describe('get itemTags', () => { it('should perform a GET query to receive the item tags', () => { + controller.$.form = {$setPristine: () => {}}; + spyOn(controller.$.form, '$setPristine'); + let res = [{id: 1, typeFk: 1, value: '1111'}]; $httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond(res); $httpBackend.expectGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`); - controller._getItemTags(); + controller.getTags(); $httpBackend.flush(); + + expect(controller.$.form.$setPristine).toHaveBeenCalledWith(); }); }); describe('submit()', () => { - it("should return an error message 'The tag must be unique' when the tag value isnt unique", () => { - controller.$scope.form = []; + // TODO: Server validation should be implemented + xit("should return an error message 'The tag must be unique' when the tag value isnt unique", () => { + controller.$.form = []; spyOn(controller.vnApp, 'showMessage').and.callThrough(); - controller.instancedItemTags = [ + controller.tags = [ {typeFk: 1, value: 123454, itemFk: 1, id: 1}, {typeFk: 1, value: 123454, itemFk: 1} ]; - controller.oldItemTags = {1: {typeFk: 1, id: 1, value: 123454, itemFk: 1}}; + controller.orgTags = {1: {typeFk: 1, id: 1, value: 123454, itemFk: 1}}; controller.submit(); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('The tag must be unique'); }); it("should perfom a query to delete tags", () => { - controller.$scope.form = {$setPristine: () => {}}; - controller.oldItemTags = {1: {id: 1, typeFk: 1, value: '1111'}}; - controller.instancedItemTags = []; - controller.removedItemTags = [1]; + controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}}; + controller.tags = []; + controller.removedTags = [1]; $httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); - $httpBackend.expectPOST(`/item/api/ItemTags/crudItemTags`).respond('ok!'); + $httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!'); controller.submit(); $httpBackend.flush(); }); it("should perfom a query to update tags", () => { - controller.$scope.form = {$setPristine: () => {}}; - controller.instancedItemTags = [{id: 1, typeFk: 1, value: '2222'}]; - controller.oldItemTags = {1: {id: 1, typeFk: 1, value: '1111'}}; + controller.tags = [{id: 1, typeFk: 1, value: '2222'}]; + controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}}; $httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); - $httpBackend.expectPOST(`/item/api/ItemTags/crudItemTags`).respond('ok!'); + $httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!'); controller.submit(); $httpBackend.flush(); }); it("should perfom a query to create new tag", () => { - controller.$scope.form = {$setPristine: () => {}}; - controller.instancedItemTags = [{typeFk: 1, value: 1111, itemFk: 1}]; + controller.tags = [{typeFk: 1, value: 1111, itemFk: 1}]; $httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); - $httpBackend.expectPOST(`/item/api/ItemTags/crudItemTags`).respond('ok!'); + $httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!'); controller.submit(); $httpBackend.flush(); }); - it("should return a message 'No changes to save' when there are no changes to apply", () => { - controller.$scope.form = {$setPristine: () => {}}; - spyOn(controller.vnApp, 'showMessage').and.callThrough(); - controller.oldItemTags = [ - {typeFk: 1, value: 1, itemFk: 1, id: 1}, - {typeFk: 2, value: 2, itemFk: 1, id: 2} - ]; - controller.instancedItemTags = []; - controller.submit(); + it("should throw 'No changes to save' error when there are no changes to apply", () => { + controller.$.watcher = { + check: () => { + throw new Error('No changes to save'); + } + }; - expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No changes to save'); + controller.orgTags = []; + controller.tags = []; + + expect(function() { + controller.submit(); + }).toThrowError(); }); }); }); diff --git a/client/item/src/tax/index.html b/client/item/src/tax/index.html index aa111756b1..c6b61ca901 100644 --- a/client/item/src/tax/index.html +++ b/client/item/src/tax/index.html @@ -1,3 +1,8 @@ + + Item tax @@ -10,8 +15,7 @@ diff --git a/client/item/src/tax/index.js b/client/item/src/tax/index.js index 7f45df878b..9ad2ad5923 100644 --- a/client/item/src/tax/index.js +++ b/client/item/src/tax/index.js @@ -1,18 +1,17 @@ import ngModule from '../module'; export default class Controller { - constructor($stateParams, $http) { + constructor($stateParams, $http, $translate, vnApp) { this.$http = $http; this.$stateParams = $stateParams; + this._ = $translate; + this.vnApp = vnApp; let filter = { fields: ['id', 'countryFk', 'taxClassFk'], include: [{ relation: 'country', scope: {fields: ['country']} - }, { - relation: 'taxClass', - scope: {fields: ['id', 'description']} }] }; @@ -21,23 +20,21 @@ export default class Controller { $http.get(url).then(json => { this.taxes = json.data; }); - - $http.get(`/item/api/TaxClasses`).then(json => { - this.classes = json.data; - }); } - + submit() { let data = []; for (let tax of this.taxes) data.push({id: tax.id, taxClassFk: tax.taxClassFk}); let url = `/item/api/Items/${this.$stateParams.id}/updateTaxes`; - this.$http.post(url, data); + this.$http.post(url, data).then( + () => this.vnApp.showMessage(this._.instant('Data saved!')) + ); } } -Controller.$inject = ['$stateParams', '$http']; +Controller.$inject = ['$stateParams', '$http', '$translate', 'vnApp']; ngModule.component('vnItemTax', { template: require('./index.html'), diff --git a/client/salix/src/module.js b/client/salix/src/module.js index 28dc0db93a..daa515e66d 100644 --- a/client/salix/src/module.js +++ b/client/salix/src/module.js @@ -56,12 +56,10 @@ function $exceptionHandler(vnApp, $window) { continueUrl = encodeURIComponent(continueUrl); $window.location = `/auth/?apiKey=${vnApp.name}&continue=${continueUrl}`; } - } else if (exception.message) { + } else if (exception.name == 'UserError') { message = exception.message; - } else if (typeof exception == 'string') { - message = exception; } else { - message = `Unknown error`; + message = 'Ups! Something went wrong'; console.error(exception); } diff --git a/client/ticket/src/note/index.js b/client/ticket/src/note/index.js index e682b55148..8c2c602c9d 100644 --- a/client/ticket/src/note/index.js +++ b/client/ticket/src/note/index.js @@ -119,6 +119,7 @@ class Controller { return this.$http.post(`/ticket/api/TicketObservations/crudTicketObservation`, observationsObj).then(() => { this.getObservations(); this._unsetDirtyForm(); + this.$scope.watcher.notifySaved(); }); } this.vnApp.showMessage(this.$translate.instant('No changes to save')); diff --git a/client/ticket/src/note/ticket-observation.spec.js b/client/ticket/src/note/ticket-observation.spec.js index 6cb7041a5c..f1bbbdd0b9 100644 --- a/client/ticket/src/note/ticket-observation.spec.js +++ b/client/ticket/src/note/ticket-observation.spec.js @@ -17,6 +17,9 @@ describe('ticket', () => { $httpBackend = _$httpBackend_; $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); controller = $componentController('vnTicketObservation', {$state: $state}); + controller.$scope.watcher = { + notifySaved: () => {} + }; })); describe('add / remove observation', () => { diff --git a/client/ticket/src/tracking/edit/index.html b/client/ticket/src/tracking/edit/index.html index dd77d479c0..a726333321 100644 --- a/client/ticket/src/tracking/edit/index.html +++ b/client/ticket/src/tracking/edit/index.html @@ -10,12 +10,12 @@ New state - + vn-one + field="$ctrl.ticket.stateFk" + url="/ticket/api/States" + label="State" + vn-focus> + diff --git a/client/ticket/src/tracking/edit/index.js b/client/ticket/src/tracking/edit/index.js index baf5e19a0a..77bc7d12fb 100644 --- a/client/ticket/src/tracking/edit/index.js +++ b/client/ticket/src/tracking/edit/index.js @@ -7,8 +7,7 @@ class Controller { this.vnApp = vnApp; this.$translate = $translate; this.ticket = { - ticketFk: $state.params.id, - text: null + ticketFk: $state.params.id }; } onSubmit() { diff --git a/e2e/helpers/components_selectors.js b/e2e/helpers/components_selectors.js index ae4b9a4815..89fcd18b93 100644 --- a/e2e/helpers/components_selectors.js +++ b/e2e/helpers/components_selectors.js @@ -4,16 +4,6 @@ // delete me, this comment is to add a commit export default { vnTextfield: 'vn-textfield > div > input', - vnTextarea: 'vn-textarea', vnSubmit: 'vn-submit > input', - vnTopbar: 'vn-topbar > header', - vnIcon: 'vn-icon', - vnSearchBar: 'vn-searchbar > form > vn-horizontal', - vnFloatButton: 'vn-float-button > button', - vnMenuItem: 'vn-menu-item > li > a', - vnAutocomplete: 'vn-autocomplete', - vnCheck: 'vn-check', - vnIconButton: 'vn-icon-button', - vnItemSummary: 'vn-item-summary > vn-card > div > vn-vertical', - vnLabelValue: 'vn-label-value' + vnFloatButton: 'vn-float-button > button' }; diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 0ec09a7f02..f8d40228b9 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -17,7 +17,7 @@ export default { }, clientsIndex: { searchClientInput: `${components.vnTextfield}`, - searchButton: `${components.vnSearchBar} > vn-icon-button`, + searchButton: `vn-searchbar vn-icon-button[icon="search"]`, searchResult: `vn-item-client a`, createClientButton: `${components.vnFloatButton}` }, @@ -33,122 +33,122 @@ export default { cancelButton: `button[href="#!/client/index"]` }, clientBasicData: { - basicDataButton: `${components.vnMenuItem}[ui-sref="client.card.basicData"]`, + basicDataButton: `vn-menu-item a[ui-sref="client.card.basicData"]`, nameInput: `${components.vnTextfield}[name="name"]`, contactInput: `${components.vnTextfield}[name="contact"]`, phoneInput: `${components.vnTextfield}[name="phone"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`, faxInput: `${components.vnTextfield}[name="fax"]`, emailInput: `${components.vnTextfield}[name="email"]`, - salesPersonInput: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] input`, - salesPersonOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`, - channelInput: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] input`, - channelMetropolisOption: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] vn-drop-down ul > li:nth-child(3)`, + salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`, + salesPersonOptionOne: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`, + channelInput: `vn-autocomplete[field="$ctrl.client.contactChannelFk"] input`, + channelMetropolisOption: `vn-autocomplete[field="$ctrl.client.contactChannelFk"] vn-drop-down ul > li:nth-child(3)`, saveButton: `${components.vnSubmit}` }, clientFiscalData: { - fiscalDataButton: `${components.vnMenuItem}[ui-sref="client.card.fiscalData"]`, + fiscalDataButton: `vn-menu-item a[ui-sref="client.card.fiscalData"]`, socialNameInput: `${components.vnTextfield}[name="socialName"]`, fiscalIdInput: `${components.vnTextfield}[name="fi"]`, - equalizationTaxCheckboxLabel: `${components.vnCheck}[label='Is equalizated'] > label > input`, + equalizationTaxCheckboxLabel: `vn-check[label='Is equalizated'] > label > input`, acceptPropagationButton: `vn-client-fiscal-data > vn-confirm button[response=ACCEPT]`, addressInput: `${components.vnTextfield}[name="street"]`, cityInput: `${components.vnTextfield}[name="city"]`, postcodeInput: `${components.vnTextfield}[name="postcode"]`, - provinceInput: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] input`, - provinceFifthOption: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] vn-drop-down ul > li:nth-child(5)`, - countryInput: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] input`, - countryThirdOption: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] vn-drop-down ul > li:nth-child(3)`, - activeCheckboxLabel: `${components.vnCheck}[label="Active"] > label`, - frozenCheckboxLabel: `${components.vnCheck}[label="Frozen"] > label`, - invoiceByAddressCheckboxInput: `${components.vnCheck}[label='Invoice by address'] > label > input`, - verifiedDataCheckboxInput: `${components.vnCheck}[label="Verified data"] > label > input`, - hasToInvoiceCheckboxLabel: `${components.vnCheck}[label='Has to invoice'] > label`, - invoiceByMailCheckboxLabel: `${components.vnCheck}[label='Invoice by mail'] > label`, - viesCheckboxInput: `${components.vnCheck}[label='Vies'] > label > input`, + provinceInput: `vn-autocomplete[field="$ctrl.client.provinceFk"] input`, + provinceFifthOption: `vn-autocomplete[field="$ctrl.client.provinceFk"] vn-drop-down ul > li:nth-child(5)`, + countryInput: `vn-autocomplete[field="$ctrl.client.countryFk"] input`, + countryThirdOption: `vn-autocomplete[field="$ctrl.client.countryFk"] vn-drop-down ul > li:nth-child(3)`, + activeCheckboxLabel: `vn-check[label="Active"] > label`, + frozenCheckboxLabel: `vn-check[label="Frozen"] > label`, + invoiceByAddressCheckboxInput: `vn-check[label='Invoice by address'] > label > input`, + verifiedDataCheckboxInput: `vn-check[label="Verified data"] > label > input`, + hasToInvoiceCheckboxLabel: `vn-check[label='Has to invoice'] > label`, + invoiceByMailCheckboxLabel: `vn-check[label='Invoice by mail'] > label`, + viesCheckboxInput: `vn-check[label='Vies'] > label > input`, saveButton: `${components.vnSubmit}` }, clientPayMethod: { - payMethodButton: `${components.vnMenuItem}[ui-sref="client.card.billingData"]`, - payMethodInput: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] input`, - payMethodIBANOption: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(5)`, - payMethodOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(2)`, + payMethodButton: `vn-menu-item a[ui-sref="client.card.billingData"]`, + payMethodInput: `vn-autocomplete[field="$ctrl.client.payMethodFk"] input`, + payMethodIBANOption: `vn-autocomplete[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(5)`, + payMethodOptionOne: `vn-autocomplete[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(2)`, IBANInput: `${components.vnTextfield}[name="iban"]`, dueDayInput: `${components.vnTextfield}[name="dueDay"]`, - receivedCoreVNHCheckbox: `${components.vnCheck}[label='Received core VNH'] > label > input`, - receivedCoreVNLCheckbox: `${components.vnCheck}[label='Received core VNL'] > label > input`, - receivedB2BVNLCheckbox: `${components.vnCheck}[label='Received B2B VNL'] > label > input`, + receivedCoreVNHCheckbox: `vn-check[label='Received core VNH'] > label > input`, + receivedCoreVNLCheckbox: `vn-check[label='Received core VNL'] > label > input`, + receivedB2BVNLCheckbox: `vn-check[label='Received B2B VNL'] > label > input`, saveButton: `${components.vnSubmit}` }, clientAddresses: { - addressesButton: `${components.vnMenuItem}[ui-sref="client.card.address.index"]`, + addressesButton: `vn-menu-item a[ui-sref="client.card.address.index"]`, createAddress: `vn-client-address-index ${components.vnFloatButton}`, - defaultCheckboxInput: `${components.vnCheck}[label='Default'] > label > input`, + defaultCheckboxInput: `vn-check[label='Default'] > label > input`, consigneeInput: `${components.vnTextfield}[name="nickname"]`, streetAddressInput: `${components.vnTextfield}[name="street"]`, postcodeInput: `${components.vnTextfield}[name="postalCode"]`, cityInput: `${components.vnTextfield}[name="city"]`, - provinceInput: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] input`, - provinceSecondOption: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] vn-drop-down ul > li:nth-child(2)`, - agencyInput: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] input`, - agenctySecondOption: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] vn-drop-down ul > li:nth-child(2)`, + provinceInput: `vn-autocomplete[field="$ctrl.address.provinceFk"] input`, + provinceSecondOption: `vn-autocomplete[field="$ctrl.address.provinceFk"] vn-drop-down ul > li:nth-child(2)`, + agencyInput: `vn-autocomplete[field="$ctrl.address.agencyModeFk"] input`, + agenctySecondOption: `vn-autocomplete[field="$ctrl.address.agencyModeFk"] vn-drop-down ul > li:nth-child(2)`, phoneInput: `${components.vnTextfield}[name="phone"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`, defaultAddress: 'vn-client-address-index vn-horizontal:nth-child(2) div[name="street"]', secondMakeDefaultStar: 'vn-client-address-index > vn-vertical > vn-card > div > vn-horizontal:nth-child(3) > vn-one > vn-horizontal > vn-none > i', - firstEditButton: `vn-client-address-index ${components.vnIconButton}[icon='edit']`, - secondEditButton: `vn-client-address-index vn-horizontal:nth-child(3) ${components.vnIconButton}[icon='edit']`, - activeCheckbox: `${components.vnCheck}[label='Enabled'] > label > input`, - equalizationTaxCheckboxLabel: `vn-client-address-edit ${components.vnCheck}[label='Is equalizated'] > label > input`, - firstObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(1) input`, - firstObservationTypeSelectOptionOne: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(1)`, - firstObservationDescriptionInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Description"] > div > input`, - secondObservationTypeSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="observation.observationTypeFk"] input`, - secondObservationTypeSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, - secondObservationDescriptionInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Description"] > div > input`, - thirdObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(3) input`, - thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`, - thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`, - addObservationButton: `${components.vnIcon}[icon="add_circle"]`, + firstEditButton: `vn-client-address-index vn-icon-button[icon='edit']`, + secondEditButton: `vn-client-address-index vn-horizontal:nth-child(3) vn-icon-button[icon='edit']`, + activeCheckbox: `vn-check[label='Enabled'] > label > input`, + equalizationTaxCheckboxLabel: `vn-client-address-edit vn-check[label='Is equalizated'] > label > input`, + firstObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"] input`, + firstObservationTypeSelectOptionOne: `vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(1)`, + firstObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(1) [model="observation.description"] input`, + secondObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"] input`, + secondObservationTypeSelectOptionTwo: `vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, + secondObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(2) [model="observation.description"] input`, + thirdObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(3) [field="observation.observationTypeFk"] input`, + thirdObservationTypeSelectOptionThree: `vn-client-address-edit [name=observations] :nth-child(3) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`, + thirdObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(3) [model="observation.description"] input`, + addObservationButton: `vn-client-address-edit vn-icon-button[icon="add_circle"]`, saveButton: `${components.vnSubmit}`, cancelButton: `button[ui-sref="client.card.address.index"]` }, clientWebAccess: { - webAccessButton: `${components.vnMenuItem}[ui-sref="client.card.webAccess"]`, - enableWebAccessCheckbox: `${components.vnCheck}[label='Enable web access'] > label > input`, + webAccessButton: `vn-menu-item a[ui-sref="client.card.webAccess"]`, + enableWebAccessCheckbox: `vn-check[label='Enable web access'] > label > input`, userNameInput: `${components.vnTextfield}[name="name"]`, saveButton: `${components.vnSubmit}` }, clientNotes: { - notesButton: `${components.vnMenuItem}[ui-sref="client.card.note.index"]`, + notesButton: `vn-menu-item a[ui-sref="client.card.note.index"]`, addNoteFloatButton: `${components.vnFloatButton}`, - noteInput: `${components.vnTextarea}[label="Note"]`, + noteInput: `vn-textarea[label="Note"]`, saveButton: `${components.vnSubmit}`, firstNoteText: 'vn-client-note .text' }, clientCredit: { - creditButton: `${components.vnMenuItem}[ui-sref="client.card.credit.index"]`, + creditButton: `vn-menu-item a[ui-sref="client.card.credit.index"]`, addCreditFloatButton: `${components.vnFloatButton}`, creditInput: `${components.vnTextfield}[name="credit"]`, saveButton: `${components.vnSubmit}`, firstCreditText: 'vn-client-credit-index .list-element' }, clientGreuge: { - greugeButton: `${components.vnMenuItem}[ui-sref="client.card.greuge.index"]`, + greugeButton: `vn-menu-item a[ui-sref="client.card.greuge.index"]`, addGreugeFloatButton: `${components.vnFloatButton}`, amountInput: `${components.vnTextfield}[name="amount"]`, descriptionInput: `${components.vnTextfield}[name="description"]`, - typeInput: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] input`, - typeSecondOption: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] vn-drop-down ul > li`, + typeInput: `vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"] input`, + typeSecondOption: `vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"] vn-drop-down ul > li`, saveButton: `${components.vnSubmit}`, firstGreugeText: 'vn-client-greuge-index .list-element' }, clientMandate: { - mandateButton: `${components.vnMenuItem}[ui-sref="client.card.mandate"]`, + mandateButton: `vn-menu-item a[ui-sref="client.card.mandate"]`, firstMandateText: 'vn-client-mandate .list-element' }, clientInvoices: { - invoicesButton: `${components.vnMenuItem}[ui-sref="client.card.invoice"]`, + invoicesButton: `vn-menu-item a[ui-sref="client.card.invoice"]`, firstInvoiceText: 'vn-client-invoice .list-element' }, itemsIndex: { @@ -158,167 +158,167 @@ export default { searchResultCloneButton: `vn-item-product .buttons > [icon="icon-clone"]`, acceptClonationAlertButton: `vn-item-index [vn-id="clone"] [response="ACCEPT"]`, searchItemInput: `${components.vnTextfield}`, - searchButton: `${components.vnSearchBar} > vn-icon-button`, + searchButton: `vn-searchbar vn-icon-button[icon="search"]`, closeItemSummaryPreview: 'vn-item-index [vn-id="preview"] button.close' }, itemCreateView: { name: `${components.vnTextfield}[name="name"]`, - typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`, - typeSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, - intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`, - intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`, - originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, - originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, + typeSelect: `vn-autocomplete[field="$ctrl.item.typeFk"] input`, + typeSelectOptionOne: `vn-autocomplete[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, + intrastatSelect: `vn-autocomplete[field="$ctrl.item.intrastatFk"] input`, + intrastatSelectOptionOne: `vn-autocomplete[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`, + originSelect: `vn-autocomplete[field="$ctrl.item.originFk"] input`, + originSelectOptionOne: `vn-autocomplete[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, createButton: `${components.vnSubmit}`, cancelButton: `button[ui-sref="item.index"]` }, itemBasicData: { goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', - basicDataButton: `${components.vnMenuItem}[ui-sref="item.card.data"]`, - typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`, - typeSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, - intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`, - intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(1)`, + basicDataButton: `vn-menu-item a[ui-sref="item.card.data"]`, + typeSelect: `vn-autocomplete[field="$ctrl.item.typeFk"] input`, + typeSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, + intrastatSelect: `vn-autocomplete[field="$ctrl.item.intrastatFk"] input`, + intrastatSelectOptionOne: `vn-autocomplete[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(1)`, nameInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`, relevancyInput: `vn-horizontal:nth-child(3) > ${components.vnTextfield}`, - originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, - originSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, - expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] input`, - expenceSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`, + originSelect: `vn-autocomplete[field="$ctrl.item.originFk"] input`, + originSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, + expenceSelect: `vn-autocomplete[field="$ctrl.item.expenceFk"] input`, + expenceSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`, submitBasicDataButton: `${components.vnSubmit}` }, itemTags: { goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', - tagsButton: `${components.vnMenuItem}[ui-sref="item.card.tags"]`, - firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnIcon}[icon="remove_circle_outline"]`, - firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, + tagsButton: `vn-menu-item a[ui-sref="item.card.tags"]`, + firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) > vn-icon[icon="remove_circle_outline"]`, + firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete[field="itemTag.tagFk"] input`, firstTagDisabled: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete > div > div > input`, - firstTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, + firstTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(2) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, firstValueInput: `vn-item-tags vn-horizontal:nth-child(2) > vn-textfield[label="Value"] > div > input`, firstRelevancyInput: `vn-horizontal:nth-child(2) > vn-textfield[label="Relevancy"] > div > input`, - secondTagSelect: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, + secondTagSelect: `vn-item-tags vn-horizontal:nth-child(3) > vn-autocomplete[field="itemTag.tagFk"] input`, secondTagDisabled: `vn-item-tags vn-horizontal:nth-child(3) > vn-autocomplete > div > div > input`, - secondTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, + secondTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(3) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, secondValueInput: `vn-item-tags vn-horizontal:nth-child(3) > vn-textfield[label="Value"] > div > input`, secondRelevancyInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Relevancy"] > div > input`, - thirdTagSelect: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, + thirdTagSelect: `vn-item-tags vn-horizontal:nth-child(4) > vn-autocomplete[field="itemTag.tagFk"] input`, thirdTagDisabled: `vn-item-tags vn-horizontal:nth-child(4) > vn-autocomplete > div > div > input`, - thirdTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, + thirdTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(4) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, thirdValueInput: `vn-item-tags vn-horizontal:nth-child(4) > vn-textfield[label="Value"] > div > input`, thirdRelevancyInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Relevancy"] > div > input`, - fourthTagSelect: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, + fourthTagSelect: `vn-item-tags vn-horizontal:nth-child(5) > vn-autocomplete[field="itemTag.tagFk"] input`, fourthTagDisabled: `vn-item-tags vn-horizontal:nth-child(5) > vn-autocomplete > div > div > input`, - fourthTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(5) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, + fourthTagSelectOptionOne: `vn-item-tags vn-horizontal:nth-child(5) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(1)`, fourthValueInput: `vn-item-tags vn-horizontal:nth-child(5) > vn-textfield[label="Value"] > div > input`, fourthRelevancyInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Relevancy"] > div > input`, - fifthTagSelect: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, + fifthTagSelect: `vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete[field="itemTag.tagFk"] input`, fifthTagDisabled: `vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete > div > div > input`, - fifthTagSelectOptionFive: `vn-item-tags vn-horizontal:nth-child(6) > ${components.vnAutocomplete}[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(5)`, + fifthTagSelectOptionFive: `vn-item-tags vn-horizontal:nth-child(6) > vn-autocomplete[field="itemTag.tagFk"] vn-drop-down ul > li:nth-child(5)`, fifthValueInput: `vn-item-tags vn-horizontal:nth-child(6) > vn-textfield[label="Value"] > div > input`, fifthRelevancyInput: `vn-horizontal:nth-child(6) > vn-textfield[label="Relevancy"] > div > input`, - addItemTagButton: `${components.vnIcon}[icon="add_circle"]`, + addItemTagButton: `vn-icon[icon="add_circle"]`, submitItemTagsButton: `${components.vnSubmit}` }, itemTax: { - taxButton: `${components.vnMenuItem}[ui-sref="item.card.tax"]`, - firstClassSelect: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, - firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`, - secondClassSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, - secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(1)`, - thirdClassSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, - thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`, + taxButton: `vn-menu-item a[ui-sref="item.card.tax"]`, + firstClassSelect: `vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"] input`, + firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > vn-autocomplete vn-drop-down ul > li:nth-child(2)`, + secondClassSelect: `vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"] input`, + secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > vn-autocomplete vn-drop-down ul > li:nth-child(1)`, + thirdClassSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="tax.taxClassFk"] input`, + thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > vn-autocomplete vn-drop-down ul > li:nth-child(2)`, submitTaxButton: `${components.vnSubmit}` }, itemBarcodes: { - barcodeButton: `${components.vnMenuItem}[ui-sref="item.card.itemBarcode"]`, - addBarcodeButton: `${components.vnIcon}[icon="add_circle"]`, + barcodeButton: `vn-menu-item a[ui-sref="item.card.itemBarcode"]`, + addBarcodeButton: `vn-icon[icon="add_circle"]`, thirdCodeInput: `vn-item-barcode vn-horizontal:nth-child(4) > ${components.vnTextfield}`, submitBarcodesButton: `${components.vnSubmit}`, - firstCodeRemoveButton: `vn-horizontal:nth-child(2) > ${components.vnIcon}[icon="remove_circle_outline"]` + firstCodeRemoveButton: `vn-horizontal:nth-child(2) > vn-icon[icon="remove_circle_outline"]` }, itemNiches: { - nicheButton: `${components.vnMenuItem}[ui-sref="item.card.niche"]`, - addNicheButton: `${components.vnIcon}[icon="add_circle"]`, - firstWarehouseSelect: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`, + nicheButton: `vn-menu-item a[ui-sref="item.card.niche"]`, + addNicheButton: `vn-icon[icon="add_circle"]`, + firstWarehouseSelect: `vn-autocomplete[field="itemNiche.warehouseFk"] input`, firstWarehouseDisabled: `vn-horizontal:nth-child(2) > vn-textfield[label="Warehouse"] > div > input`, - firstWarehouseSelectSecondOption: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(2)`, + firstWarehouseSelectSecondOption: `vn-autocomplete[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(2)`, firstCodeInput: `vn-horizontal:nth-child(2) > vn-textfield[label="Code"] > div > input`, - secondWarehouseSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`, + secondWarehouseSelect: `vn-horizontal:nth-child(3) > vn-autocomplete[field="itemNiche.warehouseFk"] input`, secondWarehouseDisabled: `vn-horizontal:nth-child(3) > vn-textfield[label="Warehouse"] > div > input`, secondCodeInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Code"] > div > input`, - secondNicheRemoveButton: `vn-horizontal:nth-child(3) > ${components.vnIcon}[icon="remove_circle_outline"]`, - thirdWarehouseSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`, + secondNicheRemoveButton: `vn-horizontal:nth-child(3) > vn-icon[icon="remove_circle_outline"]`, + thirdWarehouseSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="itemNiche.warehouseFk"] input`, thirdWarehouseDisabled: `vn-horizontal:nth-child(4) > vn-textfield[label="Warehouse"] > div > input`, - thirdWarehouseSelectFourthOption: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(4)`, + thirdWarehouseSelectFourthOption: `vn-horizontal:nth-child(4) > vn-autocomplete[field="itemNiche.warehouseFk"] vn-drop-down ul > li:nth-child(4)`, thirdCodeInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Code"] > div > input`, submitNichesButton: `${components.vnSubmit}` }, itemBotanical: { - botanicalButton: `${components.vnMenuItem}[ui-sref="item.card.botanical"]`, + botanicalButton: `vn-menu-item a[ui-sref="item.card.botanical"]`, botanicalInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`, - genusSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] input`, - genusSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(1)`, - genusSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(2)`, - speciesSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] input`, - speciesSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(1)`, - speciesSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(2)`, + genusSelect: `vn-autocomplete[field="$ctrl.botanical.genusFk"] input`, + genusSelectOptionOne: `vn-autocomplete[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(1)`, + genusSelectOptionTwo: `vn-autocomplete[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(2)`, + speciesSelect: `vn-autocomplete[field="$ctrl.botanical.specieFk"] input`, + speciesSelectOptionOne: `vn-autocomplete[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(1)`, + speciesSelectOptionTwo: `vn-autocomplete[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(2)`, submitBotanicalButton: `${components.vnSubmit}` }, itemSummary: { - basicData: `${components.vnItemSummary} vn-vertical[name="basicData"]`, - vat: `${components.vnItemSummary} vn-vertical[name="tax"]`, - tags: `${components.vnItemSummary} vn-vertical[name="tags"]`, - niche: `${components.vnItemSummary} vn-vertical[name="niche"]`, - botanical: `${components.vnItemSummary} vn-vertical[name="botanical"]`, - barcode: `${components.vnItemSummary} vn-vertical[name="barcode"]` + basicData: `vn-item-summary vn-vertical[name="basicData"]`, + vat: `vn-item-summary vn-vertical[name="tax"]`, + tags: `vn-item-summary vn-vertical[name="tags"]`, + niche: `vn-item-summary vn-vertical[name="niche"]`, + botanical: `vn-item-summary vn-vertical[name="botanical"]`, + barcode: `vn-item-summary vn-vertical[name="barcode"]` }, ticketsIndex: { createTicketButton: `${components.vnFloatButton}`, searchResult: `table > tbody > tr`, searchTicketInput: `${components.vnTextfield}`, - searchButton: `${components.vnSearchBar} > vn-icon-button` + searchButton: `vn-searchbar vn-icon-button[icon="search"]` }, ticketNotes: { - notesButton: `${components.vnMenuItem}[ui-sref="ticket.card.observation"]`, - firstNoteRemoveButton: `${components.vnIcon}[icon="remove_circle_outline"]`, - addNoteButton: `${components.vnIcon}[icon="add_circle"]`, - firstNoteSelect: `${components.vnAutocomplete}[field="ticketObservation.observationTypeFk"] input`, - firstNoteSelectSecondOption: `${components.vnAutocomplete}[field="ticketObservation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, + notesButton: `vn-menu-item a[ui-sref="ticket.card.observation"]`, + firstNoteRemoveButton: `vn-icon[icon="remove_circle_outline"]`, + addNoteButton: `vn-icon[icon="add_circle"]`, + firstNoteSelect: `vn-autocomplete[field="ticketObservation.observationTypeFk"] input`, + firstNoteSelectSecondOption: `vn-autocomplete[field="ticketObservation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, firstNoteDisabled: `vn-textfield[label="Observation type"] > div > input`, firstDescriptionInput: `vn-textfield[label="Description"] > div > input`, submitNotesButton: `${components.vnSubmit}` }, ticketExpedition: { - expeditionButton: `${components.vnMenuItem}[ui-sref="ticket.card.expedition"]`, + expeditionButton: `vn-menu-item a[ui-sref="ticket.card.expedition"]`, secondExpeditionRemoveButton: `body > vn-app > vn-vertical > vn-vertical > ui-view > vn-ticket-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-ticket-expedition > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-horizontal:nth-child(2) > vn-one:nth-child(1) > i`, secondExpeditionText: `body > vn-app > vn-vertical > vn-vertical > ui-view > vn-ticket-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-ticket-expedition > vn-vertical > vn-card > div > vn-vertical > vn-one > vn-horizontal:nth-child(2)` }, ticketPackages: { - packagesButton: `${components.vnMenuItem}[ui-sref="ticket.card.package.index"]`, - firstPackageSelect: `${components.vnAutocomplete}[label="Package"] > div > div > input`, - firstPackageSelectOptionThree: `${components.vnAutocomplete}[label="Package"] vn-drop-down ul > li:nth-child(3)`, + packagesButton: `vn-menu-item a[ui-sref="ticket.card.package.index"]`, + firstPackageSelect: `vn-autocomplete[label="Package"] > div > div > input`, + firstPackageSelectOptionThree: `vn-autocomplete[label="Package"] vn-drop-down ul > li:nth-child(3)`, firstQuantityInput: `vn-textfield[label="Quantity"] > div > input`, firstRemovePackageButton: `vn-icon[vn-tooltip="Remove package"]`, addPackageButton: `vn-icon[vn-tooltip="Add package"]`, - clearPackageSelectButton: `${components.vnAutocomplete}[label="Package"] > div > div > div > vn-icon > i`, + clearPackageSelectButton: `vn-autocomplete[label="Package"] > div > div > div > vn-icon > i`, savePackagesButton: `${components.vnSubmit}` }, ticketSales: { - saleButton: `${components.vnMenuItem}[ui-sref="ticket.card.sale"]`, + saleButton: `vn-menu-item a[ui-sref="ticket.card.sale"]`, firstSaleText: `table > tbody > tr:nth-child(1)`, secondSaleText: `table > tbody > tr:nth-child(2)` }, ticketTracking: { - trackingButton: `${components.vnMenuItem}[ui-sref="ticket.card.tracking.index"]`, + trackingButton: `vn-menu-item a[ui-sref="ticket.card.tracking.index"]`, createStateButton: `${components.vnFloatButton}`, firstSaleText: `table > tbody > tr:nth-child(1)`, secondSaleText: `table > tbody > tr:nth-child(2)` }, createStateView: { - stateInput: `${components.vnAutocomplete}[field="$ctrl.ticket.stateFk"] > div > div > input`, - stateInputOptionOne: `${components.vnAutocomplete}[field="$ctrl.ticket.stateFk"] vn-drop-down ul > li:nth-child(1)`, - clearStateInputButton: `${components.vnAutocomplete}[field="$ctrl.ticket.stateFk"] > div > div > div > vn-icon > i`, + stateInput: `vn-autocomplete[field="$ctrl.ticket.stateFk"] > div > div > input`, + stateInputOptionOne: `vn-autocomplete[field="$ctrl.ticket.stateFk"] vn-drop-down ul > li:nth-child(1)`, + clearStateInputButton: `vn-autocomplete[field="$ctrl.ticket.stateFk"] > div > div > div > vn-icon > i`, saveStateButton: `${components.vnSubmit}` } }; diff --git a/e2e/paths/client-module/06_add_address_notes.spec.js b/e2e/paths/client-module/06_add_address_notes.spec.js index 4b243698df..15e75b088d 100644 --- a/e2e/paths/client-module/06_add_address_notes.spec.js +++ b/e2e/paths/client-module/06_add_address_notes.spec.js @@ -65,7 +65,7 @@ describe('Client', () => { .waitToClick(selectors.clientAddresses.saveButton) .waitForSnackbar() .then(result => { - expect(result).toContain('No field can be blank'); + expect(result).toContain('type cannot be blank'); }); }); diff --git a/e2e/paths/ticket-module/05_create_new_tracking_state.spec.js b/e2e/paths/ticket-module/05_create_new_tracking_state.spec.js index 028c195a97..87bc5281d5 100644 --- a/e2e/paths/ticket-module/05_create_new_tracking_state.spec.js +++ b/e2e/paths/ticket-module/05_create_new_tracking_state.spec.js @@ -65,7 +65,7 @@ describe('Ticket', () => { }); }); - it(`should attempt create a new state but receive an error if state have been cleared`, () => { + it(`should attempt create a new state then clear and save it`, () => { return nightmare .waitToClick(selectors.createStateView.stateInput) .waitToClick(selectors.createStateView.stateInputOptionOne) @@ -73,7 +73,17 @@ describe('Ticket', () => { .click(selectors.createStateView.saveStateButton) .waitForSnackbar() .then(result => { - expect(result).toContain('No changes to save'); + expect(result).toContain('Data saved!'); + }); + }); + + it('should access to the create state view by clicking the create floating button', () => { + return nightmare + .click(selectors.ticketTracking.createStateButton) + .wait(selectors.createStateView.stateInput) + .parsedUrl() + .then(url => { + expect(url.hash).toContain('tracking/edit'); }); }); diff --git a/services/client/common/methods/address/crudAddressObservations.js b/services/client/common/methods/address/crudAddressObservations.js deleted file mode 100644 index 9487319e47..0000000000 --- a/services/client/common/methods/address/crudAddressObservations.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - Self.installCrudModel('crudAddressObservations'); -}; diff --git a/services/client/common/models/address-observation.js b/services/client/common/models/address-observation.js index ad82941dfa..d148b967e2 100644 --- a/services/client/common/models/address-observation.js +++ b/services/client/common/models/address-observation.js @@ -1,3 +1,25 @@ module.exports = function(Self) { - require('../methods/address/crudAddressObservations')(Self); + Self.validatesPresenceOf('observationTypeFk', { + message: 'Observation type cannot be blank' + }); + + Self.validateAsync('typeUnique', typeIsUnique, { + message: 'Observation type must be unique' + }); + async function typeIsUnique(err, done) { + let filter = { + fields: ['id'], + where: { + observationTypeFk: this.observationTypeFk, + addressFk: this.addressFk + } + }; + + if (this.id) + filter.where.id = {neq: this.id}; + + if (await Self.findOne(filter)) + err(); + done(); + } }; diff --git a/services/loopback/common/locale/en.json b/services/loopback/common/locale/en.json index 117e45e79f..cb55ef6786 100644 --- a/services/loopback/common/locale/en.json +++ b/services/loopback/common/locale/en.json @@ -17,5 +17,6 @@ "The IBAN does not have the correct format": "The IBAN does not have the correct format", "That payment method requires an IBAN": "That payment method requires an IBAN", "State cannot be blank": "State cannot be blank", - "Cannot change the payment method if no salesperson": "Cannot change the payment method if no salesperson" + "Cannot change the payment method if no salesperson": "Cannot change the payment method if no salesperson", + "Observation type cannot be blank": "Observation type cannot be blank" } \ No newline at end of file diff --git a/services/loopback/common/locale/es.json b/services/loopback/common/locale/es.json index caf866ca57..8228e1b1ee 100644 --- a/services/loopback/common/locale/es.json +++ b/services/loopback/common/locale/es.json @@ -18,6 +18,7 @@ "That payment method requires an IBAN": "El método de pago seleccionado requiere que se especifique el IBAN", "State cannot be blank": "El estado no puede estar en blanco", "Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado", - "EXPIRED_DATE": "EXPIRED_DATE", - "NO_AGENCY_AVAILABLE": "NO_AGENCY_AVAILABLE" + "can't be blank": "El campo no puede estar vacío", + "Observation type cannot be blank": "El tipo de observación no puede estar en blanco", + "Observation type must be unique": "El tipo de observación no puede repetirse" } \ No newline at end of file diff --git a/services/loopback/common/methods/item-tag/crudItemTags.js b/services/loopback/common/methods/item-tag/crudItemTags.js deleted file mode 100644 index 4a97023278..0000000000 --- a/services/loopback/common/methods/item-tag/crudItemTags.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - Self.installCrudModel('crudItemTags'); -}; diff --git a/services/loopback/common/methods/vnModel/installMethod.js b/services/loopback/common/methods/vn-model/installMethod.js similarity index 100% rename from services/loopback/common/methods/vnModel/installMethod.js rename to services/loopback/common/methods/vn-model/installMethod.js diff --git a/services/loopback/common/methods/vnModel/rawSql.js b/services/loopback/common/methods/vn-model/rawSql.js similarity index 100% rename from services/loopback/common/methods/vnModel/rawSql.js rename to services/loopback/common/methods/vn-model/rawSql.js diff --git a/services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js b/services/loopback/common/methods/vn-model/specs/installCrudModel.spec.js similarity index 100% rename from services/loopback/common/methods/vnModel/specs/installCrudModel.spec.js rename to services/loopback/common/methods/vn-model/specs/installCrudModel.spec.js diff --git a/services/loopback/common/methods/vnModel/validateBinded.js b/services/loopback/common/methods/vn-model/validateBinded.js similarity index 100% rename from services/loopback/common/methods/vnModel/validateBinded.js rename to services/loopback/common/methods/vn-model/validateBinded.js diff --git a/services/loopback/common/methods/vnModel/installCrudModel.js b/services/loopback/common/methods/vnModel/installCrudModel.js deleted file mode 100644 index 4ab6f6d1d1..0000000000 --- a/services/loopback/common/methods/vnModel/installCrudModel.js +++ /dev/null @@ -1,46 +0,0 @@ -module.exports = function(Self) { - Self.installCrudModel = function(methodName) { - let Model = this; - Model.remoteMethod(methodName, { - description: 'create, update or delete model', - accessType: 'WRITE', - accepts: [ - { - arg: 'crudStruct', - type: 'Object', - require: true, - description: 'object with instances of model to create, update or delete, Example: {create: [], update: [], delete: []}', - http: {source: 'body'} - } - ], - http: { - path: `/${methodName}`, - verb: 'post' - } - }); - Model[methodName] = async crudObject => { - let promises = []; - let transaction = await Model.beginTransaction({}); - let options = {transaction: transaction}; - - try { - if (crudObject.delete && crudObject.delete.length) { - promises.push(Model.destroyAll({id: {inq: crudObject.delete}}, options)); - } - if (crudObject.create && crudObject.create.length) { - promises.push(Model.create(crudObject.create, options)); - } - if (crudObject.update) { - crudObject.update.forEach(toUpdate => { - promises.push(Model.upsert(toUpdate, options)); - }); - } - await Promise.all(promises); - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw Array.isArray(error) ? error[0] : error; - } - }; - }; -}; diff --git a/services/loopback/common/models/item-tag.js b/services/loopback/common/models/item-tag.js index ad237ab3f4..61bd7af6b7 100644 --- a/services/loopback/common/models/item-tag.js +++ b/services/loopback/common/models/item-tag.js @@ -1,4 +1,3 @@ module.exports = Self => { - require('../methods/item-tag/crudItemTags')(Self); require('../methods/item-tag/filterItemTags')(Self); }; diff --git a/services/loopback/common/models/vn-model.js b/services/loopback/common/models/vn-model.js index abac75625a..5d1f4b7be8 100644 --- a/services/loopback/common/models/vn-model.js +++ b/services/loopback/common/models/vn-model.js @@ -1,8 +1,8 @@ module.exports = function(Self) { Self.setup = function() { Self.super_.setup.call(this); - - /* let disableMethods = { + /* + let disableMethods = { create: true, replaceOrCreate: true, patchOrCreate: true, @@ -22,7 +22,9 @@ module.exports = function(Self) { }; for (let method in disableMethods) { // this.disableRemoteMethod(method, disableMethods[method]); - } */ + } + */ + this.installCrudModel('crud'); }; Self.defineScope = function(serverFilter) { @@ -116,8 +118,51 @@ module.exports = function(Self) { }; }; - require('../methods/vnModel/rawSql')(Self); - require('../methods/vnModel/installMethod')(Self); - require('../methods/vnModel/validateBinded')(Self); - require('../methods/vnModel/installCrudModel')(Self); + Self.installCrudModel = function(methodName) { + this.remoteMethod(methodName, { + description: 'Create, update or/and delete instances from model in a single request', + accessType: 'WRITE', + accepts: [ + { + arg: 'actions', + type: 'Object', + require: true, + description: 'Instances to update, example: {create: [instances], update: [instances], delete: [ids]}', + http: {source: 'body'} + } + ], + http: { + path: `/${methodName}`, + verb: 'POST' + } + }); + this[methodName] = async actions => { + let promises = []; + let transaction = await this.beginTransaction({}); + let options = {transaction: transaction}; + + try { + if (actions.delete && actions.delete.length) { + promises.push(this.destroyAll({id: {inq: actions.delete}}, options)); + } + if (actions.create && actions.create.length) { + promises.push(this.create(actions.create, options)); + } + if (actions.update) { + actions.update.forEach(toUpdate => { + promises.push(this.upsert(toUpdate, options)); + }); + } + await Promise.all(promises); + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw Array.isArray(error) ? error[0] : error; + } + }; + }; + + require('../methods/vn-model/rawSql')(Self); + require('../methods/vn-model/installMethod')(Self); + require('../methods/vn-model/validateBinded')(Self); }; From e78d2e5ede3362ebebc0c2a697a7a786ca2c28a1 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 31 May 2018 13:34:48 +0200 Subject: [PATCH 3/6] Style fixes --- client/client/src/basic-data/index.html | 8 +- client/client/src/billing-data/index.html | 13 +-- client/client/src/create/index.html | 62 +++++++++----- .../src/credit-insurance/create/index.html | 4 +- client/client/src/fiscal-data/index.html | 82 ++++++++++--------- .../src/components/autocomplete/style.scss | 7 +- client/core/src/components/grid/style.scss | 9 +- client/item/src/create/index.html | 2 +- client/salix/src/components/home/style.scss | 8 +- .../salix/src/components/left-menu/style.scss | 12 ++- .../salix/src/components/main-menu/style.scss | 7 +- client/salix/src/styles/colors.scss | 3 +- client/salix/src/styles/effects.scss | 20 +++++ client/salix/src/styles/font-style.scss | 1 - client/salix/src/styles/index.js | 1 + client/salix/src/styles/misc.scss | 24 ++---- 16 files changed, 150 insertions(+), 113 deletions(-) create mode 100644 client/salix/src/styles/effects.scss diff --git a/client/client/src/basic-data/index.html b/client/client/src/basic-data/index.html index 17ac4cf991..0b42536e6d 100644 --- a/client/client/src/basic-data/index.html +++ b/client/client/src/basic-data/index.html @@ -10,17 +10,19 @@ Basic data - - - + + + + + Pay method - + initial-data="$ctrl.client.payMethod"> diff --git a/client/client/src/create/index.html b/client/client/src/create/index.html index 7834684c95..abf60ca466 100644 --- a/client/client/src/create/index.html +++ b/client/client/src/create/index.html @@ -6,22 +6,11 @@ save="post"> -
+
Create client - - - - - - - - {{firstName}} {{name}} + + + + + + + + - - + + + + + + + + + + + + + + diff --git a/client/client/src/credit-insurance/create/index.html b/client/client/src/credit-insurance/create/index.html index 2a83a27752..b37ed2c9a0 100644 --- a/client/client/src/credit-insurance/create/index.html +++ b/client/client/src/credit-insurance/create/index.html @@ -4,7 +4,6 @@ diff --git a/client/client/src/fiscal-data/index.html b/client/client/src/fiscal-data/index.html index 4459396249..e22006c267 100644 --- a/client/client/src/fiscal-data/index.html +++ b/client/client/src/fiscal-data/index.html @@ -24,13 +24,6 @@ vn-acl="administrative, salesAssistant, salesPerson" acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> - - - - - - + + + + + - - - - - + + + + + + + + + + + div > .mdl-textfield { position: relative; @@ -44,13 +44,12 @@ ul.vn-autocomplete { max-height: 300px; li { + @extend %clickable; display: block; padding: .8em; margin: 0; - cursor: pointer; - &.active, - &:hover { + &.active { background-color: $hover; } &.load-more { diff --git a/client/core/src/components/grid/style.scss b/client/core/src/components/grid/style.scss index 197ce71f4b..88275ff616 100644 --- a/client/core/src/components/grid/style.scss +++ b/client/core/src/components/grid/style.scss @@ -1,4 +1,4 @@ -@import "colors"; +@import "effects"; .vn-grid { border-collapse: collapse; @@ -26,13 +26,8 @@ transition: background-color 200ms ease-in-out; &.clickable { - cursor: pointer; - - &:hover { - background-color: $hover; - } + @extend %clickable; } - &.success { background-color: rgba(163, 209, 49, 0.3); diff --git a/client/item/src/create/index.html b/client/item/src/create/index.html index 2fbf02f29b..a39653d9ca 100644 --- a/client/item/src/create/index.html +++ b/client/item/src/create/index.html @@ -6,7 +6,7 @@ save="post"> -
+
New item diff --git a/client/salix/src/components/home/style.scss b/client/salix/src/components/home/style.scss index b44afdab89..6d8d3fa4a2 100644 --- a/client/salix/src/components/home/style.scss +++ b/client/salix/src/components/home/style.scss @@ -1,4 +1,4 @@ -@import "colors"; +@import "effects"; vn-home { padding: 2em; @@ -17,6 +17,7 @@ vn-home { flex-wrap: wrap; & > a { + @extend %clickable-light; overflow:hidden; border-radius: 6px; background-color: $main-01; @@ -29,11 +30,6 @@ vn-home { padding: 1em; justify-content: center; - transition: opacity 250ms ease-out; - - &:hover { - background-color: $hover; - } & > vn-icon { font-size: 4em; } diff --git a/client/salix/src/components/left-menu/style.scss b/client/salix/src/components/left-menu/style.scss index a029594e62..311d94fccf 100644 --- a/client/salix/src/components/left-menu/style.scss +++ b/client/salix/src/components/left-menu/style.scss @@ -1,8 +1,12 @@ -@import "colors"; +@import "effects"; vn-menu-item { - & > li.active { - background-color: $main-header; - color: white; + & > li { + @extend %clickable; + + &.active { + background-color: $main-header; + color: white; + } } } diff --git a/client/salix/src/components/main-menu/style.scss b/client/salix/src/components/main-menu/style.scss index eddeb2677c..88eaf31053 100644 --- a/client/salix/src/components/main-menu/style.scss +++ b/client/salix/src/components/main-menu/style.scss @@ -1,4 +1,4 @@ -@import "colors"; +@import "effects"; vn-main-menu { #user { @@ -25,9 +25,9 @@ vn-main-menu { color: white; li { + @extend %clickable-light; background-color: $main-01; margin-bottom: .6em; - cursor: pointer; padding: .8em; border-radius: .1em; min-width: 8em; @@ -36,9 +36,6 @@ vn-main-menu { padding-right: .3em; vertical-align: middle; } - &:hover { - background-color: $hover; - } &:last-child { margin-bottom: 0; } diff --git a/client/salix/src/styles/colors.scss b/client/salix/src/styles/colors.scss index 3a9cea1c66..2359f922ea 100644 --- a/client/salix/src/styles/colors.scss +++ b/client/salix/src/styles/colors.scss @@ -1,7 +1,8 @@ $main-font-color :#222222; $secondary-font-color: #9b9b9b; $main-header: #3d3d3d; -$hover: #c4c4c4; +$hover: rgba(0, 0, 0, 0.1); +$hover-opacity: .7; $main-bg: #e5e5e5; $main-01: #f7931e; $main-01-05: rgba($main-01, 0.5); diff --git a/client/salix/src/styles/effects.scss b/client/salix/src/styles/effects.scss new file mode 100644 index 0000000000..6c1b6ffa36 --- /dev/null +++ b/client/salix/src/styles/effects.scss @@ -0,0 +1,20 @@ + +@import "./colors"; + +%clickable { + cursor: pointer; + transition: background-color 250ms ease-out; + + &:hover { + background-color: $hover; + } +} + +%clickable-light { + cursor: pointer; + transition: opacity 250ms ease-out; + + &:hover { + opacity: $hover-opacity; + } +} \ No newline at end of file diff --git a/client/salix/src/styles/font-style.scss b/client/salix/src/styles/font-style.scss index a33df7a6ae..462023eca2 100644 --- a/client/salix/src/styles/font-style.scss +++ b/client/salix/src/styles/font-style.scss @@ -1,7 +1,6 @@ @import "colors"; @import "font-family"; - body { color: $main-font-color; font-family: vn-font; diff --git a/client/salix/src/styles/index.js b/client/salix/src/styles/index.js index 31ef9c8637..eaa0463b82 100644 --- a/client/salix/src/styles/index.js +++ b/client/salix/src/styles/index.js @@ -9,3 +9,4 @@ import './font-style.scss'; import './misc.scss'; import './summary.scss'; import './colors.scss'; +import './effects.scss'; diff --git a/client/salix/src/styles/misc.scss b/client/salix/src/styles/misc.scss index 2f23a655b5..7c815b1a34 100644 --- a/client/salix/src/styles/misc.scss +++ b/client/salix/src/styles/misc.scss @@ -1,7 +1,8 @@ -@import "padding"; -@import "margin"; -@import "colors"; -@import "border"; +@import "./padding"; +@import "./margin"; +@import "./colors"; +@import "./border"; +@import "./effects"; a:focus, @@ -116,17 +117,8 @@ a { } } -.vn-clickable { - cursor: pointer; - transition: background-color 250ms ease-out; - - &:hover { - background-color: $hover; - } -} - button { - @extend .vn-clickable; + @extend %clickable; } vn-button-bar { @@ -168,7 +160,7 @@ vn-main-block { font-size: 2.5em; } & > a { - @extend .vn-clickable; + @extend %clickable; display: flex; align-items: center; @@ -205,7 +197,7 @@ vn-main-block { .vn-list-item { @extend .pad-medium; @extend .border-solid-bottom; - @extend .vn-clickable; + @extend %clickable; display: block; text-decoration: none; From 0d2693d8e15835258aca92b4503a48344a0391f3 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 31 May 2018 13:45:51 +0200 Subject: [PATCH 4/6] Style fixes --- client/client/src/address/create/index.html | 2 +- client/client/src/address/edit/index.html | 2 +- client/client/src/billing-data/index.html | 16 +++++++++------- client/client/src/create/index.html | 2 +- client/client/src/fiscal-data/index.html | 8 ++++---- client/client/src/web-access/index.html | 3 +-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/client/src/address/create/index.html b/client/client/src/address/create/index.html index 3af24d2d5a..c3d1077dcf 100644 --- a/client/client/src/address/create/index.html +++ b/client/client/src/address/create/index.html @@ -9,7 +9,7 @@ Address - + diff --git a/client/client/src/address/edit/index.html b/client/client/src/address/edit/index.html index 8ef86e11df..9ccdd5fb29 100644 --- a/client/client/src/address/edit/index.html +++ b/client/client/src/address/edit/index.html @@ -26,7 +26,7 @@ Address - + - - - + + + + + - + - + - + - + - + Web access - + From 2a6dedfc91bdd2aaabbee42f31fe9db4f326c659 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 31 May 2018 13:57:48 +0200 Subject: [PATCH 5/6] Style fixes --- client/core/src/components/dialog/style.scss | 13 ++++--------- client/core/src/components/drop-down/style.scss | 8 ++------ client/core/src/components/icon-button/style.scss | 1 + client/salix/src/styles/misc.scss | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/client/core/src/components/dialog/style.scss b/client/core/src/components/dialog/style.scss index 8aa2ed7a16..1a74045469 100644 --- a/client/core/src/components/dialog/style.scss +++ b/client/core/src/components/dialog/style.scss @@ -1,4 +1,4 @@ -@import "colors"; +@import "effects"; .vn-dialog { display: none; @@ -10,7 +10,7 @@ top: 0; height: 100%; width: 100%; - background-color: rgba(1, 1, 1, .6); + background-color: rgba(0, 0, 0, .6); opacity: 0; transition: opacity 300ms ease-in-out; @@ -19,7 +19,7 @@ } & > div { position: relative; - box-shadow: 0 0 .4em rgba(1,1,1,.4); + box-shadow: 0 0 .4em rgba(0, 0, 0, .4); background-color: white; border-radius: .2em; overflow: auto; @@ -34,16 +34,11 @@ input[type="button"], input[type="submit"], input[type="reset"] { + @extend %clickable; text-transform: uppercase; background-color: transparent; border: none; - cursor: pointer; - transition: background-color 250ms; border-radius: .1em; - - &:hover { - background-color: rgba(1,1,1,.1); - } } & > button.close { position: absolute; diff --git a/client/core/src/components/drop-down/style.scss b/client/core/src/components/drop-down/style.scss index d0ab937458..89c274e7a2 100755 --- a/client/core/src/components/drop-down/style.scss +++ b/client/core/src/components/drop-down/style.scss @@ -1,4 +1,4 @@ -@import "colors"; +@import "effects"; vn-drop-down { .dropdown { @@ -47,19 +47,15 @@ vn-drop-down { list-style-type: none; } li, .status { + @extend %clickable; padding: .6em; - cursor: pointer; white-space: nowrap; - transition: background-color 250ms ease-out; display: flex; & > input[type=checkbox] { margin: 0; margin-right: .6em; } - &:hover { - background-color: rgba(0, 0, 0, .1); - } &.active { background-color: #3D3A3B; color: white; diff --git a/client/core/src/components/icon-button/style.scss b/client/core/src/components/icon-button/style.scss index 5aeef63af3..e748a4ba4c 100644 --- a/client/core/src/components/icon-button/style.scss +++ b/client/core/src/components/icon-button/style.scss @@ -6,6 +6,7 @@ vn-icon-button { color: rgba($main-01, 0.7); transition: color 200ms ease-in-out; cursor: pointer; + &.button { background-color: $main-01; color: white; diff --git a/client/salix/src/styles/misc.scss b/client/salix/src/styles/misc.scss index 7c815b1a34..8392b02108 100644 --- a/client/salix/src/styles/misc.scss +++ b/client/salix/src/styles/misc.scss @@ -167,7 +167,6 @@ vn-main-block { padding: .5em; color: white; text-decoration: none; - transition: background-color 250ms ease-out; & > vn-icon { font-size: 1.8em; @@ -212,6 +211,7 @@ vn-main-block { margin-left: .5em; transition: opacity 250ms ease-out; font-size: 2em; + &:hover { opacity: 1; } From 97c840ece71f2d3a61bc4af43975c3c7e66b29c5 Mon Sep 17 00:00:00 2001 From: Juan Date: Thu, 31 May 2018 14:40:42 +0200 Subject: [PATCH 6/6] Style fixes --- client/core/src/components/snackbar/snackbar.js | 2 +- client/core/src/components/snackbar/style.scss | 7 +++---- client/item/src/create/index.html | 10 ++++++++-- client/item/src/data/index.html | 13 +++++++++++-- client/salix/src/styles/misc.scss | 14 +++++++++----- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/client/core/src/components/snackbar/snackbar.js b/client/core/src/components/snackbar/snackbar.js index 2044829502..aeec1f12fa 100644 --- a/client/core/src/components/snackbar/snackbar.js +++ b/client/core/src/components/snackbar/snackbar.js @@ -26,7 +26,7 @@ export default class Controller extends Component { this.hide(); this.onTransitionEnd(); } - + this.clearTimeouts(); this.shown = true; this.textNode.textContent = data.message; diff --git a/client/core/src/components/snackbar/style.scss b/client/core/src/components/snackbar/style.scss index c3de5d50df..f3617fe5e6 100644 --- a/client/core/src/components/snackbar/style.scss +++ b/client/core/src/components/snackbar/style.scss @@ -41,9 +41,8 @@ vn-snackbar > div { background-color: transparent; font-weight: bold; color: $main-01; - padding: 1em; - margin: -1em; - padding-left: 1.5em; - margin-left: 0; + padding: .5em; + margin: -.5em; + margin-left: .5em; } } \ No newline at end of file diff --git a/client/item/src/create/index.html b/client/item/src/create/index.html index a39653d9ca..895d120eee 100644 --- a/client/item/src/create/index.html +++ b/client/item/src/create/index.html @@ -20,7 +20,10 @@ value-field="id" field="$ctrl.item.typeFk" where="{or: [{code: {regexp: 'search'}}, {name: {regexp: 'search'}}]}"> - {{code}} : {{name}} + +
{{::code}}
+
{{::name}}
+
- {{id}} : {{description}} + +
{{::id}}
+
{{::description}}
+
diff --git a/client/item/src/data/index.html b/client/item/src/data/index.html index d37fc0af1b..1489cedae7 100644 --- a/client/item/src/data/index.html +++ b/client/item/src/data/index.html @@ -32,7 +32,10 @@ field="$ctrl.item.intrastatFk" where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}" initial-data="$ctrl.item.intrastat"> - {{id}} : {{description}} + +
{{::id}}
+
{{::description}}
+
@@ -45,13 +48,19 @@ field="$ctrl.item.originFk" initial-data="$ctrl.item.origin"> + + +
+ -
diff --git a/client/salix/src/styles/misc.scss b/client/salix/src/styles/misc.scss index 8392b02108..4dbef54151 100644 --- a/client/salix/src/styles/misc.scss +++ b/client/salix/src/styles/misc.scss @@ -4,7 +4,6 @@ @import "./border"; @import "./effects"; - a:focus, input:focus, button:focus @@ -161,7 +160,6 @@ vn-main-block { } & > a { @extend %clickable; - display: flex; align-items: center; padding: .5em; @@ -218,15 +216,21 @@ vn-main-block { } } } + /** START - FORM ELEMENTS DISABLED **/ -fieldset[disabled] .mdl-textfield .mdl-textfield__input, .mdl-textfield.is-disabled .mdl-textfield__input, -fieldset[disabled] .mdl-checkbox .mdl-checkbox__label, .mdl-checkbox.is-disabled .mdl-checkbox__label { + +fieldset[disabled] .mdl-textfield .mdl-textfield__input, +fieldset[disabled] .mdl-checkbox .mdl-checkbox__label, +.mdl-textfield.is-disabled .mdl-textfield__input, +.mdl-checkbox.is-disabled .mdl-checkbox__label { border: none !important; color: inherit !important; } -fieldset[disabled] .mdl-textfield .mdl-textfield__label, .mdl-textfield.is-disabled.is-disabled .mdl-textfield__label { +fieldset[disabled] .mdl-textfield .mdl-textfield__label, +.mdl-textfield.is-disabled.is-disabled .mdl-textfield__label { color: $main-01 !important; } + /** END - FORM ELEMENTS DISABLED **/ .ellipsize {