Merge branch 'dev' of https://git.verdnatura.es/salix into dev

This commit is contained in:
SAMBA\bernat 2018-02-15 07:17:03 +01:00
commit dfd4c61015
15 changed files with 520 additions and 38 deletions

View File

@ -9,16 +9,19 @@
"state": "clients",
"component": "vn-client-index",
"acl": ["employee"]
}, {
},
{
"url": "/create",
"state": "create",
"component": "vn-client-create"
}, {
},
{
"url": "/clients/:id",
"state": "clientCard",
"abstract": true,
"component": "vn-client-card"
}, {
},
{
"url": "/basic-data",
"state": "clientCard.basicData",
"component": "vn-client-basic-data",
@ -29,7 +32,8 @@
"description": "Basic data",
"icon": "person"
}
}, {
},
{
"url": "/fiscal-data",
"state": "clientCard.fiscalData",
"component": "vn-client-fiscal-data",
@ -40,7 +44,8 @@
"description": "Fiscal data",
"icon": "account_balance"
}
}, {
},
{
"url": "/billing-data",
"state": "clientCard.billingData",
"component": "vn-client-billing-data",
@ -51,12 +56,14 @@
"description": "Pay method",
"icon": "assignment"
}
}, {
},
{
"url": "/addresses",
"state": "clientCard.addresses",
"component": "ui-view",
"abstract": true
}, {
},
{
"url": "/list",
"state": "clientCard.addresses.list",
"component": "vn-client-addresses",
@ -67,15 +74,18 @@
"description": "Addresses",
"icon": "local_shipping"
}
}, {
},
{
"url": "/create",
"state": "clientCard.addresses.create",
"component": "vn-address-create"
}, {
},
{
"url": "/:addressId/edit",
"state": "clientCard.addresses.edit",
"component": "vn-address-edit"
}, {
},
{
"url": "/web-access",
"state": "clientCard.webAccess",
"component": "vn-client-web-access",
@ -86,12 +96,14 @@
"description": "Web access",
"icon": "language"
}
}, {
},
{
"url": "/notes",
"state": "clientCard.notes",
"component": "ui-view",
"abstract": true
}, {
},
{
"url": "/list",
"state": "clientCard.notes.list",
"component": "vn-client-notes",
@ -102,16 +114,19 @@
"description": "Notes",
"icon": "insert_drive_file"
}
}, {
},
{
"url": "/create",
"state": "clientCard.notes.create",
"component": "vn-note-create"
}, {
},
{
"url": "/credit",
"abstract": true,
"state": "clientCard.credit",
"component": "ui-view"
}, {
},
{
"url": "/list",
"state": "clientCard.credit.list",
"component": "vn-client-credit-list",
@ -123,19 +138,22 @@
"icon": "credit_card"
},
"acl": ["manager", "salesAssistant", "teamBoss", "teamManager"]
}, {
},
{
"url": "/create",
"state": "clientCard.credit.create",
"component": "vn-client-credit-create",
"params": {
"client": "$ctrl.client"
}
}, {
},
{
"url": "/greuge",
"abstract": true,
"state": "clientCard.greuge",
"component": "ui-view"
}, {
},
{
"url": "/list",
"state": "clientCard.greuge.list",
"component": "vn-client-greuge-list",
@ -146,14 +164,16 @@
"description": "Greuge",
"icon": "work"
}
}, {
},
{
"url": "/create",
"state": "clientCard.greuge.create",
"component": "vn-client-greuge-create",
"params": {
"client": "$ctrl.client"
}
}, {
},
{
"url": "/mandate",
"state": "clientCard.mandate",
"component": "vn-client-mandate",

View File

@ -15,16 +15,19 @@
"state": "item.index",
"component": "vn-item-list",
"acl": ["developer"]
}, {
},
{
"url": "/create",
"state": "item.create",
"component": "vn-item-create"
}, {
},
{
"url": "/:id",
"state": "item.card",
"abstract": true,
"component": "vn-item-card"
}, {
},
{
"url" : "/data",
"state": "item.card.data",
"component": "vn-item-data",
@ -35,7 +38,8 @@
"description": "Basic data",
"icon": "folder"
}
},{
},
{
"url" : "/tags",
"state": "item.card.tags",
"component": "vn-item-tags",
@ -46,7 +50,8 @@
"description": "Tags",
"icon": "folder"
}
},{
},
{
"url" : "/history",
"state": "item.card.history",
"component": "vn-item-history",
@ -57,7 +62,8 @@
"description": "History",
"icon": "folder"
}
},{
},
{
"url" : "/niche",
"state": "item.card.niche",
"component": "vn-item-niche",
@ -68,7 +74,8 @@
"description": "Niche",
"icon": "folder"
}
},{
},
{
"url" : "/botanical",
"state": "item.card.botanical",
"component": "vn-item-botanical",
@ -81,7 +88,7 @@
}
},{
"url" : "/barcode",
"state": "item.card.barcode",
"state": "item.card.itemBarcode",
"component": "vn-item-barcode",
"params": {
"item": "$ctrl.item"

View File

@ -1,5 +1,32 @@
<vn-card>
<form name="form" ng-submit="$ctrl.submit()">
<vn-card>
<vn-vertical pad-large>
<vn-title>Item barcode</vn-title>
<vn-one margin-medium-top>
<vn-title>Item Barcodes</vn-title>
<mg-ajax path="/item/api/ItemBarcodes" options="mgIndex as barcodes"></mg-ajax>
<vn-horizontal ng-repeat="barcode in $ctrl.barcodes track by $index">
<vn-textfield vn-three label="code" model="barcode.code"></vn-textfield>
<vn-one pad-medium-top>
<vn-icon
pointer
medium-grey
icon="remove_circle_outline"
ng-click="$ctrl.removeBarcode($index)">
</vn-icon>
<vn-icon
pointer
margin-medium-left
orange
icon="add_circle"
ng-if = "barcode.showAddIcon"
ng-click="$ctrl.addBarcode()"
></vn-icon>
</vn-one>
</vn-horizontal>
</vn-one>
</vn-vertical>
</vn-card>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,5 +1,127 @@
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.barcodes = [];
this.removedBarcodes = [];
this.oldBarcodes = {};
}
_setIconAdd() {
if (this.barcodes.length) {
this.barcodes.map(element => {
element.showAddIcon = false;
return true;
});
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.showMessage(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.vnApp.showMessage(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'];
ngModule.component('vnItemBarcode', {
template: require('./item-barcode.html')
template: require('./item-barcode.html'),
controller: Controller
});

View File

@ -0,0 +1,142 @@
import './item-barcode.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_;
$state.params.id = '1';
controller = $componentController('vnItemBarcode', {$state: $state});
}));
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, 'showMessage').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.showMessage).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, 'showMessage').and.callThrough();
controller.oldBarcodes = [
{code: 1, itemFk: 1, id: 1},
{code: 2, itemFk: 1, id: 2}
];
controller.barcodes = [];
controller.submit();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No changes to save');
});
});
});
});

