crud-model refactor. crud-model fixes #409

This commit is contained in:
Joan Sanchez 2018-08-30 08:50:03 +02:00
parent a1b87e473e
commit e698d84a4f
51 changed files with 592 additions and 1591 deletions

View File

@ -1,11 +1,10 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="/client/api/ClientContacts" url="/client/api/ClientContacts"
fields="['id', 'clientFk', 'name', 'phone']" fields="['id', 'name', 'phone', 'clientFk']"
link="{clientFk: $ctrl.$stateParams.id}" link="{clientFk: $ctrl.$stateParams.id}"
data="contacts" on-data-change="$ctrl.onDataChange()"> data="contacts">
</vn-crud-model> </vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="contacts" data="contacts"
@ -27,15 +26,16 @@
model="contact.phone" model="contact.phone"
rule="clientContact.phone" vn-focus> rule="clientContact.phone" vn-focus>
</vn-textfield> </vn-textfield>
<vn-auto pad-medium-top> <vn-none>
<vn-icon <vn-icon
pointer pointer
medium-grey medium-grey
margin-medium-v
vn-tooltip="Remove contact" vn-tooltip="Remove contact"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.remove($index)"> ng-click="model.remove($index)">
</vn-icon> </vn-icon>
</vn-one> </vn-none>
</vn-horizontal> </vn-horizontal>
<vn-one> <vn-one>
<vn-icon-button <vn-icon-button

View File

@ -1,40 +1,30 @@
import ngModule from '../module'; import ngModule from '../module';
class Controller { class Controller {
constructor($http, $scope, $stateParams, $translate, vnApp) { constructor($scope, $stateParams, $translate) {
this.$http = $http;
this.$scope = $scope; this.$scope = $scope;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$translate = $translate; this.$translate = $translate;
this.vnApp = vnApp;
}
onDataChange() {
this.contacts = this.$scope.model.data;
} }
add() { add() {
let data = { this.$scope.model.insert({
clientFk: this.client.id, clientFk: this.client.id,
name: this.$translate.instant('Phone'), name: this.$translate.instant('Phone'),
phone: null phone: null
}; });
this.contacts.push(data);
}
remove(index) {
this.contacts.splice(index, 1);
} }
onSubmit() { onSubmit() {
this.$scope.watcher.check(); this.$scope.watcher.check();
this.$scope.watcher.realSubmit() this.$scope.model.save().then(() => {
.then(() => this.$scope.model.save(true)) this.$scope.watcher.notifySaved();
.then(() => this.$scope.watcher.notifySaved()); this.$scope.model.refresh();
});
} }
} }
Controller.$inject = ['$http', '$scope', '$stateParams', '$translate', 'vnApp']; Controller.$inject = ['$scope', '$stateParams', '$translate'];
ngModule.component('vnClientContact', { ngModule.component('vnClientContact', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,80 +0,0 @@
import './index.js';
import {watcher} from '../helpers/watcherHelper.js';
describe('Client', () => {
describe('Component vnClientContact', () => {
let $componentController;
let $scope;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
$scope.form = {$invalid: false};
$scope.model = {
refresh: () => {},
data: [
{id: 1, name: 'My contact 1', phone: '123456789'},
{id: 2, name: 'My contact 2', phone: '123456789'},
{id: 3, name: 'My contact 3', phone: '123456789'}
]
};
$scope.watcher = watcher;
controller = $componentController('vnClientContact', {$scope: $scope}, {$state: $state});
controller.client = {
id: 101
};
}));
describe('onDataChange()', () => {
it('should define contacts and oldInstances properties into controller', () => {
controller.onDataChange();
expect(controller.contacts).toBeDefined();
expect(controller.contacts.length).toEqual(3);
});
});
describe('add / remove tags', () => {
it('should add one empty contact into controller contacts collection', () => {
controller.contacts = [];
controller.add();
expect(controller.contacts.length).toEqual(1);
expect(controller.contacts[0].id).toBe(undefined);
});
it('should remove a contact that occupies the position in the index', () => {
let index = 2;
controller.onDataChange();
controller.remove(index);
expect(controller.contacts.length).toEqual(2);
expect(controller.contacts[index]).toBe(undefined);
});
});
xdescribe('onSubmit()', () => {
it("should perfom a query to delete contacts", () => {
controller.$scope.model.data = [
{id: 1, name: 'My contact 1', phone: '123456789'},
{id: 2, name: 'My contact 2', phone: '123456789'}
];
spyOn(controller.$scope.watcher, 'notifySaved');
controller.onSubmit();
expect(controller.$scope.watcher.notifySaved).toHaveBeenCalledWith();
});
});
});
});

View File

@ -1,14 +0,0 @@
module.exports.watcher = {
realSubmit: () => {
return {
then: () => {}
};
},
check: () => {},
save: () => {
return {
then: () => {}
};
},
notifySaved: () => {}
};

View File

@ -132,7 +132,7 @@ export default class CrudModel extends ModelProxy {
let url = this.saveUrl ? this.saveUrl : `${this.url}/crud`; let url = this.saveUrl ? this.saveUrl : `${this.url}/crud`;
return this.$http.post(url, changes) return this.$http.post(url, changes)
.then(() => this.resetChanges); .then(() => this.resetChanges());
} }
} }
CrudModel.$inject = ['$http', '$q']; CrudModel.$inject = ['$http', '$q'];

View File

@ -180,17 +180,6 @@ export default class Watcher extends Component {
this.vnApp.showSuccess(this._.instant('Data saved!')); this.vnApp.showSuccess(this._.instant('Data saved!'));
} }
writeData(json, resolve) {
Object.assign(this.data, json.data);
this.updateOriginalData();
resolve(json);
}
updateOriginalData() {
this.orgData = this.copyInNewObject(this.data);
this.setPristine();
}
setPristine() { setPristine() {
if (this.form) this.form.$setPristine(); if (this.form) this.form.$setPristine();
} }
@ -199,24 +188,6 @@ export default class Watcher extends Component {
if (this.form) this.form.$setDirty(); if (this.form) this.form.$setDirty();
} }
copyInNewObject(data) {
let newCopy = {};
if (data && typeof data === 'object') {
Object.keys(data).forEach(
val => {
if (!isFullEmpty(data[val])) {
if (typeof data[val] === 'object') {
newCopy[val] = this.copyInNewObject(data[val]);
} else {
newCopy[val] = data[val];
}
}
}
);
}
return newCopy;
}
callback(transition) { callback(transition) {
if (!this.state && this.dirty) { if (!this.state && this.dirty) {
this.state = transition.to().name; this.state = transition.to().name;
@ -236,6 +207,35 @@ export default class Watcher extends Component {
this.state = null; this.state = null;
} }
} }
writeData(json, resolve) {
Object.assign(this.data, json.data);
this.updateOriginalData();
resolve(json);
}
updateOriginalData() {
this.orgData = this.copyInNewObject(this.data);
this.setPristine();
}
copyInNewObject(data) {
let newCopy = {};
if (data && typeof data === 'object') {
Object.keys(data).forEach(
val => {
if (!isFullEmpty(data[val])) {
if (typeof data[val] === 'object') {
newCopy[val] = this.copyInNewObject(data[val]);
} else {
newCopy[val] = data[val];
}
}
}
);
}
return newCopy;
}
} }
Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q']; Watcher.$inject = ['$element', '$scope', '$state', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q'];

View File

