This commit is contained in:
Juan Ferrer Toribio 2018-02-01 08:50:26 +01:00
commit b5e5b5bd6b
181 changed files with 16174 additions and 1589 deletions

View File

@ -1,5 +1,4 @@
extends: [eslint:recommended, google, plugin:jasmine/recommended]
installedESLint: true
plugins:
- jasmine
env:

2
.gitignore vendored
View File

@ -3,5 +3,7 @@ node_modules
build
npm-debug.log
debug.log
datasources.test.json
datasources.development.json
.idea
.idea

19
.vscode/launch.json vendored
View File

@ -2,25 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Asociar",
"type": "node",
"request": "attach",
"port": 5858,
"address": "localhost",
"restart": false,
"sourceMaps": false,
"outFiles": [],
"localRoot": "${workspaceRoot}",
"remoteRoot": null
},
{
"name": "Asociar al proceso",
"type": "node",
"request": "attach",
"processId": "${command:PickProcess}",
"port": 5858,
"sourceMaps": false,
"outFiles": []
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
}

17
LICENSE Normal file
View File

@ -0,0 +1,17 @@
Copyright (C) 2018 - Verdnatura Levante S.L.
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
On Debian systems, the complete text of the GNU General Public
License can be found in "/usr/share/common-licenses/GPL-3".

View File

@ -6,62 +6,88 @@ Salix is also the scientific name of a beautifull tree! :)
### Prerequisites
You will need to install globally the following items:
Required applications.
* Node.js >= 8.9.4
* NGINX
You will need to install globally the following items.
```
$ npm install -g karma
$ npm install -g karma-cli
$ npm install -g gulp
$ npm install -g webpack
$ npm install -g nodemon
install nginx globally.
```
## Getting Started // ### Installing
Pull from repository.
install nodejs v6.
Ask a senior developer for the datasources.development.json files required to run the project.
on root run:
Run this commands on project root directory to install Node dependencies.
```
$ npm install
$ gulp install
```
lauching nginx:
$ ./dev.sh
Launch application in developer environment.
```
$ gulp
```
launching frontend:
Also you can run backend and frontend as independent gulp tasks.
Launching frontend.
```
$ gulp client
or start nginx before client on sequence
$ gulp clientDev
```
launching backend:
Launching backend. It restarts NGINX and Node services.
```
$ gulp services
or start the local database before services on sequence
$ gulp serivcesDev
```
Manually reset local fixtures:
Start the local database before services on sequence.
```
$ gulp servicesDev
```
Manually reset local fixtures.
```
$ gulp docker
```
to check docker images and containers status:
To check docker images and containers status.
```
$ docker images
$ docker ps -a
```
## Running the tests
for client-side unit tests run from project's root:
For client-side unit tests run from project's root.
```
$ karma start
```
for server-side unit tests run from project's root:
For server-side unit tests run from project's root.
```
$ npm run test
```
### Break down into end to end tests
Run local database plus e2e paths:
Run local database plus e2e paths.
```
$ gulp e2e
```
Just the e2e paths as the fixtures are untainted:
Just the e2e paths as the fixtures are untainted.
```
$ npm run e2e
```
## Built With
@ -75,8 +101,3 @@ $ npm run e2e
## Versioning
We use [SourceTree](https://www.sourcetreeapp.com/) for versioning. For the versions available, see the [salix project](https://git.verdnatura.es).
## License
This project is licensed under the MIT License

View File

@ -1,10 +0,0 @@
{
"name": "@salix/auth",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -1,10 +0,0 @@
{
"name": "@salix/client",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -13,7 +13,7 @@
<vn-check vn-one label="Default" field="$ctrl.address.isDefaultAddress"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield>
<vn-textfield vn-one label="Street address" field="$ctrl.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>

View File

@ -4,7 +4,7 @@ export default class Controller {
constructor($state) {
this.address = {
clientFk: parseInt($state.params.id),
isEnabled: true
isActive: true
};
}
}

View File

@ -19,7 +19,7 @@ describe('Client', () => {
it('should define and set address property', () => {
expect(controller.address.clientFk).toBe(1234);
expect(controller.address.isEnabled).toBe(true);
expect(controller.address.isActive).toBe(true);
});
});
});

View File

@ -12,14 +12,14 @@
<vn-title>Address</vn-title>
<vn-horizontal>
<vn-one>
<vn-check label="Enabled" field="$ctrl.address.isEnabled"></vn-check>
<vn-check label="Enabled" field="$ctrl.address.isActive"></vn-check>
</vn-one>
<vn-one>
<vn-check label="Is equalizated" field="$ctrl.address.isEqualizated" vn-acl="administrative"></vn-check>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" vn-focus></vn-textfield>
<vn-textfield vn-one label="Street" field="$ctrl.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
@ -36,8 +36,8 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
initial-data="$ctrl.address.defaultAgency"
field="$ctrl.address.defaultAgencyFk"
initial-data="$ctrl.address.agency"
field="$ctrl.address.agencyFk"
url="/client/api/AgencyModes"
show-field="name"
value-field="id"

View File

@ -1,4 +1,4 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/addressesList" options="mgIndex"></mg-ajax>
<mg-ajax path="/client/api/Clients/{{index.params.id}}/listAddresses" options="mgIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card>
<vn-vertical pad-large>
@ -7,26 +7,24 @@
</vn-horizontal>
<vn-horizontal ng-repeat="address in index.model.items track by address.id" class="pad-medium-top" style="align-items: center;">
<vn-one border-radius class="pad-small border-solid"
ng-class="{'bg-dark-item': address.isDefaultAddress,'bg-opacity-item': !address.isEnabled && !address.isDefaultAddress}">
ng-class="{'bg-dark-item': address.isDefaultAddress,'bg-opacity-item': !address.isActive && !address.isDefaultAddress}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h style="color:#FFA410;">
<i class="material-icons" ng-if="address.isDefaultAddress">star</i>
<i class="material-icons pointer" ng-if="!address.isDefaultAddress&&address.isEnabled" vn-tooltip="Set as default" tooltip-position="left" ng-click="$ctrl.setDefault(address.id)">star_border</i>
<i class="material-icons pointer" ng-if="!address.isDefaultAddress" vn-tooltip="Set as default" tooltip-position="left" ng-click="$ctrl.setDefault(address.id)">star_border</i>
</vn-none>
<vn-one border-solid-right>
<div><b>{{::address.consignee}}</b></div>
<div><b>{{::address.nickname}}</b></div>
<div>{{::address.street}}</div>
<div>{{::address.city}}, {{::address.province}}</div>
<div>{{::address.phone}}, {{::address.mobile}}</div>
</vn-one>
<vn-vertical vn-one pad-medium-h>
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'pad-small-top': $index}">
<b margin-medium-right>{{::observation.observationType.description}}:</b>
<span>{{::observation.description}}</span>
</vn-one>
</vn-vertical>
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{address.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
@ -35,9 +33,7 @@
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-paging index="index" total="index.model.total"></vn-paging>
<vn-float-button
fixed-bottom-right
ui-sref="clientCard.addresses.create"

View File

@ -6,9 +6,10 @@ class ClientAddresses {
this.$scope = $scope;
}
setDefault(id) {
this.$http.patch(`/client/api/Addresses/${id}`, {id: id, isDefaultAddress: true}).then(() => {
this.$scope.index.accept();
});
let params = {isDefaultAddress: true};
this.$http.patch(`/client/api/Addresses/${id}`, params).then(
() => this.$scope.index.accept()
);
}
}
ClientAddresses.$inject = ['$http', '$scope'];

View File