View File

@ -76,6 +76,22 @@ gulp.task('e2e-only', () => {
.pipe(jasmine({reporter: 'none'}));
});
/**
* Runs the backend tests.
*/
// gulp.task('test', ['test-only'], async () => {
// gulp.watch('./services/**/*', ['test-only']);
// gulp.unwatch('./services/node_modules');
// });
// gulp.task('test-only', () => {
// const jasmine = require('gulp-jasmine');
// gulp.src('./services/loopback/common/**/*[sS]pec.js')
// .pipe(jasmine(
// require('./services-test.config')
// ));
// });
/**
* Cleans all generated project files.
*/
@ -181,7 +197,7 @@ gulp.task('nginx-start', ['nginx-conf'], async () => {
if (isWindows)
nginxBin = `start /B ${nginxBin}`;
log(`Application will be at http://${proxyConf.host}:${proxyConf.port}/`);
log(`Application will be available at http://${proxyConf.host}:${proxyConf.port}/`);
await execP(`${nginxBin} -c "${nginxConf}" -p "${nginxDir}"`);
});

24
services-test.config.js Normal file
View File

@ -0,0 +1,24 @@
// const SpecReporter = require('jasmine-spec-reporter').SpecReporter;
// module.exports = {
// reporter: new SpecReporter({
// spec: {
// // displayStacktrace: 'summary',
// displaySuccessful: false,
// displayFailedSpec: true,
// displaySpecDuration: true
// }
// }),
// config: {
// spec_dir: 'services',
// spec_files: [
// // '**/*.spec.js',
// 'auth/server/**/*.spec.js',
// 'client/common/**/*.spec.js',
// 'loopback/common/**/*.spec.js'
// ],
// helpers: [
// '/services/utils/jasmineHelpers.js'
// ]
// }
// };