@ -1,8 +1,8 @@
import ngModule from '../module'; import ngModule from '../module';
import splitingRegister from './spliting-register'; import splitingRegister from './spliting-register';
factory.$inject = ['$http', '$window', '$ocLazyLoad', '$translatePartialLoader', '$translate']; factory.$inject = ['$http', '$window', '$ocLazyLoad', '$translatePartialLoader', '$translate', '$q'];
export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $translate) { export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $translate, $q) {
class ModuleLoader { class ModuleLoader {
constructor() { constructor() {
this._loaded = {}; this._loaded = {};
@ -15,7 +15,7 @@ export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $t
if (loaded[moduleName] instanceof Promise) if (loaded[moduleName] instanceof Promise)
return loaded[moduleName]; return loaded[moduleName];
if (loaded[moduleName] === false) if (loaded[moduleName] === false)
return Promise.reject( return $q.reject(
new Error(`Module dependency loop detected: ${moduleName}`)); new Error(`Module dependency loop detected: ${moduleName}`));
loaded[moduleName] = false; loaded[moduleName] = false;

View File

@ -0,0 +1,19 @@
module.exports.crudModel = {
data: [],
insert: () => {
return new Promise(accept => {
accept();
});
},
remove: () => {
return new Promise(accept => {
accept();
});
},
save: () => {
return new Promise(accept => {
accept();
});
},
refresh: () => {}
};

View File

@ -0,0 +1,11 @@
module.exports.watcher = {
realSubmit: () => {
return new Promise(accept => {
accept();
});
},
check: () => {},
notifySaved: () => {},
setDirty: () => {},
setPristine: () => {}
};

View File

@ -1,11 +1,19 @@
<vn-crud-model
vn-id="model"
url="/item/api/ItemBarcodes"
fields="['id', 'itemFk', 'code']"
link="{itemFk: $ctrl.$stateParams.id}"
data="barcodes">
</vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="barcodes"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Barcodes</vn-title> <vn-title>Barcodes</vn-title>
<vn-horizontal ng-repeat="barcode in $ctrl.barcodes track by $index"> <vn-horizontal ng-repeat="barcode in barcodes track by $index">
<vn-textfield <vn-textfield
vn-three vn-three
label="Code" label="Code"
@ -13,15 +21,17 @@
vn-acl="buyer, replenisher" vn-acl="buyer, replenisher"
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-none>
<vn-icon <vn-icon
pad-medium-top medium-grey
margin-medium-v
vn-acl="buyer,replenisher" vn-acl="buyer,replenisher"
pointer pointer
medium-grey
vn-tooltip="Remove barcode" vn-tooltip="Remove barcode"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.removeBarcode($index)"> ng-click="model.remove($index)">
</vn-icon> </vn-icon>
</vn-none>
</vn-horizontal> </vn-horizontal>
<vn-one> <vn-one>
<vn-icon-button <vn-icon-button
@ -29,7 +39,7 @@
vn-tooltip="Add barcode" vn-tooltip="Add barcode"
vn-bind="+" vn-bind="+"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.addBarcode()"> ng-click="model.insert()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,129 +1,21 @@
import ngModule from '../module'; import ngModule from '../module';
export default class Controller { export default class Controller {
constructor($state, $scope, $http, $q, $translate, vnApp) { constructor($stateParams, $scope) {
this.$state = $state; this.$stateParams = $stateParams;
this.$scope = $scope; this.$scope = $scope;
this.$http = $http;
this.$q = $q;
this.$translate = $translate;
this.vnApp = vnApp;
this.barcodes = [];
this.removedBarcodes = [];
this.oldBarcodes = {};
} }
_setIconAdd() { onSubmit() {
if (this.barcodes.length) { this.$scope.watcher.check();
this.barcodes.map(element => { this.$scope.model.save().then(() => {
element.showAddIcon = false; this.$scope.watcher.notifySaved();
return true; this.$scope.model.refresh();
});
this.barcodes[this.barcodes.length - 1].showAddIcon = true;
} else {
this.addBarcode();
}
}
_setDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setDirty();
}
}
_unsetDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setPristine();
}
}
_equalBarcodes(oldBarcode, newBarcode) {
return oldBarcode.id === newBarcode.id && oldBarcode.code === newBarcode.code;
}
addBarcode() {
this.barcodes.push({code: null, itemFk: this.$state.params.id, showAddIcon: true});
this._setIconAdd();
}
removeBarcode(index) {
let item = this.barcodes[index];
if (item) {
this.barcodes.splice(index, 1);
this._setIconAdd();
if (item.id) {
this.removedBarcodes.push(item.id);
this._setDirtyForm();
}
}
}
submit() {
let codes = [];
let repeatedBarcodes = false;
let canSubmit;
let barcodesObj = {
delete: this.removedBarcodes,
create: [],
update: []
};
for (let i = 0; i < this.barcodes.length; i++) {
let barcode = this.barcodes[i];
let isNewBarcode = barcode.id === undefined;
if (barcode.code && codes.indexOf(barcode.code) !== -1) {
repeatedBarcodes = true;
break;
}
if (barcode.code) codes.push(barcode.code);
if (isNewBarcode && barcode.code) {
barcodesObj.create.push(barcode);
} else if (!isNewBarcode && !this._equalBarcodes(this.oldBarcodes[barcode.id], barcode)) {
barcodesObj.update.push(barcode);
}
}
if (repeatedBarcodes) {
return this.vnApp.showError(this.$translate.instant('The barcode must be unique'));
}
canSubmit = barcodesObj.update.length > 0 || barcodesObj.create.length > 0 || barcodesObj.delete.length > 0;
if (canSubmit) {
return this.$http.post(`/item/api/ItemBarcodes/crudItemBarcodes`, barcodesObj).then(() => {
this.getBarcodes();
this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
});
}
this.vnApp.showError(this.$translate.instant('No changes to save'));
}
setOldBarcodes(response) {
this._setIconAdd();
response.data.forEach(barcode => {
this.oldBarcodes[barcode.id] = Object.assign({}, barcode);
}); });
} }
getBarcodes() {
let filter = {
where: {itemFk: this.$state.params.id}
};
this.$http.get(`/item/api/ItemBarcodes?filter=${JSON.stringify(filter)}`).then(response => {
this.barcodes = response.data;
this.setOldBarcodes(response);
});
}
$onInit() {
this.getBarcodes();
}
} }
Controller.$inject = ['$state', '$scope', '$http', '$q', '$translate', 'vnApp']; Controller.$inject = ['$stateParams', '$scope'];
ngModule.component('vnItemBarcode', { ngModule.component('vnItemBarcode', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,146 +0,0 @@
import './index.js';
describe('Item', () => {
describe('Component vnItemBarcode', () => {
let $componentController;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('item');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$state.params.id = '1';
controller = $componentController('vnItemBarcode', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
}));
describe('add / remove barcode()', () => {
it('should add one empty barcode into controller barcodes collection and call _setIconAdd()', () => {
controller.barcodes = [];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.addBarcode();
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.barcodes.length).toEqual(1);
expect(controller.barcodes[0].id).toBe(undefined);
expect(controller.barcodes[0].showAddIcon).toBeTruthy();
});
it('should remove a barcode that occupies the position in the index given and call _setIconAdd()', () => {
let index = 2;
controller.barcodes = [
{id: 1, code: '1111', showAddIcon: false},
{id: 2, code: '2222', showAddIcon: false},
{id: 3, code: '3333', showAddIcon: true}
];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.removeBarcode(index);
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.barcodes.length).toEqual(2);
expect(controller.barcodes[0].showAddIcon).toBeFalsy();
expect(controller.barcodes[1].showAddIcon).toBeTruthy();
expect(controller.barcodes[index]).toBe(undefined);
});
});
describe('_equalBarcodes()', () => {
it('should return true if two barcodes are equals independent of control attributes', () => {
let code1 = {id: 1, code: '1111', showAddIcon: true};
let code2 = {id: 1, code: '1111', showAddIcon: false};
let equals = controller._equalBarcodes(code2, code1);
expect(equals).toBeTruthy();
});
it('should return false if two barcodes aint equals independent of control attributes', () => {
let code1 = {id: 1, code: '1111', showAddIcon: true};
let code2 = {id: 1, code: '2222', showAddIcon: true};
let equals = controller._equalBarcodes(code2, code1);
expect(equals).toBeFalsy();
});
});
describe('getBarcodes()', () => {
it('should perform a GET query to receive the item barcodes', () => {
let filter = {
where: {itemFk: '1'}
};
let res = [{id: 1, code: '1111'}];
$httpBackend.when('GET', `/item/api/ItemBarcodes?filter=${JSON.stringify(filter)}`).respond(res);
$httpBackend.expectGET(`/item/api/ItemBarcodes?filter=${JSON.stringify(filter)}`);
controller.getBarcodes();
$httpBackend.flush();
});
});
describe('submit()', () => {
it("should return an error message 'The barcode must be unique' when the code isnt unique", () => {
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.barcodes = [
{code: 123454, itemFk: 1, id: 1},
{code: 123454, itemFk: 1}
];
controller.oldBarcodes = {1: {id: 1, code: 123454, itemFk: 1}};
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('The barcode must be unique');
});
it("should perfom a query to delete barcodes", () => {
controller.removedBarcodes = [1, 2, 3];
$httpBackend.when('GET', `/item/api/ItemBarcodes?filter={"where":{"itemFk":"1"}}`).respond([{code: 123454, itemFk: 1, id: 1}]);
$httpBackend.when('POST', `/item/api/ItemBarcodes/crudItemBarcodes`).respond('ok!');
$httpBackend.expectPOST(`/item/api/ItemBarcodes/crudItemBarcodes`);
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to update barcodes", () => {
controller.barcodes = [{code: 2222, itemFk: 1, id: 1}];
controller.oldBarcodes = {1: {id: 1, code: 1111, itemFk: 1}};
$httpBackend.when('GET', `/item/api/ItemBarcodes?filter={"where":{"itemFk":"1"}}`).respond([{}]);
$httpBackend.when('POST', `/item/api/ItemBarcodes/crudItemBarcodes`).respond('ok!');
$httpBackend.expectPOST(`/item/api/ItemBarcodes/crudItemBarcodes`);
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to create new barcode", () => {
controller.barcodes = [{code: 1111, itemFk: 1}];
$httpBackend.when('GET', `/item/api/ItemBarcodes?filter={"where":{"itemFk":"1"}}`).respond([{}]);
$httpBackend.when('POST', `/item/api/ItemBarcodes/crudItemBarcodes`).respond('ok!');
$httpBackend.expectPOST(`/item/api/ItemBarcodes/crudItemBarcodes`);
controller.submit();
$httpBackend.flush();
});
it("should return a message 'No changes to save' when there are no changes to apply", () => {
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.oldBarcodes = [
{code: 1, itemFk: 1, id: 1},
{code: 2, itemFk: 1, id: 2}
];
controller.barcodes = [];
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
});
});
});
});

View File

@ -37,10 +37,11 @@
<vn-tr ng-class="{'warning': $ctrl.isToday(sale.date)}" <vn-tr ng-class="{'warning': $ctrl.isToday(sale.date)}"
ng-repeat="sale in sales" vn-repeat-last on-last="$ctrl.scrollToActive()"> ng-repeat="sale in sales" vn-repeat-last on-last="$ctrl.scrollToActive()">
<vn-td>{{::sale.date | date:'dd/MM/yyyy HH:mm' }}</vn-td> <vn-td>{{::sale.date | date:'dd/MM/yyyy HH:mm' }}</vn-td>
<vn-td number>{{::sale.origin | dashIfEmpty}}</vn-td> <vn-td ng-click="$ctrl.showDescriptor($event, sale)" pointer number class="link">
{{::sale.origin | dashIfEmpty}}</vn-td>
<vn-td>{{::sale.stateName | dashIfEmpty}}</vn-td> <vn-td>{{::sale.stateName | dashIfEmpty}}</vn-td>
<vn-td>{{::sale.reference | dashIfEmpty}}</vn-td> <vn-td>{{::sale.reference | dashIfEmpty}}</vn-td>
<vn-td>{{sale.name | dashIfEmpty}}</vn-td> <vn-td >{{sale.name | dashIfEmpty}}</vn-td>
<vn-td number>{{::sale.in | dashIfEmpty}}</vn-td> <vn-td number>{{::sale.in | dashIfEmpty}}</vn-td>
<vn-td number>{{::sale.out | dashIfEmpty}}</vn-td> <vn-td number>{{::sale.out | dashIfEmpty}}</vn-td>
<vn-td number><span class="balance">{{::sale.balance | dashIfEmpty}}</span></vn-td> <vn-td number><span class="balance">{{::sale.balance | dashIfEmpty}}</span></vn-td>
@ -53,3 +54,4 @@
</vn-vertical> </vn-vertical>
</vn-card> </vn-card>
</vn-vertical> </vn-vertical>
<vn-ticket-descriptor-popover vn-id="descriptor"></vn-ticket-descriptor-popover>

