Merge branch 'dev' into test

This commit is contained in:
Joan Sanchez 2018-05-31 14:58:51 +02:00
commit d316b17899
99 changed files with 1199 additions and 1154 deletions

View File

@ -45,7 +45,7 @@ vn-login > div {
position: absolute; position: absolute;
width: 0; width: 0;
top: .3em; top: .3em;
right: 4em; right: 3em;
overflow: visible; overflow: visible;
} }
} }

View File

@ -9,7 +9,7 @@
<form name="form" ng-submit="watcher.submitGo('client.card.address.index')"> <form name="form" ng-submit="watcher.submitGo('client.card.address.index')">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Address</vn-title> <vn-title>Address</vn-title>
<vn-horizontal> <vn-horizontal pad-small-v>
<vn-check vn-one label="Default" field="$ctrl.address.isDefaultAddress"></vn-check> <vn-check vn-one label="Default" field="$ctrl.address.isDefaultAddress"></vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -15,41 +15,9 @@ describe('Client', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$state.params.addressId = '1'; $state.params.addressId = '1';
controller = $componentController('vnClientAddressEdit', {$state: $state}); 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();
});
});
}); });
}); });

View File

@ -1,19 +1,32 @@
<mg-ajax <mg-ajax
path="/client/api/Addresses/{{edit.params.addressId}}" path="/client/api/Addresses/{{edit.params.addressId}}"
actions="$ctrl.address = edit.model; $ctrl._setIconAdd();" actions="$ctrl.address = edit.model"
options="mgEdit"> options="mgEdit">
</mg-ajax> </mg-ajax>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.address"
url="/client/api/Addresses" url="/client/api/Addresses"
id-field="id" id-field="id"
data="$ctrl.address" form="form">
form="addressForm">
</vn-watcher> </vn-watcher>
<form name="addressForm" ng-submit="$ctrl.submit()"> <vn-crud-model
vn-id="model"
url="/client/api/AddressObservations"
fields="['id', 'addressFk', 'observationTypeFk', 'description']"
link="{addressFk: $ctrl.$stateParams.addressId}"
data="observations">
</vn-crud-model>
<vn-crud-model
url="/client/api/ObservationTypes"
fields="['id', 'description']"
data="types">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Address</vn-title> <vn-title>Address</vn-title>
<vn-horizontal> <vn-horizontal pad-small-v>
<vn-check vn-one label="Enabled" field="$ctrl.address.isActive"></vn-check> <vn-check vn-one label="Enabled" field="$ctrl.address.isActive"></vn-check>
<vn-check <vn-check
vn-one label="Is equalizated" vn-one label="Is equalizated"
@ -49,32 +62,17 @@
<vn-textfield vn-one label="Phone" field="$ctrl.address.phone"></vn-textfield> <vn-textfield vn-one label="Phone" field="$ctrl.address.phone"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.address.mobile"></vn-textfield> <vn-textfield vn-one label="Mobile" field="$ctrl.address.mobile"></vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-one margin-medium-top> <vn-title>Notes</vn-title>
</vn-card> <div name="observations">
</form> <vn-horizontal ng-repeat="observation in observations">
<vn-watcher form="notesForm"></vn-watcher>
<form name="notesForm" ng-submit="$ctrl.submit()">
<vn-card pad-large>
<vn-title>Notes</vn-title>
<mg-ajax path="/client/api/ObservationTypes" options="mgIndex as observationsTypes"></mg-ajax>
<vn-horizontal ng-repeat="observation in $ctrl.observations track by $index">
<vn-autocomplete <vn-autocomplete
ng-if="!observation.id"
vn-one vn-one
vn-focus vn-focus
initial-data="observation.observationType" data="types"
field="observation.observationTypeFk" field="observation.observationTypeFk"
data="observationsTypes.model"
show-field="description" show-field="description"
label="Observation type"> label="Observation type">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield
ng-if="observation.id"
vn-one
label="Observation type"
model="observation.observationType.description"
disabled="true">
</vn-textfield>
<vn-textfield <vn-textfield
vn-two vn-two
margin-large-right margin-large-right
@ -92,17 +90,15 @@
</vn-icon> </vn-icon>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
</vn-one> </div>
<vn-one> <vn-icon-button
<vn-icon-button pointer
pointer vn-bind="+"
vn-bind="+" vn-tooltip="Add note"
vn-tooltip="Add note" icon="add_circle"
icon="add_circle" ng-if="types.length > observations.length"
ng-if="observationsTypes.model.length > $ctrl.observations.length" ng-click="model.insert()">
ng-click="$ctrl.addObservation()"> </vn-icon-button>
</vn-icon-button>
</vn-one>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Save"></vn-submit> <vn-submit label="Save"></vn-submit>

View File

@ -1,151 +1,30 @@
import ngModule from '../../module'; import ngModule from '../../module';
export default class Controller { export default class Controller {
constructor($state, $scope, $http, $q, $translate, vnApp) { constructor($scope, $state) {
this.$ = $scope;
this.$state = $state; this.$state = $state;
this.$scope = $scope; this.$stateParams = $state.params;
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});
} }
removeObservation(index) { removeObservation(index) {
let item = this.observations[index]; this.$.watcher.setDirty();
if (item) { this.$.model.remove(index);
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;
} }
submit() { submit() {
if (this.$scope.addressForm.$invalid || this.$scope.notesForm.$invalid) { this.$.watcher.check();
this.vnApp.showMessage( this.$.watcher.realSubmit()
this.$translate.instant('Some fields are invalid') .then(() => this.$.model.save(true))
); .then(() => {
return false; this.$.watcher.setPristine();
} this.$.watcher.notifySaved();
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.card.reload(); 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', { ngModule.component('vnClientAddressEdit', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -10,17 +10,19 @@
<vn-title>Basic data</vn-title> <vn-title>Basic data</vn-title>
<vn-horizontal> <vn-horizontal>
<vn-textfield vn-one label="Comercial Name" field="$ctrl.client.name" vn-focus></vn-textfield> <vn-textfield vn-one label="Comercial Name" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Contact" field="$ctrl.client.contact"></vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield vn-one label="Phone" field="$ctrl.client.phone"></vn-textfield> <vn-textfield vn-one label="Contact" field="$ctrl.client.contact"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.client.mobile"></vn-textfield>
<vn-textfield vn-one <vn-textfield vn-one
label="Email" label="Email"
field="$ctrl.client.email" field="$ctrl.client.email"
info="You can save multiple emails"> info="You can save multiple emails">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Phone" field="$ctrl.client.phone"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.client.mobile"></vn-textfield>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one
initial-data="$ctrl.client.salesPerson" initial-data="$ctrl.client.salesPerson"

View File

@ -9,26 +9,31 @@
<vn-card pad-large> <vn-card pad-large>
<vn-title>Pay method</vn-title> <vn-title>Pay method</vn-title>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-two <vn-autocomplete
vn-one
label="Pay method"
vn-acl="administrative, salesAssistant" vn-acl="administrative, salesAssistant"
field="$ctrl.client.payMethodFk" field="$ctrl.client.payMethodFk"
url="/client/api/PayMethods" url="/client/api/PayMethods"
select-fields="ibanRequired" select-fields="ibanRequired"
initial-data="$ctrl.client.payMethod" initial-data="$ctrl.client.payMethod">
label="Pay method">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield <vn-textfield
vn-two label="IBAN" vn-one
field="$ctrl.client.iban" label="Due day"
vn-acl="administrative, salesAssistant">
</vn-textfield>
<vn-textfield
vn-one label="Due day"
field="$ctrl.client.dueDay" field="$ctrl.client.dueDay"
vn-acl="administrative, salesAssistant"> vn-acl="administrative, salesAssistant">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal margin-medium-bottom> <vn-horizontal>
<vn-textfield
vn-one
label="IBAN"
field="$ctrl.client.iban"
vn-acl="administrative, salesAssistant">
</vn-textfield>
</vn-horizontal>
<vn-horizontal pad-small-v>
<vn-one> <vn-one>
<vn-check <vn-check
label="Received core VNH" label="Received core VNH"

View File

@ -6,22 +6,11 @@
save="post"> save="post">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium> <form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;"> <div style="max-width: 50em; margin: 0 auto;">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Create client</vn-title> <vn-title>Create client</vn-title>
<vn-horizontal>
<vn-textfield vn-two label="Business name" field="$ctrl.client.socialName"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi"></vn-textfield>
<vn-check
vn-one
label="Is equalizated"
field="$ctrl.client.isEqualizated">
</vn-check>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield vn-two label="Comercial Name" field="$ctrl.client.name" vn-focus></vn-textfield> <vn-textfield vn-two label="Comercial Name" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Web user" field="$ctrl.client.userName"></vn-textfield>
<vn-textfield vn-two label="Email" field="$ctrl.client.email" info="You can save multiple emails"></vn-textfield>
<vn-autocomplete vn-one <vn-autocomplete vn-one
field="$ctrl.client.salesPersonFk" field="$ctrl.client.salesPersonFk"
url="/client/api/Clients/activeSalesPerson" url="/client/api/Clients/activeSalesPerson"
@ -31,6 +20,17 @@
<tpl-item>{{firstName}} {{name}}</tpl-item> <tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Business name" field="$ctrl.client.socialName"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-two
label="Street"
field="$ctrl.client.street">
</vn-textfield>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
vn-one vn-one
@ -38,18 +38,22 @@
field="$ctrl.client.postcode"> field="$ctrl.client.postcode">
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
vn-two vn-one
label="Street"
field="$ctrl.client.street">
</vn-textfield>
<vn-textfield
vn-two
label="City" label="City"
field="$ctrl.client.city"> field="$ctrl.client.city">
</vn-textfield> </vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
field="$ctrl.client.countryFk"
url="/client/api/Countries"
show-field="country"
value-field="id"
label="Country">
</vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
initial-data="$ctrl.client.province"
field="$ctrl.client.provinceFk" field="$ctrl.client.provinceFk"
url="/client/api/Provinces" url="/client/api/Provinces"
show-field="name" show-field="name"
@ -57,6 +61,26 @@
label="Province"> label="Province">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Web user"
field="$ctrl.client.userName">
</vn-textfield>
<vn-textfield
vn-one
label="Email"
field="$ctrl.client.email"
info="You can save multiple emails">
</vn-textfield>
</vn-horizontal>
<vn-horizontal pad-small-v>
<vn-check
vn-one
label="Is equalizated"
field="$ctrl.client.isEqualizated">
</vn-check>
</vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit label="Create"></vn-submit> <vn-submit label="Create"></vn-submit>

View File

@ -4,7 +4,6 @@
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
vn-one vn-one
margin-medium-right
label="Credit" label="Credit"
model="$ctrl.creditClassification.credit", model="$ctrl.creditClassification.credit",
rule="CreditInsurance.credit" rule="CreditInsurance.credit"
@ -12,8 +11,7 @@
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
vn-one vn-one
argin-medium-right
label="Grade" label="Grade"
model="$ctrl.creditClassification.grade" model="$ctrl.creditClassification.grade"
rule="CreditInsurance.grade"> rule="CreditInsurance.grade">

View File

@ -13,6 +13,7 @@ describe('Client', () => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientCreditInsuranceIndex'); controller = $componentController('vnClientCreditInsuranceIndex');
})); }));

View File

@ -14,6 +14,7 @@ describe('Client', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
let $state = {params: {classificationId: 1}}; let $state = {params: {classificationId: 1}};
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientCreditInsuranceInsuranceIndex', {$state: $state}); controller = $componentController('vnClientCreditInsuranceInsuranceIndex', {$state: $state});
})); }));

View File

@ -34,6 +34,7 @@ describe('Client', () => {
$state = _$state_; $state = _$state_;
$state.params.id = 101; $state.params.id = 101;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientCreditCreate', {$scope: $scope}, {$state: $state}); controller = $componentController('vnClientCreditCreate', {$scope: $scope}, {$state: $state});
})); }));
describe('onSubmit()', () => { describe('onSubmit()', () => {

View File

@ -13,6 +13,7 @@ describe('Descriptor', () => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientDescriptor'); controller = $componentController('vnClientDescriptor');
})); }));

View File

@ -14,6 +14,7 @@ describe('Client', () => {
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new(); $scope = $rootScope.$new();
controller = $componentController('vnClientFiscalData', {$scope: $scope}); controller = $componentController('vnClientFiscalData', {$scope: $scope});
})); }));

View File

@ -24,13 +24,6 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield> </vn-textfield>
<vn-check
vn-one
label="Is equalizated"
field="$ctrl.client.isEqualizated"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
@ -40,13 +33,6 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield> </vn-textfield>
<vn-textfield
vn-one
label="City"
field="$ctrl.client.city"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
@ -56,17 +42,15 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield> </vn-textfield>
<vn-autocomplete <vn-textfield
vn-one vn-one
initial-data="$ctrl.client.province" label="City"
field="$ctrl.client.provinceFk" field="$ctrl.client.city"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Province"
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-autocomplete> </vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
initial-data="$ctrl.client.country" initial-data="$ctrl.client.country"
@ -78,8 +62,19 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete
vn-one
initial-data="$ctrl.client.province"
field="$ctrl.client.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Province"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal margin-small-bottom> <vn-horizontal pad-small-v>
<vn-check <vn-check
vn-one vn-one
label="Active" label="Active"
@ -94,21 +89,8 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check> </vn-check>
<vn-check
vn-two
label="Invoice by address"
field="$ctrl.client.hasToInvoiceByAddress"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-two
label="Verified data"
field="$ctrl.client.isTaxDataChecked"
vn-acl="administrative, salesAssistant, salesAssistant">
</vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal pad-small-v>
<vn-check <vn-check
vn-one vn-one
label="Has to invoice" label="Has to invoice"
@ -116,6 +98,15 @@
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check> </vn-check>
<vn-check
vn-one
label="Vies"
field="$ctrl.client.isVies"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
</vn-horizontal>
<vn-horizontal pad-small-v>
<vn-check <vn-check
vn-one vn-one
label="Invoice by mail" label="Invoice by mail"
@ -125,12 +116,27 @@
</vn-check> </vn-check>
<vn-check <vn-check
vn-one vn-one
label="Vies" label="Invoice by address"
field="$ctrl.client.isVies" field="$ctrl.client.hasToInvoiceByAddress"
vn-acl="administrative, salesAssistant, salesPerson" vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}"> acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check> </vn-check>
</vn-horizontal> </vn-horizontal>
<vn-horizontal pad-small-v>
<vn-check
vn-one
label="Is equalizated"
field="$ctrl.client.isEqualizated"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-one
label="Verified data"
field="$ctrl.client.isTaxDataChecked"
vn-acl="administrative, salesAssistant, salesAssistant">
</vn-check>
</vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>
<vn-submit <vn-submit

