diff --git a/client/client/src/address/edit/address-edit.spec.js b/client/client/src/address/edit/address-edit.spec.js index 14c771a1f..3b9ae5385 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 dc7218e8e..8ef86e11d 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 117560e06..5a55d78d8 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 6162faee4..e8dfe906f 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 560ef77fa..9c216be38 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 44a41911c..fb18d0eaa 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 000000000..ab010d651 --- /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 bb15e926b..790f76870 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 77904844c..5a43a6988 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 000000000..597656c74 --- /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 06fa796f1..585af2d71 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 ad9ec7f4c..b7187a808 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 2b1309fcd..3e31cab81 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 651777fa5..92cea41aa 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 a01d3e139..9fec4ba91 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 11930afd6..d3b91be9e 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 f8fe5fa50..18eaa613c 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 66997ae5e..e63393898 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 6be96a46a..41e2380a6 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 aa111756b..c6b61ca90 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 7f45df878..9ad2ad592 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 28dc0db93..daa515e66 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 e682b5514..8c2c602c9 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 6cb7041a5..f1bbbdd0b 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 dd77d479c..a72633332 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 baf5e19a0..77bc7d12f 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 ae4b9a481..89fcd18b9 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 0ec09a7f0..f8d40228b 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 4b243698d..15e75b088 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 028c195a9..87bc5281d 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 9487319e4..000000000 --- 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 ad82941df..d148b967e 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 117e45e79..cb55ef678 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 caf866ca5..8228e1b1e 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 4a9702327..000000000 --- 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 4ab6f6d1d..000000000 --- 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 ad237ab3f..61bd7af6b 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 abac75625..5d1f4b7be 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); };