View File

@ -110,6 +110,20 @@ class Controller {
if (!(today - comparedDate)) if (!(today - comparedDate))
return true; return true;
} }
showDescriptor(event, sale) {
if (!sale.isTicket) return;
this.$scope.descriptor.ticketFk = sale.origin;
this.$scope.descriptor.parent = event.target;
this.$scope.descriptor.show();
event.preventDefault();
event.stopImmediatePropagation();
}
onDescriptorLoad() {
this.$scope.popover.relocate();
}
} }
Controller.$inject = ['$scope', '$http', '$state', '$window']; Controller.$inject = ['$scope', '$http', '$state', '$window'];

View File

@ -7,6 +7,9 @@ import './diary';
import './create'; import './create';
import './card'; import './card';
import './descriptor'; import './descriptor';
import './descriptor-popover';
import './ticket-descriptor';
import './ticket-descriptor-popover';
import './data'; import './data';
import './tags'; import './tags';
import './tax'; import './tax';
@ -16,4 +19,4 @@ import './niche';
import './botanical'; import './botanical';
import './barcode'; import './barcode';
import './summary'; import './summary';
import './descriptor-popover';

View File

@ -1,48 +1,46 @@
<vn-crud-model
vn-id="model"
url="/item/api/ItemNiches"
fields="['id', 'itemFk', 'warehouseFk', 'code']"
link="{itemFk: $ctrl.$stateParams.id}"
data="niches">
</vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.item" data="niches"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Niches</vn-title> <vn-title>Niches</vn-title>
<vn-horizontal ng-repeat="itemNiche in $ctrl.niches track by $index"> <vn-horizontal ng-repeat="niche in niches track by $index">
<vn-autocomplete <vn-autocomplete
ng-if="!itemNiche.id" disabled="niche.id != null"
vn-one url="/item/api/Warehouses"
vn-focus
data="$ctrl.warehouses"
show-field="name" show-field="name"
value-field="id" value-field="id"
initial-data="itemNiche.warehouse" field="niche.warehouseFk"
field="itemNiche.warehouseFk"
label="Warehouse" label="Warehouse"
vn-acl="buyer,replenisher"> vn-acl="buyer,replenisher" vn-one vn-focus>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield
ng-if="itemNiche.id"
vn-focus
vn-one
label="Warehouse"
model="itemNiche.warehouse.name"
disabled="true">
</vn-textfield>
<vn-textfield <vn-textfield
vn-three vn-three
label="Code" label="Code"
model="itemNiche.code" model="niche.code"
rule="itemNiche.code" rule="itemNiche.code"
vn-acl="buyer,replenisher"> vn-acl="buyer,replenisher">
</vn-textfield> </vn-textfield>
<vn-icon <vn-none>
pad-medium-top <vn-icon
vn-acl="buyer,replenisher" medium-grey
pointer margin-medium-v
medium-grey vn-acl="buyer,replenisher"
vn-tooltip="Remove niche" pointer
icon="remove_circle_outline" vn-tooltip="Remove niche"
ng-click="$ctrl.removeNiche($index)"> icon="remove_circle_outline"
</vn-icon> ng-click="model.remove($index)">
</vn-icon>
</vn-none>
</vn-horizontal> </vn-horizontal>
<vn-one> <vn-one>
<vn-icon-button <vn-icon-button
@ -50,7 +48,7 @@
vn-tooltip="Add niche" vn-tooltip="Add niche"
vn-bind="+" vn-bind="+"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.addNiche()"> ng-click="model.insert()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,148 +1,21 @@
import ngModule from '../module'; import ngModule from '../module';
export default class Controller { export default class Controller {
constructor($stateParams, $scope, $http, $translate, vnApp) { constructor($stateParams, $scope) {
this.params = $stateParams; this.$stateParams = $stateParams;
this.$scope = $scope; this.$scope = $scope;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.warehouses = [];
this.niches = [];
this.removedNiches = [];
this.oldNiches = {};
} }
$onInit() { onSubmit() {
this.getNiches(); this.$scope.watcher.check();
this.getWarehouses(); this.$scope.model.save().then(() => {
} this.$scope.watcher.notifySaved();
this.$scope.model.refresh();
_setIconAdd() {
if (this.niches.length) {
this.niches.map(element => {
element.showAddIcon = false;
return true;
});
this.niches[this.niches.length - 1].showAddIcon = true;
} else {
this.addNiche();
}
}
_setDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setDirty();
}
}
_unsetDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setPristine();
}
}
addNiche() {
this.niches.push({code: null, itemFk: this.params.id, showAddIcon: true});
this._setIconAdd();
}
removeNiche(index) {
let item = this.niches[index];
if (item) {
this.niches.splice(index, 1);
this._setIconAdd();
if (item.id) {
this.removedNiches.push(item.id);
this._setDirtyForm();
}
}
}
_equalNiches(oldNiche, newNiche) {
return oldNiche.id === newNiche.id && oldNiche.code === newNiche.code && oldNiche.warehouseFk === newNiche.warehouseFk;
}
setOldNiches(response) {
this._setIconAdd();
response.data.forEach(niche => {
this.oldNiches[niche.id] = Object.assign({}, niche);
}); });
} }
getNiches() {
let filter = {
where: {itemFk: this.params.id},
include: {relation: 'warehouse'}
};
this.$http.get(`/item/api/ItemNiches?filter=${JSON.stringify(filter)}`).then(response => {
this.niches = response.data;
this.setOldNiches(response);
});
}
getWarehouse(id, warehouses) {
return warehouses.find(warehouse => warehouse.id === id);
}
getWarehouses() {
this.$http.get(`/item/api/Warehouses`).then(response => {
this.warehouses = response.data;
});
}
submit() {
let warehousesDefined = [];
let repeatedWarehouse = false;
let canSubmit;
let nichesObj = {
delete: this.removedNiches,
create: [],
update: []
};
this.niches.forEach(niche => {
let isNewNiche = !niche.id;
if (warehousesDefined.indexOf(niche.warehouseFk) !== -1) {
repeatedWarehouse = true;
return;
}
warehousesDefined.push(niche.warehouseFk);
if (isNewNiche) {
nichesObj.create.push(niche);
}
if (!isNewNiche && !this._equalNiches(this.oldNiches[niche.id], niche)) {
nichesObj.update.push(niche);
}
});
if (this.$scope.form.$invalid) {
return this.vnApp.showError(this.$translate.instant('Some fields are invalid'));
}
if (repeatedWarehouse) {
return this.vnApp.showError(this.$translate.instant('The niche must be unique'));
}
canSubmit = nichesObj.update.length > 0 || nichesObj.create.length > 0 || nichesObj.delete.length > 0;
if (canSubmit) {
return this.$http.post(`/item/api/ItemNiches/crudItemNiches`, nichesObj).then(() => {
this.getNiches();
this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
});
}
this.vnApp.showError(this.$translate.instant('No changes to save'));
}
} }
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; Controller.$inject = ['$stateParams', '$scope'];
ngModule.component('vnItemNiche', { ngModule.component('vnItemNiche', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,159 +0,0 @@
import './index.js';
describe('Item', () => {
describe('Component vnItemNiche', () => {
let $componentController;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('item');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnItemNiche', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
}));
describe('add / remove niche', () => {
it('should add one empty niche into controller niches collection and call _setIconAdd()', () => {
controller.niches = [];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.addNiche();
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.niches.length).toEqual(1);
expect(controller.niches[0].id).toBe(undefined);
expect(controller.niches[0].showAddIcon).toBeTruthy();
});
it('should remove a niche that occupies the position in the index given and call _setIconAdd()', () => {
let index = 2;
controller.niches = [
{id: 1, warehouseFk: 1, code: '1111', showAddIcon: false},
{id: 2, warehouseFk: 2, code: '2222', showAddIcon: false},
{id: 3, warehouseFk: 3, code: '3333', showAddIcon: true}
];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.removeNiche(index);
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.niches.length).toEqual(2);
expect(controller.niches[0].showAddIcon).toBeFalsy();
expect(controller.niches[1].showAddIcon).toBeTruthy();
expect(controller.niches[index]).toBe(undefined);
});
});
describe('_equalNiches()', () => {
it('should return true if two niches are equals independent of control attributes', () => {
let niche1 = {id: 1, warehouseFk: 1, code: '1111', showAddIcon: true};
let niche2 = {id: 1, warehouseFk: 1, code: '1111', showAddIcon: false};
let equals = controller._equalNiches(niche2, niche1);
expect(equals).toBeTruthy();
});
it('should return false if two niches aint equals independent of control attributes', () => {
let niche1 = {id: 1, warehouseFk: 1, code: '1111', showAddIcon: true};
let niche2 = {id: 1, warehouseFk: 1, code: '2222', showAddIcon: true};
let equals = controller._equalNiches(niche2, niche1);
expect(equals).toBeFalsy();
});
});
describe('get Niches / Warehouses', () => {
it('should perform a GET query to receive the item niches', () => {
let res = [{id: 1, warehouseFk: 1, code: '1111'}];
$httpBackend.whenGET(`/item/api/ItemNiches?filter={"where":{},"include":{"relation":"warehouse"}}`).respond(res);
$httpBackend.expectGET(`/item/api/ItemNiches?filter={"where":{},"include":{"relation":"warehouse"}}`);
controller.getNiches();
$httpBackend.flush();
});
it('should perform a GET query to receive the all warehouses', () => {
let res = [
{id: 1, warehouseFk: 1, name: 'warehouse one'},
{id: 2, warehouseFk: 2, name: 'warehouse two'}
];
$httpBackend.whenGET(`/item/api/Warehouses`).respond(res);
$httpBackend.expectGET(`/item/api/Warehouses`);
controller.getWarehouses();
$httpBackend.flush();
});
});
describe('submit()', () => {
it("should return an error message 'The niche must be unique' when the niche warehouse isnt unique", () => {
controller.$scope.form = {};
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.niches = [
{warehouseFk: 1, code: 123454, itemFk: 1, id: 1},
{warehouseFk: 1, code: 123454, itemFk: 1}
];
controller.oldNiches = {1: {warehouseFk: 1, id: 1, code: 123454, itemFk: 1}};
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('The niche must be unique');
});
it("should perfom a query to delete niches", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.oldNiches = {1: {id: 1, warehouseFk: 1, code: '1111'}};
controller.niches = [];
controller.removedNiches = [1];
$httpBackend.whenGET(`/item/api/ItemNiches?filter={"where":{},"include":{"relation":"warehouse"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemNiches/crudItemNiches`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to update niches", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.niches = [{id: 1, warehouseFk: 1, code: '2222'}];
controller.oldNiches = {1: {id: 1, warehouseFk: 1, code: '1111'}};
$httpBackend.whenGET(`/item/api/ItemNiches?filter={"where":{},"include":{"relation":"warehouse"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemNiches/crudItemNiches`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to create new niche", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.niches = [{warehouseFk: 1, code: 1111, itemFk: 1}];
$httpBackend.whenGET(`/item/api/ItemNiches?filter={"where":{},"include":{"relation":"warehouse"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemNiches/crudItemNiches`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should return a message 'No changes to save' when there are no changes to apply", () => {
controller.$scope.form = {$setPristine: () => {}};
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.oldNiches = [
{warehouseFk: 1, code: 1, itemFk: 1, id: 1},
{warehouseFk: 2, code: 2, itemFk: 1, id: 2}
];
controller.niches = [];
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
});
});
});
});