View File

@ -8,7 +8,7 @@
<form name="form" ng-submit="watcher.submit()"> <form name="form" ng-submit="watcher.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Web access</vn-title> <vn-title>Web access</vn-title>
<vn-horizontal> <vn-horizontal pad-small-v>
<vn-check <vn-check
vn-one vn-one
label="Enable web access" label="Enable web access"
@ -20,7 +20,6 @@
<vn-textfield <vn-textfield
vn-focus vn-focus
vn-one vn-one
margin-medium-top
label="User" label="User"
field="$ctrl.account.name"> field="$ctrl.account.name">
</vn-textfield> </vn-textfield>

View File

@ -62,6 +62,15 @@ export default class Autocomplete extends Input {
this.refreshDisplayed(); this.refreshDisplayed();
} }
set data(value) {
this._data = value;
this.refreshSelection();
}
get data() {
return this._data;
}
selectionIsValid(selection) { selectionIsValid(selection) {
return selection return selection
&& selection[this.valueField] == this._field && selection[this.valueField] == this._field
@ -212,7 +221,7 @@ export default class Autocomplete extends Input {
showDropDown(search) { showDropDown(search) {
Object.assign(this.$.dropDown.$.model, { Object.assign(this.$.dropDown.$.model, {
url: this.url, url: this.url,
staticData: this.data staticData: this._data
}); });
asignProps(this, this.$.dropDown, [ asignProps(this, this.$.dropDown, [
@ -220,9 +229,9 @@ export default class Autocomplete extends Input {
'showField', 'showField',
'where', 'where',
'order', 'order',
'limit',
'showFilter', 'showFilter',
'multiple', 'multiple',
'limit',
'$transclude' '$transclude'
]); ]);

View File

@ -66,6 +66,7 @@ describe('Component vnAutocomplete', () => {
}; };
let json = encodeURIComponent(JSON.stringify(filter)); let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.whenGET(`localhost?filter=${json}`).respond({});
$httpBackend.expectGET(`localhost?filter=${json}`); $httpBackend.expectGET(`localhost?filter=${json}`);
controller.field = data.id; controller.field = data.id;
$httpBackend.flush(); $httpBackend.flush();

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
vn-autocomplete > div > .mdl-textfield { vn-autocomplete > div > .mdl-textfield {
position: relative; position: relative;
@ -44,13 +44,12 @@ ul.vn-autocomplete {
max-height: 300px; max-height: 300px;
li { li {
@extend %clickable;
display: block; display: block;
padding: .8em; padding: .8em;
margin: 0; margin: 0;
cursor: pointer;
&.active, &.active {
&:hover {
background-color: $hover; background-color: $hover;
} }
&.load-more { &.load-more {

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
.vn-dialog { .vn-dialog {
display: none; display: none;
@ -10,7 +10,7 @@
top: 0; top: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: rgba(1, 1, 1, .6); background-color: rgba(0, 0, 0, .6);
opacity: 0; opacity: 0;
transition: opacity 300ms ease-in-out; transition: opacity 300ms ease-in-out;
@ -19,7 +19,7 @@
} }
& > div { & > div {
position: relative; 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; background-color: white;
border-radius: .2em; border-radius: .2em;
overflow: auto; overflow: auto;
@ -34,16 +34,11 @@
input[type="button"], input[type="button"],
input[type="submit"], input[type="submit"],
input[type="reset"] { input[type="reset"] {
@extend %clickable;
text-transform: uppercase; text-transform: uppercase;
background-color: transparent; background-color: transparent;
border: none; border: none;
cursor: pointer;
transition: background-color 250ms;
border-radius: .1em; border-radius: .1em;
&:hover {
background-color: rgba(1,1,1,.1);
}
} }
& > button.close { & > button.close {
position: absolute; position: absolute;

View File

@ -1,7 +1,7 @@
<vn-model <vn-rest-model
vn-id="model" vn-id="model"
on-data-change="$ctrl.onModelDataChange()"> on-data-change="$ctrl.onModelDataChange()">
</vn-model> </vn-rest-model>
<vn-popover <vn-popover
vn-id="popover" vn-id="popover"
on-open="$ctrl.onOpen()" on-open="$ctrl.onOpen()"

View File

@ -1,7 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
import Component from '../../lib/component'; import Component from '../../lib/component';
import './style.scss'; import './style.scss';
import './model';
export default class DropDown extends Component { export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $http) { constructor($element, $scope, $transclude, $timeout, $http) {
@ -366,14 +365,18 @@ ngModule.component('vnDropDown', {
template: require('./drop-down.html'), template: require('./drop-down.html'),
controller: DropDown, controller: DropDown,
bindings: { bindings: {
showField: '@?',
valueField: '@?',
where: '@?',
order: '@?',
limit: '<?',
showFilter: '<?',
multiple: '<?',
field: '=?', field: '=?',
data: '<?', data: '<?',
selection: '=?', selection: '=?',
search: '<?', search: '<?',
limit: '<?',
showFilter: '<?',
parent: '<?', parent: '<?',
multiple: '<?',
onSelect: '&?' onSelect: '&?'
}, },
transclude: { transclude: {

View File

@ -32,7 +32,7 @@ describe('Component vnDropDown', () => {
$scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions}); $scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions});
$scope.popover.$postLink(); $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 = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
controller.$postLink(); controller.$postLink();
controller.parent = angular.element('<vn-parent></vn-parent>')[0]; controller.parent = angular.element('<vn-parent></vn-parent>')[0];

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
vn-drop-down { vn-drop-down {
.dropdown { .dropdown {
@ -47,19 +47,15 @@ vn-drop-down {
list-style-type: none; list-style-type: none;
} }
li, .status { li, .status {
@extend %clickable;
padding: .6em; padding: .6em;
cursor: pointer;
white-space: nowrap; white-space: nowrap;
transition: background-color 250ms ease-out;
display: flex; display: flex;
& > input[type=checkbox] { & > input[type=checkbox] {
margin: 0; margin: 0;
margin-right: .6em; margin-right: .6em;
} }
&:hover {
background-color: rgba(0, 0, 0, .1);
}
&.active { &.active {
background-color: #3D3A3B; background-color: #3D3A3B;
color: white; color: white;

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
.vn-grid { .vn-grid {
border-collapse: collapse; border-collapse: collapse;
@ -26,13 +26,8 @@
transition: background-color 200ms ease-in-out; transition: background-color 200ms ease-in-out;
&.clickable { &.clickable {
cursor: pointer; @extend %clickable;
&:hover {
background-color: $hover;
}
} }
&.success { &.success {
background-color: rgba(163, 209, 49, 0.3); background-color: rgba(163, 209, 49, 0.3);

View File

@ -6,6 +6,7 @@ vn-icon-button {
color: rgba($main-01, 0.7); color: rgba($main-01, 0.7);
transition: color 200ms ease-in-out; transition: color 200ms ease-in-out;
cursor: pointer; cursor: pointer;
&.button { &.button {
background-color: $main-01; background-color: $main-01;
color: white; color: white;

View File

@ -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('<div></div>');
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();
});
});
});
});

View File

@ -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 './watcher/watcher';
import './textfield/textfield';
import './paging/paging'; import './paging/paging';
import './icon/icon'; import './icon/icon';
import './dialog/dialog'; import './dialog/dialog';

View File

@ -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: '<?',
data: '=?',
fields: '<?',
link: '<?'
}
});

View File

@ -0,0 +1,103 @@
import ngModule from '../../module';
import ModelProxy from '../model-proxy/model-proxy';
export default class CrudModel extends ModelProxy {
constructor($http, $q) {
super();
this.$http = $http;
this.$q = $q;
this.primaryKey = 'id';
this.autoLoad = true;
}
$onInit() {
if (this.autoLoad)
this.refresh();
}
refresh() {
if (!this.url) return;
let filter = this.filter;
if (!filter) {
let where = Object.assign({}, this.link, this.where);
filter = {
fields: this.fields,
where: where,
include: this.include,
order: this.order,
limit: this.limit
};
}
let urlFilter = encodeURIComponent(JSON.stringify(filter));
return this.$http.get(`${this.url}?filter=${urlFilter}`).then(res => {
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: '<?',
data: '=?',
fields: '<?',
link: '<?',
url: '@?',
saveUrl: '@?',
where: '<?',
include: '<?',
order: '@?',
limit: '<?',
filter: '<?',
primaryKey: '@?',
autoLoad: '<?'
}
});

View File

@ -1,6 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
export default class Model { export default class RestModel {
constructor($http, $q, $filter) { constructor($http, $q, $filter) {
this.$http = $http; this.$http = $http;
this.$q = $q; this.$q = $q;
@ -104,10 +104,10 @@ export default class Model {
this.onDataChange(); this.onDataChange();
} }
} }
Model.$inject = ['$http', '$q', '$filter']; RestModel.$inject = ['$http', '$q', '$filter'];
ngModule.component('vnModel', { ngModule.component('vnRestModel', {
controller: Model, controller: RestModel,
bindings: { bindings: {
url: '@?', url: '@?',
staticData: '<?', staticData: '<?',

View File

@ -1,6 +1,6 @@
import './model.js'; import './rest-model.js';
describe('Component vnModel', () => { describe('Component vnRestModel', () => {
let $componentController; let $componentController;
let $httpBackend; let $httpBackend;
let controller; let controller;
@ -11,7 +11,7 @@ describe('Component vnModel', () => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
controller = $componentController('vnModel', {$httpBackend}); controller = $componentController('vnRestModel', {$httpBackend});
})); }));
describe('set url', () => { describe('set url', () => {

View File

@ -26,7 +26,7 @@ export default class Controller extends Component {
this.hide(); this.hide();
this.onTransitionEnd(); this.onTransitionEnd();
} }
this.clearTimeouts(); this.clearTimeouts();
this.shown = true; this.shown = true;
this.textNode.textContent = data.message; this.textNode.textContent = data.message;

View File

@ -41,9 +41,8 @@ vn-snackbar > div {
background-color: transparent; background-color: transparent;
font-weight: bold; font-weight: bold;
color: $main-01; color: $main-01;
padding: 1em; padding: .5em;
margin: -1em; margin: -.5em;
padding-left: 1.5em; margin-left: .5em;
margin-left: 0;
} }
} }

View File

@ -26,7 +26,8 @@ export default class Tooltip extends Component {
this.parent = parent; this.parent = parent;
this.$element.addClass('show'); this.$element.addClass('show');
this.relocate(); 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() { hide() {
this.$element.removeClass('show'); this.$element.removeClass('show');
this.cancelTimeout();
}
cancelTimeout() {
if (this.relocateTimeout) { if (this.relocateTimeout) {
this.$timeout.cancel(this.relocateTimeout); this.$timeout.cancel(this.relocateTimeout);
this.relocateTimeout = null; this.relocateTimeout = null;
@ -102,6 +106,8 @@ export default class Tooltip extends Component {
} }
calcCoords(); calcCoords();
// Overflow
let axisOverflow = let axisOverflow =
axis == 'x' && (left < min || left > maxLeft) || axis == 'x' && (left < min || left > maxLeft) ||
axis == 'y' && (top < min || top > maxTop); axis == 'y' && (top < min || top > maxTop);
@ -124,8 +130,6 @@ export default class Tooltip extends Component {
calcCoords(); calcCoords();
} }
// Overflow
function range(coord, min, max) { function range(coord, min, max) {
return Math.min(Math.max(coord, min), max); return Math.min(Math.max(coord, min), max);
} }
@ -186,7 +190,7 @@ export default class Tooltip extends Component {
this.arrow = arrow; this.arrow = arrow;
} }
$destroy() { $onDestroy() {
this.hide(); this.hide();
} }
} }

View File

@ -4,6 +4,7 @@ import getModifiedData from '../../lib/modified';
import copyObject from '../../lib/copy'; import copyObject from '../../lib/copy';
import isEqual from '../../lib/equals'; import isEqual from '../../lib/equals';
import isFullEmpty from '../../lib/full-empty'; import isFullEmpty from '../../lib/full-empty';
import UserError from '../../lib/user-error';
/** /**
* Component that checks for changes on a specific model property and * Component that checks for changes on a specific model property and
@ -12,14 +13,15 @@ import isFullEmpty from '../../lib/full-empty';
* properties are provided. * properties are provided.
*/ */
export default class Watcher extends Component { 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); super($element);
this.$scope = $scope; this.$ = $scope;
this.$state = $state; this.$state = $state;
this.$http = $http; this.$http = $http;
this.$translate = $translate; this._ = $translate;
this.$attrs = $attrs; this.$attrs = $attrs;
this.vnApp = vnApp; this.vnApp = vnApp;
this.$q = $q;
this.state = null; this.state = null;
this.deregisterCallback = $transitions.onStart({}, this.deregisterCallback = $transitions.onStart({},
@ -28,34 +30,38 @@ export default class Watcher extends Component {
} }
$onInit() { $onInit() {
if (this.get && this.url) { if (this.get && this.url)
this.fetchData(); this.fetchData();
} else if (this.get && !this.url) { else if (this.get && !this.url)
throw new Error('Error: Parameter url ommitted'); throw new Error('URL parameter ommitted');
}
} }
$onChanges(changes) { $onChanges(changes) {
if (this.data) { if (this.data)
this.updateOriginalData(); this.updateOriginalData();
}
} }
$onDestroy() { $onDestroy() {
this.deregisterCallback(); 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() { fetchData() {
let id = this.data[this.idField]; let id = this.data[this.idField];
// return new Promise((resolve, reject) => { return this.$http.get(`${this.url}/${id}`).then(
this.$http.get(`${this.url}/${id}`).then(
json => { json => {
this.data = copyObject(json.data); this.data = copyObject(json.data);
this.updateOriginalData(); this.updateOriginalData();
} }
// json => reject(json)
); );
// });
} }
/** /**
@ -64,9 +70,10 @@ export default class Watcher extends Component {
* @return {Promise} The request promise * @return {Promise} The request promise
*/ */
submitBack() { submitBack() {
return this.submit().then( return this.submit().then(res => {
() => this.window.history.back() this.window.history.back();
); return res;
});
} }
/** /**
@ -77,9 +84,10 @@ export default class Watcher extends Component {
* @return {Promise} The request promise * @return {Promise} The request promise
*/ */
submitGo(state, params) { submitGo(state, params) {
return this.submit().then( return this.submit().then(res => {
() => this.$state.go(state, params || {}) this.$state.go(state, params || {});
); return res;
});
} }
/** /**
@ -88,89 +96,107 @@ export default class Watcher extends Component {
* @return {Promise} The http request promise * @return {Promise} The http request promise
*/ */
submit() { submit() {
if (this.form) { try {
this.check();
} catch (err) {
return this.$q.reject(err);
}
return this.realSubmit().then(res => {
this.notifySaved();
return res;
});
}
/**
* Submits the data without checking data validity or changes.
*
* @return {Promise} The http request promise
*/
realSubmit() {
if (this.form)
this.form.$setSubmitted(); this.form.$setSubmitted();
if (!this.form.$valid)
return new Promise(
(resolve, reject) => this.invalidForm(reject)
);
}
if (!this.dataChanged()) { if (!this.dataChanged()) {
return new Promise( this.updateOriginalData();
(resolve, reject) => this.noChanges(reject) return this.$q.resolve();
);
} }
let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post'); let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post');
let changedData = isPost let changedData = isPost
? this.data ? this.data
: getModifiedData(this.data, this.orgData); : 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.accept().then(
json => this.writeData({data: json}, resolve),
json => reject(json)
);
});
}
// XXX: Alternative when mgCrud is not used
let id = this.idField ? this.orgData[this.idField] : null; let id = this.idField ? this.orgData[this.idField] : null;
if (id) { // If watcher is associated to mgCrud
return new Promise((resolve, reject) => {
this.$http.patch(`${this.url}/${id}`, changedData).then( if (this.save && this.save.accept) {
json => this.writeData(json, resolve), if (id)
json => reject(json) changedData[this.idField] = id;
this.save.model = changedData;
return this.$q((resolve, reject) => {
this.save.accept().then(
json => this.writeData({data: json}, resolve),
reject
); );
}); });
} }
return new Promise((resolve, reject) => { // When mgCrud is not used
if (id) {
return this.$q((resolve, reject) => {
this.$http.patch(`${this.url}/${id}`, changedData).then(
json => this.writeData(json, resolve),
reject
);
});
}
return this.$q((resolve, reject) => {
this.$http.post(this.url, changedData).then( this.$http.post(this.url, changedData).then(
json => this.writeData(json, resolve), json => this.writeData(json, resolve),
json => reject(json) reject
); );
}); });
} }
/**
* 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'));
}
/**
* Notifies the user that the data has been saved.
*/
notifySaved() {
this.vnApp.showMessage(this._.instant('Data saved!'));
}
writeData(json, resolve) { writeData(json, resolve) {
Object.assign(this.data, json.data); Object.assign(this.data, json.data);
this.updateOriginalData(); this.updateOriginalData();
resolve(json); 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() { updateOriginalData() {
this.orgData = this.copyInNewObject(this.data); this.orgData = this.copyInNewObject(this.data);
if (this.form && this.form.$dirty) this.setPristine();
this.form.$setPristine(); }
setPristine() {
if (this.form) this.form.$setPristine();
}
setDirty() {
if (this.form) this.form.$setDirty();
} }
copyInNewObject(data) { copyInNewObject(data) {
@ -192,22 +218,15 @@ export default class Watcher extends Component {
} }
callback(transition) { callback(transition) {
let dataChanged = this.dataChanged(); if (!this.state && this.dirty) {
if (!this.state && dataChanged) {
this.state = transition.to().name; this.state = transition.to().name;
this.$scope.confirm.show(); this.$.confirm.show();
return false; return false;
} }
return true; 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) { onConfirmResponse(response) {
if (response === 'ACCEPT') { if (response === 'ACCEPT') {
if (this.data) if (this.data)
@ -218,18 +237,17 @@ 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', { ngModule.component('vnWatcher', {
template: require('./watcher.html'), template: require('./watcher.html'),
bindings: { bindings: {
url: '@?', url: '@?',
idField: '@?', idField: '@?',
requiredField: '@?',
data: '<', data: '<',
form: '<', form: '<',
save: '<', save: '<',
get: '=?' get: '<?'
}, },
controller: Watcher controller: Watcher
}); });

View File

@ -3,6 +3,7 @@ import getModifiedData from '../../lib/modified';
describe('Component vnWatcher', () => { describe('Component vnWatcher', () => {
let $componentController; let $componentController;
let $rootScope;
let $scope; let $scope;
let $element; let $element;
let $state; let $state;
@ -12,25 +13,28 @@ describe('Component vnWatcher', () => {
let $translate; let $translate;
let controller; let controller;
let $attrs; let $attrs;
let $q;
beforeEach(() => { beforeEach(() => {
angular.mock.module('client'); 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_; $componentController = _$componentController_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$element = angular.element('<div></div>'); $element = angular.element('<div></div>');
$state = _$state_; $state = _$state_;
vnApp = _vnApp_; vnApp = _vnApp_;
$transitions = _$transitions_; $transitions = _$transitions_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$translate = _$translate_; $translate = _$translate_;
$q = _$q_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$attrs = { $attrs = {
save: "patch" 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()', () => { describe('$onInit()', () => {
@ -48,26 +52,7 @@ describe('Component vnWatcher', () => {
expect(function() { expect(function() {
controller.$onInit(); controller.$onInit();
}).toThrow(new Error('Error: Parameter url ommitted')); }).toThrowError(/parameter/);
});
});
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();
}); });
}); });
@ -115,61 +100,51 @@ describe('Component vnWatcher', () => {
}); });
}); });
describe('submit()', () => { describe('check()', () => {
describe('when controller.form', () => { it(`should throw error if controller.form is invalid`, () => {
it(`should call controller.form.setSubminted if controller.form is defined`, () => { controller.form = {$invalid: true};
controller.form = {$setSubmitted: () => {}};
spyOn(controller.form, '$setSubmitted');
controller.submit();
expect(controller.form.$setSubmitted).toHaveBeenCalledWith(); expect(function() {
}); controller.check();
}).toThrowError();
it(`should call controller.invalidForm if controller.form.$valid is not defined`, () => {
controller.form = {$setSubmitted: () => {}};
spyOn(controller, 'invalidForm');
controller.submit();
expect(controller.invalidForm).toHaveBeenCalledWith(jasmine.any(Function));
});
}); });
describe('when !controller.dataChanged()', () => { it(`should throw error if controller.dirty is true`, () => {
it(`should call controller.noChanges()`, () => { controller.form = {$invalid: true};
spyOn(controller, 'noChanges');
controller.submit();
expect(controller.noChanges).toHaveBeenCalledWith(jasmine.any(Function)); 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: () => {},
$setPristine: () => {}
};
spyOn(controller.form, '$setSubmitted');
controller.realSubmit();
expect(controller.form.$setSubmitted).toHaveBeenCalledWith();
}); });
}); });
describe('when controller.save()', () => { describe('when controller.save()', () => {
it(`should set controller.save.model property`, () => { it(`should set controller.save.model property`, () => {
controller.save = {accept: () => {}}; controller.save = {accept: () => $q.resolve()};
controller.data = {originalInfo: 'original data', info: 'new data'}; controller.data = {originalInfo: 'original data', info: 'new data'};
controller.orgData = {originalInfo: 'original data'}; controller.orgData = {originalInfo: 'original data'};
controller.submit(); controller.realSubmit();
expect(controller.save.model).toEqual({info: 'new data'}); 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', () => { 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 = () => { controller.dataChanged = () => {
return true; return true;
}; };
@ -182,7 +157,7 @@ describe('Component vnWatcher', () => {
spyOn(controller, 'writeData').and.callThrough(); spyOn(controller, 'writeData').and.callThrough();
$httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json); $httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json);
$httpBackend.expectPATCH(`${controller.url}/1`); $httpBackend.expectPATCH(`${controller.url}/1`);
controller.submit() controller.realSubmit()
.then(() => { .then(() => {
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
done(); done();
@ -191,7 +166,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 = () => { controller.dataChanged = () => {
return true; return true;
}; };
@ -202,7 +177,7 @@ describe('Component vnWatcher', () => {
spyOn(controller, 'writeData').and.callThrough(); spyOn(controller, 'writeData').and.callThrough();
$httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json); $httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json);
$httpBackend.expectPOST(`${controller.url}`, controller.data); $httpBackend.expectPOST(`${controller.url}`, controller.data);
controller.submit() controller.realSubmit()
.then(() => { .then(() => {
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function)); expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
done(); done();
@ -224,18 +199,9 @@ 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('callback()', () => {
describe(`when dataChanged() returns true and there's no state in the controller`, () => { 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')}; $scope.confirm = {show: jasmine.createSpy('show')};
controller.dataChanged = () => { controller.dataChanged = () => {
return true; return true;
@ -247,7 +213,7 @@ describe('Component vnWatcher', () => {
let result = controller.callback(transition); let result = controller.callback(transition);
expect(controller.state).toEqual('Batman'); expect(controller.state).toEqual('Batman');
expect(controller.$scope.confirm.show).toHaveBeenCalledWith(); expect(controller.$.confirm.show).toHaveBeenCalledWith();
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
}); });

View File

@ -9,9 +9,10 @@ describe('Directive zoomImage', () => {
angular.mock.module('client'); angular.mock.module('client');
}); });
beforeEach(angular.mock.inject(($compile, $rootScope) => { beforeEach(angular.mock.inject(($compile, $rootScope, $httpBackend) => {
compile = $compile; compile = $compile;
scope = $rootScope.$new(); scope = $rootScope.$new();
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
})); }));
afterEach(() => { afterEach(() => {

View File

@ -7,9 +7,9 @@ import ngModule from '../module';
* @property {Snackbar} snackbar The main object to show messages. * @property {Snackbar} snackbar The main object to show messages.
*/ */
export default class App { export default class App {
constructor($rootScope) { constructor() {
this.loaderStatus = 0; this.loaderStatus = 0;
this.$rootScope = $rootScope; this.loading = false;
} }
showMessage(message) { showMessage(message) {
if (this.snackbar) if (this.snackbar)
@ -22,14 +22,13 @@ export default class App {
pushLoader() { pushLoader() {
this.loaderStatus++; this.loaderStatus++;
if (this.loaderStatus === 1) if (this.loaderStatus === 1)
this.$rootScope.loading = true; this.loading = true;
} }
popLoader() { popLoader() {
this.loaderStatus--; this.loaderStatus--;
if (this.loaderStatus === 0) if (this.loaderStatus === 0)
this.$rootScope.loading = false; this.loading = false;
} }
} }
App.$inject = ['$rootScope'];
ngModule.service('vnApp', App); ngModule.service('vnApp', App);

View File

@ -1,6 +1,6 @@
/** /**
* Transforms a UTC date to JSON date without datetime. * Transforms a UTC date to JSON date without datetime.
* *
* @param {date} date Date to format * @param {date} date Date to format
* @return {String} Formatted date string * @return {String} Formatted date string
*/ */
@ -12,10 +12,10 @@ export function toJsonDate(date) {
let year = date.getFullYear(); let year = date.getFullYear();
if (day < 10) if (day < 10)
day = `0${day}` day = `0${day}`;
if (month < 10) if (month < 10)
month = `0${month}` month = `0${month}`;
return new Date(`${year}-${month}-${day}`); return new Date(`${year}-${month}-${day}`);
} }

View File

@ -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';
}
}

View File

@ -13,3 +13,5 @@ import './equals';
import './modified'; import './modified';
import './key-codes'; import './key-codes';
import './get-watchers'; import './get-watchers';
import './http-error';
import './user-error';

View File

@ -1,7 +1,8 @@
import ngModule from '../module'; import ngModule from '../module';
import HttpError from './http-error';
interceptor.$inject = ['$q', '$window', 'vnApp', '$translate', '$cookies']; interceptor.$inject = ['$q', 'vnApp', '$cookies'];
function interceptor($q, $window, vnApp, $translate, $cookies) { function interceptor($q, vnApp, $cookies) {
return { return {
request: function(config) { request: function(config) {
vnApp.pushLoader(); vnApp.pushLoader();
@ -16,42 +17,14 @@ function interceptor($q, $window, vnApp, $translate, $cookies) {
return $q.reject(rejection); return $q.reject(rejection);
}, },
response: function(response) { response: function(response) {
switch (response.config.method) {
case 'PUT':
case 'POST':
case 'PATCH':
vnApp.showMessage($translate.instant('Data saved!'));
}
vnApp.popLoader(); vnApp.popLoader();
return response; return response;
}, },
responseError: function(rejection) { responseError: function(rejection) {
vnApp.popLoader(); vnApp.popLoader();
let data = rejection.data; let err = new HttpError(rejection.statusText);
let error; Object.assign(err, rejection);
return $q.reject(err);
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);
} }
}; };
} }

View File

@ -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';
}
}

View File

@ -15,8 +15,12 @@ describe('Item', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$state.params.id = '1'; $state.params.id = '1';
controller = $componentController('vnItemBarcode', {$state: $state}); controller = $componentController('vnItemBarcode', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
})); }));
describe('add / remove barcode()', () => { describe('add / remove barcode()', () => {

View File

@ -1,3 +1,7 @@
<vn-watcher
vn-id="watcher"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Item barcode</vn-title> <vn-title>Item barcode</vn-title>

View File

@ -95,6 +95,7 @@ export default class Controller {
return this.$http.post(`/item/api/ItemBarcodes/crudItemBarcodes`, barcodesObj).then(() => { return this.$http.post(`/item/api/ItemBarcodes/crudItemBarcodes`, barcodesObj).then(() => {
this.getBarcodes(); this.getBarcodes();
this._unsetDirtyForm(); this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
}); });
} }
this.vnApp.showMessage(this.$translate.instant('No changes to save')); this.vnApp.showMessage(this.$translate.instant('No changes to save'));

View File

@ -14,6 +14,7 @@ describe('ItemBotanical', () => {
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$state = { $state = {
params: { params: {
id: 123 id: 123

View File

@ -6,7 +6,6 @@
vn-id="watcher" vn-id="watcher"
data="$ctrl.botanical" data="$ctrl.botanical"
id-field="itemFk" id-field="itemFk"
required-field="itemFk"
form="form" form="form"
save="patch"> save="patch">
</vn-watcher> </vn-watcher>

View File

@ -4,8 +4,11 @@ class Controller {
constructor($http, $state) { constructor($http, $state) {
this.$http = $http; this.$http = $http;
this.$state = $state; this.$state = $state;
this.botanical = {
itemFk: this.$state.params.id
};
} }
_getBotanical() { _getBotanical() {
let filter = { let filter = {
where: {itemFk: this.$state.params.id}, where: {itemFk: this.$state.params.id},
@ -13,16 +16,8 @@ class Controller {
}; };
this.$http.get(`/item/api/ItemBotanicals?filter=${JSON.stringify(filter)}`) this.$http.get(`/item/api/ItemBotanicals?filter=${JSON.stringify(filter)}`)
.then(res => { .then(res => {
if (res.data.length) { if (res.data.length)
this.botanical = res.data[0]; this.botanical = res.data[0];
} else {
this.botanical = {
itemFk: this.$state.params.id,
botanical: null,
genusFk: null,
specieFk: null
};
}
}); });
} }

View File

@ -14,6 +14,7 @@ describe('Item', () => {
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$state = { $state = {
params: { params: {
id: 123 id: 123

View File

@ -6,7 +6,7 @@
save="post"> save="post">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium> <form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;"> <div style="max-width: 50em; margin: 0 auto;">
<vn-card pad-large> <vn-card pad-large>
<vn-title>New item</vn-title> <vn-title>New item</vn-title>
<vn-horizontal> <vn-horizontal>
@ -20,7 +20,10 @@
value-field="id" value-field="id"
field="$ctrl.item.typeFk" field="$ctrl.item.typeFk"
where="{or: [{code: {regexp: 'search'}}, {name: {regexp: 'search'}}]}"> where="{or: [{code: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
<tpl-item>{{code}} : {{name}}</tpl-item> <tpl-item style="display: flex;">
<div style="width: 3em; padding-right: 1em;">{{::code}}</div>
<div>{{::name}}</div>
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="/item/api/Intrastats" url="/item/api/Intrastats"
@ -29,7 +32,10 @@
value-field="id" value-field="id"
field="$ctrl.item.intrastatFk" field="$ctrl.item.intrastatFk"
where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}"> where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}">
<tpl-item>{{id}} : {{description}}</tpl-item> <tpl-item style="display: flex;">
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
<div>{{::description}}</div>
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -32,7 +32,10 @@
field="$ctrl.item.intrastatFk" field="$ctrl.item.intrastatFk"
where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}" where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}"
initial-data="$ctrl.item.intrastat"> initial-data="$ctrl.item.intrastat">
<tpl-item>{{id}} : {{description}}</tpl-item> <tpl-item style="display: flex;">
<div style="width: 6em; text-align: right; padding-right: 1em;">{{::id}}</div>
<div>{{::description}}</div>
</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield> <vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield>
</vn-horizontal> </vn-horizontal>
@ -45,13 +48,19 @@
field="$ctrl.item.originFk" field="$ctrl.item.originFk"
initial-data="$ctrl.item.origin"> initial-data="$ctrl.item.origin">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield
vn-one
label="Reference"
field="$ctrl.item.description">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="/item/api/Expences" url="/item/api/Expences"
label="Expence" label="Expence"
field="$ctrl.item.expenceFk" field="$ctrl.item.expenceFk"
initial-data="$ctrl.item.expence"> initial-data="$ctrl.item.expence">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield vn-one label="Reference" field="$ctrl.item.description"></vn-textfield>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>

View File

@ -14,6 +14,11 @@ export default class Controller {
this.oldNiches = {}; this.oldNiches = {};
} }
$onInit() {
this.getNiches();
this.getWarehouses();
}
_setIconAdd() { _setIconAdd() {
if (this.niches.length) { if (this.niches.length) {
this.niches.map(element => { this.niches.map(element => {
@ -130,15 +135,11 @@ export default class Controller {
return this.$http.post(`/item/api/ItemNiches/crudItemNiches`, nichesObj).then(() => { return this.$http.post(`/item/api/ItemNiches/crudItemNiches`, nichesObj).then(() => {
this.getNiches(); this.getNiches();
this._unsetDirtyForm(); this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
}); });
} }
this.vnApp.showMessage(this.$translate.instant('No changes to save')); this.vnApp.showMessage(this.$translate.instant('No changes to save'));
} }
$onInit() {
this.getNiches();
this.getWarehouses();
}
} }
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp'];

View File

@ -15,7 +15,11 @@ describe('Item', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnItemNiche', {$state: $state}); controller = $componentController('vnItemNiche', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
})); }));
describe('add / remove niche', () => { describe('add / remove niche', () => {

View File

@ -6,7 +6,7 @@
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Item tags</vn-title> <vn-title>Item tags</vn-title>
<vn-horizontal ng-repeat="itemTag in $ctrl.instancedItemTags"> <vn-horizontal ng-repeat="itemTag in $ctrl.tags">
<vn-autocomplete <vn-autocomplete
vn-id="tag" vn-id="tag"
vn-one vn-one
@ -15,7 +15,7 @@
data="tags.model" data="tags.model"
show-field="name" show-field="name"
label="Tag" label="Tag"
on-change="$ctrl.checkAutocompleteChanges(itemTag)" on-change="itemTag.value = null"
vn-acl="buyer" vn-acl="buyer"
vn-focus vn-focus
disabled="itemTag.id != null"> disabled="itemTag.id != null">
@ -54,7 +54,7 @@
medium-grey medium-grey
vn-tooltip="Remove tag" vn-tooltip="Remove tag"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.removeItemTag($index)"> ng-click="$ctrl.removeTag($index)">
</vn-icon> </vn-icon>
</vn-horizontal> </vn-horizontal>
<vn-one> <vn-one>
@ -62,7 +62,7 @@
vn-bind="+" vn-bind="+"
vn-tooltip="Add tag" vn-tooltip="Add tag"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.addItemTag()"> ng-click="$ctrl.addTag()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,71 +1,63 @@
import ngModule from '../module'; import ngModule from '../module';
class Controller { class Controller {
constructor($stateParams, $scope, $http, $translate, vnApp) { constructor($stateParams, $scope, $http) {
this.params = $stateParams; this.params = $stateParams;
this.$scope = $scope; this.$ = $scope;
this.$http = $http; this.$http = $http;
this.$translate = $translate; this.tags = [];
this.vnApp = vnApp; this.removedTags = [];
this.itemTagTypes = [];
this.removedItemTags = [];
this.oldItemTags = {};
} }
_setIconAdd() { $onInit() {
if (this.instancedItemTags && this.instancedItemTags.length) { this.getTags();
this.instancedItemTags.map(element => { }
element.showAddIcon = false;
return true; 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() { addTag() {
if (this.$scope.form) { this.tags.push({
this.$scope.form.$setDirty(); itemFk: this.params.id,
} priority: this.getHighestPriority(this.tags)
} });
_unsetDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setPristine();
}
} }
checkAutocompleteChanges(itemTag) { getHighestPriority(tags) {
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) {
let max = 0; let max = 0;
instancedItemTags.forEach(tag => { tags.forEach(tag => {
if (tag.priority > max) if (tag.priority > max)
max = tag.priority; max = tag.priority;
}); });
return max + 1; 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) { getSourceTable(selection) {
if (!selection || selection.isFree === true) if (!selection || selection.isFree === true)
return null; return null;
@ -78,86 +70,38 @@ class Controller {
} }
} }
_equalItemTags(oldTag, newTag) { tagIsEqual(oldTag, newTag) {
return oldTag.tagFk === newTag.tagFk && oldTag.value === newTag.value && oldTag.priority === newTag.priority; 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);
});
} }
submit() { submit() {
let itemTagsDefined = []; this.$.watcher.check();
let repeatedItemTags = false;
let canSubmit; let changes = {
let tagsObj = { delete: this.removedTags,
delete: this.removedItemTags,
create: [], create: [],
update: [] update: []
}; };
this.instancedItemTags.forEach(tag => { this.tags.forEach(tag => {
let isNewTag = !tag.id; if (!tag.id)
changes.create.push(tag);
if (itemTagsDefined.indexOf(tag.tagFk) !== -1) { else if (!this.tagIsEqual(this.orgTags[tag.id], tag)) {
repeatedItemTags = true;
return;
}
itemTagsDefined.push(tag.tagFk);
if (isNewTag) {
tagsObj.create.push(tag);
}
if (!isNewTag && !this._equalItemTags(this.oldItemTags[tag.id], tag)) {
let tagToUpdate = Object.assign({}, tag); let tagToUpdate = Object.assign({}, tag);
delete tagToUpdate.tag; delete tagToUpdate.tag;
delete tagToUpdate.showAddIcon; changes.update.push(tagToUpdate);
tagsObj.update.push(tagToUpdate);
} }
}); });
if (this.$scope.form.$invalid) { this.$http.post(`/item/api/ItemTags/crud`, changes).then(() => {
return this.vnApp.showMessage(this.$translate.instant('Some fields are invalid')); this.getTags();
} this.$.watcher.notifySaved();
});
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();
} }
} }
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; Controller.$inject = ['$stateParams', '$scope', '$http'];
ngModule.component('vnItemTags', { ngModule.component('vnItemTags', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -15,38 +15,43 @@ describe('Item', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnItemTags', {$state: $state}); controller = $componentController('vnItemTags', {$state: $state});
controller.$.form = {
$setPristine: () => {},
$dirty: true
};
controller.$.watcher = {
notifySaved: () => {},
check: () => {}
};
})); }));
describe('add / remove tags', () => { describe('add / remove tags', () => {
it('should add one empty tag into controller tags collection and call _setIconAdd()', () => { it('should add one empty tag into controller tags collection', () => {
controller.instancedItemTags = []; controller.tags = [];
spyOn(controller, '_setIconAdd').and.callThrough(); controller.addTag();
controller.addItemTag();
expect(controller._setIconAdd).toHaveBeenCalledWith(); expect(controller.tags.length).toEqual(1);
expect(controller.instancedItemTags.length).toEqual(1); expect(controller.tags[0].id).toBe(undefined);
expect(controller.instancedItemTags[0].id).toBe(undefined);
expect(controller.instancedItemTags[0].showAddIcon).toBeTruthy();
}); });
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; let index = 2;
controller.instancedItemTags = [ controller.tags = [
{id: 1, typeFk: 1, value: '1111', showAddIcon: false}, {id: 1, typeFk: 1, value: '1111', showAddIcon: false},
{id: 2, typeFk: 2, value: '2222', showAddIcon: false}, {id: 2, typeFk: 2, value: '2222', showAddIcon: false},
{id: 3, typeFk: 3, value: '3333', showAddIcon: true} {id: 3, typeFk: 3, value: '3333', showAddIcon: true}
]; ];
spyOn(controller, '_setIconAdd').and.callThrough(); controller.removeTag(index);
controller.removeItemTag(index); expect(controller.tags.length).toEqual(2);
expect(controller.tags[index]).toBe(undefined);
expect(controller._setIconAdd).toHaveBeenCalledWith(); expect(controller.$.form.$setDirty).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);
}); });
}); });
@ -89,11 +94,11 @@ describe('Item', () => {
}); });
}); });
describe('_equalItemTags()', () => { describe('tagIsEqual()', () => {
it('should return true if two tags are equals independent of control attributes', () => { it('should return true if two tags are equals independent of control attributes', () => {
let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true}; let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true};
let tag2 = {id: 1, typeFk: 1, value: '1111', showAddIcon: false}; 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(); expect(equals).toBeTruthy();
}); });
@ -101,7 +106,7 @@ describe('Item', () => {
it('should return false if two tags aint equal independent of control attributes', () => { it('should return false if two tags aint equal independent of control attributes', () => {
let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true}; let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true};
let tag2 = {id: 1, typeFk: 1, value: '2222', 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(); expect(equals).toBeFalsy();
}); });
@ -109,73 +114,78 @@ describe('Item', () => {
describe('get itemTags', () => { describe('get itemTags', () => {
it('should perform a GET query to receive the item tags', () => { 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'}]; let res = [{id: 1, typeFk: 1, value: '1111'}];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond(res); $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"}}`); $httpBackend.expectGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`);
controller._getItemTags(); controller.getTags();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.$.form.$setPristine).toHaveBeenCalledWith();
}); });
}); });
describe('submit()', () => { describe('submit()', () => {
it("should return an error message 'The tag must be unique' when the tag value isnt unique", () => { // TODO: Server validation should be implemented
controller.$scope.form = []; 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(); spyOn(controller.vnApp, 'showMessage').and.callThrough();
controller.instancedItemTags = [ controller.tags = [
{typeFk: 1, value: 123454, itemFk: 1, id: 1}, {typeFk: 1, value: 123454, itemFk: 1, id: 1},
{typeFk: 1, value: 123454, itemFk: 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(); controller.submit();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('The tag must be unique'); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('The tag must be unique');
}); });
it("should perfom a query to delete tags", () => { it("should perfom a query to delete tags", () => {
controller.$scope.form = {$setPristine: () => {}}; controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}};
controller.oldItemTags = {1: {id: 1, typeFk: 1, value: '1111'}}; controller.tags = [];
controller.instancedItemTags = []; controller.removedTags = [1];
controller.removedItemTags = [1];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); $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(); controller.submit();
$httpBackend.flush(); $httpBackend.flush();
}); });
it("should perfom a query to update tags", () => { it("should perfom a query to update tags", () => {
controller.$scope.form = {$setPristine: () => {}}; controller.tags = [{id: 1, typeFk: 1, value: '2222'}];
controller.instancedItemTags = [{id: 1, typeFk: 1, value: '2222'}]; controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}};
controller.oldItemTags = {1: {id: 1, typeFk: 1, value: '1111'}};
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); $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(); controller.submit();
$httpBackend.flush(); $httpBackend.flush();
}); });
it("should perfom a query to create new tag", () => { it("should perfom a query to create new tag", () => {
controller.$scope.form = {$setPristine: () => {}}; controller.tags = [{typeFk: 1, value: 1111, itemFk: 1}];
controller.instancedItemTags = [{typeFk: 1, value: 1111, itemFk: 1}];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]); $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(); controller.submit();
$httpBackend.flush(); $httpBackend.flush();
}); });
it("should return a message 'No changes to save' when there are no changes to apply", () => { it("should throw 'No changes to save' error when there are no changes to apply", () => {
controller.$scope.form = {$setPristine: () => {}}; controller.$.watcher = {
spyOn(controller.vnApp, 'showMessage').and.callThrough(); check: () => {
controller.oldItemTags = [ throw new Error('No changes to save');
{typeFk: 1, value: 1, itemFk: 1, id: 1}, }
{typeFk: 2, value: 2, itemFk: 1, id: 2} };
];
controller.instancedItemTags = [];
controller.submit();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No changes to save'); controller.orgTags = [];
controller.tags = [];
expect(function() {
controller.submit();
}).toThrowError();
}); });
}); });
}); });

View File

@ -1,3 +1,8 @@
<vn-crud-model
url="/item/api/TaxClasses"
fields="['id', 'description', 'code']"
data="classes">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Item tax</vn-title> <vn-title>Item tax</vn-title>
@ -10,8 +15,7 @@
<vn-autocomplete vn-one <vn-autocomplete vn-one
label="Class" label="Class"
field="tax.taxClassFk" field="tax.taxClassFk"
initial-data="tax.taxClass" data="classes"
data="$ctrl.classes"
value-field="id" value-field="id"
show-field="description"> show-field="description">
</vn-autocomplete> </vn-autocomplete>

View File

@ -1,18 +1,17 @@
import ngModule from '../module'; import ngModule from '../module';
export default class Controller { export default class Controller {
constructor($stateParams, $http) { constructor($stateParams, $http, $translate, vnApp) {
this.$http = $http; this.$http = $http;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this._ = $translate;
this.vnApp = vnApp;
let filter = { let filter = {
fields: ['id', 'countryFk', 'taxClassFk'], fields: ['id', 'countryFk', 'taxClassFk'],
include: [{ include: [{
relation: 'country', relation: 'country',
scope: {fields: ['country']} scope: {fields: ['country']}
}, {
relation: 'taxClass',
scope: {fields: ['id', 'description']}
}] }]
}; };
@ -21,23 +20,21 @@ export default class Controller {
$http.get(url).then(json => { $http.get(url).then(json => {
this.taxes = json.data; this.taxes = json.data;
}); });
$http.get(`/item/api/TaxClasses`).then(json => {
this.classes = json.data;
});
} }
submit() { submit() {
let data = []; let data = [];
for (let tax of this.taxes) for (let tax of this.taxes)
data.push({id: tax.id, taxClassFk: tax.taxClassFk}); data.push({id: tax.id, taxClassFk: tax.taxClassFk});
let url = `/item/api/Items/${this.$stateParams.id}/updateTaxes`; 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', { ngModule.component('vnItemTax', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -3,7 +3,7 @@
<a ui-sref="home" title="{{'Home' | translate}}"> <a ui-sref="home" title="{{'Home' | translate}}">
<img class="logo" src="./logo.svg" alt="Logo"></img> <img class="logo" src="./logo.svg" alt="Logo"></img>
</a> </a>
<vn-spinner enable="$root.loading"></vn-spinner> <vn-spinner enable="$ctrl.vnApp.loading"></vn-spinner>
<vn-main-menu></vn-main-menu> <vn-main-menu></vn-main-menu>
</vn-topbar> </vn-topbar>
<vn-vertical vn-one ui-view scrollable class="main-view"> <vn-vertical vn-one ui-view scrollable class="main-view">

View File

@ -14,7 +14,7 @@ vn-app {
} }
vn-spinner { vn-spinner {
float: left; float: left;
padding: .4em; padding: 1em .4em;
} }
} }
.main-view { .main-view {

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
vn-home { vn-home {
padding: 2em; padding: 2em;
@ -17,6 +17,7 @@ vn-home {
flex-wrap: wrap; flex-wrap: wrap;
& > a { & > a {
@extend %clickable-light;
overflow:hidden; overflow:hidden;
border-radius: 6px; border-radius: 6px;
background-color: $main-01; background-color: $main-01;
@ -29,11 +30,6 @@ vn-home {
padding: 1em; padding: 1em;
justify-content: center; justify-content: center;
transition: opacity 250ms ease-out;
&:hover {
background-color: $hover;
}
& > vn-icon { & > vn-icon {
font-size: 4em; font-size: 4em;
} }

View File

@ -1,8 +1,12 @@
@import "colors"; @import "effects";
vn-menu-item { vn-menu-item {
& > li.active { & > li {
background-color: $main-header; @extend %clickable;
color: white;
&.active {
background-color: $main-header;
color: white;
}
} }
} }

View File

@ -1,4 +1,4 @@
@import "colors"; @import "effects";
vn-main-menu { vn-main-menu {
#user { #user {
@ -25,9 +25,9 @@ vn-main-menu {
color: white; color: white;
li { li {
@extend %clickable-light;
background-color: $main-01; background-color: $main-01;
margin-bottom: .6em; margin-bottom: .6em;
cursor: pointer;
padding: .8em; padding: .8em;
border-radius: .1em; border-radius: .1em;
min-width: 8em; min-width: 8em;
@ -36,9 +36,6 @@ vn-main-menu {
padding-right: .3em; padding-right: .3em;
vertical-align: middle; vertical-align: middle;
} }
&:hover {
background-color: $hover;
}
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@ -6,29 +6,6 @@ export const appName = 'salix';
const ngModule = ng.module('salix', ['vnCore']); const ngModule = ng.module('salix', ['vnCore']);
export default ngModule; 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']; run.$inject = ['$window', '$rootScope', 'vnApp', '$state'];
export function run($window, $rootScope, vnApp, $state) { export function run($window, $rootScope, vnApp, $state) {
$window.validations = {}; $window.validations = {};
@ -37,8 +14,56 @@ export function run($window, $rootScope, vnApp, $state) {
$rootScope.$on('$viewContentLoaded', () => {}); $rootScope.$on('$viewContentLoaded', () => {});
window.myAppErrorLog = []; window.myAppErrorLog = [];
$state.defaultErrorHandler(function(error) { $state.defaultErrorHandler(function(error) {
if (error.type === HOOK_ABORTED_TRANSITION) if (error.type === 3) // ABORTED_TRANSITION
window.myAppErrorLog.push(error); window.myAppErrorLog.push(error);
}); });
} }
ngModule.run(run); 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.name == 'UserError') {
message = exception.message;
} else {
message = 'Ups! Something went wrong';
console.error(exception);
}
vnApp.showError(message);
};
}
ngModule.factory('$exceptionHandler', $exceptionHandler);

View File

@ -1,7 +1,8 @@
$main-font-color :#222222; $main-font-color :#222222;
$secondary-font-color: #9b9b9b; $secondary-font-color: #9b9b9b;
$main-header: #3d3d3d; $main-header: #3d3d3d;
$hover: #c4c4c4; $hover: rgba(0, 0, 0, 0.1);
$hover-opacity: .7;
$main-bg: #e5e5e5; $main-bg: #e5e5e5;
$main-01: #f7931e; $main-01: #f7931e;
$main-01-05: rgba($main-01, 0.5); $main-01-05: rgba($main-01, 0.5);

View File

@ -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;
}
}

View File

@ -1,7 +1,6 @@
@import "colors"; @import "colors";
@import "font-family"; @import "font-family";
body { body {
color: $main-font-color; color: $main-font-color;
font-family: vn-font; font-family: vn-font;

View File

@ -9,3 +9,4 @@ import './font-style.scss';
import './misc.scss'; import './misc.scss';
import './summary.scss'; import './summary.scss';
import './colors.scss'; import './colors.scss';
import './effects.scss';

View File

@ -1,8 +1,8 @@
@import "padding"; @import "./padding";
@import "margin"; @import "./margin";
@import "colors"; @import "./colors";
@import "border"; @import "./border";
@import "./effects";
a:focus, a:focus,
input:focus, input:focus,
@ -116,17 +116,8 @@ a {
} }
} }
.vn-clickable {
cursor: pointer;
transition: background-color 250ms ease-out;
&:hover {
background-color: $hover;
}
}
button { button {
@extend .vn-clickable; @extend %clickable;
} }
vn-button-bar { vn-button-bar {
@ -168,14 +159,12 @@ vn-main-block {
font-size: 2.5em; font-size: 2.5em;
} }
& > a { & > a {
@extend .vn-clickable; @extend %clickable;
display: flex; display: flex;
align-items: center; align-items: center;
padding: .5em; padding: .5em;
color: white; color: white;
text-decoration: none; text-decoration: none;
transition: background-color 250ms ease-out;
& > vn-icon { & > vn-icon {
font-size: 1.8em; font-size: 1.8em;
@ -205,7 +194,7 @@ vn-main-block {
.vn-list-item { .vn-list-item {
@extend .pad-medium; @extend .pad-medium;
@extend .border-solid-bottom; @extend .border-solid-bottom;
@extend .vn-clickable; @extend %clickable;
display: block; display: block;
text-decoration: none; text-decoration: none;
@ -220,21 +209,28 @@ vn-main-block {
margin-left: .5em; margin-left: .5em;
transition: opacity 250ms ease-out; transition: opacity 250ms ease-out;
font-size: 2em; font-size: 2em;
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
} }
} }
} }
/** START - FORM ELEMENTS DISABLED **/ /** 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; border: none !important;
color: inherit !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; color: $main-01 !important;
} }
/** END - FORM ELEMENTS DISABLED **/ /** END - FORM ELEMENTS DISABLED **/
.ellipsize { .ellipsize {

View File

@ -15,6 +15,7 @@ describe('ticket', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnTicketDataStepOne', {$state: $state}); controller = $componentController('vnTicketDataStepOne', {$state: $state});
})); }));
@ -37,6 +38,9 @@ describe('ticket', () => {
describe('onStepChange()', () => { describe('onStepChange()', () => {
it('should call onStepChange method and return a NO_AGENCY_AVAILABLE signal error', async () => { 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 = { controller.ticket = {
id: 1, id: 1,
clientFk: 1, clientFk: 1,
@ -45,22 +49,16 @@ describe('ticket', () => {
companyFk: 442, companyFk: 442,
warehouseFk: 1, warehouseFk: 1,
shipped: new Date(), shipped: new Date(),
landed: new Date() landed: landed
}; };
let data = { let response = {error: new Error('NO_AGENCY_AVAILABLE')};
addressFk: 121,
agencyModeFk: 1,
warehouseFk: 1,
landed: new Date()
};
let response = {data: {error: new Error('NO_AGENCY_AVAILABLE')}};
$httpBackend.whenPOST(`/ticket/api/sales/1/priceDifference`, data).respond(400, response); $httpBackend.whenPOST(`/ticket/api/sales/1/priceDifference`).respond(400, response);
$httpBackend.expectPOST(`/ticket/api/sales/1/priceDifference`, data); $httpBackend.expectPOST(`/ticket/api/sales/1/priceDifference`);
await controller.onStepChange(); controller.onStepChange();
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });
}); });
}); });

View File

@ -119,6 +119,7 @@ class Controller {
return this.$http.post(`/ticket/api/TicketObservations/crudTicketObservation`, observationsObj).then(() => { return this.$http.post(`/ticket/api/TicketObservations/crudTicketObservation`, observationsObj).then(() => {
this.getObservations(); this.getObservations();
this._unsetDirtyForm(); this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
}); });
} }
this.vnApp.showMessage(this.$translate.instant('No changes to save')); this.vnApp.showMessage(this.$translate.instant('No changes to save'));

View File

@ -15,7 +15,11 @@ describe('ticket', () => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $state = _$state_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnTicketObservation', {$state: $state}); controller = $componentController('vnTicketObservation', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
})); }));
describe('add / remove observation', () => { describe('add / remove observation', () => {

View File

@ -14,6 +14,7 @@ describe('Ticket', () => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_, $rootScope) => { beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_, $rootScope) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = { $scope = {
index: { index: {
accept: function() {} accept: function() {}
@ -35,11 +36,6 @@ describe('Ticket', () => {
describe('submit()', () => { describe('submit()', () => {
it('should perform a post', () => { it('should perform a post', () => {
spyOn(controller.$.index, 'accept'); spyOn(controller.$.index, 'accept');
let packagesObj = {
delete: controller.removedPackages,
create: [],
update: []
};
let query = '/ticket/api/TicketPackagings/crudTicketPackaging'; let query = '/ticket/api/TicketPackagings/crudTicketPackaging';
controller.removedPackages = []; controller.removedPackages = [];
controller.oldPackages = []; controller.oldPackages = [];

View File

@ -15,6 +15,7 @@ describe('Ticket', () => {
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => { $scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => {
return { return {

View File

@ -10,12 +10,12 @@
<vn-title>New state</vn-title> <vn-title>New state</vn-title>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete
vn-one vn-one
field="$ctrl.ticket.stateFk" field="$ctrl.ticket.stateFk"
url="/ticket/api/States" url="/ticket/api/States"
label="State" label="State"
vn-focus> vn-focus>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-button-bar> <vn-button-bar>

View File

@ -7,8 +7,7 @@ class Controller {
this.vnApp = vnApp; this.vnApp = vnApp;
this.$translate = $translate; this.$translate = $translate;
this.ticket = { this.ticket = {
ticketFk: $state.params.id, ticketFk: $state.params.id
text: null
}; };
} }
onSubmit() { onSubmit() {

View File

@ -15,6 +15,7 @@ describe('ticket', () => {
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => { beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_, $rootScope) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => { $scope.index = {model: {instances: [{id: 1}, {id: 2}]}, accept: () => {
return { return {

View File

@ -4,16 +4,6 @@
// delete me, this comment is to add a commit // delete me, this comment is to add a commit
export default { export default {
vnTextfield: 'vn-textfield > div > input', vnTextfield: 'vn-textfield > div > input',
vnTextarea: 'vn-textarea',
vnSubmit: 'vn-submit > input', vnSubmit: 'vn-submit > input',
vnTopbar: 'vn-topbar > header', vnFloatButton: 'vn-float-button > button'
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'
}; };

View File

@ -17,7 +17,7 @@ export default {
}, },
clientsIndex: { clientsIndex: {
searchClientInput: `${components.vnTextfield}`, searchClientInput: `${components.vnTextfield}`,
searchButton: `${components.vnSearchBar} > vn-icon-button`, searchButton: `vn-searchbar vn-icon-button[icon="search"]`,
searchResult: `vn-item-client a`, searchResult: `vn-item-client a`,
createClientButton: `${components.vnFloatButton}` createClientButton: `${components.vnFloatButton}`
}, },
@ -33,122 +33,122 @@ export default {
cancelButton: `button[href="#!/client/index"]` cancelButton: `button[href="#!/client/index"]`
}, },
clientBasicData: { clientBasicData: {
basicDataButton: `${components.vnMenuItem}[ui-sref="client.card.basicData"]`, basicDataButton: `vn-menu-item a[ui-sref="client.card.basicData"]`,
nameInput: `${components.vnTextfield}[name="name"]`, nameInput: `${components.vnTextfield}[name="name"]`,
contactInput: `${components.vnTextfield}[name="contact"]`, contactInput: `${components.vnTextfield}[name="contact"]`,
phoneInput: `${components.vnTextfield}[name="phone"]`, phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`,
faxInput: `${components.vnTextfield}[name="fax"]`, faxInput: `${components.vnTextfield}[name="fax"]`,
emailInput: `${components.vnTextfield}[name="email"]`, emailInput: `${components.vnTextfield}[name="email"]`,
salesPersonInput: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] input`, salesPersonInput: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] input`,
salesPersonOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`, salesPersonOptionOne: `vn-autocomplete[field="$ctrl.client.salesPersonFk"] vn-drop-down ul > li:nth-child(1)`,
channelInput: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] input`, channelInput: `vn-autocomplete[field="$ctrl.client.contactChannelFk"] input`,
channelMetropolisOption: `${components.vnAutocomplete}[field="$ctrl.client.contactChannelFk"] vn-drop-down ul > li:nth-child(3)`, channelMetropolisOption: `vn-autocomplete[field="$ctrl.client.contactChannelFk"] vn-drop-down ul > li:nth-child(3)`,
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientFiscalData: { clientFiscalData: {
fiscalDataButton: `${components.vnMenuItem}[ui-sref="client.card.fiscalData"]`, fiscalDataButton: `vn-menu-item a[ui-sref="client.card.fiscalData"]`,
socialNameInput: `${components.vnTextfield}[name="socialName"]`, socialNameInput: `${components.vnTextfield}[name="socialName"]`,
fiscalIdInput: `${components.vnTextfield}[name="fi"]`, 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]`, acceptPropagationButton: `vn-client-fiscal-data > vn-confirm button[response=ACCEPT]`,
addressInput: `${components.vnTextfield}[name="street"]`, addressInput: `${components.vnTextfield}[name="street"]`,
cityInput: `${components.vnTextfield}[name="city"]`, cityInput: `${components.vnTextfield}[name="city"]`,
postcodeInput: `${components.vnTextfield}[name="postcode"]`, postcodeInput: `${components.vnTextfield}[name="postcode"]`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] input`, provinceInput: `vn-autocomplete[field="$ctrl.client.provinceFk"] input`,
provinceFifthOption: `${components.vnAutocomplete}[field="$ctrl.client.provinceFk"] vn-drop-down ul > li:nth-child(5)`, provinceFifthOption: `vn-autocomplete[field="$ctrl.client.provinceFk"] vn-drop-down ul > li:nth-child(5)`,
countryInput: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] input`, countryInput: `vn-autocomplete[field="$ctrl.client.countryFk"] input`,
countryThirdOption: `${components.vnAutocomplete}[field="$ctrl.client.countryFk"] vn-drop-down ul > li:nth-child(3)`, countryThirdOption: `vn-autocomplete[field="$ctrl.client.countryFk"] vn-drop-down ul > li:nth-child(3)`,
activeCheckboxLabel: `${components.vnCheck}[label="Active"] > label`, activeCheckboxLabel: `vn-check[label="Active"] > label`,
frozenCheckboxLabel: `${components.vnCheck}[label="Frozen"] > label`, frozenCheckboxLabel: `vn-check[label="Frozen"] > label`,
invoiceByAddressCheckboxInput: `${components.vnCheck}[label='Invoice by address'] > label > input`, invoiceByAddressCheckboxInput: `vn-check[label='Invoice by address'] > label > input`,
verifiedDataCheckboxInput: `${components.vnCheck}[label="Verified data"] > label > input`, verifiedDataCheckboxInput: `vn-check[label="Verified data"] > label > input`,
hasToInvoiceCheckboxLabel: `${components.vnCheck}[label='Has to invoice'] > label`, hasToInvoiceCheckboxLabel: `vn-check[label='Has to invoice'] > label`,
invoiceByMailCheckboxLabel: `${components.vnCheck}[label='Invoice by mail'] > label`, invoiceByMailCheckboxLabel: `vn-check[label='Invoice by mail'] > label`,
viesCheckboxInput: `${components.vnCheck}[label='Vies'] > label > input`, viesCheckboxInput: `vn-check[label='Vies'] > label > input`,
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientPayMethod: { clientPayMethod: {
payMethodButton: `${components.vnMenuItem}[ui-sref="client.card.billingData"]`, payMethodButton: `vn-menu-item a[ui-sref="client.card.billingData"]`,
payMethodInput: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] input`, payMethodInput: `vn-autocomplete[field="$ctrl.client.payMethodFk"] input`,
payMethodIBANOption: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(5)`, payMethodIBANOption: `vn-autocomplete[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)`, payMethodOptionOne: `vn-autocomplete[field="$ctrl.client.payMethodFk"] vn-drop-down ul > li:nth-child(2)`,
IBANInput: `${components.vnTextfield}[name="iban"]`, IBANInput: `${components.vnTextfield}[name="iban"]`,
dueDayInput: `${components.vnTextfield}[name="dueDay"]`, dueDayInput: `${components.vnTextfield}[name="dueDay"]`,
receivedCoreVNHCheckbox: `${components.vnCheck}[label='Received core VNH'] > label > input`, receivedCoreVNHCheckbox: `vn-check[label='Received core VNH'] > label > input`,
receivedCoreVNLCheckbox: `${components.vnCheck}[label='Received core VNL'] > label > input`, receivedCoreVNLCheckbox: `vn-check[label='Received core VNL'] > label > input`,
receivedB2BVNLCheckbox: `${components.vnCheck}[label='Received B2B VNL'] > label > input`, receivedB2BVNLCheckbox: `vn-check[label='Received B2B VNL'] > label > input`,
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientAddresses: { 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}`, 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"]`, consigneeInput: `${components.vnTextfield}[name="nickname"]`,
streetAddressInput: `${components.vnTextfield}[name="street"]`, streetAddressInput: `${components.vnTextfield}[name="street"]`,
postcodeInput: `${components.vnTextfield}[name="postalCode"]`, postcodeInput: `${components.vnTextfield}[name="postalCode"]`,
cityInput: `${components.vnTextfield}[name="city"]`, cityInput: `${components.vnTextfield}[name="city"]`,
provinceInput: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] input`, provinceInput: `vn-autocomplete[field="$ctrl.address.provinceFk"] input`,
provinceSecondOption: `${components.vnAutocomplete}[field="$ctrl.address.provinceFk"] vn-drop-down ul > li:nth-child(2)`, provinceSecondOption: `vn-autocomplete[field="$ctrl.address.provinceFk"] vn-drop-down ul > li:nth-child(2)`,
agencyInput: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] input`, agencyInput: `vn-autocomplete[field="$ctrl.address.agencyModeFk"] input`,
agenctySecondOption: `${components.vnAutocomplete}[field="$ctrl.address.agencyModeFk"] vn-drop-down ul > li:nth-child(2)`, agenctySecondOption: `vn-autocomplete[field="$ctrl.address.agencyModeFk"] vn-drop-down ul > li:nth-child(2)`,
phoneInput: `${components.vnTextfield}[name="phone"]`, phoneInput: `${components.vnTextfield}[name="phone"]`,
mobileInput: `${components.vnTextfield}[name="mobile"]`, mobileInput: `${components.vnTextfield}[name="mobile"]`,
defaultAddress: 'vn-client-address-index vn-horizontal:nth-child(2) div[name="street"]', 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', 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']`, firstEditButton: `vn-client-address-index vn-icon-button[icon='edit']`,
secondEditButton: `vn-client-address-index vn-horizontal:nth-child(3) ${components.vnIconButton}[icon='edit']`, secondEditButton: `vn-client-address-index vn-horizontal:nth-child(3) vn-icon-button[icon='edit']`,
activeCheckbox: `${components.vnCheck}[label='Enabled'] > label > input`, activeCheckbox: `vn-check[label='Enabled'] > label > input`,
equalizationTaxCheckboxLabel: `vn-client-address-edit ${components.vnCheck}[label='Is equalizated'] > label > input`, equalizationTaxCheckboxLabel: `vn-client-address-edit vn-check[label='Is equalizated'] > label > input`,
firstObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(1) input`, firstObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"] input`,
firstObservationTypeSelectOptionOne: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(1)`, firstObservationTypeSelectOptionOne: `vn-client-address-edit [name=observations] :nth-child(1) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(1)`,
firstObservationDescriptionInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Description"] > div > input`, firstObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(1) [model="observation.description"] input`,
secondObservationTypeSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="observation.observationTypeFk"] input`, secondObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"] input`,
secondObservationTypeSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, secondObservationTypeSelectOptionTwo: `vn-client-address-edit [name=observations] :nth-child(2) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`,
secondObservationDescriptionInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Description"] > div > input`, secondObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(2) [model="observation.description"] input`,
thirdObservationTypeSelect: `${components.vnAutocomplete}[field="observation.observationTypeFk"]:nth-child(3) input`, thirdObservationTypeSelect: `vn-client-address-edit [name=observations] :nth-child(3) [field="observation.observationTypeFk"] input`,
thirdObservationTypeSelectOptionThree: `${components.vnAutocomplete}[field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`, thirdObservationTypeSelectOptionThree: `vn-client-address-edit [name=observations] :nth-child(3) [field="observation.observationTypeFk"] vn-drop-down ul > li:nth-child(3)`,
thirdObservationDescriptionInput: `vn-horizontal:nth-child(5) > vn-textfield[label="Description"] > div > input`, thirdObservationDescriptionInput: `vn-client-address-edit [name=observations] :nth-child(3) [model="observation.description"] input`,
addObservationButton: `${components.vnIcon}[icon="add_circle"]`, addObservationButton: `vn-client-address-edit vn-icon-button[icon="add_circle"]`,
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
cancelButton: `button[ui-sref="client.card.address.index"]` cancelButton: `button[ui-sref="client.card.address.index"]`
}, },
clientWebAccess: { clientWebAccess: {
webAccessButton: `${components.vnMenuItem}[ui-sref="client.card.webAccess"]`, webAccessButton: `vn-menu-item a[ui-sref="client.card.webAccess"]`,
enableWebAccessCheckbox: `${components.vnCheck}[label='Enable web access'] > label > input`, enableWebAccessCheckbox: `vn-check[label='Enable web access'] > label > input`,
userNameInput: `${components.vnTextfield}[name="name"]`, userNameInput: `${components.vnTextfield}[name="name"]`,
saveButton: `${components.vnSubmit}` saveButton: `${components.vnSubmit}`
}, },
clientNotes: { clientNotes: {
notesButton: `${components.vnMenuItem}[ui-sref="client.card.note.index"]`, notesButton: `vn-menu-item a[ui-sref="client.card.note.index"]`,
addNoteFloatButton: `${components.vnFloatButton}`, addNoteFloatButton: `${components.vnFloatButton}`,
noteInput: `${components.vnTextarea}[label="Note"]`, noteInput: `vn-textarea[label="Note"]`,
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
firstNoteText: 'vn-client-note .text' firstNoteText: 'vn-client-note .text'
}, },
clientCredit: { clientCredit: {
creditButton: `${components.vnMenuItem}[ui-sref="client.card.credit.index"]`, creditButton: `vn-menu-item a[ui-sref="client.card.credit.index"]`,
addCreditFloatButton: `${components.vnFloatButton}`, addCreditFloatButton: `${components.vnFloatButton}`,
creditInput: `${components.vnTextfield}[name="credit"]`, creditInput: `${components.vnTextfield}[name="credit"]`,
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
firstCreditText: 'vn-client-credit-index .list-element' firstCreditText: 'vn-client-credit-index .list-element'
}, },
clientGreuge: { clientGreuge: {
greugeButton: `${components.vnMenuItem}[ui-sref="client.card.greuge.index"]`, greugeButton: `vn-menu-item a[ui-sref="client.card.greuge.index"]`,
addGreugeFloatButton: `${components.vnFloatButton}`, addGreugeFloatButton: `${components.vnFloatButton}`,
amountInput: `${components.vnTextfield}[name="amount"]`, amountInput: `${components.vnTextfield}[name="amount"]`,
descriptionInput: `${components.vnTextfield}[name="description"]`, descriptionInput: `${components.vnTextfield}[name="description"]`,
typeInput: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] input`, typeInput: `vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"] input`,
typeSecondOption: `${components.vnAutocomplete}[field="$ctrl.greuge.greugeTypeFk"] vn-drop-down ul > li`, typeSecondOption: `vn-autocomplete[field="$ctrl.greuge.greugeTypeFk"] vn-drop-down ul > li`,
saveButton: `${components.vnSubmit}`, saveButton: `${components.vnSubmit}`,
firstGreugeText: 'vn-client-greuge-index .list-element' firstGreugeText: 'vn-client-greuge-index .list-element'
}, },
clientMandate: { 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' firstMandateText: 'vn-client-mandate .list-element'
}, },
clientInvoices: { 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' firstInvoiceText: 'vn-client-invoice .list-element'
}, },
itemsIndex: { itemsIndex: {
@ -158,167 +158,167 @@ export default {
searchResultCloneButton: `vn-item-product .buttons > [icon="icon-clone"]`, searchResultCloneButton: `vn-item-product .buttons > [icon="icon-clone"]`,
acceptClonationAlertButton: `vn-item-index [vn-id="clone"] [response="ACCEPT"]`, acceptClonationAlertButton: `vn-item-index [vn-id="clone"] [response="ACCEPT"]`,
searchItemInput: `${components.vnTextfield}`, 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' closeItemSummaryPreview: 'vn-item-index [vn-id="preview"] button.close'
}, },
itemCreateView: { itemCreateView: {
name: `${components.vnTextfield}[name="name"]`, name: `${components.vnTextfield}[name="name"]`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`, typeSelect: `vn-autocomplete[field="$ctrl.item.typeFk"] input`,
typeSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, typeSelectOptionOne: `vn-autocomplete[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`, intrastatSelect: `vn-autocomplete[field="$ctrl.item.intrastatFk"] input`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`, intrastatSelectOptionOne: `vn-autocomplete[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(2)`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, originSelect: `vn-autocomplete[field="$ctrl.item.originFk"] input`,
originSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, originSelectOptionOne: `vn-autocomplete[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`,
createButton: `${components.vnSubmit}`, createButton: `${components.vnSubmit}`,
cancelButton: `button[ui-sref="item.index"]` cancelButton: `button[ui-sref="item.index"]`
}, },
itemBasicData: { itemBasicData: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
basicDataButton: `${components.vnMenuItem}[ui-sref="item.card.data"]`, basicDataButton: `vn-menu-item a[ui-sref="item.card.data"]`,
typeSelect: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] input`, typeSelect: `vn-autocomplete[field="$ctrl.item.typeFk"] input`,
typeSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`, typeSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.typeFk"] vn-drop-down ul > li:nth-child(2)`,
intrastatSelect: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] input`, intrastatSelect: `vn-autocomplete[field="$ctrl.item.intrastatFk"] input`,
intrastatSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(1)`, intrastatSelectOptionOne: `vn-autocomplete[field="$ctrl.item.intrastatFk"] vn-drop-down ul > li:nth-child(1)`,
nameInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`, nameInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`,
relevancyInput: `vn-horizontal:nth-child(3) > ${components.vnTextfield}`, relevancyInput: `vn-horizontal:nth-child(3) > ${components.vnTextfield}`,
originSelect: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] input`, originSelect: `vn-autocomplete[field="$ctrl.item.originFk"] input`,
originSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`, originSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.originFk"] vn-drop-down ul > li:nth-child(2)`,
expenceSelect: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] input`, expenceSelect: `vn-autocomplete[field="$ctrl.item.expenceFk"] input`,
expenceSelectOptionTwo: `${components.vnAutocomplete}[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`, expenceSelectOptionTwo: `vn-autocomplete[field="$ctrl.item.expenceFk"] vn-drop-down ul > li:nth-child(2)`,
submitBasicDataButton: `${components.vnSubmit}` submitBasicDataButton: `${components.vnSubmit}`
}, },
itemTags: { itemTags: {
goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]', goToItemIndexButton: 'vn-item-descriptor [ui-sref="item.index"]',
tagsButton: `${components.vnMenuItem}[ui-sref="item.card.tags"]`, tagsButton: `vn-menu-item a[ui-sref="item.card.tags"]`,
firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnIcon}[icon="remove_circle_outline"]`, firstRemoveTagButton: `vn-item-tags vn-horizontal:nth-child(2) > vn-icon[icon="remove_circle_outline"]`,
firstTagSelect: `vn-item-tags vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="itemTag.tagFk"] input`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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`, 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}` submitItemTagsButton: `${components.vnSubmit}`
}, },
itemTax: { itemTax: {
taxButton: `${components.vnMenuItem}[ui-sref="item.card.tax"]`, taxButton: `vn-menu-item a[ui-sref="item.card.tax"]`,
firstClassSelect: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, firstClassSelect: `vn-horizontal:nth-child(2) > vn-autocomplete[field="tax.taxClassFk"] input`,
firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`, firstClassSelectOptionTwo: `vn-horizontal:nth-child(2) > vn-autocomplete vn-drop-down ul > li:nth-child(2)`,
secondClassSelect: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, secondClassSelect: `vn-horizontal:nth-child(3) > vn-autocomplete[field="tax.taxClassFk"] input`,
secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(1)`, secondClassSelectOptionOne: `vn-horizontal:nth-child(3) > vn-autocomplete vn-drop-down ul > li:nth-child(1)`,
thirdClassSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="tax.taxClassFk"] input`, thirdClassSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="tax.taxClassFk"] input`,
thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete} vn-drop-down ul > li:nth-child(2)`, thirdClassSelectOptionTwo: `vn-horizontal:nth-child(4) > vn-autocomplete vn-drop-down ul > li:nth-child(2)`,
submitTaxButton: `${components.vnSubmit}` submitTaxButton: `${components.vnSubmit}`
}, },
itemBarcodes: { itemBarcodes: {
barcodeButton: `${components.vnMenuItem}[ui-sref="item.card.itemBarcode"]`, barcodeButton: `vn-menu-item a[ui-sref="item.card.itemBarcode"]`,
addBarcodeButton: `${components.vnIcon}[icon="add_circle"]`, addBarcodeButton: `vn-icon[icon="add_circle"]`,
thirdCodeInput: `vn-item-barcode vn-horizontal:nth-child(4) > ${components.vnTextfield}`, thirdCodeInput: `vn-item-barcode vn-horizontal:nth-child(4) > ${components.vnTextfield}`,
submitBarcodesButton: `${components.vnSubmit}`, 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: { itemNiches: {
nicheButton: `${components.vnMenuItem}[ui-sref="item.card.niche"]`, nicheButton: `vn-menu-item a[ui-sref="item.card.niche"]`,
addNicheButton: `${components.vnIcon}[icon="add_circle"]`, addNicheButton: `vn-icon[icon="add_circle"]`,
firstWarehouseSelect: `${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`, firstWarehouseSelect: `vn-autocomplete[field="itemNiche.warehouseFk"] input`,
firstWarehouseDisabled: `vn-horizontal:nth-child(2) > vn-textfield[label="Warehouse"] > div > 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`, 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`, secondWarehouseDisabled: `vn-horizontal:nth-child(3) > vn-textfield[label="Warehouse"] > div > input`,
secondCodeInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Code"] > 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"]`, secondNicheRemoveButton: `vn-horizontal:nth-child(3) > vn-icon[icon="remove_circle_outline"]`,
thirdWarehouseSelect: `vn-horizontal:nth-child(4) > ${components.vnAutocomplete}[field="itemNiche.warehouseFk"] input`, thirdWarehouseSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="itemNiche.warehouseFk"] input`,
thirdWarehouseDisabled: `vn-horizontal:nth-child(4) > vn-textfield[label="Warehouse"] > div > 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`, thirdCodeInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Code"] > div > input`,
submitNichesButton: `${components.vnSubmit}` submitNichesButton: `${components.vnSubmit}`
}, },
itemBotanical: { 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}`, botanicalInput: `vn-horizontal:nth-child(2) > ${components.vnTextfield}`,
genusSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] input`, genusSelect: `vn-autocomplete[field="$ctrl.botanical.genusFk"] input`,
genusSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(1)`, genusSelectOptionOne: `vn-autocomplete[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)`, genusSelectOptionTwo: `vn-autocomplete[field="$ctrl.botanical.genusFk"] vn-drop-down ul > li:nth-child(2)`,
speciesSelect: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] input`, speciesSelect: `vn-autocomplete[field="$ctrl.botanical.specieFk"] input`,
speciesSelectOptionOne: `${components.vnAutocomplete}[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(1)`, speciesSelectOptionOne: `vn-autocomplete[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)`, speciesSelectOptionTwo: `vn-autocomplete[field="$ctrl.botanical.specieFk"] vn-drop-down ul > li:nth-child(2)`,
submitBotanicalButton: `${components.vnSubmit}` submitBotanicalButton: `${components.vnSubmit}`
}, },
itemSummary: { itemSummary: {
basicData: `${components.vnItemSummary} vn-vertical[name="basicData"]`, basicData: `vn-item-summary vn-vertical[name="basicData"]`,
vat: `${components.vnItemSummary} vn-vertical[name="tax"]`, vat: `vn-item-summary vn-vertical[name="tax"]`,
tags: `${components.vnItemSummary} vn-vertical[name="tags"]`, tags: `vn-item-summary vn-vertical[name="tags"]`,
niche: `${components.vnItemSummary} vn-vertical[name="niche"]`, niche: `vn-item-summary vn-vertical[name="niche"]`,
botanical: `${components.vnItemSummary} vn-vertical[name="botanical"]`, botanical: `vn-item-summary vn-vertical[name="botanical"]`,
barcode: `${components.vnItemSummary} vn-vertical[name="barcode"]` barcode: `vn-item-summary vn-vertical[name="barcode"]`
}, },
ticketsIndex: { ticketsIndex: {
createTicketButton: `${components.vnFloatButton}`, createTicketButton: `${components.vnFloatButton}`,
searchResult: `table > tbody > tr`, searchResult: `table > tbody > tr`,
searchTicketInput: `${components.vnTextfield}`, searchTicketInput: `${components.vnTextfield}`,
searchButton: `${components.vnSearchBar} > vn-icon-button` searchButton: `vn-searchbar vn-icon-button[icon="search"]`
}, },
ticketNotes: { ticketNotes: {
notesButton: `${components.vnMenuItem}[ui-sref="ticket.card.observation"]`, notesButton: `vn-menu-item a[ui-sref="ticket.card.observation"]`,
firstNoteRemoveButton: `${components.vnIcon}[icon="remove_circle_outline"]`, firstNoteRemoveButton: `vn-icon[icon="remove_circle_outline"]`,
addNoteButton: `${components.vnIcon}[icon="add_circle"]`, addNoteButton: `vn-icon[icon="add_circle"]`,
firstNoteSelect: `${components.vnAutocomplete}[field="ticketObservation.observationTypeFk"] input`, firstNoteSelect: `vn-autocomplete[field="ticketObservation.observationTypeFk"] input`,
firstNoteSelectSecondOption: `${components.vnAutocomplete}[field="ticketObservation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`, firstNoteSelectSecondOption: `vn-autocomplete[field="ticketObservation.observationTypeFk"] vn-drop-down ul > li:nth-child(2)`,
firstNoteDisabled: `vn-textfield[label="Observation type"] > div > input`, firstNoteDisabled: `vn-textfield[label="Observation type"] > div > input`,
firstDescriptionInput: `vn-textfield[label="Description"] > div > input`, firstDescriptionInput: `vn-textfield[label="Description"] > div > input`,
submitNotesButton: `${components.vnSubmit}` submitNotesButton: `${components.vnSubmit}`
}, },
ticketExpedition: { 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`, 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)` 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: { ticketPackages: {
packagesButton: `${components.vnMenuItem}[ui-sref="ticket.card.package.index"]`, packagesButton: `vn-menu-item a[ui-sref="ticket.card.package.index"]`,
firstPackageSelect: `${components.vnAutocomplete}[label="Package"] > div > div > input`, firstPackageSelect: `vn-autocomplete[label="Package"] > div > div > input`,
firstPackageSelectOptionThree: `${components.vnAutocomplete}[label="Package"] vn-drop-down ul > li:nth-child(3)`, firstPackageSelectOptionThree: `vn-autocomplete[label="Package"] vn-drop-down ul > li:nth-child(3)`,
firstQuantityInput: `vn-textfield[label="Quantity"] > div > input`, firstQuantityInput: `vn-textfield[label="Quantity"] > div > input`,
firstRemovePackageButton: `vn-icon[vn-tooltip="Remove package"]`, firstRemovePackageButton: `vn-icon[vn-tooltip="Remove package"]`,
addPackageButton: `vn-icon[vn-tooltip="Add 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}` savePackagesButton: `${components.vnSubmit}`
}, },
ticketSales: { 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)`, firstSaleText: `table > tbody > tr:nth-child(1)`,
secondSaleText: `table > tbody > tr:nth-child(2)` secondSaleText: `table > tbody > tr:nth-child(2)`
}, },
ticketTracking: { ticketTracking: {
trackingButton: `${components.vnMenuItem}[ui-sref="ticket.card.tracking.index"]`, trackingButton: `vn-menu-item a[ui-sref="ticket.card.tracking.index"]`,
createStateButton: `${components.vnFloatButton}`, createStateButton: `${components.vnFloatButton}`,
firstSaleText: `table > tbody > tr:nth-child(1)`, firstSaleText: `table > tbody > tr:nth-child(1)`,
secondSaleText: `table > tbody > tr:nth-child(2)` secondSaleText: `table > tbody > tr:nth-child(2)`
}, },
createStateView: { createStateView: {
stateInput: `${components.vnAutocomplete}[field="$ctrl.ticket.stateFk"] > div > div > input`, stateInput: `vn-autocomplete[field="$ctrl.ticket.stateFk"] > div > div > input`,
stateInputOptionOne: `${components.vnAutocomplete}[field="$ctrl.ticket.stateFk"] vn-drop-down ul > li:nth-child(1)`, stateInputOptionOne: `vn-autocomplete[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`, clearStateInputButton: `vn-autocomplete[field="$ctrl.ticket.stateFk"] > div > div > div > vn-icon > i`,
saveStateButton: `${components.vnSubmit}` saveStateButton: `${components.vnSubmit}`
} }
}; };

View File

@ -67,7 +67,7 @@ describe('Client', () => {
.click(selectors.createClientView.createButton) .click(selectors.createClientView.createButton)
.waitForSnackbar() .waitForSnackbar()
.then(result => { .then(result => {
expect(result).toEqual('Some fields are invalid'); expect(result).toContain('Some fields are invalid');
}); });
}); });

View File

@ -65,7 +65,7 @@ describe('Client', () => {
.waitToClick(selectors.clientAddresses.saveButton) .waitToClick(selectors.clientAddresses.saveButton)
.waitForSnackbar() .waitForSnackbar()
.then(result => { .then(result => {
expect(result).toContain('No field can be blank'); expect(result).toContain('type cannot be blank');
}); });
}); });

View File

@ -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 return nightmare
.waitToClick(selectors.createStateView.stateInput) .waitToClick(selectors.createStateView.stateInput)
.waitToClick(selectors.createStateView.stateInputOptionOne) .waitToClick(selectors.createStateView.stateInputOptionOne)
@ -73,7 +73,17 @@ describe('Ticket', () => {
.click(selectors.createStateView.saveStateButton) .click(selectors.createStateView.saveStateButton)
.waitForSnackbar() .waitForSnackbar()
.then(result => { .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');
}); });
}); });

46
package-lock.json generated
View File

@ -103,19 +103,19 @@
"dev": true "dev": true
}, },
"angular": { "angular": {
"version": "1.6.8", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.6.8.tgz", "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.0.tgz",
"integrity": "sha512-9WErZIOw1Cu1V5Yxdvxz/6YpND8ntdP71fdPpufPFJvZodZXqCjQBYrHqEoMZreO5i84O3D/Jw/vepoFt68Azw==" "integrity": "sha512-3LboCLjrOuC7dWh953O0+dI3dJ7PexYRSCIrfqoN5qoHyja/wak3eWoxPKb2Sl2qwiPbrUV5KJXwgpUQ48McBQ=="
}, },
"angular-cookies": { "angular-cookies": {
"version": "1.6.4", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.6.4.tgz", "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.0.tgz",
"integrity": "sha1-wo8/aqx6mCbB5F8daAckADblsm0=" "integrity": "sha512-bxY7SAl7M+P+DazcDq4OVSFhmR0QET6KWw7bsxh4V22Ky+NcGbdyFySRNqu0TtWB5LkiGvo0wCFLd/vDyuMQOQ=="
}, },
"angular-mocks": { "angular-mocks": {
"version": "1.6.6", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.6.6.tgz", "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.0.tgz",
"integrity": "sha1-yTAY54OMbcXOrxprz5vhPIMOpRU=", "integrity": "sha512-tBlj9jIEpbgiYY1VpV6XAi+5JSAO0AXFziVW4TSIFETB23fautoREI7XbOeRgy/QmOhZA4P320gs2XgpbvLd0w==",
"dev": true "dev": true
}, },
"angular-paging": { "angular-paging": {
@ -124,19 +124,19 @@
"integrity": "sha1-cC9XTW0UBpADXqxkOV/jEfeYf7s=" "integrity": "sha1-cC9XTW0UBpADXqxkOV/jEfeYf7s="
}, },
"angular-translate": { "angular-translate": {
"version": "2.17.0", "version": "2.18.1",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.17.0.tgz", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz",
"integrity": "sha512-SudfI0R0Hhtvngc0X3wFChXQGmw90o95i+QPZ11LhJJryneTq8LR3+3E4E7jgHA4fu6TcswgcfZ9+cp5ckbUHw==", "integrity": "sha512-Mw0kFBqsv5j8ItL9IhRZunIlVmIRW6iFsiTmRs9wGr2QTt8z4rehYlWyHos8qnXc/kyOYJiW50iH50CSNHGB9A==",
"requires": { "requires": {
"angular": "1.6.8" "angular": "1.7.0"
} }
}, },
"angular-translate-loader-partial": { "angular-translate-loader-partial": {
"version": "2.17.0", "version": "2.18.1",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.17.0.tgz", "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.1.tgz",
"integrity": "sha512-pyRJcRc93iwiUnRnh9ZfehbQE/yxO5T6jmEqIvLEVz8gKLjDqDLKcaQFgPef9wCIN2n3e531YbStkkbSH3LYmQ==", "integrity": "sha512-+bPzY3+F2I1tb+X5bscvZq0OGoVEVkHwPGZvaY4nhbktpshArYpvIEV+RQFUa/QNj8vQc3iQ/pruJDb8w3zIdw==",
"requires": { "requires": {
"angular-translate": "2.17.0" "angular-translate": "2.18.1"
} }
}, },
"ansi-align": { "ansi-align": {
@ -1383,7 +1383,7 @@
"bluebird": { "bluebird": {
"version": "3.5.1", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
"integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
"dev": true "dev": true
}, },
"bn.js": { "bn.js": {
@ -10785,7 +10785,7 @@
"jasmine-spec-reporter": { "jasmine-spec-reporter": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", "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, "dev": true,
"requires": { "requires": {
"colors": "1.1.2" "colors": "1.1.2"
@ -10933,7 +10933,7 @@
"karma": { "karma": {
"version": "1.7.1", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
"integrity": "sha1-hcwI6eCiLXzpzKN8ShvoJPaisa4=", "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==",
"dev": true, "dev": true,
"requires": { "requires": {
"bluebird": "3.5.1", "bluebird": "3.5.1",
@ -10985,7 +10985,7 @@
"karma-chrome-launcher": { "karma-chrome-launcher": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", "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, "dev": true,
"requires": { "requires": {
"fs-access": "1.0.1", "fs-access": "1.0.1",
@ -11822,7 +11822,7 @@
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz", "resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
"integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=", "integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=",
"requires": { "requires": {
"angular": "1.6.8" "angular": "1.7.0"
} }
}, },
"micromatch": { "micromatch": {
@ -19992,7 +19992,7 @@
"useragent": { "useragent": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
"integrity": "sha1-IX+UOtVAyyEoZYqyP8lg9qiMmXI=", "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
"dev": true, "dev": true,
"requires": { "requires": {
"lru-cache": "4.1.1", "lru-cache": "4.1.1",

View File

@ -10,11 +10,11 @@
}, },
"dependencies": { "dependencies": {
"@uirouter/angularjs": "^1.0.3", "@uirouter/angularjs": "^1.0.3",
"angular": "^1.6.8", "angular": "^1.7.0",
"angular-cookies": "^1.6.4", "angular-cookies": "^1.7.0",
"angular-paging": "^2.2.2", "angular-paging": "^2.2.2",
"angular-translate": "^2.17.0", "angular-translate": "^2.18.1",
"angular-translate-loader-partial": "^2.17.0", "angular-translate-loader-partial": "^2.18.1",
"flatpickr": "^4.4.6", "flatpickr": "^4.4.6",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"material-design-lite": "^1.3.0", "material-design-lite": "^1.3.0",
@ -25,7 +25,7 @@
"validator": "^6.2.1" "validator": "^6.2.1"
}, },
"devDependencies": { "devDependencies": {
"angular-mocks": "^1.6.6", "angular-mocks": "^1.7.0",
"assets-webpack-plugin": "^3.5.1", "assets-webpack-plugin": "^3.5.1",
"babel": "^6.23.0", "babel": "^6.23.0",
"babel-core": "^6.26.0", "babel-core": "^6.26.0",

View File

@ -1,3 +0,0 @@
module.exports = Self => {
Self.installCrudModel('crudAddressObservations');
};

View File

@ -1,3 +1,25 @@
module.exports = function(Self) { 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();
}
}; };

View File

@ -17,5 +17,6 @@
"The IBAN does not have the correct format": "The IBAN does not have the correct format", "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", "That payment method requires an IBAN": "That payment method requires an IBAN",
"State cannot be blank": "State cannot be blank", "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"
} }

View File

@ -18,7 +18,9 @@
"That payment method requires an IBAN": "El método de pago seleccionado requiere que se especifique el IBAN", "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", "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", "Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado",
"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",
"The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero", "The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero",
"The grade must be similar to the last one": "El grado debe ser similar al último crédito" "The grade must be similar to the last one": "El grado debe ser similar al último crédito"
} }

View File

@ -1,3 +0,0 @@
module.exports = Self => {
Self.installCrudModel('crudItemTags');
};

View File

@ -8,15 +8,13 @@ module.exports = Self => {
required: true, required: true,
description: 'ticket id', description: 'ticket id',
http: {source: 'path'} http: {source: 'path'}
}, }, {
{
arg: 'data', arg: 'data',
type: 'Object', type: 'Object',
required: true, required: true,
description: 'landed, addressFk, agencyModeFk, warehouseFk', description: 'landed, addressFk, agencyModeFk, warehouseFk',
http: {source: 'body'} http: {source: 'body'}
}, }, {
{
arg: 'context', arg: 'context',
type: 'object', type: 'object',
http: function(ctx) { http: function(ctx) {

View File

@ -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;
}
};
};
};

View File

@ -1,4 +1,3 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-tag/crudItemTags')(Self);
require('../methods/item-tag/filterItemTags')(Self); require('../methods/item-tag/filterItemTags')(Self);
}; };

View File

@ -1,8 +1,8 @@
module.exports = function(Self) { module.exports = function(Self) {
Self.setup = function() { Self.setup = function() {
Self.super_.setup.call(this); Self.super_.setup.call(this);
/*
/* let disableMethods = { let disableMethods = {
create: true, create: true,
replaceOrCreate: true, replaceOrCreate: true,
patchOrCreate: true, patchOrCreate: true,
@ -22,7 +22,9 @@ module.exports = function(Self) {
}; };
for (let method in disableMethods) { for (let method in disableMethods) {
// this.disableRemoteMethod(method, disableMethods[method]); // this.disableRemoteMethod(method, disableMethods[method]);
} */ }
*/
this.installCrudModel('crud');
}; };
Self.defineScope = function(serverFilter) { Self.defineScope = function(serverFilter) {
@ -116,8 +118,51 @@ module.exports = function(Self) {
}; };
}; };
require('../methods/vnModel/rawSql')(Self); Self.installCrudModel = function(methodName) {
require('../methods/vnModel/installMethod')(Self); this.remoteMethod(methodName, {
require('../methods/vnModel/validateBinded')(Self); description: 'Create, update or/and delete instances from model in a single request',
require('../methods/vnModel/installCrudModel')(Self); 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);
}; };