View File

@ -11,9 +11,6 @@ module.exports = Self => {
http: {source: 'body'}
}
],
returns: {
arg: 'sumAmount'
},
http: {
path: `/crudAddressObservations`,
verb: 'post'

View File

@ -0,0 +1,36 @@
module.exports = Self => {
Self.remoteMethod('crudItemBarcodes', {
description: 'create, update or delete barcodes',
accessType: 'WRITE',
accepts: [
{
arg: 'barcodes',
type: 'Object',
require: true,
description: 'object with barcodes to create, update or delete, Example: {create: [], update: [], delete: []}',
http: {source: 'body'}
}
],
http: {
path: `/crudItemBarcodes`,
verb: 'post'
}
});
Self.crudItemBarcodes = barcodes => {
let promises = [];
if (barcodes.delete && barcodes.delete.length) {
promises.push(Self.destroyAll({id: {inq: barcodes.delete}}));
}
if (barcodes.create.length) {
promises.push(Self.create(barcodes.create));
}
if (barcodes.update.length) {
barcodes.update.forEach(barcode => {
promises.push(Self.upsert(barcode));
});
}
return Promise.all(promises);
};
};

View File

@ -0,0 +1,51 @@
const crudItemBarcodes = require('../crudItemBarcodes');
const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
describe('Item crudItemBarcodes()', () => {
it('should call the destroyAll methodif there are ids in delete Array', done => {
let self = jasmine.createSpyObj('self', ['remoteMethod', 'crudItemBarcodes', 'destroyAll', 'create', 'upsert']);
crudItemBarcodes(self);
self.crudItemBarcodes({
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', 'crudItemBarcodes', 'destroyAll', 'create', 'upsert']);
crudItemBarcodes(self);
self.crudItemBarcodes({
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', 'crudItemBarcodes', 'destroyAll', 'create', 'upsert']);
crudItemBarcodes(self);
self.crudItemBarcodes({
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));
});
});

View File

@ -79,6 +79,11 @@
"type": "hasMany",
"model": "ItemTag",
"foreignKey": "itemFk"
},
"itemBarcode": {
"type": "hasMany",
"model": "ItemBarcode",
"foreignKey": "itemFk"
}
}
}

View File

@ -0,0 +1,3 @@
module.exports = function(Self) {
require('../methods/item/crudItemBarcodes.js')(Self);
};

View File

@ -0,0 +1,28 @@
{
"name": "ItemBarcode",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemBarcode",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "String",
"required": true
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
}
}
}

View File

@ -37,5 +37,8 @@
},
"ItemLog": {
"dataSource": "vn"
},
"ItemBarcode": {
"dataSource": "vn"
}
}

View File

@ -19,6 +19,7 @@ jasmine.loadConfig({
spec_files: [
'auth/server/**/*[sS]pec.js',
'client/common/**/*[sS]pec.js',
'item/common/**/*[sS]pec.js',
'loopback/common/**/*[sS]pec.js'
],
helpers: [