View File

@ -1,18 +1,27 @@
<mg-ajax path="/item/api/Tags" options="mgIndex as tags"></mg-ajax> <vn-crud-model
vn-id="model"
url="/item/api/ItemTags"
fields="['id', 'itemFk', 'tagFk', 'value', 'priority']"
link="{itemFk: $ctrl.$stateParams.id}"
filter="{include: {relation: 'tag'}}"
order="priority ASC"
data="itemTags">
</vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="itemTags"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Tags</vn-title> <vn-title>Tags</vn-title>
<vn-horizontal ng-repeat="itemTag in $ctrl.tags"> <vn-horizontal ng-repeat="itemTag in itemTags">
<vn-autocomplete <vn-autocomplete
vn-id="tag" vn-id="tag"
vn-one vn-one
initial-data="itemTag.tag"
field="itemTag.tagFk" field="itemTag.tagFk"
data="tags.model" url="/item/api/Tags"
select-fields="['id','name','isFree']"
show-field="name" show-field="name"
label="Tag" label="Tag"
on-change="itemTag.value = null" on-change="itemTag.value = null"
@ -54,7 +63,7 @@
margin-medium-v margin-medium-v
vn-tooltip="Remove tag" vn-tooltip="Remove tag"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.removeTag($index)" ng-click="model.remove($index)"
tabindex="-1"> tabindex="-1">
</vn-icon-button> </vn-icon-button>
</vn-none> </vn-none>
@ -64,7 +73,7 @@
vn-bind="+" vn-bind="+"
vn-tooltip="Add tag" vn-tooltip="Add tag"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.addTag()"> ng-click="$ctrl.add()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,63 +1,27 @@
import ngModule from '../module'; import ngModule from '../module';
class Controller { class Controller {
constructor($stateParams, $scope, $http) { constructor($stateParams, $scope) {
this.params = $stateParams; this.$stateParams = $stateParams;
this.$ = $scope; this.$scope = $scope;
this.$http = $http;
this.tags = [];
this.removedTags = [];
} }
$onInit() { add() {
this.getTags(); this.$scope.model.insert({
} itemFk: this.$stateParams.id,
priority: this.getHighestPriority()
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.tags = response.data;
this.itemTags = JSON.parse(JSON.stringify(this.tags));
this.orgTags = {};
this.tags.forEach(tag => {
this.orgTags[tag.id] = Object.assign({}, tag);
});
this.$.form.$setPristine();
}); });
} }
addTag() { getHighestPriority() {
this.tags.push({
itemFk: this.params.id,
priority: this.getHighestPriority(this.tags)
});
}
getHighestPriority(tags) {
let max = 0; let max = 0;
tags.forEach(tag => { this.$scope.model.data.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;
@ -70,43 +34,18 @@ class Controller {
} }
} }
tagIsEqual(oldTag, newTag) { onSubmit() {
return oldTag.tagFk === newTag.tagFk && this.$scope.watcher.check();
oldTag.value === newTag.value && this.$scope.model.save().then(() => {
oldTag.priority === newTag.priority; this.$scope.watcher.notifySaved();
} this.$scope.model.refresh();
submit() {
this.$.watcher.check();
let changes = {
delete: this.removedTags,
create: [],
update: []
};
this.tags.forEach(tag => {
if (!tag.id)
changes.create.push(tag);
else if (!this.tagIsEqual(this.orgTags[tag.id], tag)) {
let tagToUpdate = Object.assign({}, tag);
delete tagToUpdate.tag;
changes.update.push(tagToUpdate);
}
});
this.$http.post(`/item/api/ItemTags/crud`, changes).then(() => {
this.getTags();
this.$.watcher.notifySaved();
}); });
} }
} }
Controller.$inject = ['$stateParams', '$scope', '$http']; Controller.$inject = ['$stateParams', '$scope'];
ngModule.component('vnItemTags', { ngModule.component('vnItemTags', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller
bindings: {
itemTags: '='
}
}); });

View File