@ -19,8 +19,7 @@
<vn-textfield vn-one
label="Email"
field="$ctrl.client.email"
info="You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main"
>
info="You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
@ -30,10 +29,9 @@
url="/client/api/Clients/activeSalesPerson"
show-field="name"
value-field="id"
select-fields="surname"
select-fields="name"
label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {surname: {regexp: 'search'}}]}}"
>
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.contactChannel"

View File

@ -41,9 +41,7 @@ export default class Controller {
returnDialog(response) {
if (response === 'ACCEPT') {
this.$http.post(`/mailer/notification/payment-update/${this.client.id}`).then(
() => {
this.vnApp.showMessage(this.translate.instant('Notification sent!'));
}
() => this.vnApp.showMessage(this.translate.instant('Notification sent!'))
);
}
}

View File

@ -14,6 +14,7 @@ describe('Client', () => {
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
let submit = jasmine.createSpy('submit').and.returnValue(Promise.resolve());
$scope.watcher = {submit};

View File

@ -1,4 +1,4 @@
<mg-ajax path="/client/api/Clients/createUserProfile" options="vnPost"></mg-ajax>
<mg-ajax path="/client/api/Clients/createWithUser" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
@ -25,10 +25,10 @@
url="/client/api/Clients/activeSalesPerson"
show-field="name"
value-field="id"
select-fields="surname"
select-fields="name"
label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {surname: {regexp: 'search'}}]}}"
></vn-autocomplete>
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>

View File

@ -5,7 +5,7 @@
<vn-grid-header on-order="$ctrl.onOrder(field, order)">
<vn-column-header vn-one pad-medium-h field="amount" text="Credit"></vn-column-header>
<vn-column-header vn-two pad-medium-h field="created" text="Since" default-order="ASC"></vn-column-header>
<vn-column-header vn-two pad-medium-h field="employee.name" text="Employee" order-locked></vn-column-header>
<vn-column-header vn-two pad-medium-h field="worker.firstName" text="Employee" order-locked></vn-column-header>
</vn-grid-header>
<vn-one class="list list-content">
<vn-horizontal
@ -14,7 +14,7 @@
ng-repeat="credit in index.model.instances track by credit.id">
<vn-one pad-medium-h>{{::credit.amount | number:2}} €</vn-one>
<vn-two pad-medium-h>{{::credit.created | date:'dd/MM/yyyy HH:mm' }}</vn-two>
<vn-two pad-medium-h>{{::credit.employee.name}} {{::credit.employee.surname}}</vn-two>
<vn-two pad-medium-h>{{::credit.worker.firstName}} {{::credit.worker.name}}</vn-two>
</vn-horizontal>
</vn-one>
<vn-one class="text-center pad-small-v" ng-if="index.model.count === 0" translate>No results</vn-one>

View File

@ -10,9 +10,9 @@
<vn-vertical pad-large>
<vn-title>Fiscal data</vn-title>
<vn-horizontal>
<vn-textfield autofocus vn-two label="Social name" field="$ctrl.client.socialName" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi" vn-acl="administrative"></vn-textfield>
<vn-check vn-one label="Is equalizated" field="$ctrl.client.isEqualizated" vn-acl="administrative"></vn-check>
<vn-textfield autofocus vn-two label="Social name" field="$ctrl.client.socialName" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi" vn-acl="administrative"></vn-textfield>
<vn-check vn-one label="Is equalizated" field="$ctrl.client.isEqualizated" vn-acl="administrative"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Street" field="$ctrl.client.street" vn-focus vn-acl="administrative"></vn-textfield>
@ -27,18 +27,16 @@
show-field="name"
value-field="id"
label="Province"
vn-acl="administrative"
>
vn-acl="administrative">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.country"
field="$ctrl.client.countryFk"
url="/client/api/Countries"
show-field="name"
show-field="country"
value-field="id"
label="Country"
vn-acl="administrative"
>
vn-acl="administrative">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal margin-small-bottom>
@ -64,17 +62,14 @@
</vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save" vn-acl="administrative"></vn-submit>
</vn-button-bar>
</form>
<vn-dialog
vn-id="propagate-isEqualizated"
on-response="$ctrl.returnDialogEt(response)"
>
on-response="$ctrl.returnDialogEt(response)">
<tpl-body>
<vn-vertical>
<vn-one text-center translate>You changes the equivalen

View File

@ -1,7 +1,7 @@
<a ui-sref="clientCard.basicData({ id: {{$ctrl.client.id}} })" pad-medium border-solid-bottom>
<div class="vn-item-client-name">{{$ctrl.client.name}}</div>
<div><span translate>Client id</span>: <b>{{$ctrl.client.id}}</b></div>
<div><span translate>Phone</span>: <b>{{$ctrl.client.phone | phone}}</b></div>
<div><span translate>Town/City</span>: <b>{{$ctrl.client.city}}</b></div>
<div><span translate>Email</span>: <b>{{$ctrl.client.email}}</b></div>
<a ui-sref="clientCard.basicData({ id: {{::$ctrl.client.id}} })" pad-medium border-solid-bottom>
<div class="vn-item-client-name">{{::$ctrl.client.name}}</div>
<div><span translate>Client id</span>: <b>{{::$ctrl.client.id}}</b></div>
<div><span translate>Phone</span>: <b>{{::$ctrl.client.phone | phone}}</b></div>
<div><span translate>Town/City</span>: <b>{{::$ctrl.client.city}}</b></div>
<div><span translate>Email</span>: <b>{{::$ctrl.client.email}}</b></div>
</a>

View File

