diff --git a/client/item/src/niche/item-niche.html b/client/item/src/niche/item-niche.html
index 9d2297ca2..7a7ea9f1d 100644
--- a/client/item/src/niche/item-niche.html
+++ b/client/item/src/niche/item-niche.html
@@ -1,5 +1,48 @@
-
-
- Niche
-
-
\ No newline at end of file
+
diff --git a/client/item/src/niche/item-niche.js b/client/item/src/niche/item-niche.js
index caa84c9ea..f12160483 100644
--- a/client/item/src/niche/item-niche.js
+++ b/client/item/src/niche/item-niche.js
@@ -1,5 +1,142 @@
import ngModule from '../module';
+export default class Controller {
+ constructor($state, $scope, $http, $q, $translate, vnApp) {
+ this.$state = $state;
+ this.$scope = $scope;
+ this.$http = $http;
+ this.$q = $q;
+ this.$translate = $translate;
+ this.vnApp = vnApp;
+
+ this.warehouses = [];
+ this.niches = [];
+ this.removedNiches = [];
+ this.oldNiches = {};
+ }
+
+ _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.$state.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;
+ }
+
+ 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 (repeatedWarehouse) {
+ return this.vnApp.showMessage(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.vnApp.showMessage(this.$translate.instant('No changes to save'));
+ }
+
+ setOldNiches(response) {
+ this._setIconAdd();
+ response.data.forEach(niche => {
+ this.oldNiches[niche.id] = Object.assign({}, niche);
+ });
+ }
+
+ getWarehouse(id, warehouses) {
+ return warehouses.find(warehouse => warehouse.id === id);
+ }
+
+ getNiches() {
+ let filter = {
+ where: {itemFk: this.$state.params.id},
+ include: {relation: 'warehouse'}
+ };
+ this.$http.get(`/item/api/ItemNiches?filter=${JSON.stringify(filter)}`).then(response => {
+ this.niches = response.data;
+ this.setOldNiches(response);
+ });
+ }
+
+ getWarehouses() {
+ this.$http.get(`/item/api/Warehouses`).then(response => {
+ this.warehouses = response.data;
+ });
+ }
+
+ $onInit() {
+ this.getNiches();
+ this.getWarehouses();
+ }
+}
+
+Controller.$inject = ['$state', '$scope', '$http', '$q', '$translate', 'vnApp'];
+
ngModule.component('vnItemNiche', {
- template: require('./item-niche.html')
+ template: require('./item-niche.html'),
+ controller: Controller
});
diff --git a/client/item/src/niche/item-niche.spec.js b/client/item/src/niche/item-niche.spec.js
new file mode 100644
index 000000000..b86fa9061
--- /dev/null
+++ b/client/item/src/niche/item-niche.spec.js
@@ -0,0 +1,150 @@
+import './item-niche.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_;
+ controller = $componentController('vnItemNiche', {$state: $state});
+ }));
+
+ 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.when('GET', `/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.when('GET', `/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 code isnt unique", () => {
+ spyOn(controller.vnApp, 'showMessage').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.showMessage).toHaveBeenCalledWith('The niche must be unique');
+ });
+
+ it("should perfom a query to delete niches", () => {
+ controller.oldNiches = {1: {id: 1, warehouseFk: 1, code: '1111'}};
+ controller.niches = [];
+ controller.removedNiches = [1];
+
+ $httpBackend.when('GET', `/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.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.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", () => {
+ spyOn(controller.vnApp, 'showMessage').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.showMessage).toHaveBeenCalledWith('No changes to save');
+ });
+ });
+ });
+});
diff --git a/client/production/src/index/index.html b/client/production/src/index/index.html
index 63e899206..be21fd321 100644
--- a/client/production/src/index/index.html
+++ b/client/production/src/index/index.html
@@ -4,28 +4,27 @@
diff --git a/client/route/src/create/create.html b/client/route/src/create/create.html
index 90dddbf71..8b7b7c6ba 100644
--- a/client/route/src/create/create.html
+++ b/client/route/src/create/create.html
@@ -12,15 +12,14 @@
Create Route
-
+ field="$ctrl.delivery.agency">
-
+ filter="{where: {isActive:1, warehouseFk:1}}">
+
-
diff --git a/client/route/src/search-panel/search-panel.html b/client/route/src/search-panel/search-panel.html
index 126b7ca9d..3661a2c5f 100644
--- a/client/route/src/search-panel/search-panel.html
+++ b/client/route/src/search-panel/search-panel.html
@@ -7,8 +7,8 @@
label="Zone"
field="$ctrl.filter.zone"
url="/route/api/Zones"
- order="printingOrder ASC"
- >
+ order="printingOrder ASC">
+
diff --git a/services/client/common/models/observation-type.json b/services/client/common/models/observation-type.json
index ac3b84092..b1a45e883 100644
--- a/services/client/common/models/observation-type.json
+++ b/services/client/common/models/observation-type.json
@@ -17,14 +17,6 @@
"type": "string",
"required": true
}
- },
- "acls": [
- {
- "accessType": "READ",
- "principalType": "ROLE",
- "principalId": "$everyone",
- "permission": "ALLOW"
- }
- ]
+ }
}
\ No newline at end of file
diff --git a/services/item/common/methods/item/crudItemNiches.js b/services/item/common/methods/item/crudItemNiches.js
new file mode 100644
index 000000000..ed3440494
--- /dev/null
+++ b/services/item/common/methods/item/crudItemNiches.js
@@ -0,0 +1,36 @@
+module.exports = Self => {
+ Self.remoteMethod('crudItemNiches', {
+ description: 'create, update or delete niches',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'niches',
+ type: 'Object',
+ require: true,
+ description: 'object with niches to create, update or delete, Example: {create: [], update: [], delete: []}',
+ http: {source: 'body'}
+ }
+ ],
+ http: {
+ path: `/crudItemNiches`,
+ verb: 'post'
+ }
+ });
+
+ Self.crudItemNiches = niches => {
+ let promises = [];
+
+ if (niches.delete && niches.delete.length) {
+ promises.push(Self.destroyAll({id: {inq: niches.delete}}));
+ }
+ if (niches.create.length) {
+ promises.push(Self.create(niches.create));
+ }
+ if (niches.update.length) {
+ niches.update.forEach(niche => {
+ promises.push(Self.upsert(niche));
+ });
+ }
+ return Promise.all(promises);
+ };
+};
diff --git a/services/item/common/methods/item/specs/crudItemNiches.spec.js b/services/item/common/methods/item/specs/crudItemNiches.spec.js
new file mode 100644
index 000000000..d6b4bf175
--- /dev/null
+++ b/services/item/common/methods/item/specs/crudItemNiches.spec.js
@@ -0,0 +1,51 @@
+const crudItemNiches = require('../crudItemNiches');
+const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
+
+describe('Item crudItemNiches()', () => {
+ it('should call the destroyAll method if there are ids in delete Array', done => {
+ let self = jasmine.createSpyObj('self', ['remoteMethod', 'crudItemNiches', 'destroyAll', 'create', 'upsert']);
+
+ crudItemNiches(self);
+ self.crudItemNiches({
+ delete: [1],
+ create: [],
+ update: []
+ }).then(result => {
+ expect(self.destroyAll).toHaveBeenCalledWith({id: {inq: [1]}});
+ done();
+ })
+ .catch(catchErrors(done));
+ });
+
+ it('should call the create method if there are ids in create Array', done => {
+ let self = jasmine.createSpyObj('self', ['remoteMethod', 'crudItemNiches', 'destroyAll', 'create', 'upsert']);
+
+ crudItemNiches(self);
+ self.crudItemNiches({
+ delete: [],
+ create: [1],
+ update: []
+ }).then(result => {
+ expect(self.create).toHaveBeenCalledWith([1]);
+ done();
+ })
+ .catch(catchErrors(done));
+ });
+
+ it('should call the upsert method as many times as ids in update Array', done => {
+ let self = jasmine.createSpyObj('self', ['remoteMethod', 'crudItemNiches', 'destroyAll', 'create', 'upsert']);
+
+ crudItemNiches(self);
+ self.crudItemNiches({
+ delete: [],
+ create: [],
+ update: [1, 2]
+ }).then(result => {
+ expect(self.upsert).toHaveBeenCalledWith(1);
+ expect(self.upsert).toHaveBeenCalledWith(2);
+ expect(self.upsert.calls.count()).toEqual(2);
+ done();
+ })
+ .catch(catchErrors(done));
+ });
+});
diff --git a/services/item/common/models/item.json b/services/item/common/models/item.json
index a4121e8f5..daff68084 100644
--- a/services/item/common/models/item.json
+++ b/services/item/common/models/item.json
@@ -84,6 +84,11 @@
"type": "hasMany",
"model": "ItemBarcode",
"foreignKey": "itemFk"
+ },
+ "itemNiche": {
+ "type": "hasMany",
+ "model": "ItemNiche",
+ "foreignKey": "itemFk"
}
}
}
\ No newline at end of file
diff --git a/services/item/common/models/itemNiche.js b/services/item/common/models/itemNiche.js
new file mode 100644
index 000000000..8f9d85da7
--- /dev/null
+++ b/services/item/common/models/itemNiche.js
@@ -0,0 +1,3 @@
+module.exports = function(Self) {
+ require('../methods/item/crudItemNiches.js')(Self);
+};
diff --git a/services/item/common/models/itemNiche.json b/services/item/common/models/itemNiche.json
new file mode 100644
index 000000000..035c07e6b
--- /dev/null
+++ b/services/item/common/models/itemNiche.json
@@ -0,0 +1,28 @@
+{
+ "name": "ItemNiche",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "itemPlacement",
+ "database": "vn"
+ }
+ },
+ "properties": {
+ "code": {
+ "type": "String",
+ "required": true
+ }
+ },
+ "relations": {
+ "item": {
+ "type": "belongsTo",
+ "model": "Item",
+ "foreignKey": "itemFk"
+ },
+ "warehouse": {
+ "type": "belongsTo",
+ "model": "Warehouse",
+ "foreignKey": "warehouseFk"
+ }
+ }
+}
diff --git a/services/item/common/models/warehouse.json b/services/item/common/models/warehouse.json
new file mode 100644
index 000000000..d52991ed6
--- /dev/null
+++ b/services/item/common/models/warehouse.json
@@ -0,0 +1,29 @@
+{
+ "name": "Warehouse",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "warehouse",
+ "database": "vn"
+ }
+ },
+ "properties": {
+ "id": {
+ "type": "Number",
+ "id": true,
+ "description": "Identifier"
+ },
+ "name": {
+ "type": "String",
+ "required": true
+ }
+ },
+ "acls": [
+ {
+ "accessType": "READ",
+ "principalType": "ROLE",
+ "principalId": "$everyone",
+ "permission": "ALLOW"
+ }
+ ]
+ }
diff --git a/services/item/server/model-config.json b/services/item/server/model-config.json
index c021a3fab..83b473a5a 100644
--- a/services/item/server/model-config.json
+++ b/services/item/server/model-config.json
@@ -35,6 +35,9 @@
"Tag": {
"dataSource": "vn"
},
+ "ItemNiche": {
+ "dataSource": "vn"
+ },
"ItemLog": {
"dataSource": "vn"
},
@@ -47,6 +50,9 @@
"ItemPlacement": {
"dataSource": "vn"
},
+ "Warehouse": {
+ "dataSource": "vn"
+ },
"Specie": {
"dataSource": "edi"
},