@ -1,71 +1,34 @@
import './index.js'; import './index.js';
import {crudModel} from '../../../helpers/crudModelHelper';
describe('Item', () => { describe('Item', () => {
describe('Component vnItemTags', () => { describe('Component vnItemTags', () => {
let $componentController; let $componentController;
let $state; let $scope;
let controller; let controller;
let $httpBackend;
beforeEach(() => { beforeEach(() => {
angular.mock.module('item'); angular.mock.module('item');
}); });
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => { beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$state = _$state_; $scope = $rootScope.$new();
$httpBackend = _$httpBackend_; $scope.model = crudModel;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $scope.model.data = [{priority: 1}, {priority: 2}, {priority: 1}];
controller = $componentController('vnItemTags', {$state: $state}); controller = $componentController('vnItemTags', {$scope});
controller.$.form = {
$setPristine: () => {},
$dirty: true
};
controller.$.watcher = {
notifySaved: () => {},
check: () => {}
};
})); }));
describe('add / remove tags', () => {
it('should add one empty tag into controller tags collection', () => {
controller.tags = [];
controller.addTag();
expect(controller.tags.length).toEqual(1);
expect(controller.tags[0].id).toBe(undefined);
});
it('should remove a tag that occupies the position in the index', () => {
controller.$.form = {$setDirty: () => {}};
spyOn(controller.$.form, '$setDirty');
let index = 2;
controller.tags = [
{id: 1, typeFk: 1, value: '1111', showAddIcon: false},
{id: 2, typeFk: 2, value: '2222', showAddIcon: false},
{id: 3, typeFk: 3, value: '3333', showAddIcon: true}
];
controller.removeTag(index);
expect(controller.tags.length).toEqual(2);
expect(controller.tags[index]).toBe(undefined);
expect(controller.$.form.$setDirty).toHaveBeenCalledWith();
});
});
describe('getHighestPriority', () => { describe('getHighestPriority', () => {
it('should return the highest priority value + 1 from the array', () => { it('should return the highest priority value + 1 from the array', () => {
let tags = [{priority: 1}, {priority: 2}, {priority: 1}]; let result = controller.getHighestPriority();
let result = controller.getHighestPriority(tags);
expect(result).toEqual(3); expect(result).toEqual(3);
}); });
it('should return 1 when there is no priority defined', () => { it('should return 1 when there is no priority defined', () => {
let tags = []; $scope.model.data = [];
let result = controller.getHighestPriority(tags); let result = controller.getHighestPriority();
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
@ -93,100 +56,5 @@ describe('Item', () => {
expect(result).toBe("/api/ItemTags/filterItemTags/3"); expect(result).toBe("/api/ItemTags/filterItemTags/3");
}); });
}); });
describe('tagIsEqual()', () => {
it('should return true if two tags are equals independent of control attributes', () => {
let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true};
let tag2 = {id: 1, typeFk: 1, value: '1111', showAddIcon: false};
let equals = controller.tagIsEqual(tag2, tag1);
expect(equals).toBeTruthy();
});
it('should return false if two tags aint equal independent of control attributes', () => {
let tag1 = {id: 1, typeFk: 1, value: '1111', showAddIcon: true};
let tag2 = {id: 1, typeFk: 1, value: '2222', showAddIcon: true};
let equals = controller.tagIsEqual(tag2, tag1);
expect(equals).toBeFalsy();
});
});
describe('get itemTags', () => {
it('should perform a GET query to receive the item tags', () => {
controller.$.form = {$setPristine: () => {}};
spyOn(controller.$.form, '$setPristine');
let res = [{id: 1, typeFk: 1, value: '1111'}];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond(res);
$httpBackend.expectGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`);
controller.getTags();
$httpBackend.flush();
expect(controller.$.form.$setPristine).toHaveBeenCalledWith();
});
});
describe('submit()', () => {
// TODO: Server validation should be implemented
xit("should return an error message 'The tag must be unique' when the tag value isnt unique", () => {
controller.$.form = [];
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.tags = [
{typeFk: 1, value: 123454, itemFk: 1, id: 1},
{typeFk: 1, value: 123454, itemFk: 1}
];
controller.orgTags = {1: {typeFk: 1, id: 1, value: 123454, itemFk: 1}};
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('The tag must be unique');
});
it("should perfom a query to delete tags", () => {
controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}};
controller.tags = [];
controller.removedTags = [1];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to update tags", () => {
controller.tags = [{id: 1, typeFk: 1, value: '2222'}];
controller.orgTags = {1: {id: 1, typeFk: 1, value: '1111'}};
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to create new tag", () => {
controller.tags = [{typeFk: 1, value: 1111, itemFk: 1}];
$httpBackend.whenGET(`/item/api/ItemTags?filter={"where":{},"order":"priority ASC","include":{"relation":"tag"}}`).respond([]);
$httpBackend.expectPOST(`/item/api/ItemTags/crud`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should throw 'No changes to save' error when there are no changes to apply", () => {
controller.$.watcher = {
check: () => {
throw new Error('No changes to save');
}
};
controller.orgTags = [];
controller.tags = [];
expect(function() {
controller.submit();
}).toThrowError();
});
});
}); });
}); });

View File

@ -0,0 +1,6 @@
<vn-popover vn-id="popover">
<vn-ticket-descriptor
ticket="$ctrl.ticket"
quicklinks="$ctrl.quicklinks">
</vn-ticket-descriptor>
</vn-popover>

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
import Component from 'core/src/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $scope, $http) {
super($element, $scope);
this.$http = $http;
this._quicklinks = {};
this.isTooltip = true;
this.clear();
}
set ticketFk(value) {
if (value) {
let filter = {
include: [
{relation: 'warehouse', scope: {fields: ['name']}},
{relation: 'agencyMode', scope: {fields: ['name']}},
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name', 'isActive', 'isFreezed', 'isTaxDataChecked'],
include: {
relation: 'salesPerson',
fields: ['firstName', 'name']
}
}
},
{
relation: 'tracking',
scope: {
fields: ['stateFk'],
include: {
relation: 'state',
fields: ['name']
}
}
}
]
};
let json = encodeURIComponent(JSON.stringify(filter));
let query = `/ticket/api/Tickets/${value}?filter=${json}`;
this.$http.get(query).then(res => {
if (res.data)
this.ticket = res.data;
});
} else
this.clear();
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
}
clear() {
this.ticket = null;
}
show() {
this.$.popover.parent = this.parent;
this.$.popover.show();
}
}
Controller.$inject = ['$element', '$scope', '$http'];
ngModule.component('vnTicketDescriptorPopover', {
template: require('./index.html'),
bindings: {
ticketFk: '<',
quicklinks: '<'
},
controller: Controller
});

View File

@ -0,0 +1,64 @@
import './index.js';
describe('Item', () => {
describe('Component vnTicketDescriptorPopover', () => {
let $componentController;
let $scope;
let controller;
let $httpBackend;
let $element;
beforeEach(() => {
angular.mock.module('item');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$element = angular.element('<vn-ticket-descriptor-popover></vn-ticket-descriptor-popover>');
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
controller = $componentController('vnTicketDescriptorPopover', {$scope, $element, $httpBackend});
controller.parent = 'mariano';
controller.$.popover = {show: () => {}};
}));
describe('ticketFk setter', () => {
it(`shoud call clear if the given values is null`, () => {
spyOn(controller, 'clear');
controller.ticketFk = null;
expect(controller.clear).toHaveBeenCalledWith();
expect(controller.ticket).toEqual(null);
});
});
describe('quicklinks setter', () => {
it(`shoud set _quicklinks to a given value`, () => {
controller.quicklinks = 3;
expect(controller._quicklinks).toEqual(3);
});
});
describe('clear()', () => {
it(`should set ticket to null`, () => {
controller.ticket = '1';
controller.clear();
expect(controller.ticket).toEqual(null);
});
});
describe('show()', () => {
it(`should set $.popover.parent and call $.popover.show`, () => {
spyOn(controller.$.popover, 'show');
controller.show();
expect(controller.$.popover.show).toHaveBeenCalledWith();
expect(controller.$.popover.parent).toEqual('mariano');
});
});
});
});

View File

@ -0,0 +1,15 @@
vn-ticket-descriptor-popover {
vn-ticket-descriptor {
display: block;
width: 16em;
max-height: 28em;
&>vn-card {
margin: 0!important;
}
.header > a:first-child {
display: none
}
}
}

View File

@ -11,30 +11,30 @@
<div pad-medium> <div pad-medium>
<h5>{{::$ctrl.client.name}}</h5> <h5>{{::$ctrl.client.name}}</h5>
<vn-label-value label="Id" <vn-label-value label="Id"
value="{{::$ctrl.ticket.id}}"> value="{{$ctrl.ticket.id}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Client" <vn-label-value label="Client"
class="link" class="link"
ng-click="$ctrl.showDescriptor($event, $ctrl.ticket.client.id)" ng-click="$ctrl.showDescriptor($event, $ctrl.ticket.client.id)"
value="{{::$ctrl.ticket.client.name}}"> value="{{$ctrl.ticket.client.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="State" <vn-label-value label="State"
value="{{$ctrl.ticket.tracking.state.name}}"> value="{{$ctrl.ticket.tracking.state.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Sales person" <vn-label-value label="Sales person"
value="{{::$ctrl.ticket.client.salesPerson.firstName}} {{::$ctrl.ticket.client.salesPerson.name}}"> value="{{$ctrl.ticket.client.salesPerson.firstName}} {{$ctrl.ticket.client.salesPerson.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Shipped" <vn-label-value label="Shipped"
value="{{$ctrl.ticket.shipped | date: 'dd/MM/yyyy HH:mm' }}"> value="{{$ctrl.ticket.shipped | date: 'dd/MM/yyyy HH:mm' }}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Agency" <vn-label-value label="Agency"
value="{{::$ctrl.ticket.agencyMode.name}}"> value="{{$ctrl.ticket.agencyMode.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Warehouse" <vn-label-value label="Warehouse"
value="{{::$ctrl.ticket.warehouse.name}}"> value="{{$ctrl.ticket.warehouse.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Alias" <vn-label-value label="Alias"
value="{{::$ctrl.ticket.nickname}}"> value="{{$ctrl.ticket.nickname}}">
</vn-label-value> </vn-label-value>
</div> </div>
<vn-horizontal pad-medium-bottom class="footer"> <vn-horizontal pad-medium-bottom class="footer">

View File

@ -0,0 +1,30 @@
import ngModule from '../module';
class Controller {
constructor($http, $scope) {
this.$scope = $scope;
this.$http = $http;
}
showDescriptor(event, clientFk) {
this.$scope.descriptor.clientFk = clientFk;
this.$scope.descriptor.parent = event.target;
this.$scope.descriptor.show();
event.preventDefault();
event.stopImmediatePropagation();
}
onDescriptorLoad() {
this.$scope.popover.relocate();
}
}
Controller.$inject = ['$http', '$scope'];
ngModule.component('vnTicketDescriptor', {
template: require('./index.html'),
bindings: {
ticket: '<'
},
controller: Controller
});

View File

@ -1,11 +1,8 @@
auth: [] auth: []
client: [] client: []
core: [] core: []
item: [] item: [client]
#locator: []
#production: []
salix: [] salix: []
#route: []
ticket: [item, client] ticket: [item, client]
order: [item, ticket] order: [item, ticket]
claim: [item, client] claim: [item, client]

View File

@ -163,7 +163,7 @@
"params": { "params": {
"ticket": "$ctrl.ticket" "ticket": "$ctrl.ticket"
}, },
"acl": ["production"] "acl": ["production", "administrative"]
}, },
{ {
"url" : "/sale-checked", "url" : "/sale-checked",

View File

@ -1,42 +0,0 @@
import ngModule from '../module';
class Controller {
constructor($http, $scope) {
this.$ = $scope;
this.$http = $http;
}
getClientDebt(clientFk) {
this.$http.get(`/client/api/Clients/${clientFk}/getDebt`).then(response => {
this.clientDebt = response.data.debt;
});
}
showDescriptor(event, clientFk) {
this.$.descriptor.clientFk = clientFk;
this.$.descriptor.parent = event.target;
this.$.descriptor.show();
event.preventDefault();
event.stopImmediatePropagation();
}
onDescriptorLoad() {
this.$.popover.relocate();
}
$onChanges() {
if (this.ticket)
this.getClientDebt(this.ticket.clientFk);
}
}
Controller.$inject = ['$http', '$scope'];
ngModule.component('vnTicketDescriptor', {
template: require('./index.html'),
bindings: {
ticket: '<'
},
controller: Controller
});

View File

@ -1,36 +0,0 @@
import './index';
describe('Ticket', () => {
describe('Component vnTicketDescriptor', () => {
let $componentController;
let controller;
let $scope;
let $httpBackend;
beforeEach(() => {
angular.mock.module('ticket');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
controller = $componentController('vnTicketDescriptor', {$scope: $scope});
}));
describe('getClientDebt()', () => {
it('should store the client debt into the controller', () => {
let clientFk = 101;
let response = {debt: 10};
$httpBackend.when('GET', `/client/api/Clients/${clientFk}/getDebt`).respond(response);
$httpBackend.expect('GET', `/client/api/Clients/${clientFk}/getDebt`);
controller.getClientDebt(clientFk);
$httpBackend.flush();
expect(controller.clientDebt).toEqual(response.debt);
});
});
});
});

View File

@ -44,7 +44,6 @@ Shipment: Salida
Shipped: F. envio Shipped: F. envio
Some fields are invalid: Algunos campos no son válidos Some fields are invalid: Algunos campos no son válidos
State: Estado State: Estado
The observation type must be unique: El tipo de observación debe ser único
Tickets: Tickets Tickets: Tickets
Total import: Importe total Total import: Importe total
Warehouse: Almacén Warehouse: Almacén

View File

@ -1,38 +1,36 @@
<vn-crud-model
vn-id="model"
url="/ticket/api/TicketObservations"
fields="['id', 'ticketFk', 'observationTypeFk', 'description']"
link="{ticketFk: $ctrl.$stateParams.id}"
data="observations">
</vn-crud-model>
<vn-crud-model
url="/ticket/api/ObservationTypes"
data="observationTypes">
</vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
url="/ticket/api/Observation" data="observations"
id-field="id"
data="$ctrl.address"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card pad-large> <vn-card pad-large>
<vn-one margin-medium-top> <vn-one margin-medium-top>
<vn-title>Notes</vn-title> <vn-title>Notes</vn-title>
<mg-ajax path="/ticket/api/ObservationTypes" options="mgIndex as observationTypes"></mg-ajax> <vn-horizontal ng-repeat="observation in observations track by $index">
<vn-horizontal ng-repeat="ticketObservation in $ctrl.ticketObservations track by $index">
<vn-autocomplete <vn-autocomplete
ng-if="!ticketObservation.id" ng-attr-disabled="observation.id ? true : false"
vn-one field="observation.observationTypeFk"
vn-focus data="observationTypes"
initial-data="ticketObservation.observationType"
field="ticketObservation.observationTypeFk"
data="observationTypes.model"
show-field="description" show-field="description"
label="Observation type"> label="Observation type" vn-one vn-focus>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield
ng-if="ticketObservation.id"
vn-one
label="Observation type"
model="ticketObservation.observationType.description"
disabled="true">
</vn-textfield>
<vn-textfield <vn-textfield
vn-two vn-two
margin-large-right margin-large-right
label="Description" label="Description"
model="ticketObservation.description" model="observation.description"
rule="ticketObservation.description"> rule="ticketObservation.description">
</vn-textfield> </vn-textfield>
<vn-auto pad-medium-top> <vn-auto pad-medium-top>
@ -41,7 +39,7 @@
medium-grey medium-grey
vn-tooltip="Remove note" vn-tooltip="Remove note"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.removeObservation($index)"> ng-click="model.remove($index)">
</vn-icon> </vn-icon>
</vn-auto> </vn-auto>
</vn-horizontal> </vn-horizontal>
@ -51,8 +49,8 @@
pointer vn-tooltip="Add note" pointer vn-tooltip="Add note"
icon="add_circle" icon="add_circle"
vn-bind="+" vn-bind="+"
ng-if="observationTypes.model.length > $ctrl.ticketObservations.length" ng-if="observationTypes.length > observations.length"
ng-click="$ctrl.addObservation()"> ng-click="model.insert()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,136 +1,21 @@
import ngModule from '../module'; import ngModule from '../module';
class Controller { class Controller {
constructor($stateParams, $scope, $http, $translate, vnApp) { constructor($stateParams, $scope) {
this.params = $stateParams; this.$stateParams = $stateParams;
this.$scope = $scope; this.$scope = $scope;
this.$http = $http;
this.$translate = $translate;
this.vnApp = vnApp;
this.ticketObservations = [];
this.oldObservations = {};
this.removedObservations = [];
} }
_setIconAdd() { onSubmit() {
if (this.ticketObservations.length) { this.$scope.watcher.check();
this.ticketObservations.map(element => { this.$scope.model.save().then(() => {
element.showAddIcon = false; this.$scope.watcher.notifySaved();
return true; this.$scope.model.refresh();
});
this.ticketObservations[this.ticketObservations.length - 1].showAddIcon = true;
}
}
_setDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setDirty();
}
}
_unsetDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setPristine();
}
}
addObservation() {
this.ticketObservations.push({description: null, ticketFk: this.params.id, showAddIcon: true});
this._setIconAdd();
}
removeObservation(index) {
let item = this.ticketObservations[index];
if (item) {
this.ticketObservations.splice(index, 1);
this._setIconAdd();
if (item.id) {
this.removedObservations.push(item.id);
this._setDirtyForm();
}
}
}
_equalObservations(oldObservation, newObservation) {
return oldObservation.id === newObservation.id && oldObservation.observationTypeFk === newObservation.observationTypeFk && oldObservation.description === newObservation.description;
}
setOldObservations(response) {
this._setIconAdd();
response.data.forEach(observation => {
this.oldObservations[observation.id] = Object.assign({}, observation);
}); });
} }
getObservations() {
let filter = {
where: {ticketFk: this.params.id},
include: ['observationType']
};
this.$http.get(`/ticket/api/TicketObservations?filter=${JSON.stringify(filter)}`).then(response => {
this.ticketObservations = response.data;
this.setOldObservations(response);
});
}
submit() {
let typesDefined = [];
let repeatedType = false;
let canSubmit;
let observationsObj = {
delete: this.removedObservations,
create: [],
update: []
};
this.ticketObservations.forEach(observation => {
let isNewObservation = !observation.id;
delete observation.showAddIcon;
if (typesDefined.indexOf(observation.observationTypeFk) !== -1) {
repeatedType = true;
return;
}
typesDefined.push(observation.observationTypeFk);
if (isNewObservation && observation.description && observation.observationTypeFk) {
observationsObj.create.push(observation);
}
if (!isNewObservation && !this._equalObservations(this.oldObservations[observation.id], observation)) {
observationsObj.update.push(observation);
}
});
if (this.$scope.form.$invalid) {
return this.vnApp.showError(this.$translate.instant('Some fields are invalid'));
}
if (repeatedType) {
return this.vnApp.showError(this.$translate.instant('The observation type must be unique'));
}
canSubmit = observationsObj.update.length > 0 || observationsObj.create.length > 0 || observationsObj.delete.length > 0;
if (canSubmit) {
return this.$http.post(`/ticket/api/TicketObservations/crudTicketObservation`, observationsObj).then(() => {
this.getObservations();
this._unsetDirtyForm();
this.$scope.watcher.notifySaved();
});
}
this.vnApp.showError(this.$translate.instant('No changes to save'));
}
$onInit() {
this.getObservations();
}
} }
Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnApp']; Controller.$inject = ['$stateParams', '$scope'];
ngModule.component('vnTicketObservation', { ngModule.component('vnTicketObservation', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -1,164 +0,0 @@
import './index.js';
describe('ticket', () => {
describe('Component vnTicketObservation', () => {
let $componentController;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('ticket');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnTicketObservation', {$state: $state});
controller.$scope.watcher = {
notifySaved: () => {}
};
}));
describe('add / remove observation', () => {
it('should add one empty observation into controller observations collection and call _setIconAdd()', () => {
controller.ticketObservations = [];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.addObservation();
expect(controller.ticketObservations.length).toEqual(1);
expect(controller.ticketObservations[0].id).toBe(undefined);
expect(controller.ticketObservations[0].showAddIcon).toBeTruthy();
expect(controller._setIconAdd).toHaveBeenCalledWith();
});
it('should remove an observation that occupies the position in the index given and call _setIconAdd()', () => {
let index = 2;
controller.ticketObservations = [
{id: 1, observationTypeFk: 1, description: 'one', showAddIcon: false},
{id: 2, observationTypeFk: 2, description: 'two', showAddIcon: false},
{id: 3, observationTypeFk: 3, description: 'three', showAddIcon: true}
];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.removeObservation(index);
expect(controller.ticketObservations.length).toEqual(2);
expect(controller.ticketObservations[0].showAddIcon).toBeFalsy();
expect(controller.ticketObservations[1].showAddIcon).toBeTruthy();
expect(controller.ticketObservations[index]).toBe(undefined);
expect(controller._setIconAdd).toHaveBeenCalledWith();
});
});
describe('_equalObservations()', () => {
it('should return true if two observations are equals independent of control attributes', () => {
let observationOne = {id: 1, observationTypeFk: 1, description: 'one', showAddIcon: true};
let observationTwo = {id: 1, observationTypeFk: 1, description: 'one', showAddIcon: false};
let equals = controller._equalObservations(observationOne, observationTwo);
expect(equals).toBeTruthy();
});
it('should return false if two observations aint equals independent of control attributes', () => {
let observationOne = {id: 1, observationTypeFk: 1, description: 'one', showAddIcon: true};
let observationTwo = {id: 1, observationTypeFk: 1, description: 'two', showAddIcon: true};
let equals = controller._equalObservations(observationOne, observationTwo);
expect(equals).toBeFalsy();
});
});
describe('get Observations()', () => {
it('should perform a GET query to receive the ticket observations', () => {
let response = [{id: 1, observationTypeFk: 1, description: 'one'}];
spyOn(controller, 'setOldObservations');
$httpBackend.whenGET(`/ticket/api/TicketObservations?filter={"where":{},"include":["observationType"]}`).respond(response);
$httpBackend.expectGET(`/ticket/api/TicketObservations?filter={"where":{},"include":["observationType"]}`);
controller.getObservations();
$httpBackend.flush();
expect(controller.setOldObservations).toHaveBeenCalledWith(jasmine.any(Object));
});
});
describe('submit()', () => {
it("should return an error message 'Some fields are invalid'", () => {
controller.$scope.form = {};
controller.$scope.form.$invalid = true;
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.ticketObservations = [
{id: 1, observationTypeFk: 1, description: 'one', itemFk: 1},
{observationTypeFk: 1, description: 'one', itemFk: 1}
];
controller.oldObservations = {1: {id: 1, observationTypeFk: 1, description: 'one', itemFk: 1}};
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('Some fields are invalid');
});
it("should return an error message 'The observation type must be unique'", () => {
controller.$scope.form = {};
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.ticketObservations = [
{id: 1, observationTypeFk: 1, description: 'one', itemFk: 1},
{observationTypeFk: 1, description: 'one', itemFk: 1}
];
controller.oldObservations = {1: {id: 1, observationTypeFk: 1, description: 'one', itemFk: 1}};
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('The observation type must be unique');
});
it("should perfom a query to delete observations", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.oldObservations = {1: {id: 1, observationTypeFk: 1, description: 'one'}};
controller.ticketObservations = [];
controller.removedObservations = [1];
$httpBackend.whenGET(`/ticket/api/TicketObservations?filter={"where":{},"include":["observationType"]}`).respond([]);
$httpBackend.expectPOST(`/ticket/api/TicketObservations/crudTicketObservation`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to update observations", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.ticketObservations = [{id: 1, observationTypeFk: 1, description: 'number one!'}];
controller.oldObservations = {1: {id: 1, observationTypeFk: 1, description: 'one'}};
$httpBackend.whenGET(`/ticket/api/TicketObservations?filter={"where":{},"include":["observationType"]}`).respond([]);
$httpBackend.expectPOST(`/ticket/api/TicketObservations/crudTicketObservation`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should perfom a query to create new observation", () => {
controller.$scope.form = {$setPristine: () => {}};
controller.ticketObservations = [{observationTypeFk: 2, description: 'two'}];
$httpBackend.whenGET(`/ticket/api/TicketObservations?filter={"where":{},"include":["observationType"]}`).respond([]);
$httpBackend.expectPOST(`/ticket/api/TicketObservations/crudTicketObservation`).respond('ok!');
controller.submit();
$httpBackend.flush();
});
it("should return a message 'No changes to save' when there are no changes to apply", () => {
controller.$scope.form = {$setPristine: () => {}};
spyOn(controller.vnApp, 'showError').and.callThrough();
controller.oldObservations = [
{id: 1, observationTypeFk: 1, description: 'one', showAddIcon: false},
{id: 2, observationTypeFk: 2, description: 'two', showAddIcon: true}
];
controller.ticketObservations = [];
controller.submit();
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
});
});
});
});

View File

@ -1,17 +1,16 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="/ticket/api/TicketPackagings" url="/ticket/api/TicketPackagings"
fields="['id', 'ticketFk', 'packagingFk', 'quantity', 'created']"
link="{ticketFk: $ctrl.$stateParams.id}" link="{ticketFk: $ctrl.$stateParams.id}"
data="packages" on-data-change="$ctrl.getPackages()"> data="packages" on-data-change="$ctrl.onDataChange()">
</vn-crud-model> </vn-crud-model>
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="packages" data="packages"
form="form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()">
<form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
<vn-title>Packages</vn-title> <vn-title>Packages</vn-title>
<vn-one> <vn-one>
@ -45,7 +44,7 @@
medium-grey medium-grey
vn-tooltip="Remove package" vn-tooltip="Remove package"
icon="remove_circle_outline" icon="remove_circle_outline"
ng-click="$ctrl.removePackage($index)"> ng-click="model.remove($index)">
</vn-icon> </vn-icon>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
@ -56,7 +55,7 @@
vn-tooltip="Add package" vn-tooltip="Add package"
vn-bind="+" vn-bind="+"
icon="add_circle" icon="add_circle"
ng-click="$ctrl.addPackage()"> ng-click="$ctrl.add()">
</vn-icon-button> </vn-icon-button>
</vn-one> </vn-one>
</vn-card> </vn-card>

View File

@ -1,80 +1,32 @@
import ngModule from '../module'; import ngModule from '../module';
class Controller { class Controller {
constructor($http, $scope, $stateParams, $translate, vnApp) { constructor($scope, $stateParams) {
this.$http = $http;
this.$scope = $scope; this.$scope = $scope;
this.$stateParams = $stateParams; this.$stateParams = $stateParams;
this.$translate = $translate;
this.vnApp = vnApp;
this.removedPackages = [];
} }
submit() { add() {
let query = `/ticket/api/TicketPackagings/crud`; this.$scope.model.insert({
let packagesObj = { packagingFk: null,
delete: this.removedPackages, quantity: null,
create: [], created: new Date(),
update: [] ticketFk: this.$stateParams.id
};
this.packages.forEach(item => {
if (typeof item.id === 'undefined')
packagesObj.create.push(item);
if (typeof item.id !== 'undefined' && !this.packageEquals(item, this.oldPackages[item.id]))
packagesObj.update.push(item);
}); });
}
onSubmit() {
this.$scope.watcher.check(); this.$scope.watcher.check();
this.$scope.model.save().then(() => {
this.$http.post(query, packagesObj).then(res => {
this.$scope.watcher.notifySaved(); this.$scope.watcher.notifySaved();
this.$scope.model.refresh(); this.$scope.model.refresh();
}); });
} }
removePackage(index) {
if (this.packages[index] && this.packages[index].id)
this.removedPackages.push(this.packages[index].id);
this.packages.splice(index, 1);
}
addPackage() {
let data = {
packagingFk: null,
quantity: null,
created: Date.now(),
ticketFk: this.ticket.id
};
this.packages.push(data);
}
getPackages() {
this.packages = this.$scope.model.data;
this.setOldPackages();
}
setOldPackages() {
this.oldPackages = [];
this.removedPackages = [];
this.packages.forEach(item => {
this.oldPackages[item.id] = Object.assign({}, item);
});
}
packageEquals(newPackage, oldPackage) {
return newPackage.packagingFk === oldPackage.packagingFk && newPackage.quantity == oldPackage.quantity;
}
} }
Controller.$inject = ['$http', '$scope', '$stateParams', '$translate', 'vnApp']; Controller.$inject = ['$scope', '$stateParams'];
ngModule.component('vnTicketPackageIndex', { ngModule.component('vnTicketPackageIndex', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller
bindings: {
ticket: '<'
}
}); });

View File

@ -1,88 +0,0 @@
import './index.js';
describe('Ticket', () => {
describe('Component vnTicketPackageIndex', () => {
let $componentController;
let controller;
let $httpBackend;
let $scope;
beforeEach(() => {
angular.mock.module('ticket');
});
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_, $rootScope) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = {
model: {
refresh: () => {}
},
watcher: {
check: () => {},
notifySaved: () => {}
}
};
controller = $componentController('vnTicketPackageIndex', {$scope: $scope});
}));
describe('removePackage()', () => {
it('should push a package to removedPackages in the controller', () => {
controller.packages = [{id: 1}, {id: 2}];
controller.removePackage(0);
expect(controller.removedPackages).toEqual([1]);
});
});
describe('submit()', () => {
it('should perform a post', () => {
spyOn(controller.$scope.watcher, 'notifySaved');
spyOn(controller.$scope.model, 'refresh');
controller.removedPackages = [];
controller.oldPackages = [];
controller.oldPackages[1] = {id: 1, packagingFk: 1, quantity: 5, ticketFk: 1};
controller.packages = [
{quantity: 5, packagingFk: 2, ticketFk: 1},
{id: 1, packagingFk: 1, quantity: 25, ticketFk: 1}
];
let data = {
delete: [],
create: [{quantity: 5, packagingFk: 2, ticketFk: 1}],
update: [{id: 1, packagingFk: 1, quantity: 25, ticketFk: 1}]
};
$httpBackend.when('POST', '/ticket/api/TicketPackagings/crud', data).respond(200);
$httpBackend.expect('POST', '/ticket/api/TicketPackagings/crud', data).respond('ok');
controller.submit();
$httpBackend.flush();
expect(controller.$scope.model.refresh).toHaveBeenCalledWith();
expect(controller.$scope.watcher.notifySaved).toHaveBeenCalledWith();
});
});
describe('packageEquals()', () => {
it('should return true if the old package and the new one matches', () => {
let oldPackage = {quantity: 5, packagingFk: 2};
let newPackage = {quantity: 5, packagingFk: 2};
let result = controller.packageEquals(oldPackage, newPackage);
expect(result).toBeTruthy();
});
it(`should return false if the old package and the new one doesn't match`, () => {
let oldPackage = {quantity: 5, packagingFk: 2};
let newPackage = {quantity: 6, packagingFk: 2};
let result = controller.packageEquals(oldPackage, newPackage);
expect(result).toBeFalsy();
});
});
});
});