@ -2,12 +2,12 @@
<vn-vertical pad-large>
<vn-title>Notes</vn-title>
<vn-one
ng-repeat="n in $ctrl.observations"
pad-small border-solid
border-radius
margin-small-bottom style="align-items: center;">
ng-repeat="n in $ctrl.observations"
pad-small border-solid
border-radius
margin-small-bottom style="align-items: center;">
<vn-horizontal>
<vn-one >{{::n.employee.name}} {{::n.employee.surname}}</vn-one>
<vn-one >{{::n.worker.firstName}} {{::n.worker.name}}</vn-one>
<vn-auto>{{::n.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal>

View File

@ -15,7 +15,8 @@ describe('Client', () => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientNotes', {$state: $state});
}));
describe('$onChanges()', () => {

View File

@ -16,7 +16,7 @@ export default class Controller {
isCustomer() {
if (this.client && this.client.id) {
this.$http.get(`/client/api/Clients/${this.client.id}/getRoleCustomer`).then(res => {
this.$http.get(`/client/api/Clients/${this.client.id}/hasCustomerRole`).then(res => {
this.canChangePassword = (res.data) ? res.data.isCustomer : false;
});
} else {

View File

@ -15,6 +15,7 @@ describe('Component VnClientWebAccess', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
vnApp = _vnApp_;
spyOn(vnApp, 'showError');
controller = $componentController('vnClientWebAccess', {$scope: $scope});
@ -37,8 +38,8 @@ describe('Component VnClientWebAccess', () => {
controller.client = {id: '1234'};
controller.isCustomer();
$httpBackend.when('GET', `/client/api/Clients/${controller.client.id}/getRoleCustomer`).respond('ok');
$httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/getRoleCustomer`);
$httpBackend.when('GET', `/client/api/Clients/${controller.client.id}/hasCustomerRole`).respond('ok');
$httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/hasCustomerRole`);
$httpBackend.flush();
});
});

View File

@ -1,10 +0,0 @@
{
"name": "@salix/core",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -18,5 +18,5 @@
item-width="$ctrl.width"
multiple="$ctrl.multiple"
parent = "$ctrl.element"
><vn-item ng-transclude="tplItem">{{$parent.item.name}}</vn-item></vn-drop-down>
><vn-item ng-transclude="tplItem">{{$parent.item[$ctrl.showField]}}</vn-item></vn-drop-down>
</vn-vertical>

View File

@ -2,7 +2,6 @@ import {module} from '../module';
import Component from '../lib/component';
import copyObject from '../lib/copy';
import './style.scss';
import { log } from 'util';
class Autocomplete extends Component {
constructor($element, $scope, $http, $timeout, $filter) {
@ -20,9 +19,8 @@ class Autocomplete extends Component {
this._field = null;
this._preLoad = false;
this.maxRow = 10;
this.showField = this.showField || 'name';
this.valueField = this.valueField || 'id';
this.order = this.order || 'name ASC';
this.showField = 'name';
this.valueField = 'id';
this.items = copyObject(this.data) || [];
this.displayValueMultiCheck = [];
this._multiField = [];
@ -195,6 +193,10 @@ class Autocomplete extends Component {
return fields;
}
getOrder() {
return this.order ? this.order : `${this.showField} ASC`;
}
findItems(search) {
if (this.url && search && !this.finding) {
this.maxRow = false;
@ -208,7 +210,7 @@ class Autocomplete extends Component {
Object.assign(filter.where, this.filter.where);
}
}
filter.order = this.order;
filter.order = this.getOrder();
let json = JSON.stringify(filter);
this.finding = true;
this.$http.get(`${this.url}?filter=${json}`).then(
@ -252,7 +254,7 @@ class Autocomplete extends Component {
filter.skip = this.items.length;
}
filter.limit = this.maxRow;
filter.order = this.order;
filter.order = this.getOrder();
}
if (this.filter) {
Object.assign(filter, this.filter);

View File

@ -16,6 +16,7 @@ describe('Component vnAutocomplete', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$element = angular.element('<div></div>');
controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
@ -150,7 +151,7 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@ -163,8 +164,8 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
controller.filterSearch = "{where: {surname: {regexp: 'search'}}}";
let json = JSON.stringify({where: {surname: {regexp: search}}, order: controller.order});
controller.filterSearch = "{where: {name: {regexp: 'search'}}}";
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@ -177,7 +178,7 @@ describe('Component vnAutocomplete', () => {
controller.url = 'test.com';
let search = 'Joker';
controller.multiple = true;
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}, {id: 4, name: 'Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);

View File

@ -9,7 +9,8 @@ describe('Directive acl', () => {
});
compile = (hasPermissions, _element) => {
inject(($compile, $rootScope, aclService, _$timeout_) => {
inject(($compile, $rootScope, aclService, _$timeout_, _$httpBackend_) => {
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
spyOn(aclService, 'aclPermission').and.returnValue(hasPermissions);
scope = $rootScope.$new();
$timeout = _$timeout_;

View File

@ -20,8 +20,9 @@ describe('Directive dialog', () => {
});
};
beforeEach(angular.mock.inject(_$componentController_ => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element('<div></div>');
controller = $componentController('vnDialog', {$element});
}));

View File

@ -8,12 +8,13 @@ describe('Directive focus', () => {
});
compile = (_element, _childElement) => {
inject(($compile, $rootScope) => {
inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
if (_childElement) {
let childElement = angular.element(_childElement);
$element[0] < childElement;
$element[0] < childElement; // FIXME
$element[0].firstChild.focus = jasmine.createSpy(focus);
}
$element[0].focus = jasmine.createSpy('focus');

View File

@ -8,8 +8,9 @@ describe('Directive vnId', () => {
});
compile = _element => {
inject(($compile, $rootScope) => {
inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
$compile($element)($scope);
$scope.$digest();

View File

@ -8,8 +8,9 @@ describe('Directive validation', () => {
});
compile = (_element, validations, value) => {
inject(($compile, $rootScope, aclService, _$timeout_, $window) => {
inject(($compile, $rootScope, aclService, _$timeout_, $window, _$httpBackend_) => {
$window.validations = validations;
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
scope = $rootScope.$new();
scope.user = {name: value};
element = angular.element(_element);

View File

@ -11,7 +11,8 @@ describe('Component vnDropDown', () => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_) => {
beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_, _$httpBackend_) => {
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$componentController = _$componentController_;
$element = angular.element('<div><ul><li></li></ul></div>');
$timeout = _$timeout_;

View File

@ -15,6 +15,7 @@ describe('Component vnIconMenu', () => {
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$scope = $rootScope.$new();
$element = angular.element('<div></div>');

View File

@ -25,6 +25,7 @@ describe('Component vnWatcher', () => {
vnApp = _vnApp_;
$transitions = _$transitions_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$translate = _$translate_;
$attrs = {
save: "patch"

View File

@ -7,7 +7,7 @@
<vn-icon-button icon="textsms" ng-click="$ctrl.doAction('addComment')"></vn-icon-button>
</vn-none>
<vn-none margin-medium-right>
<vn-icon-menu icon="person" url="/client/api/Clients/employeeList" selected="$ctrl.actionWorker"></vn-icon-menu>
<vn-icon-menu icon="person" url="/client/api/Clients/listWorkers" selected="$ctrl.actionWorker"></vn-icon-menu>
</vn-none>
<vn-none margin-medium-right>
<vn-icon-menu icon="query_builder" items="$ctrl.parent.sharedData.hourItems" selected="$ctrl.actionHours"></vn-icon-menu>

View File

@ -1,24 +1,23 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.route"
form="form"
>
form="form">
</vn-watcher>
<form name="form" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Logistic data</vn-title>
<vn-horizontal>
<vn-date-picker vn-one label="Start Hour" model="$ctrl.route.startHour" ini-options="{enableTime: true, noCalendar: true, enableSeconds: false, dateFormat: 'H:i'}"></vn-date-picker>
<vn-date-picker vn-one label="End Hour" model="$ctrl.route.endHour" ini-options="{enableTime: true, noCalendar: true, enableSeconds: false, dateFormat: 'H:i'}"></vn-date-picker>
<vn-textfield vn-one label="Start Km" model="$ctrl.route.starKm"></vn-textfield>
<vn-textfield vn-one label="End Km" model="$ctrl.route.endKm"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Packages" model="$ctrl.route.packages"></vn-textfield>
<vn-textfield vn-one label="M3" model="$ctrl.route.m3"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
<vn-title>Logistic data</vn-title>
<vn-horizontal>
<vn-date-picker vn-one label="Start Hour" model="$ctrl.route.startHour" ini-options="{enableTime: true, noCalendar: true, enableSeconds: false, dateFormat: 'H:i'}"></vn-date-picker>
<vn-date-picker vn-one label="End Hour" model="$ctrl.route.endHour" ini-options="{enableTime: true, noCalendar: true, enableSeconds: false, dateFormat: 'H:i'}"></vn-date-picker>
<vn-textfield vn-one label="Start Km" model="$ctrl.route.starKm"></vn-textfield>
<vn-textfield vn-one label="End Km" model="$ctrl.route.endKm"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Packages" model="$ctrl.route.packages"></vn-textfield>
<vn-textfield vn-one label="M3" model="$ctrl.route.m3"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>

View File

@ -10,12 +10,10 @@
order="printingOrder ASC"
></vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Postcode" model="$ctrl.filter.postcode"></vn-textfield>
<vn-textfield vn-one label="Route_Id" model="$ctrl.filter.id"></vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>

View File

@ -1,10 +0,0 @@
{
"name": "@salix/salix",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
}
}

View File

@ -4,7 +4,7 @@
}
@font-face {
font-family: vn-font;
src: url(./fonts/Roboto.ttf);
src: url(./fonts/Roboto-Regular.ttf);
}
@font-face {
font-family: vn-font-bold;

Binary file not shown.

View File

@ -1,10 +0,0 @@
{
"name": "@salix/vendor",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -8,8 +8,6 @@ services:
build:
context: ./services
dockerfile: /auth/Dockerfile
expose:
- "3000"
ports:
- "3000:3000"
@ -21,8 +19,6 @@ services:
build:
context: ./services
dockerfile: /salix/Dockerfile
expose:
- "3001"
ports:
- "3001:3001"
@ -34,8 +30,6 @@ services:
build:
context: ./services
dockerfile: /client/Dockerfile
expose:
- "3002"
ports:
- "3002:3002"
@ -46,8 +40,6 @@ services:
image: "mailer:${TAG}"
build:
context: ./services/mailer
expose:
- "3003"
ports:
- "3003:3003"
@ -59,8 +51,6 @@ services:
build:
context: ./services
dockerfile: /production/Dockerfile
expose:
- "3004"
ports:
- "3004:3004"
@ -72,8 +62,6 @@ services:
build:
context: ./services
dockerfile: /route/Dockerfile
expose:
- "3005"
ports:
- "3005:3005"
print:
@ -84,8 +72,6 @@ services:
build:
context: ./services
dockerfile: /print/Dockerfile
expose:
- "3006"
ports:
- "3006:3006"
item:
@ -96,8 +82,6 @@ services:
build:
context: ./services
dockerfile: /item/Dockerfile
expose:
- "3007"
ports:
- "3007:3007"
nginx:
@ -106,8 +90,6 @@ services:
privileged: true
build:
context: ./services/nginx
expose:
- "80"
ports:
- "80:80"
mem_limit: 200m

View File

@ -87,7 +87,7 @@ export default {
addressesButton: `${components.vnMenuItem}[ui-sref="clientCard.addresses.list"]`,
createAddress: `${components.vnFloatButton}`,
defaultCheckboxInput: `${components.vnCheck}[label='Default'] > label > input`,
consigneeInput: `${components.vnTextfield}[name="consignee"]`,
consigneeInput: `${components.vnTextfield}[name="nickname"]`,
streetAddressInput: `${components.vnTextfield}[name="street"]`,
postcodeInput: `${components.vnTextfield}[name="postcode"]`,
cityInput: `${components.vnTextfield}[name="city"]`,

View File

@ -250,8 +250,8 @@ describe('Edit addresses path', () => {
nightmare
.waitForSnackbarReset()
.waitToClick(selectors.addresses.addressesButton)
.wait(selectors.addresses.defaultAddress)
.getInnerText(selectors.addresses.defaultAddress)
.wait(selectors.addresses.isDefaultAddress)
.getInnerText(selectors.addresses.isDefaultAddress)
.then(result => {
expect(result).toContain('320 Park Avenue New York');
done();
@ -263,8 +263,8 @@ describe('Edit addresses path', () => {
nightmare
.waitForSnackbarReset()
.waitToClick(selectors.addresses.secondMakeDefaultStar)
.waitForTextInElement(selectors.addresses.defaultAddress, 'Somewhere in Thailand')
.getInnerText(selectors.addresses.defaultAddress)
.waitForTextInElement(selectors.addresses.isDefaultAddress, 'Somewhere in Thailand')
.getInnerText(selectors.addresses.isDefaultAddress)
.then(result => {
expect(result).toContain('Somewhere in Thailand');
done();
@ -274,7 +274,7 @@ describe('Edit addresses path', () => {
it(`should click on the edit icon of the default address`, done => {
nightmare
.waitForTextInElement(selectors.addresses.defaultAddress, 'Somewhere in Thailand')
.waitForTextInElement(selectors.addresses.isDefaultAddress, 'Somewhere in Thailand')
.waitToClick(selectors.addresses.firstEditButton)
.waitForURL('/edit')
.url()

View File

@ -1,5 +1,5 @@
var gulp = require('gulp');
const jasmine = require('gulp-jasmine');
var jasmine = require('gulp-jasmine');
var gutil = require('gulp-util');
var wrap = require('gulp-wrap');
var concat = require('gulp-concat');
@ -12,70 +12,64 @@ var del = require('del');
var fs = require('fs');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var exec = require('child_process').exec;
// Configuration
var srcDir = './client';
var buildDir = './services/nginx/static';
var langs = ['es', 'en'];
var modules = require('./client/modules.json');
var webpackConfig = require('./webpack.config.js');
// Main tasks
gulp.task('default', function() {
gulp.task('default', () => {
return gulp.start('services', 'client');
});
gulp.task('build', ['clean'], function() {
return gulp.start('routes', 'locales', 'webpack');
});
gulp.task('client', ['clean'], function() {
gulp.task('client', ['clean'], () => {
return gulp.start('watch', 'routes', 'locales', 'webpack-dev-server');
});
gulp.task('nginxRestart', callback => {
gulp.task('services', callback => {
let isWindows = /^win/.test(process.platform);
let command = isWindows ? '.\\dev.cmd' : './dev.sh';
let command = isWindows ? 'docker inspect dblocal | findstr Status' : 'docker inspect dblocal | grep Status';
exec(command, (err, stdout, stderr) => {
console.log(stdout);
let isNotRunning = !stdout.includes('running');
if (isNotRunning) {
runSequence('docker', 'waitForMySQL', 'run-services');
} else {
runSequence('run-services');
}
callback(err);
});
});
gulp.task('services', () => {
process.env.NODE_ENV = gutil.env.env || 'development';
const pathServices = './services/';
const services = fs.readdirSync(pathServices);
gulp.task('run-services', ['nginx'], () => {
const servicesPath = './services/';
const services = fs.readdirSync(servicesPath);
services.splice(services.indexOf('loopback'), 1);
return services.forEach(service => {
const serviceJs = pathServices.concat(service, '/server/server.js');
const serviceJs = servicesPath.concat(service, '/server/server.js');
if (fs.existsSync(serviceJs))
require(serviceJs).start();
});
});
gulp.task('clientDev', callback => {
runSequence('nginxRestart', 'client', callback);
gulp.task('test', callback => {
return require('./services_tests').start();
});
gulp.task('servicesDev', callback => {
let isWindows = /^win/.test(process.platform);
let command = isWindows ? 'docker inspect dblocal | findstr Status' : 'docker inspect dblocal | grep Status';
gutil.env.env = 'test';
exec(command, (err, stdout, stderr) => {
let isNotRunning = !stdout.includes('running');
if (isNotRunning) {
runSequence('docker', 'waitForMySQL', 'services');
} else {
runSequence('services');
}
callback(err);
});
gulp.task('e2e', callback => {
runSequence('docker', 'waitForMySQL', 'run-e2e', callback);
});
gulp.task('run-e2e', callback => {
gulp.src('./e2e_tests.js')
.pipe(jasmine({reporter: 'none'}));
});
gulp.task('clean', function() {
@ -83,14 +77,14 @@ gulp.task('clean', function() {
});
gulp.task('install', () => {
const pathServices = './services/';
const fileJson = [];
const services = fs.readdirSync(pathServices);
const servicesPath = './services/';
const jsonFile = [];
const services = fs.readdirSync(servicesPath);
services.push('..');
services.forEach(service => {
fileJson.push(pathServices.concat(service, '/package.json'));
jsonFile.push(servicesPath.concat(service, '/package.json'));
});
return gulp.src(fileJson)
return gulp.src(jsonFile)
.pipe(print(filepath => {
return `Installing packages in ${filepath}`;
}))
@ -99,6 +93,22 @@ gulp.task('install', () => {
}));
});
gulp.task('build', ['clean'], () => {
return gulp.start('routes', 'locales', 'webpack');
});
// Nginx
gulp.task('nginx', callback => {
let isWindows = /^win/.test(process.platform);
let command = isWindows ? 'start.cmd' : 'start.sh';
command = `./services/nginx/${command}`;
exec(command, (err, stdout, stderr) => {
console.log(stdout);
callback(err);
});
});
// Webpack
gulp.task('webpack', function(cb) {
@ -171,48 +181,14 @@ gulp.task('routes', function() {
});
// Watch
gulp.task('watch', function() {
gulp.watch(routeFiles, ['routes']);
gulp.watch(localeFiles, ['locales']);
});
// Server side unit tests
gulp.task('test', callback => {
return require('./services_tests').start();
});
// E2E tests
// e2e tests
gulp.task('e2e', callback => {
runSequence('docker', 'waitForMySQL', 'endToEndTests', callback);
});
gulp.task('waitForMySQL', callback => {
let maxInterval = 30000;
let interval = 1000;
let timer = 0;
console.log('Waiting for MySQL init process...');
let waitForLocaldb = setInterval(() => {
if (timer < maxInterval) {
timer += interval;
exec('docker logs --tail 4 dblocal', (err, stdout, stderr) => {
if (stdout.includes('MySQL init process done. Ready for start up.')) {
clearInterval(waitForLocaldb);
callback(err);
}
});
} else {
console.log(`MySQL connection not established whithin ${maxInterval / 1000} secs!`);
clearInterval(waitForLocaldb);
}
}, interval);
});
gulp.task('endToEndTests', callback => {
gulp.src('./e2e_tests.js')
.pipe(jasmine({reporter: 'none'}));
});
// docker dblocal
gulp.task('docker', callback => {
runSequence('deleteDockerDb', 'deleteDockerImageDb', 'buildDockerDb', 'runDockerDb', callback);
});
@ -242,3 +218,24 @@ gulp.task('deleteDockerDb', callback => {
callback(err = null);
});
});
gulp.task('waitForMySQL', callback => {
let maxInterval = 30000;
let interval = 1000;
let timer = 0;
console.log('Waiting for MySQL init process...');
let waitForLocaldb = setInterval(() => {
if (timer < maxInterval) {
timer += interval;
exec('docker logs --tail 4 dblocal', (err, stdout, stderr) => {
if (stdout.includes('MySQL init process done. Ready for start up.')) {
clearInterval(waitForLocaldb);
callback(err);
}
});
} else {
console.log(`MySQL connection not established whithin ${maxInterval / 1000} secs!`);
clearInterval(waitForLocaldb);
}
}, interval);
});

View File

@ -8,7 +8,7 @@ webpackConfig.plugins = [];
// Generated on Tue Aug 22 2017 13:37:43 GMT+0200 (CEST)
module.exports = function(config) {
config.set({
let baseConfig = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
@ -61,20 +61,7 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['ChromeNoSandboxHeadless'],
customLaunchers: {
ChromeNoSandboxHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'--headless',
'--disable-gpu',
// Without a remote debugging port, Google Chrome exits immediately.
' --remote-debugging-port=9222'
]
}
},
browsers: [],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
@ -91,5 +78,39 @@ module.exports = function(config) {
'karma-firefox-launcher',
'karma-sourcemap-loader'
]
});
};
let browserConfig;
if (process.env.FIREFOX_BIN) {
browserConfig = {
browsers: ['FirefoxHeadless'],
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['--headless']
}
}
};
} else {
browserConfig = {
browsers: ['ChromeNoSandboxHeadless'],
customLaunchers: {
ChromeNoSandboxHeadless: {
base: 'Chrome',
flags: [
'--no-sandbox',
// See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'--headless',
'--disable-gpu',
// Without a remote debugging port, Google Chrome exits immediately.
' --remote-debugging-port=9222'
]
}
}
};
}
Object.assign(baseConfig, browserConfig);
config.set(baseConfig);
};

14888
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,14 @@
"version": "1.0.0",
"author": "Verdnatura Levante SL",
"description": "Salix application",
"license": "ISC",
"license": "GPL-3.0",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
"url": "https://git.verdnatura.es/salix"
},
"dependencies": {
"@uirouter/angularjs": "^1.0.3",
"angular": "^1.6.4",
"angular": "^1.6.8",
"angular-cookies": "^1.6.4",
"angular-paging": "^2.2.2",
"angular-translate": "^2.13.1",
@ -23,7 +23,7 @@
"validator": "^6.2.1"
},
"devDependencies": {
"angular-mocks": "1.6.6",
"angular-mocks": "^1.6.6",
"babel": "^6.5.2",
"babel-core": "^6.22.1",
"babel-loader": "^6.4.1",
@ -50,19 +50,19 @@
"gulp-wrap": "^0.13.0",
"gulp-yaml": "^1.0.1",
"html-loader": "^0.4.4",
"jasmine": "^2.7.0",
"jasmine": "^2.9.0",
"jasmine-spec-reporter": "^4.2.1",
"karma": "^1.7.0",
"karma": "^1.7.1",
"karma-chrome-launcher": "^2.2.0",
"karma-firefox-launcher": "^1.0.1",
"karma-jasmine": "^1.1.0",
"karma-firefox-launcher": "^1.1.0",
"karma-jasmine": "^1.1.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.4",
"karma-webpack": "^2.0.9",
"md5": "^2.2.1",
"merge-stream": "^1.0.1",
"mysql": "^2.15.0",
"nightmare": "^2.10.0",
"node-sass": "^3.11.0",
"node-sass": "^3.13.1",
"raw-loader": "*",
"run-sequence": "^2.2.0",
"sass-loader": "^4.0.2",

View File

@ -1,27 +0,0 @@
{
"user": {
"dataSource": "salix"
},
"AccessToken": {
"dataSource": "salix",
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
}
},
"ACL": {
"dataSource": "salix"
},
"RoleMapping": {
"dataSource": "salix"
},
"Role": {
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
}
}

View File

@ -1,64 +0,0 @@
var request = require('request');
var app = require('../../../server/server');
module.exports = function(Client) {
Client.remoteMethod('activate', {
description: 'Activate or deactive client',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'Model id',
http: {source: 'path'}
}, {
arg: 'context',
type: 'object',
http: function(ctx) {
return ctx;
}
}
],
returns: {
arg: 'active',
type: 'boolean'
},
http: {
verb: 'put',
path: '/:id/activate'
}
});
Client.activate = function(id, ctx, cb) {
Client.findById(id, function(err, client) {
if (!err) {
Client.update({id: client.id}, {active: !client.active});
let filter = {where: {clientFk: client.id}, fields: ['started', 'ended']};
app.models.CreditClassification.findOne(filter, function(error, data) {
if (error)
return;
let currentDate = new Date();
if (data && client.active && (data.ended >= currentDate || data.ended == null)) {
let referer = ctx.req.headers.referer;
var options = {
url: `${referer}/mailer/notification/client-deactivate/${client.id}`,
method: 'POST',
headers: {
'content-type': 'application/json',
'Authorization': ctx.req.headers.authorization
},
json: {}
};
request(options);
}
});
cb(null, !client.active);
}
});
};
};

View File

@ -1,147 +0,0 @@
var app = require('../../../server/server');
module.exports = function(Client) {
var CREDIT_CARD = 5;
var models = app.models;
Client.observe('before save', function(ctx, next) {
if (ctx.currentInstance) {
let dataChange = Object.assign({}, ctx.data);
let userId = ctx.options.accessToken.userId;
Object.assign(ctx.data, doIfNullSalesPerson(ctx.currentInstance));
if (!ctx.data.dueDay)
ctx.data.dueDay = 5;
if (dataChange.hasOwnProperty('equalizationTax') && !canMarkEqualizationTax(ctx.data))
next(generateErrorEqualizationTax());
else if (dataChange.hasOwnProperty('credit'))
canChangeCredit(dataChange, userId, next);
else
next();
} else if (ctx.where && ctx.where.id) {
Client.findById(ctx.where.id, (_, instance) => {
Object.assign(ctx.data, doIfNullSalesPerson(instance));
if (instance
&& instance.payMethodFk != ctx.data.payMethodFk
&& instance.dueDay == ctx.data.dueDay)
ctx.data.dueDay = 5;
if (instance.fi && ctx.data.equalizationTax && !canMarkEqualizationTax(instance)) {
next(generateErrorEqualizationTax());
} else if (instance.equalizationTax !== undefined && instance.equalizationTax && ctx.data.fi && canMarkEqualizationTax(ctx.data)) {
next(generateErrorEqualizationTax());
} else {
next();
}
});
} else {
// newInstance
next();
}
});
function doIfNullSalesPerson(instance) {
var data = {};
if (instance.salesPerson === null) {
data.credit = 0;
data.discount = 0;
data.payMethodFk = CREDIT_CARD;
}
return data;
}
function canMarkEqualizationTax(instance) {
var firstLetter = (instance && instance.fi) ? instance.fi.toUpperCase().charAt(0) : '';
if (firstLetter == "A" || firstLetter == "B")
return false;
return true;
}
function generateErrorEqualizationTax() {
var error = new Error();
error.message = "No se puede marcar el recargo de equivalencia";
error.status = 500;
return error;
}
function generateErrorCredit() {
var error = new Error();
error.message = "No tienes privilegios para modificar el crédito";
error.status = 500;
return error;
}
function canChangeCredit(data, userId, done) {
let filter = {
fields: ['roleFk'],
where: {
maxAmount: {gt: data.credit}
}
};
models.ClientCreditLimit.find(filter,
(_, res) => limitCb(_, res));
function limitCb(_, instances) {
let requiredRoles = [];
for (instance of instances)
requiredRoles.push(instance.roleFk);
let where = {
roleId: {inq: requiredRoles},
principalType: 'USER',
principalId: userId
};
models.RoleMapping.count(where,
(_, res) => roleCb(_, res));
}
function roleCb(_, count) {
// si el usuario no tiene alguno de los roles no continua
if (count <= 0) {
done(generateErrorCredit());
return;
}
// si tiene el rol hay que validar que el último movimiento no fuese crédito 0 insertado por gerencia
validate();
}
// Si se puso a 0 por gerencia, solo gerencia puede aumentarlo
function validate() {
let query = 'SELECT * FROM ClientCredit WHERE clientFk = ? ORDER BY created DESC LIMIT 1';
Client.dataSource.connector.execute(query, [data.id],
(_, res) => maxCb(_, res));
}
function maxCb(_, instances) {
if (!instances || instances.length !== 1 || instances[0].employeeFk == userId || instances[0].amount > 0) {
done();
return;
}
// el ultimo registro tiene valor 0, hay que comprobar que no fue editado por un gerente
let sql = `SELECT count(distinct r.id) as hasManagerRole
FROM ClientCredit cc
JOIN Employee em ON (em.id = cc.employeeFk)
JOIN Account ac ON (ac.id = em.userFk)
JOIN RoleMapping rm ON (rm.principalId = ac.id)
JOIN Role r on (r.id = rm.roleId)
WHERE rm.principalType = 'USER'
AND cc.employeeFk = ?
AND r.\`name\` = 'manager'`;
Client.dataSource.connector.execute(sql, [instances[0].employeeFk], (_, res) => clientCreditCb(_, res));
}
function clientCreditCb(_, instance) {
if (!instance || (instance.length && instance[0].hasManagerRole > 0)) {
done(generateErrorCredit());
return;
}
done();
}
}
};

View File

@ -1,46 +0,0 @@
module.exports = function(Client) {
Client.remoteMethod('card', {
description: 'Get client for card call',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'Model id',
http: {source: 'path'}
},
returns: {
arg: 'data',
type: 'Client',
root: true
},
http: {
verb: 'get',
path: '/:id/card'
}
});
Client.card = function(id, callback) {
let filter = {
where: {
id: id
},
include: require('./card.json')
};
Client.find(filter, function(error, instances) {
if (error) throw error;
callback(null, formatCard(instances[0]));
});
};
function formatCard(card) {
let cardFormated = JSON.parse(JSON.stringify(card));
if (cardFormated.salesPersonFk)
cardFormated.salesPerson = {
id: card.salesPerson().id,
name: `${card.salesPerson().name} ${card.salesPerson().surname}`
};
return cardFormated;
}
};

View File

@ -1,33 +0,0 @@
[
{
"relation": "salesPerson",
"scope": {
"fields": ["id", "name", "surname"]
}
}, {
"relation": "contactChannel",
"scope": {
"fields": ["id", "name"]
}
}, {
"relation": "province",
"scope": {
"fields": ["id", "name"]
}
}, {
"relation": "country",
"scope": {
"fields": ["id", "name"]
}
}, {
"relation": "payMethod",
"scope": {
"fields": ["id", "name"]
}
}, {
"relation": "account",
"scope": {
"fields": ["id", "name", "active"]
}
}
]

View File

@ -1,42 +0,0 @@
module.exports = function(Client) {
Client.remoteMethod('employeeList', {
description: 'List employee',
accessType: 'READ',
returns: {
arg: 'data',
type: 'Employee',
root: true
},
http: {
path: `/employeeList`,
verb: 'get'
}
});
let getEmployees = listEmployees => {
let employees = [];
listEmployees.forEach(function(e) {
employees.push({id: e.id, name: e.name});
}, this);
return employees;
};
Client.employeeList = function(callback) {
let query = `SELECT em.id, CASE em.surname WHEN NULL THEN em.name ELSE concat(em.name, " ", em.surname) END \`name\`
FROM salix.Employee em
JOIN salix.Account ac ON em.userFk = ac.id
JOIN salix.RoleMapping rm on ac.id=rm.principalId
JOIN salix.Role rl on rm.roleId = rl.id
WHERE ac.active
and rl.\`name\`='employee'
ORDER BY em.name ASC`;
Client.rawSql(query, [], callback)
.then(response => {
callback(null, getEmployees(response));
})
.catch(reject => {
callback(reject, null);
});
};
};

View File

@ -1,45 +0,0 @@
module.exports = Client => {
Client.remoteMethod('getRoleCustomer', {
description: 'devuelve true/false si es Customer el client',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'string',
required: true,
description: 'Model id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
required: true,
description: 'Filter defining where',
http: function(context) {
return context.req.query;
}
}
],
returns: {
arg: 'data',
type: 'boolean',
root: true
},
http: {
path: `/:id/getRoleCustomer`,
verb: 'get'
}
});
Client.getRoleCustomer = (id, context, callback) => {
let query = `SELECT count(*) isCustomer FROM salix.Account ac JOIN salix.Role r ON r.id = ac.roleFK WHERE r.\`name\`='customer' AND ac.id IN (?)`;
const params = [id];
Client.rawSql(query, params, callback)
.then(response => {
callback(null, response[0]);
})
.catch(reject => {
callback(reject, null);
});
};
};

View File

@ -97,7 +97,7 @@ describe('Client Create', () => {
socialName: client.socialName
};
app.models.Client.createUserProfile(formerAccountData, (err, client) => {
app.models.Client.createWithUser(formerAccountData, (err, client) => {
expect(err.details.codes.name[0]).toEqual('uniqueness');
done();
});
@ -107,7 +107,7 @@ describe('Client Create', () => {
});
it('should create a new account', done => {
app.models.Client.createUserProfile(newAccountData, (error, client) => {
app.models.Client.createWithUser(newAccountData, (error, client) => {
if (error) return catchErrors(done)(error);
app.models.Account.findOne({where: {name: newAccountData.userName}})
.then(account => {

View File

@ -1,8 +1,8 @@
const app = require('../../../../server/server');
const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
describe('Client getRoleCustomer', () => {
it('should call the getRoleCustomer() method with a customer id', done => {
describe('Client hasCustomerRole', () => {
it('should call the hasCustomerRole() method with a customer id', done => {
let id = 1;
let params = {};
@ -13,10 +13,10 @@ describe('Client getRoleCustomer', () => {
done();
};
app.models.Client.getRoleCustomer(id, params, callback);
app.models.Client.hasCustomerRole(id, params, callback);
});
it('should call the getRoleCustomer() method with a non customer id', done => {
it('should call the hasCustomerRole() method with a non customer id', done => {
let id = 8;
let params = {};
@ -27,10 +27,10 @@ describe('Client getRoleCustomer', () => {
done();
};
app.models.Client.getRoleCustomer(id, params, callback);
app.models.Client.hasCustomerRole(id, params, callback);
});
it('should call the getRoleCustomer() method with an unreal id', done => {
it('should call the hasCustomerRole() method with an unreal id', done => {
let id = 999;
let params = {};
@ -41,10 +41,10 @@ describe('Client getRoleCustomer', () => {
done();
};
app.models.Client.getRoleCustomer(id, params, callback);
app.models.Client.hasCustomerRole(id, params, callback);
});
it('should call the getRoleCustomer() method with an invalid id', done => {
it('should call the hasCustomerRole() method with an invalid id', done => {
let id = 'WRONG!';
let params = {};
@ -55,6 +55,6 @@ describe('Client getRoleCustomer', () => {
done();
};
app.models.Client.getRoleCustomer(id, params, callback);
app.models.Client.hasCustomerRole(id, params, callback);
});
});

View File

@ -2,7 +2,7 @@ const app = require('../../../../server/server');
const catchErrors = require('../../../../../../services/utils/jasmineHelpers');
describe('Client addresses', () => {
it('should call the addressesList method and receive total results and items', done => {
it('should call the listAddresses method and receive total results and items', done => {
let id = 1;
let params = {
page: 1,
@ -15,6 +15,6 @@ describe('Client addresses', () => {
expect(Object.keys(result)).toEqual(['total', 'items']);
done();
};
app.models.Client.addressesList(id, params, callback);
app.models.Client.listAddresses(id, params, callback);
});
});

View File

@ -1,15 +1,15 @@
const app = require('../../../../server/server');
const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
describe('Client employeeList', () => {
it('should call the employeeList()', done => {
let callback = (error, result) => {
if (error) return catchErrors(done)(error);
describe('Client listWorkers', () => {
it('should call the listWorkers()', done => {
app.models.Client.listWorkers()
.then(result => {
let amountOfEmployees = Object.keys(result).length;
expect(amountOfEmployees).toEqual(6);
done();
};
app.models.Client.employeeList(callback);
})
.catch(catchErrors(done));
});
});

View File

@ -1,4 +1,4 @@
const totalGreuge = require('../total');
const totalGreuge = require('../sumAmount');
const catchErrors = require('../../../../../../services/utils/jasmineHelpers').catchErrors;
describe('Greuge totalGreuge()', () => {

View File

@ -1,6 +1,6 @@
module.exports = Self => {
Self.remoteMethod('sumAmount', {
description: 'returns sum greuge.ammount from client',
description: 'Returns the sum of greuge for a client',
accessType: 'READ',
accepts: [{
arg: 'id',
@ -19,16 +19,13 @@ module.exports = Self => {
});
Self.sumAmount = (clientFk, callback) => {
let query = `SELECT sum(amount) as sumAmount FROM vn.greuge WHERE clientFk = ?`;
Self.rawSql(query, [clientFk], callback).then(response => {
if (response.length) {
callback(null, response[0].sumAmount);
} else {
callback(null, 0);
}
})
.catch(reject => {
callback(reject, null);
});
let query = `SELECT SUM(amount) AS sumAmount FROM vn.greuge WHERE clientFk = ?`;
Self.rawSql(query, [clientFk])
.then(response => {
callback(null, response.length ? response[0].sumAmount : 0);
})
.catch(err => {
callback(err);
});
};
};

View File

@ -1,75 +0,0 @@
module.exports = function(Self) {
Self.validate('default', isEnabled, {message: 'No se puede poner predeterminado un consignatario desactivado'});
function isEnabled(err) {
if (!this.isEnabled && this.isDefaultAddress) err();
}
Self.beforeRemote('create', function(ctx, modelInstance, next) {
var data = ctx.req.body;
create(data, next);
});
function create(data, next) {
if (data.isDefaultAddress) {
removeAllDefault(data, next);
} else {
next();
}
}
Self.beforeRemote('prototype.patchAttributes', function(ctx, modelInstance, next) {
let newData = ctx.req.body;
newData.id = ctx.req.params.id;
getAddress(ctx, newData, next);
});
Self.beforeRemote('findById', function(ctx, modelInstance, next) {
ctx.args.filter = {
include: [{
relation: "province",
scope: {
fields: ["id", "name"]
}
},
{
relation: "defaultAgency",
scope: {
fields: ["id", "name"]
}
}
]
};
next();
});
function getAddress(ctx, newData, next) {
Self.findOne({where: {id: newData.id}}, (_, oldData) => {
if (oldData)
callbackGetAddress(ctx, newData, oldData, next);
});
}
function callbackGetAddress(ctx, newData, oldData, next) {
if (newData.isDefaultAddress) {
removeAllDefault(oldData, next);
} else if (oldData.isDefaultAddress && newData.hasOwnProperty('isDefaultAddress') && !newData.isDefaultAddress) {
next(generateErrorDefaultAddress());
} else
next();
}
function removeAllDefault(client, next) {
if (client && client.clientFk)
Self.updateAll({clientFk: client.clientFk, isDefaultAddress: {neq: 0}}, {isDefaultAddress: false}, next);
else
next();
}
function generateErrorDefaultAddress() {
var error = new Error();
error.message = "No se puede desmarcar el consignatario predeterminado";
error.status = 500;
return error;
}
};

View File

@ -1,6 +1,12 @@
{
"name": "AgencyMode",
"description": "Agency modes",
"base": "VnModel",
"options": {
"mysql": {
"table": "agencyMode"
}
},
"properties": {
"id": {
"type": "Number",
@ -20,7 +26,7 @@
"inflation": {
"type": "Number"
},
"sendMailTo": {
"reportMail": {
"type": "string"
}
},

View File

@ -10,9 +10,9 @@ module.exports = function(Self) {
limit: params.size,
order: params.order || 'created DESC',
include: {
relation: "employee",
relation: "worker",
scope: {
fields: ["id", "name", "surname"]
fields: ["id", "firstName", "name"]
}
}
};

View File

@ -1,5 +1,6 @@
{
"name": "ClientCredit",
"description": "Log of credit changes",
"base": "VnModel",
"options": {
"mysql": {
@ -27,10 +28,10 @@
"model": "Client",
"foreignKey": "clientFk"
},
"employee": {
"worker": {
"type": "belongsTo",
"model": "Employee",
"foreignKey": "employeeFk"
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -8,13 +8,11 @@ module.exports = function(Self) {
ctx.instance.created = Date();
let token = ctx.options.accessToken;
let userId = token && token.userId;
let app = require('../../server/server');
let Employee = app.models.Employee;
Employee.findOne({where: {userFk: userId}}, (err, user) => {
if (user) {
ctx.instance.employeeFk = user.id;
next();
}
Self.app.models.Worker.findOne({where: {userFk: userId}}, (err, user) => {
if (err) return next(err);
ctx.instance.workerFk = user.id;
next();
});
});
};

View File

@ -1,7 +1,12 @@
{
"name": "ClientObservation",
"description": "Notas de los clientes.",
"description": "Client notes",
"base": "VnModel",
"options": {
"mysql": {
"table": "clientObservation"
}
},
"properties": {
"id": {
"type": "Number",
@ -21,10 +26,10 @@
}
},
"relations": {
"employee": {
"worker": {
"type": "belongsTo",
"model": "Employee",
"foreignKey": "employeeFk"
"model": "Worker",
"foreignKey": "workerFk"
},
"client": {
"type": "hasOne",
@ -33,6 +38,6 @@
}
},
"scope": {
"include": "employee"
"include": "worker"
}
}

View File

@ -1,73 +0,0 @@
var app = require('../../server/server');
module.exports = function(Self) {
var models = app.models;
// Methods
require('../methods/client/activate.js')(Self);
require('../methods/client/addresses.js')(Self);
require('../methods/client/before-save.js')(Self);
require('../methods/client/card.js')(Self);
require('../methods/client/create.js')(Self);
require('../methods/client/employee.js')(Self);
require('../methods/client/filter.js')(Self);
require('../methods/client/roles.js')(Self);
require('../methods/client/salesperson.js')(Self);
require('../methods/client/addressesPropagateRe.js')(Self);
// Validations
Self.validatesUniquenessOf('fi', {
message: 'El NIF/CIF debe ser único'
});
Self.validatesUniquenessOf('socialName', {
message: 'La razón social debe ser única'
});
Self.validatesFormatOf('postcode', {
message: 'El código postal solo debe contener números',
allowNull: true,
allowBlank: true,
with: /^\d+$/
});
Self.validatesFormatOf('email', {
message: 'Correo electrónico inválido',
allowNull: true,
allowBlank: true,
with: /^[\w|.|-]+@\w[\w|.|-]*\w(,[\w|.|-]+@\w[\w|.|-]*\w)*$/
});
Self.validatesLengthOf('postcode', {
allowNull: true,
allowBlank: true,
min: 3, max: 10
});
var validateIban = require('../validations/validateIban');
Self.validateBinded('iban', validateIban, {
message: 'El iban no tiene el formato correcto'
});
let validateDni = require('../validations/validateDni');
Self.validateBinded('fi', validateDni, {
message: 'DNI Incorrecto'
});
Self.validate('payMethod', hasSalesMan, {
message: 'No se puede cambiar la forma de pago si no hay comercial asignado'
});
function hasSalesMan(err) {
if (this.payMethod && !this.salesPerson)
err();
}
Self.validateAsync('payMethodFk', hasIban, {
message: 'El método de pago seleccionado requiere que se especifique el IBAN'
});
function hasIban(err, done) {
models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequired && !this.iban)
err();
done();
});
}
};

View File

@ -1,10 +1,10 @@
{
"name": "Company",
"description": "Companies",
"base": "VnModel",
"options": {
"mysql": {
"table": "company",
"database": "vn"
"table": "company"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "greugeType",
"database": "vn"
"table": "greugeType"
}
},
"properties": {

View File

@ -1,6 +1,6 @@
module.exports = function(Self) {
require('../methods/greuge/filter.js')(Self);
require('../methods/greuge/total.js')(Self);
require('../methods/greuge/sumAmount.js')(Self);
Self.validatesLengthOf('description', {
max: 45,

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "greuge",
"database": "vn"
"table": "greuge"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "mandate",
"database": "vn"
"table": "mandate"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "mandateType",
"database": "vn"
"table": "mandateType"
}
},
"properties": {

View File

@ -1,3 +0,0 @@
{
"Hello": "Hello english"
}

View File

@ -1,3 +0,0 @@
{
"Hello": "Hola español"
}

View File

@ -1,64 +1,18 @@
{
"user": {
"dataSource": "salix"
"AddressObservation": {
"dataSource": "vn"
},
"AccessToken": {
"dataSource": "salix",
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
}
},
"ACL": {
"dataSource": "salix"
},
"RoleMapping": {
"dataSource": "salix"
},
"Role": {
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
},
"Client": {
"AgencyMode": {
"dataSource": "vn"
},
"ClientCredit": {
"dataSource": "vn"
},
"ClientCreditLimit": {
"dataSource": "salix"
},
"ClientObservation": {
"dataSource": "salix"
"dataSource": "vn"
},
"PayMethod": {
"dataSource": "salix"
},
"Address": {
"dataSource": "salix"
},
"AgencyMode": {
"dataSource": "salix"
},
"Province": {
"dataSource": "salix"
},
"Country": {
"dataSource": "salix"
},
"ContactChannel": {
"dataSource": "salix"
},
"Employee": {
"dataSource": "salix"
},
"CreditClassification": {
"dataSource": "salix"
"Company": {
"dataSource": "vn"
},
"Greuge": {
"dataSource": "vn"
@ -72,12 +26,6 @@
"MandateType": {
"dataSource": "vn"
},
"Company": {
"dataSource": "vn"
},
"AddressObservation": {
"dataSource": "vn"
},
"ObservationType": {
"dataSource": "vn"
}

View File

@ -5,8 +5,6 @@ ENV TZ GMT-1
COPY *.sql /docker-entrypoint-initdb.d/
RUN chmod -R 755 /docker-entrypoint-initdb.d
CMD ["mysqld"]

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "expence",
"database": "vn"
"table": "expence"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "ink",
"database": "vn"
"table": "ink"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "intrastat",
"database": "vn"
"table": "intrastat"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "itemType",
"database": "vn"
"table": "itemType"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "item",
"database": "vn"
"table": "item"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "origin",
"database": "vn"
"table": "origin"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "producer",
"database": "vn"
"table": "producer"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "taxClass",
"database": "vn"
"table": "taxClass"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "taxCode",
"database": "vn"
"table": "taxCode"
}
},
"properties": {

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "taxType",
"database": "vn"
"table": "taxType"
}
},
"properties": {

View File

@ -1,29 +1,4 @@
{
"user": {
"dataSource": "salix"
},
"AccessToken": {
"dataSource": "salix",
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
}
},
"ACL": {
"dataSource": "salix"
},
"RoleMapping": {
"dataSource": "salix"
},
"Role": {
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
},
"Item": {
"dataSource": "vn"
},
@ -53,8 +28,5 @@
},
"Expence": {
"dataSource": "vn"
},
"Country": {
"dataSource": "salix"
}
}

View File

@ -0,0 +1,23 @@
exports.UserError = class extends Error {
constructor(message) {
super(message);
this.statusCode = 400;
}
};
exports.getFinalState = function(ctx) {
if (ctx.isNewInstance)
return ctx.instance;
if (ctx.currentInstance)
return Object.assign({},
ctx.currentInstance.__data,
ctx.data || ctx.instance
);
return null;
};
exports.isMultiple = function(ctx) {
return !ctx.isNewInstance && !ctx.currentInstance;
};

Some files were not shown because too many files have changed in this diff Show More