Views migrated to vn schema, Node v8, Error handling, async-await, bugs solved

This commit is contained in:
Juan Ferrer Toribio 2018-01-29 12:37:54 +01:00
parent 506d6e0688
commit a3188b204f
152 changed files with 16013 additions and 1164 deletions

View File

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

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}"
}
]
}

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,14 +7,14 @@
</vn-horizontal>
<vn-horizontal ng-repeat="i in index.model.items track by i.id" class="pad-medium-top" style="align-items: center;">
<vn-one border-radius class="pad-small border-solid"
ng-class="{'bg-dark-item': i.isDefaultAddress,'bg-opacity-item': !i.isEnabled && !i.isDefaultAddress}">
ng-class="{'bg-dark-item': i.isDefaultAddress,'bg-opacity-item': !i.isActive && !i.isDefaultAddress}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h style="color:#FFA410;">
<i class="material-icons" ng-if="i.isDefaultAddress">star</i>
<i class="material-icons pointer" ng-if="!i.isDefaultAddress&&i.isEnabled" vn-tooltip="Set as default" tooltip-position="left" ng-click="$ctrl.setDefault(i.id)">star_border</i>
<i class="material-icons pointer" ng-if="!i.isDefaultAddress&&i.isActive" vn-tooltip="Set as default" tooltip-position="left" ng-click="$ctrl.setDefault(i.id)">star_border</i>
</vn-none>
<vn-one>
<div><b>{{::i.consignee}}</b></div>
<div><b>{{::i.nickname}}</b></div>
<div>{{::i.street}}</div>
<div>{{::i.city}}, {{::i.province}}</div>
<div>{{::i.phone}}, {{::i.mobile}}</div>

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

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

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

@ -37,8 +37,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