View File

@ -3,7 +3,6 @@ export * from './module';
import './search-panel'; import './search-panel';
import './index'; import './index';
import './card'; import './card';
import './descriptor';
import './summary'; import './summary';
import './data'; import './data';
import './data/step-one'; import './data/step-one';

View File

@ -37,6 +37,6 @@
</vn-pagination> </vn-pagination>
</vn-card> </vn-card>
</vn-vertical> </vn-vertical>
<a ui-sref="ticket.card.tracking.edit" vn-bind="+" vn-visible-by="production" fixed-bottom-right> <a ui-sref="ticket.card.tracking.edit" vn-bind="+" vn-visible-by="production, administrative" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>
</a> </a>

View File

@ -240,22 +240,19 @@ export default {
addBarcodeButton: `vn-icon[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) > vn-icon[icon="remove_circle_outline"]` firstCodeRemoveButton: `vn-item-barcode vn-horizontal vn-none vn-icon[icon="remove_circle_outline"]`
}, },
itemNiches: { itemNiches: {
nicheButton: `vn-menu-item a[ui-sref="item.card.niche"]`, nicheButton: `vn-menu-item a[ui-sref="item.card.niche"]`,
addNicheButton: `vn-icon[icon="add_circle"]`, addNicheButton: `vn-icon[icon="add_circle"]`,
firstWarehouseSelect: `vn-autocomplete[field="itemNiche.warehouseFk"] input`, firstWarehouseSelect: `vn-autocomplete[field="niche.warehouseFk"] input`,
firstWarehouseDisabled: `vn-horizontal:nth-child(2) > vn-textfield[label="Warehouse"] input`, firstWarehouseSelectSecondOption: `vn-autocomplete[field="niche.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"] input`, firstCodeInput: `vn-horizontal:nth-child(2) > vn-textfield[label="Code"] input`,
secondWarehouseSelect: `vn-horizontal:nth-child(3) > vn-autocomplete[field="itemNiche.warehouseFk"] input`, secondWarehouseSelect: `vn-horizontal:nth-child(3) > vn-autocomplete[field="niche.warehouseFk"] input`,
secondWarehouseDisabled: `vn-horizontal:nth-child(3) > vn-textfield[label="Warehouse"] input`,
secondCodeInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Code"] input`, secondCodeInput: `vn-horizontal:nth-child(3) > vn-textfield[label="Code"] input`,
secondNicheRemoveButton: `vn-horizontal:nth-child(3) > vn-icon[icon="remove_circle_outline"]`, secondNicheRemoveButton: `vn-horizontal:nth-child(3) > vn-none > vn-icon[icon="remove_circle_outline"]`,
thirdWarehouseSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="itemNiche.warehouseFk"] input`, thirdWarehouseSelect: `vn-horizontal:nth-child(4) > vn-autocomplete[field="niche.warehouseFk"] input`,
thirdWarehouseDisabled: `vn-horizontal:nth-child(4) > vn-textfield[label="Warehouse"] input`, thirdWarehouseSelectFourthOption: `vn-horizontal:nth-child(4) > vn-autocomplete[field="niche.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"] input`, thirdCodeInput: `vn-horizontal:nth-child(4) > vn-textfield[label="Code"] input`,
submitNichesButton: `${components.vnSubmit}` submitNichesButton: `${components.vnSubmit}`
}, },

View File

@ -65,10 +65,10 @@ describe('Item', () => {
.click(selectors.itemBasicData.basicDataButton) .click(selectors.itemBasicData.basicDataButton)
.wait(selectors.itemBasicData.nameInput) .wait(selectors.itemBasicData.nameInput)
.click(selectors.itemTags.tagsButton) .click(selectors.itemTags.tagsButton)
.waitForTextInInput(selectors.itemTags.firstTagDisabled, 'Diámetro') .waitForTextInInput(selectors.itemTags.firstTagSelect, 'Ancho de la base')
.getInputValue(selectors.itemTags.firstTagDisabled) .getInputValue(selectors.itemTags.firstTagSelect)
.then(result => { .then(result => {
expect(result).toEqual('Diámetro'); expect(result).toEqual('Ancho de la base');
}); });
}); });
@ -92,8 +92,8 @@ describe('Item', () => {
it(`should confirm the second select is the expected one`, () => { it(`should confirm the second select is the expected one`, () => {
return nightmare return nightmare
.waitForTextInInput(selectors.itemTags.secondTagDisabled, 'Variedad') .waitForTextInInput(selectors.itemTags.secondTagSelect, 'Variedad')
.getInputValue(selectors.itemTags.secondTagDisabled) .getInputValue(selectors.itemTags.secondTagSelect)
.then(result => { .then(result => {
expect(result).toEqual('Variedad'); expect(result).toEqual('Variedad');
}); });
@ -119,8 +119,8 @@ describe('Item', () => {
it(`should confirm the third select is the expected one`, () => { it(`should confirm the third select is the expected one`, () => {
return nightmare return nightmare
.waitForTextInInput(selectors.itemTags.thirdTagDisabled, 'Longitud(cm)') .waitForTextInInput(selectors.itemTags.thirdTagSelect, 'Longitud(cm)')
.getInputValue(selectors.itemTags.thirdTagDisabled) .getInputValue(selectors.itemTags.thirdTagSelect)
.then(result => { .then(result => {
expect(result).toEqual('Longitud(cm)'); expect(result).toEqual('Longitud(cm)');
}); });
@ -146,8 +146,8 @@ describe('Item', () => {
it(`should confirm the fourth select is the expected one`, () => { it(`should confirm the fourth select is the expected one`, () => {
return nightmare return nightmare
.waitForTextInInput(selectors.itemTags.fourthTagDisabled, 'Proveedor') .waitForTextInInput(selectors.itemTags.fourthTagSelect, 'Proveedor')
.getInputValue(selectors.itemTags.fourthTagDisabled) .getInputValue(selectors.itemTags.fourthTagSelect)
.then(result => { .then(result => {
expect(result).toEqual('Proveedor'); expect(result).toEqual('Proveedor');
}); });
@ -173,8 +173,8 @@ describe('Item', () => {
it(`should confirm the fifth select is the expected one`, () => { it(`should confirm the fifth select is the expected one`, () => {
return nightmare return nightmare
.waitForTextInInput(selectors.itemTags.fifthTagDisabled, 'Color') .waitForTextInInput(selectors.itemTags.fifthTagSelect, 'Color')
.getInputValue(selectors.itemTags.fifthTagDisabled) .getInputValue(selectors.itemTags.fifthTagSelect)
.then(result => { .then(result => {
expect(result).toEqual('Color'); expect(result).toEqual('Color');
}); });

View File

@ -63,8 +63,8 @@ describe('Item', () => {
.click(selectors.itemBasicData.basicDataButton) .click(selectors.itemBasicData.basicDataButton)
.wait(selectors.itemBasicData.nameInput) .wait(selectors.itemBasicData.nameInput)
.click(selectors.itemNiches.nicheButton) .click(selectors.itemNiches.nicheButton)
.waitForTextInInput(selectors.itemNiches.firstWarehouseDisabled, 'Warehouse One') .waitForTextInInput(selectors.itemNiches.firstWarehouseSelect, 'Warehouse One')
.getInputValue(selectors.itemNiches.firstWarehouseDisabled) .getInputValue(selectors.itemNiches.firstWarehouseSelect)
.then(result => { .then(result => {
expect(result).toEqual('Warehouse One'); expect(result).toEqual('Warehouse One');
return nightmare return nightmare
@ -77,7 +77,7 @@ describe('Item', () => {
it(`should confirm the second niche is the expected one`, () => { it(`should confirm the second niche is the expected one`, () => {
return nightmare return nightmare
.getInputValue(selectors.itemNiches.secondWarehouseDisabled) .getInputValue(selectors.itemNiches.secondWarehouseSelect)
.then(result => { .then(result => {
expect(result).toEqual('Warehouse Three'); expect(result).toEqual('Warehouse Three');
return nightmare return nightmare
@ -90,9 +90,9 @@ describe('Item', () => {
it(`should confirm the third niche is the expected one`, () => { it(`should confirm the third niche is the expected one`, () => {
return nightmare return nightmare
.getInputValue(selectors.itemNiches.thirdWarehouseDisabled) .getInputValue(selectors.itemNiches.thirdWarehouseSelect)
.then(result => { .then(result => {
expect(result).toEqual('Warehouse Four'); expect(result).toEqual('Warehouse Two');
return nightmare return nightmare
.getInputValue(selectors.itemNiches.thirdCodeInput); .getInputValue(selectors.itemNiches.thirdCodeInput);
}) })

View File

@ -1,3 +1,7 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-barcode/crudItemBarcodes')(Self); require('../methods/item-barcode/crudItemBarcodes')(Self);
Self.validatesUniquenessOf('code', {
message: `Barcode must be unique`
});
}; };

View File

@ -1,3 +1,23 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-niche/crudItemNiches')(Self); /* Self.validateAsync('warehouseFk', validateWarehouseUniqueness, {
message: `The warehouse can't be repeated`
});
async function validateWarehouseUniqueness(err, done) {
let where = {
itemFk: this.itemFk,
warehouseFk: this.warehouseFk
};
if (this.id != null)
where.id = {neq: this.id};
let warehouseExists = await Self.findOne({where: where});
console.log(warehouseExists);
if (warehouseExists)
err();
done();
} */
}; };

View File

@ -1,29 +1,38 @@
{ {
"name": "ItemNiche", "name": "ItemNiche",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "itemPlacement", "table": "itemPlacement",
"database": "vn" "database": "vn"
} }
},
"properties": {
"code": {
"type": "String",
"required": true
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
}, },
"warehouse": { "properties": {
"type": "belongsTo", "id": {
"model": "Warehouse", "type": "Number",
"foreignKey": "warehouseFk", "id": 1,
"required": true "description": "Identifier"
} },
} "warehouseFk": {
"type": "Number",
"description": "Identifier"
},
"code": {
"type": "String",
"required": true
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
},
"warehouse": {
"type": "belongsTo",
"model": "Warehouse",
"foreignKey": "warehouseFk",
"required": true
}
}
} }

View File

@ -30,5 +30,6 @@
"The grade must be an integer greater than or equal to zero": "The grade must be an integer greater than or equal to zero", "The grade must be an integer greater than or equal to zero": "The grade must be an integer greater than or equal to zero",
"Sample type cannot be blank": "Sample type cannot be blank", "Sample type cannot be blank": "Sample type cannot be blank",
"The new quantity should be smaller than the old one": "La nueva cantidad debe ser menor que la anterior", "The new quantity should be smaller than the old one": "La nueva cantidad debe ser menor que la anterior",
"The package cannot be blank": "The package cannot be blank" "The package cannot be blank": "The package cannot be blank",
"The warehouse can't be repeated": "The warehouse can't be repeated"
} }