@ -163,8 +163,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.order});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);

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,55 +12,62 @@ 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() {
gulp.task('build', ['clean'], () => {
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 => {
let isWindows = /^win/.test(process.platform);
let command = isWindows ? '.\\dev.cmd' : './dev.sh';
exec(command, (err, stdout, stderr) => {
console.log(stdout);
callback(err);
});
});
gulp.task('services', () => {
gulp.task('services', ['nginx'], () => {
process.env.NODE_ENV = gutil.env.env || 'development';
const pathServices = './services/';
const services = fs.readdirSync(pathServices);
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('clean', function() {
return del([`${buildDir}/*`, `!${buildDir}/templates`, `!${buildDir}/images`], {force: true});
});
gulp.task('install', () => {
const servicesPath = './services/';
const jsonFile = [];
const services = fs.readdirSync(servicesPath);
services.push('..');
services.forEach(service => {
jsonFile.push(servicesPath.concat(service, '/package.json'));
});
return gulp.src(jsonFile)
.pipe(print(filepath => {
return `Installing packages in ${filepath}`;
}))
.pipe(install({
npm: ['--no-package-lock']
}));
});
gulp.task('servicesDev', callback => {
@ -78,25 +85,16 @@ gulp.task('servicesDev', callback => {
});
});
gulp.task('clean', function() {
return del([`${buildDir}/*`, `!${buildDir}/templates`, `!${buildDir}/images`], {force: true});
});
// Nginx
gulp.task('install', () => {
const pathServices = './services/';
const fileJson = [];
const services = fs.readdirSync(pathServices);
services.push('..');
services.forEach(service => {
fileJson.push(pathServices.concat(service, '/package.json'));
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);
});
return gulp.src(fileJson)
.pipe(print(filepath => {
return `Installing packages in ${filepath}`;
}))
.pipe(install({
npm: ['--no-package-lock']
}));
});
// Webpack
@ -171,17 +169,20 @@ gulp.task('routes', function() {
});
// Watch
gulp.task('watch', function() {
gulp.watch(routeFiles, ['routes']);
gulp.watch(localeFiles, ['locales']);
});
// Server side unit tests
// Services tests
gulp.task('test', callback => {
return require('./services_tests').start();
});
// e2e tests
// E2E tests
gulp.task('e2e', callback => {
runSequence('docker', 'waitForMySQL', 'endToEndTests', callback);
});
@ -212,7 +213,8 @@ gulp.task('endToEndTests', callback => {
.pipe(jasmine({reporter: 'none'}));
});
// docker dblocal
// Docker
gulp.task('docker', callback => {
runSequence('deleteDockerDb', 'deleteDockerImageDb', 'buildDockerDb', 'runDockerDb', callback);
});

14854
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -62,7 +62,7 @@
"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

@ -22,6 +22,6 @@
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
"dataSource": "account"
}
}

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,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

@ -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,11 @@
{
"name": "AgencyMode",
"base": "VnModel",
"options": {
"mysql": {
"table": "agencyMode"
}
},
"properties": {
"id": {
"type": "Number",
@ -20,7 +25,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

@ -27,10 +27,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

@ -2,6 +2,11 @@
"name": "ClientObservation",
"description": "Notas de los clientes.",
"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

@ -3,8 +3,7 @@
"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

@ -22,7 +22,7 @@
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
"dataSource": "account"
},
"Client": {
"dataSource": "vn"
@ -31,34 +31,34 @@
"dataSource": "vn"
},
"ClientCreditLimit": {
"dataSource": "salix"
"dataSource": "vn"
},
"ClientObservation": {
"dataSource": "salix"
"dataSource": "vn"
},
"PayMethod": {
"dataSource": "salix"
"dataSource": "vn"
},
"Address": {
"dataSource": "salix"
"dataSource": "vn"
},
"AgencyMode": {
"dataSource": "salix"
"dataSource": "vn"
},
"Province": {
"dataSource": "salix"
"dataSource": "vn"
},
"Country": {
"dataSource": "salix"
"dataSource": "vn"
},
"ContactChannel": {
"dataSource": "salix"
"dataSource": "vn"
},
"Employee": {
"dataSource": "salix"
"Worker": {
"dataSource": "vn"
},
"CreditClassification": {
"dataSource": "salix"
"dataSource": "vn"
},
"Greuge": {
"dataSource": "vn"

View File

@ -3,15 +3,7 @@ FROM mysql:5.6.37
ENV MYSQL_ALLOW_EMPTY_PASSWORD yes
ENV TZ GMT-1
COPY localDB01StructureAccount.sql /docker-entrypoint-initdb.d
COPY localDB02StructureVn2008.sql /docker-entrypoint-initdb.d
COPY localDB03StructureVn.sql /docker-entrypoint-initdb.d
COPY localDB04StructureOthersDB.sql /docker-entrypoint-initdb.d
COPY localDB05StructureUtil.sql /docker-entrypoint-initdb.d
COPY localDB06ViewsVn.sql /docker-entrypoint-initdb.d
COPY localDB07OthersViews.sql /docker-entrypoint-initdb.d
COPY localDB08Views2008.sql /docker-entrypoint-initdb.d
COPY localDB09Inserts.sql /docker-entrypoint-initdb.d
COPY *.sql /docker-entrypoint-initdb.d/
RUN chmod -R 755 /docker-entrypoint-initdb.d

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

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

View File

@ -1,3 +0,0 @@
module.exports = function(Self) {
Self.defineScope({where: {isManaged: {neq: 0}}});
};

View File

@ -0,0 +1,62 @@
var request = require('request');
module.exports = function(Self) {
Self.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'
}
});
Self.activate = function(id, ctx, cb) {
Self.findById(id, function(err, client) {
if (err) return cb(err);
Self.update({id: client.id}, {active: !client.active});
let filter = {where: {clientFk: client.id}, fields: ['started', 'ended']};
Self.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,6 +1,6 @@
module.exports = Client => {
Client.remoteMethod('activeSalesPerson', {
description: 'returns actives employees with salesperson role',
description: 'Returns actives workers with salesperson role',
accessType: 'READ',
accepts: [{
arg: 'filter',
@ -11,7 +11,7 @@ module.exports = Client => {
}],
returns: {
arg: 'data',
type: 'Employee',
type: 'Worker',
root: true
},
http: {
@ -25,21 +25,19 @@ module.exports = Client => {
let limit = filter.limit || 10;
let where = getCondition(filter.where, limit, skip);
let query = `SELECT em.id, em.name, em.surname
FROM salix.Employee em
JOIN salix.Account ac ON em.userFk = ac.id
JOIN salix.Role r ON r.id = ac.roleFK
WHERE ac.active AND r.\`name\`='salesPerson' ${where.sql}
ORDER BY em.name ASC
LIMIT ? OFFSET ?`;
let query =
`SELECT em.id, em.firstName, em.name
FROM worker em
JOIN account.user ac ON em.userFk = ac.id
JOIN account.role r ON r.id = ac.role
WHERE ac.active AND r.\`name\` = 'salesPerson' ${where.sql}
ORDER BY em.name ASC
LIMIT ? OFFSET ?`;
Client.rawSql(query, where.params, callback)
.then(response => {
callback(null, formatSalesPerson(response));
})
.catch(reject => {
callback(reject, null);
});
Client.rawSql(query, where.params).then(
response => callback(null, formatSalesPerson(response)),
err => callback(err)
);
};
function getCondition(where, limit, skip) {
@ -48,7 +46,7 @@ module.exports = Client => {
params: []
};
if (where && where.or) {
out.sql = `AND (em.name regexp ? OR em.surname regexp ?)`;
out.sql = `AND (em.firstName regexp ? OR em.name regexp ?)`;
where.or.forEach(val => {
Object.keys(val).forEach(key => {
out.params.push(val[key].regexp);
@ -66,7 +64,7 @@ module.exports = Client => {
response.forEach(person => {
results.push({
id: person.id,
name: `${person.name} ${person.surname}`
name: `${person.firstName} ${person.name}`
});
});

View File

@ -0,0 +1,43 @@
module.exports = function(Client) {
Client.remoteMethod('addressesPropagateRe', {
description: 'Change property isEqualizated in all client addresses',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'string',
required: true,
description: 'Client id',
http: {source: 'path'}
},
{
arg: 'data',
type: 'Object',
required: true,
description: 'data with new value',
http: {source: 'body'}
}
],
returns: {
arg: 'data',
type: 'boolean',
root: true
},
http: {
path: `/:id/addressesPropagateRe`,
verb: 'patch'
}
});
Client.addressesPropagateRe = (id, data, callback) => {
if (data.hasOwnProperty('isEqualizated')) {
Client.app.models.Address.updateAll({clientFk: id}, data, (err, info) => {
if (err)
return callback(err, null);
callback(null, true);
});
} else {
callback(null, false);
}
};
}

View File

@ -0,0 +1,76 @@
module.exports = function(Self) {
Self.remoteMethod('card', {
description: 'Get client basic data',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
returns: {
arg: 'data',
type: 'Object',
root: true
},
http: {
verb: 'GET',
path: '/:id/card'
}
});
Self.card = function(id, cb) {
let filter = {
where: {
id: id
},
include: [
{
relation: 'salesPerson',
scope: {
fields: ['id', 'firstName', 'name']
}
}, {
relation: 'contactChannel',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'province',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'country',
scope: {
fields: ['id', 'country']
}
}, {
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'account',
scope: {
fields: ['id', 'name', 'active']
}
}
]
};
Self.findOne(filter, function(err, card) {
if (err) return cb(err);
let formatedCard = JSON.parse(JSON.stringify(card));
if (formatedCard.salesPersonFk)
formatedCard.salesPerson = {
id: card.salesPerson().id,
name: `${card.salesPerson().firstName} ${card.salesPerson().name}`
};
cb(null, formatedCard);
});
};
};

View File

@ -0,0 +1,60 @@
let md5 = require('md5');
module.exports = function(Self) {
Self.remoteMethod('createWithUser', {
description: 'Creates both client and its web account',
accepts: {
arg: 'data',
type: 'object',
http: {source: 'body'}
},
returns: {
root: true,
type: 'boolean'
},
http: {
verb: 'post',
path: '/createWithUser'
}
});
Self.createWithUser = (data, callback) => {
let firstEmail = data.email ? data.email.split(',')[0] : null;
let user = {
name: data.userName,
email: firstEmail,
password: md5(parseInt(Math.random() * 100000000000000))
};
let Account = Self.app.models.Account;
Account.beginTransaction({}, (error, transaction) => {
if (error) return callback(error);
Account.create(user, {transaction}, (error, account) => {
if (error) {
transaction.rollback();
return callback(error);
}
let client = {
name: data.name,
fi: data.fi,
socialName: data.socialName,
id: account.id,
email: data.email,
salesPersonFk: data.salesPersonFk
};
Self.create(client, {transaction}, (error, newClient) => {
if (error) {
transaction.rollback();
return callback(error);
}
transaction.commit();
callback(null, newClient);
});
});
});
};
};

View File

@ -0,0 +1,45 @@
module.exports = Self => {
Self.remoteMethod('hasCustomerRole', {
description: 'Comprueba si un usuario tiene el rol de cliente',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'string',
required: true,
description: 'The user id',
http: {source: 'path'}
}, {
arg: 'context',
type: 'object',
required: true,
description: 'Filter defining where',
http: function(context) {
return context.req.query;
}
}
],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:id/hasCustomerRole`,
verb: 'GET'
}
});
Self.hasCustomerRole = (id, context, callback) => {
let query =
`SELECT COUNT(*) > 0 isCustomer
FROM salix.Account A
JOIN salix.Role r ON r.id = A.roleFK
WHERE r.name = 'customer'
AND A.id IN (?)`;
Self.rawSql(query, [id]).then(
instances => callback(null, instances[0]),
err => callback(err)
);
};
};

View File

@ -0,0 +1,70 @@
module.exports = function(Client) {
Client.remoteMethod('listAddresses', {
description: 'List items using a filter',
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(ctx) {
return ctx.req.query;
}
}
],
returns: {
arg: 'data',
type: ['Address'],
root: true
},
http: {
path: `/:id/listAddresses`,
verb: 'GET'
}
});
Client.listAddresses = function(id, params, callback) {
let filter = {
where: {
clientFk: id
},
skip: (params.page - 1) * params.size,
limit: params.size,
order: ['isDefaultAddress DESC', 'isActive DESC']
};
let total = null;
let items = null;
function response(type, value) {
if (type === 'total') {
total = value;
} else {
items = value;
}
if (total !== null && items !== null) {
callback(null, {total: total, items: items});
}
}
Client.app.models.Address.find(filter, function(err, instances) {
if (err) return callback(err);
response('find', instances);
});
Client.app.models.Address.count(filter.where, function(err, total) {
if (err) return callback(err);
response('total', total);
});
};
};

View File

@ -0,0 +1,30 @@
module.exports = function(Self) {
Self.remoteMethod('listWorkers', {
description: 'List workers',
accessType: 'READ',
returns: {
arg: 'data',
type: 'Worker',
root: true
},
http: {
path: `/listWorkers`,
verb: 'GET'
}
});
Self.listWorkers = function() {
let query =
`SELECT w.id,
CONCAT(w.firstName, IFNULL(CONCAT(" ", w.name), "")) \`name\`
FROM worker w
JOIN account.user u ON w.userFk = u.id
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.active
AND r.\`name\` = 'employee'
ORDER BY w.name ASC`;
return Self.rawSql(query);
};
};

View File

@ -1,3 +0,0 @@
module.exports = function(Self) {
Self.defineScope({where: {isManaged: {neq: 0}}});
};

View File

@ -1,6 +1,11 @@
{
"name": "Account",
"base": "VnModel",
"options": {
"mysql": {
"table": "user"
}
},
"properties": {
"id": {
"type": "number",

View File

@ -0,0 +1,49 @@
var UserError = require('../helpers').UserError;
var getFinalState = require('../helpers').getFinalState;
var isMultiple = require('../helpers').isMultiple;
module.exports = function(Self) {
Self.validate('isDefaultAddress', isActive,
{message: 'No se puede poner predeterminado un consignatario desactivado'}
);
function isActive(err) {
if (!this.isActive && this.isDefaultAddress) err();
}
Self.beforeRemote('findById', function(ctx, modelInstance, next) {
ctx.args.filter = {
include: [{
relation: 'province',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'agency',
scope: {
fields: ['id', 'name']
}
}]
};
next();
});
// Helpers
Self.observe('before save', async function(ctx) {
if (isMultiple(ctx)) return;
let changes = ctx.data || ctx.instance;
let finalState = getFinalState(ctx);
if (changes.isActive == false && finalState.isDefaultAddress)
throw new UserError('No se puede desmarcar el consignatario predeterminado');
if (changes.isDefaultAddress == true) {
let filter = {
clientFk: finalState.clientFk,
isDefaultAddress: {neq: false}
};
await Self.updateAll(filter, {isDefaultAddress: false});
}
});
};

View File

@ -1,13 +1,18 @@
{
"name": "Address",
"base": "VnModel",
"options": {
"mysql": {
"table": "address"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"consignee": {
"nickname": {
"type": "string",
"required": true
},
@ -19,7 +24,7 @@
"type": "string",
"required": true
},
"postcode": {
"postalCode": {
"type": "string"
},
"phone": {
@ -28,7 +33,7 @@
"mobile": {
"type": "string"
},
"isEnabled": {
"isActive": {
"type": "boolean"
},
"isDefaultAddress": {
@ -56,10 +61,10 @@
"model": "Client",
"foreignKey": "clientFk"
},
"defaultAgency": {
"agency": {
"type": "belongsTo",
"model": "AgencyMode",
"foreignKey": "defaultAgencyFk"
"foreignKey": "agencyFk"
}
}
}

View File

@ -1,3 +1,3 @@
module.exports = function(Self) {
require('../methods/agency/list.js')(Self);
Self.defineScope({where: {isManaged: {neq: 0}}});
};

View File

@ -1,6 +1,11 @@
{
"name": "Agency",
"base": "VnModel",
"options": {
"mysql": {
"table": "agency"
}
},
"properties": {
"id": {
"id": true,

View File

@ -1,6 +1,11 @@
{
"name": "ClientCreditLimit",
"base": "VnModel",
"options": {
"mysql": {
"table": "clientCreditLimit"
}
},
"properties": {
"id": {
"type": "Number",

View File

@ -0,0 +1,160 @@
var UserError = require('../helpers').UserError;
var getFinalState = require('../helpers').getFinalState;
var isMultiple = require('../helpers').isMultiple;
module.exports = function(Self) {
// Methods
require('../methods/client/activate')(Self);
require('../methods/client/listAddresses')(Self);
require('../methods/client/card')(Self);
require('../methods/client/createWithUser')(Self);
require('../methods/client/listWorkers')(Self);
require('../methods/client/filter')(Self);
require('../methods/client/hasCustomerRole')(Self);
require('../methods/client/activeSalesPerson')(Self);
require('../methods/client/addressesPropagateRe')(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) {
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequired && !this.iban)
err();
done();
});
}
// Hooks
Self.observe('before save', async function(ctx) {
let changes = ctx.data || ctx.instance;
let finalState = getFinalState(ctx);
if (changes.salesPerson === null) {
changes.credit = 0;
changes.discount = 0;
changes.payMethodFk = 5; // Credit card
}
if (changes.payMethodFk !== undefined && changes.dueDay === undefined)
changes.dueDay = 5;
if (isMultiple(ctx)) return;
if (changes.isEqualizated || changes.fi !== undefined) {
let fiLetter = finalState.fi && finalState.fi.toUpperCase().charAt(0);
let canMarkEqualizationTax = fiLetter != 'A' && fiLetter != 'B';
if (finalState.isEqualizated && !canMarkEqualizationTax)
throw new UserError('No se puede marcar el recargo de equivalencia');
}
if (changes.credit !== undefined)
try {
await validateCreditChange(ctx, finalState);
} catch (e) {
throw new UserError('No tienes privilegios para modificar el crédito');
}
});
async function validateCreditChange(ctx, finalState) {
let models = Self.app.models;
let userId = ctx.options.accessToken.userId;
let filter = {
fields: ['roleFk'],
where: {
maxAmount: {gt: ctx.data.credit}
}
};
let limits = await models.ClientCreditLimit.find(filter);
if (limits.length == 0)
throw new Error('Credit limits not found');
// Si el usuario no tiene alguno de los roles no continua
let requiredRoles = [];
for (limit of limits)
requiredRoles.push(limit.roleFk);
let where = {
roleId: {inq: requiredRoles},
principalType: 'USER',
principalId: userId
};
let count = await models.RoleMapping.count(where);
if (count <= 0)
throw new Error('The role cannot set this credit amount');
// Si se puso a 0 por gerencia, solo gerencia puede aumentarlo
let query = 'SELECT * FROM clientCredit WHERE clientFk = ? ORDER BY created DESC LIMIT 1';
let instances = await Self.rawSql(query, [finalState.id]);
if (instances.length !== 1 || instances[0].workerFk == userId || instances[0].amount > 0)
return;
query = `SELECT COUNT(distinct r.id) > 0 as hasManagerRole
FROM clientCredit cc
JOIN worker em ON em.id = cc.workerFk
JOIN account.user ac ON ac.id = em.userFk
JOIN salix.RoleMapping rm ON rm.principalId = ac.id
JOIN account.role r on r.id = rm.roleId
WHERE rm.principalType = 'USER'
AND cc.workerFk = ?
AND r.name = 'manager'`;
let instance = await Self.rawSql(query, [instances[0].workerFk]);
if (instance[0].hasManagerRole > 0)
throw new Error('Only manager can change the credit');
}
};

View File

@ -3,8 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "client",
"database": "vn"
"table": "client"
}
},
"properties": {
@ -121,7 +120,7 @@
},
"salesPerson": {
"type": "belongsTo",
"model": "Employee",
"model": "Worker",
"foreignKey": "salesPersonFk"
},
"province":{

View File

@ -1,6 +1,11 @@
{
"name": "ContactChannel",
"base": "VnModel",
"options": {
"mysql": {
"table": "contactChannel"
}
},
"properties": {
"id": {
"type": "Number",

View File

@ -1,19 +1,21 @@
{
"name": "Country",
"base": "VnModel",
"options": {
"mysql": {
"table": "country"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"country": {
"type": "string",
"required": true
},
"inCee": {
"type": "Number"
},
"code": {
"type": "string"
}
@ -23,11 +25,6 @@
"type": "belongsTo",
"model": "Currency",
"foreignKey": "currencyFk"
},
"realCountry": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "realCountryFk"
}
},
"acls": [

View File

@ -2,6 +2,11 @@
"name": "CreditClassification",
"description": "Clientes clasificados.",
"base": "VnModel",
"options": {
"mysql": {
"table": "creditClassification"
}
},
"properties": {
"id": {
"id": true,

View File

@ -1,6 +1,11 @@
{
"name": "PayMethod",
"base": "VnModel",
"options": {
"mysql": {
"table": "payMethod"
}
},
"properties": {
"id": {
"type": "Number",

View File

@ -1,6 +1,11 @@
{
"name": "Province",
"base": "VnModel",
"options": {
"mysql": {
"table": "province"
}
},
"properties": {
"id": {
"type": "Number",

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