View File

@ -42,5 +42,9 @@
"This order is not editable": "Esta orden no se puede modificar", "This order is not editable": "Esta orden no se puede modificar",
"You can't create an order for a frozen client": "No puedes crear una orden para un cliente congelado", "You can't create an order for a frozen client": "No puedes crear una orden para un cliente congelado",
"You can't create an order for a client that has a debt": "No puedes crear una orden para un cliente con deuda", "You can't create an order for a client that has a debt": "No puedes crear una orden para un cliente con deuda",
"is not a valid date": "No es una fecha valida" "is not a valid date": "No es una fecha valida",
"Barcode must be unique": "El código de barras debe ser único",
"The warehouse can't be repeated": "El almacén no puede repetirse",
"The tag can't be repeated": "El tag no puede repetirse",
"The observation type can't be repeated": "El tipo de observación no puede repetirse"
} }

View File

@ -1,3 +1,21 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-tag/filterItemTags')(Self); require('../methods/item-tag/filterItemTags')(Self);
/* Self.validateAsync('tagFk', validateTagUniqueness, {
message: `The tag can't be repeated`
});
async function validateTagUniqueness(err, done) {
let tagExists = await Self.findOne({
where: {
itemFk: this.itemFk,
tagFk: this.tagFk
}
});
if (tagExists)
err();
done();
} */
}; };

View File

@ -140,27 +140,35 @@ module.exports = function(Self) {
} }
}); });
this[methodName] = async actions => { this[methodName] = async actions => {
let promises = [];
let transaction = await this.beginTransaction({}); let transaction = await this.beginTransaction({});
let options = {transaction: transaction}; let options = {transaction: transaction};
try { try {
if (actions.delete && actions.delete.length) { if (actions.delete && actions.delete.length) {
promises.push(this.destroyAll({id: {inq: actions.delete}}, options)); await this.destroyAll({id: {inq: actions.delete}}, options);
}
if (actions.create && actions.create.length) {
promises.push(this.create(actions.create, options));
} }
if (actions.update) { if (actions.update) {
actions.update.forEach(toUpdate => { try {
promises.push(this.upsert(toUpdate, options)); let promises = [];
}); actions.update.forEach(toUpdate => {
promises.push(this.upsert(toUpdate, options));
});
await Promise.all(promises);
} catch (error) {
throw error[0];
}
}
if (actions.create && actions.create.length) {
try {
await this.create(actions.create, options);
} catch (error) {
throw error[error.length - 1];
}
} }
await Promise.all(promises);
await transaction.commit(); await transaction.commit();
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
throw Array.isArray(error) ? error[0] : error; throw error;
} }
}; };
}; };

View File

@ -1,3 +1,21 @@
module.exports = function(Self) { module.exports = function(Self) {
require('../methods/notes/crudTicketObservation.js')(Self); require('../methods/notes/crudTicketObservation.js')(Self);
/* Self.validateAsync('observationTypeFk', validateObservationUniqueness, {
message: `The observation type can't be repeated`
});
async function validateObservationUniqueness(err, done) {
let observationExists = await Self.findOne({
where: {
ticketFk: this.ticketFk,
observationTypeFk: this.observationTypeFk
}
});
if (observationExists)
err();
done();
} */
}; };