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

This commit is contained in:
Javi Gallego 2018-03-13 14:07:47 +01:00
commit a6994b0179
97 changed files with 2367 additions and 915 deletions

6
.env.json Normal file
View File

@ -0,0 +1,6 @@
{
"salixHost": "localhost",
"salixPort": "3306",
"salixUser": "root",
"salixPassword": "root"
}

6
Jenkinsfile vendored
View File

@ -6,10 +6,14 @@ def branchTest = "test";
env.BRANCH_NAME = branchName; env.BRANCH_NAME = branchName;
env.TAG = "${env.BUILD_NUMBER}"; env.TAG = "${env.BUILD_NUMBER}";
env.salixUser="${env.salixUser}";
env.salixPassword="${env.salixPassword}";
switch (branchName){ switch (branchName){
case branchTest: case branchTest:
env.NODE_ENV = "test"; env.NODE_ENV = "test";
env.salixHost = "${env.testSalixHost}";
env.salixPort = "${env.testSalixPort}";
break; break;
case branchProduction: case branchProduction:
env.DOCKER_HOST = "tcp://172.16.255.29:2375"; env.DOCKER_HOST = "tcp://172.16.255.29:2375";
@ -20,7 +24,7 @@ switch (branchName){
node node
{ {
stage ('Print environment variables'){ stage ('Print environment variables'){
echo "Branch ${branchName}, Build ${env.TAG}, NODE_ENV ${env.NODE_ENV} en docker Host ${env.DOCKER_HOST}" echo "Branch ${branchName}, Build ${env.TAG}, salixHost ${env.salixHost}, NODE_ENV ${env.NODE_ENV} en docker Host ${env.DOCKER_HOST}"
} }
stage ('Checkout') { stage ('Checkout') {
checkout scm checkout scm

View File

@ -1,7 +1,7 @@
{ {
"module": "client", "module": "client",
"name": "Clients", "name": "Clients",
"icon": "/static/images/icon_client.png", "icon": "person",
"validations" : true, "validations" : true,
"routes": [ "routes": [
{ {

View File

@ -59,9 +59,7 @@
field="observation.observationTypeFk" field="observation.observationTypeFk"
data="observationsTypes.model" data="observationsTypes.model"
show-field="description" show-field="description"
label="Observation type" label="Observation type">
order="description ASC"
filter-search="{where: {description: {regexp: 'search'}}}">
<tpl-item>{{$parent.$parent.item.description}}</tpl-item> <tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield <vn-textfield

View File

@ -30,7 +30,7 @@
<span>{{::observation.description}}</span> <span>{{::observation.description}}</span>
</vn-one> </vn-one>
</vn-vertical> </vn-vertical>
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{address.id}}})"> <a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{::address.id}}})">
<vn-icon-button icon="edit"></vn-icon-button> <vn-icon-button icon="edit"></vn-icon-button>
</a> </a>
</vn-horizontal> </vn-horizontal>

View File

@ -30,7 +30,7 @@
value-field="id" value-field="id"
select-fields="name" select-fields="name"
label="Salesperson" label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}"> where="{or: [{firstName: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
initial-data="$ctrl.client.contactChannel" initial-data="$ctrl.client.contactChannel"

View File

@ -26,7 +26,7 @@
value-field="id" value-field="id"
select-fields="name" select-fields="name"
label="Salesperson" label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}"> where="{or: [{firstName: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>

View File

@ -25,6 +25,7 @@ class ClientDescriptor {
} }
} }
} }
ClientDescriptor.$inject = ['$http'];
ngModule.component('vnClientDescriptor', { ngModule.component('vnClientDescriptor', {
template: require('./descriptor.html'), template: require('./descriptor.html'),

View File

@ -9,13 +9,6 @@ vn-autocomplete > div > .mdl-textfield {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
} }
& > .icons { & > .icons {
display: none; display: none;

View File

@ -1,7 +1,4 @@
vn-date-picker { vn-date-picker {
div {
outline: none; //remove chrome outline
}
.mdl-chip__action { .mdl-chip__action {
position: absolute; position: absolute;
width: auto; width: auto;

View File

@ -1,35 +1,38 @@
<div <vn-model
class="body" vn-id="model"
ng-mousedown="$ctrl.onMouseDown($event)"> on-data-change="$ctrl.onModelDataChange()">
<div ng-show="$ctrl.showFilter" class="filter"> </vn-model>
<input <vn-popover
type="text" vn-id="popover"
ng-model="$ctrl.search" on-open="$ctrl.onOpen()"
tabindex="-1" on-close="$ctrl.onClose()">
class="search" <div class="dropdown">
ng-blur="$ctrl.onFocusOut()" <div ng-show="$ctrl.showFilter" class="filter">
translate-attr="{placeholder: 'Search'}"/> <input
<vn-icon type="text"
icon="clear" ng-model="$ctrl.search"
ng-click="$ctrl.onClearClick()" tabindex="-1"
translate-attr="{title: 'Clear'}"> class="search"
</vn-icon> ng-blur="$ctrl.onFocusOut()"
</div> translate-attr="{placeholder: 'Search'}"/>
<vn-model <vn-icon
vn-id="model" icon="clear"
on-data-change="$ctrl.onModelDataChange()"> ng-click="$ctrl.onClearClick()"
</vn-model> translate-attr="{title: 'Clear'}">
<div class="list" tabindex="-1"> </vn-icon>
<ul </div>
class="dropdown" <div class="list" tabindex="-1">
ng-click="$ctrl.onContainerClick($event)"> <ul
</ul> class="dropdown"
<div ng-click="$ctrl.onContainerClick($event)">
ng-if="$ctrl.statusText" </ul>
ng-click="$ctrl.onLoadMoreClick($event)" <div
class="status" ng-if="$ctrl.statusText"
translate> ng-click="$ctrl.onLoadMoreClick($event)"
{{$ctrl.statusText}} class="status"
translate>
{{$ctrl.statusText}}
</div>
</div> </div>
</div> </div>
</div> </vn-popover>

View File

@ -4,45 +4,34 @@ import './style.scss';
import './model'; import './model';
export default class DropDown extends Component { export default class DropDown extends Component {
constructor($element, $scope, $transclude, $timeout, $http, $translate) { constructor($element, $scope, $transclude, $timeout, $http) {
super($element, $scope); super($element, $scope);
this.$transclude = $transclude; this.$transclude = $transclude;
this.$timeout = $timeout; this.$timeout = $timeout;
this.$translate = $translate;
this.valueField = 'id'; this.valueField = 'id';
this.showField = 'name'; this.showField = 'name';
this._search = undefined; this._search = undefined;
this._shown = false;
this._activeOption = -1; this._activeOption = -1;
this.showLoadMore = true; this.showLoadMore = true;
this.showFilter = true; this.showFilter = true;
this.input = this.element.querySelector('.search');
this.body = this.element.querySelector('.body');
this.container = this.element.querySelector('ul');
this.list = this.element.querySelector('.list');
this.docKeyDownHandler = e => this.onDocKeyDown(e); this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.docFocusInHandler = e => this.onDocFocusIn(e); }
this.element.addEventListener('mousedown', $postLink() {
e => this.onBackgroundMouseDown(e)); this.input = this.element.querySelector('.search');
this.element.addEventListener('focusin', this.ul = this.element.querySelector('ul');
e => this.onFocusIn(e)); this.list = this.element.querySelector('.list');
this.list.addEventListener('scroll', this.list.addEventListener('scroll', e => this.onScroll(e));
e => this.onScroll(e));
} }
get shown() { get shown() {
return this._shown; return this.$.popover.shown;
} }
set shown(value) { set shown(value) {
if (value) this.$.popover.shown = value;
this.show();
else
this.hide();
} }
get search() { get search() {
@ -97,62 +86,18 @@ export default class DropDown extends Component {
* @param {String} search The initial search term or %null * @param {String} search The initial search term or %null
*/ */
show(search) { show(search) {
if (this._shown) return;
this._shown = true;
this._activeOption = -1;
this.search = search; this.search = search;
this._activeOption = -1;
this.buildList(); this.buildList();
this.element.style.display = 'block'; this.$.popover.parent = this.parent;
this.list.scrollTop = 0; this.$.popover.show();
this.$timeout(() => this.$element.addClass('shown'), 40);
this.document.addEventListener('keydown', this.docKeyDownHandler);
this.document.addEventListener('focusin', this.docFocusInHandler);
this.relocate();
this.input.focus();
} }
/** /**
* Hides the drop-down. * Hides the drop-down.
*/ */
hide() { hide() {
if (!this._shown) return; this.$.popover.hide();
this._shown = false;
this.element.style.display = '';
this.$element.removeClass('shown');
this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.document.removeEventListener('focusin', this.docFocusInHandler);
if (this.parent)
this.parent.focus();
}
/**
* Repositions the drop-down to a correct location relative to the parent.
*/
relocate() {
if (!this.parent) return;
let style = this.body.style;
style.width = '';
style.height = '';
let parentRect = this.parent.getBoundingClientRect();
let bodyRect = this.body.getBoundingClientRect();
let top = parentRect.top + parentRect.height;
let height = bodyRect.height;
let width = Math.max(bodyRect.width, parentRect.width);
let margin = 10;
if (top + height + margin > window.innerHeight)
top = Math.max(parentRect.top - height, margin);
style.top = `${top}px`;
style.left = `${parentRect.left}px`;
style.width = `${width}px`;
if (height + margin * 2 > window.innerHeight)
style.height = `${window.innerHeight - margin * 2}px`;
} }
/** /**
@ -190,7 +135,7 @@ export default class DropDown extends Component {
let data = this.$.model.data; let data = this.$.model.data;
if (option >= 0 && data && option < data.length) { if (option >= 0 && data && option < data.length) {
this.activeLi = this.container.children[option]; this.activeLi = this.ul.children[option];
this.activeLi.className = 'active'; this.activeLi.className = 'active';
} }
} }
@ -224,7 +169,7 @@ export default class DropDown extends Component {
} }
if (!this.multiple) if (!this.multiple)
this.hide(); this.$.popover.hide();
} }
refreshModel() { refreshModel() {
@ -260,22 +205,14 @@ export default class DropDown extends Component {
return undefined; return undefined;
} }
onMouseDown(event) { onOpen() {
this.lastMouseEvent = event; this.document.addEventListener('keydown', this.docKeyDownHandler);
this.list.scrollTop = 0;
this.input.focus();
} }
onBackgroundMouseDown(event) { onClose() {
if (event != this.lastMouseEvent) this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.hide();
}
onFocusIn(event) {
this.lastFocusEvent = event;
}
onDocFocusIn(event) {
if (event !== this.lastFocusEvent)
this.hide();
} }
onClearClick() { onClearClick() {
@ -299,7 +236,7 @@ export default class DropDown extends Component {
onContainerClick(event) { onContainerClick(event) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
let index = getPosition(this.container, event); let index = getPosition(this.ul, event);
if (index != -1) this.selectOption(index); if (index != -1) this.selectOption(index);
} }
@ -318,9 +255,6 @@ export default class DropDown extends Component {
case 13: // Enter case 13: // Enter
this.selectOption(option); this.selectOption(option);
break; break;
case 27: // Escape
this.hide();
break;
case 38: // Up case 38: // Up
this.moveToOption(option <= 0 ? nOpts : option - 1); this.moveToOption(option <= 0 ? nOpts : option - 1);
break; break;
@ -342,8 +276,7 @@ export default class DropDown extends Component {
} }
buildList() { buildList() {
this.destroyScopes(); this.destroyList();
this.scopes = [];
let hasTemplate = this.$transclude && let hasTemplate = this.$transclude &&
this.$transclude.isSlotFilled('tplItem'); this.$transclude.isSlotFilled('tplItem');
@ -377,23 +310,26 @@ export default class DropDown extends Component {
} }
} }
this.container.innerHTML = ''; this.ul.appendChild(fragment);
this.container.appendChild(fragment);
this.activateOption(this._activeOption); this.activateOption(this._activeOption);
this.relocate(); this.$.$applyAsync(() => this.$.popover.relocate());
} }
destroyScopes() { destroyList() {
this.ul.innerHTML = '';
if (this.scopes) if (this.scopes)
for (let scope of this.scopes) for (let scope of this.scopes)
scope.$destroy(); scope.$destroy();
this.scopes = [];
} }
$onDestroy() { $onDestroy() {
this.destroyScopes(); this.destroyList();
} }
} }
DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http', '$translate']; DropDown.$inject = ['$element', '$scope', '$transclude', '$timeout', '$http'];
/** /**
* Gets the position of an event element relative to a parent. * Gets the position of an event element relative to a parent.

View File

@ -7,6 +7,7 @@ describe('Component vnDropDown', () => {
let $element; let $element;
let $scope; let $scope;
let $httpBackend; let $httpBackend;
let $transitions;
let $q; let $q;
let $filter; let $filter;
let controller; let controller;
@ -15,18 +16,25 @@ describe('Component vnDropDown', () => {
angular.mock.module('client'); angular.mock.module('client');
}); });
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_) => { beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$timeout_, _$httpBackend_, _$q_, _$filter_, _$transitions_) => {
$componentController = _$componentController_; $componentController = _$componentController_;
$element = angular.element(`<div>${template}</div>`); $element = angular.element(`<div>${template}</div>`);
$timeout = _$timeout_; $timeout = _$timeout_;
$transitions = _$transitions_;
$q = _$q_; $q = _$q_;
$filter = _$filter_; $filter = _$filter_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({}); $httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
let popoverTemplate = require('../popover/popover.html');
let $popover = angular.element(`<div>${popoverTemplate}</div>`);
$scope.popover = $componentController('vnPopover', {$element: $popover, $scope, $timeout, $transitions});
$scope.popover.$postLink();
$scope.model = $componentController('vnModel', {$httpBackend, $q, $filter}); $scope.model = $componentController('vnModel', {$httpBackend, $q, $filter});
controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null}); controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
controller.$postLink();
controller.parent = angular.element('<vn-parent></vn-parent>')[0]; controller.parent = angular.element('<vn-parent></vn-parent>')[0];
})); }));

View File

@ -1,30 +1,7 @@
vn-drop-down { vn-drop-down {
z-index: 10; .dropdown {
position: fixed;
display: none;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.shown {
& > .body {
transform: translateY(0);
opacity: 1;
}
}
& > .body {
position: fixed;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
border-radius: .1em;
background-color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transform: translateY(-.4em);
opacity: 0;
transition-property: opacity, transform;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
& > .filter { & > .filter {
position: relative; position: relative;
@ -35,6 +12,7 @@ vn-drop-down {
box-sizing: border-box; box-sizing: border-box;
border: none; border: none;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
font-size: inherit;
padding: .6em; padding: .6em;
} }
& > vn-icon[icon=clear] { & > vn-icon[icon=clear] {
@ -64,10 +42,9 @@ vn-drop-down {
ul { ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style-type: none;
} }
li, .status { li, .status {
outline: none;
list-style-type: none;
padding: .6em; padding: .6em;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;

View File

@ -2,8 +2,6 @@ import './textfield/textfield';
import './watcher/watcher'; import './watcher/watcher';
import './paging/paging'; import './paging/paging';
import './icon/icon'; import './icon/icon';
import './autocomplete/autocomplete';
import './popover/popover';
import './dialog/dialog'; import './dialog/dialog';
import './confirm/confirm'; import './confirm/confirm';
import './title/title'; import './title/title';
@ -12,7 +10,10 @@ import './spinner/spinner';
import './snackbar/snackbar'; import './snackbar/snackbar';
import './tooltip/tooltip'; import './tooltip/tooltip';
import './icon-menu/icon-menu'; import './icon-menu/icon-menu';
import './popover/popover';
import './autocomplete/autocomplete';
import './drop-down/drop-down'; import './drop-down/drop-down';
import './menu/menu';
import './column-header/column-header'; import './column-header/column-header';
import './grid-header/grid-header'; import './grid-header/grid-header';
import './multi-check/multi-check'; import './multi-check/multi-check';

View File

@ -0,0 +1,24 @@
import ngModule from '../../module';
import Popover from '../popover/popover';
export default class Menu extends Popover {
$postLink() {
super.$postLink();
this.element.addEventListener('click',
() => this.onClick());
}
onClick() {
this.hide();
}
}
ngModule.component('vnMenu', {
template: require('../popover/popover.html'),
controller: Menu,
transclude: true,
bindings: {
onOpen: '&?',
onClose: '&?'
}
});

View File

@ -2,10 +2,6 @@
vn-icon{ vn-icon{
cursor: pointer; cursor: pointer;
} }
&:focus, &:active, &:hover{
outline: none;
border: none;
}
.primaryCheckbox { .primaryCheckbox {
vn-icon{ vn-icon{
font-size: 22px; font-size: 22px;

View File

@ -0,0 +1,4 @@
<div class="popover">
<div class="arrow"></div>
<div class="content" ng-transclude></div>
</div>

View File

@ -1,205 +1,223 @@
import ngModule from '../../module'; import ngModule from '../../module';
import './style.css'; import Component from '../../lib/component';
import './style.scss';
directive.$inject = ['vnPopover']; /**
export function directive(vnPopover) { * A simple popover.
return { */
restrict: 'A', export default class Popover extends Component {
link: function($scope, $element, $attrs) { constructor($element, $scope, $timeout, $transitions) {
$element.on('click', function(event) { super($element, $scope);
vnPopover.showComponent($attrs.vnDialog, $scope, $element); this.$timeout = $timeout;
event.preventDefault();
});
}
};
}
ngModule.directive('vnPopover', directive);
export class Popover {
constructor($document, $compile, $transitions) {
this.document = $document[0];
this.$compile = $compile;
this.$transitions = $transitions; this.$transitions = $transitions;
this.removeScope = false; this._shown = false;
this.popOpens = 0;
} }
_init() {
this.docMouseDownHandler = e => this.onDocMouseDown(e); $postLink() {
this.document.addEventListener('mousedown', this.docMouseDownHandler); this.$element.addClass('vn-popover');
this.docKeyDownHandler = e => this.onDocKeyDown(e); this.docKeyDownHandler = e => this.onDocKeyDown(e);
this.docFocusInHandler = e => this.onDocFocusIn(e);
this.element.addEventListener('mousedown',
e => this.onBackgroundMouseDown(e));
this.element.addEventListener('focusin',
e => this.onFocusIn(e));
this.popover = this.element.querySelector('.popover');
this.popover.addEventListener('mousedown',
e => this.onMouseDown(e));
this.arrow = this.element.querySelector('.arrow');
this.content = this.element.querySelector('.content');
}
set child(value) {
this.content.appendChild(value);
}
get child() {
return this.content.firstChild;
}
get shown() {
return this._shown;
}
set shown(value) {
if (value)
this.show();
else
this.hide();
}
/**
* Shows the popover. If a parent is specified it is shown in a visible
* relative position to it.
*/
show() {
if (this._shown) return;
this._shown = true;
this.element.style.display = 'block';
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.$element.addClass('shown');
this.showTimeout = null;
}, 30);
this.document.addEventListener('keydown', this.docKeyDownHandler); this.document.addEventListener('keydown', this.docKeyDownHandler);
this.deregisterCallback = this.$transitions.onStart({}, this.document.addEventListener('focusin', this.docFocusInHandler);
() => this.hideAll());
this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
this.relocate();
if (this.onOpen)
this.onOpen();
} }
_destroy() {
this.document.removeEventListener('mousedown', this.docMouseDownHandler); /**
* Hides the popover.
*/
hide() {
if (!this._shown) return;
this._shown = false;
this.$element.removeClass('shown');
this.$timeout.cancel(this.showTimeout);
this.showTimeout = this.$timeout(() => {
this.element.style.display = 'none';
this.showTimeout = null;
}, 250);
this.document.removeEventListener('keydown', this.docKeyDownHandler); this.document.removeEventListener('keydown', this.docKeyDownHandler);
this.docMouseDownHandler = null; this.document.removeEventListener('focusin', this.docFocusInHandler);
this.docKeyDownHandler = null;
this.deregisterCallback();
}
show(childElement, parent, popoverId) {
this.childElement = childElement;
let popover = this.document.createElement('div');
this.popOpens++;
if (!popoverId) { if (this.deregisterCallback)
popoverId = 'popover-' + this.popOpens; this.deregisterCallback();
popover.id = popoverId;
}
popover.className = 'vn-popover'; if (this.parent)
popover.addEventListener('mousedown', this.parent.focus();
e => this.onPopoverMouseDown(e));
popover.appendChild(childElement);
this.popover = popover;
let style = popover.style; if (this.onClose)
this.onClose();
let spacing = 0;
let screenMargin = 20;
let dblMargin = screenMargin * 2;
let width = popover.offsetWidth;
let height = popover.offsetHeight;
let innerWidth = window.innerWidth;
let innerHeight = window.innerHeight;
if (width + dblMargin > innerWidth) {
width = innerWidth - dblMargin;
style.width = width + 'px';
}
if (height + dblMargin > innerHeight) {
height = innerHeight - dblMargin;
style.height = height + 'px';
}
if (parent) {
let parentNode = parent;
let rect = parentNode.getBoundingClientRect();
let left = rect.left;
let top = rect.top + spacing + parentNode.offsetHeight;
if (left + width > innerWidth)
left -= (left + width) - innerWidth + margin;
if (top + height > innerHeight)
top -= height + parentNode.offsetHeight + spacing * 2;
if (left < 0)
left = screenMargin;
if (top < 0)
top = screenMargin;
style.top = (top) + 'px';
style.left = (left) + 'px';
style.minWidth = (rect.width) + 'px';
}
this.document.body.appendChild(popover);
if (this.popOpens === 1) {
this._init();
}
return popoverId;
} }
showComponent(childComponent, $scope, parent) { /**
let childElement = this.document.createElement(childComponent); * Repositions the popover to a correct location relative to the parent.
let id = 'popover-' + this.popOpens; */
childElement.id = id; relocate() {
this.removeScope = true; if (!(this.parent && this._shown)) return;
this.$compile(childElement)($scope.$new());
this.show(childElement, parent, id);
return childElement;
}
_checkOpens() { let style = this.popover.style;
this.popOpens = this.document.querySelectorAll('*[id^="popover-"]').length; style.width = '';
if (this.popOpens === 0) { style.height = '';
this._destroy();
}
}
_removeElement(val) { let arrowStyle = this.arrow.style;
if (!val) return; arrowStyle.top = '';
let element = angular.element(val); arrowStyle.bottom = '';
let parent = val.parentNode;
if (element.scope() && element.scope().$id > 1) {
element.scope().$destroy();
}
element.remove();
if (parent.className.indexOf('vn-popover') !== -1)
this._removeElement(parent);
}
hide(id) { let parentRect = this.parent.getBoundingClientRect();
let popover = this.document.querySelector(`#${id}`); let popoverRect = this.popover.getBoundingClientRect();
if (popover) { let arrowRect = this.arrow.getBoundingClientRect();
this._removeElement(popover);
}
this._checkOpens();
}
hideChilds(id) { let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;
let popovers = this.document.querySelectorAll('*[id^="popover-"]');
let idNumber = parseInt(id.split('-')[1], 10);
popovers.forEach(
val => {
if (parseInt(val.id.split('-')[1], 10) > idNumber)
this._removeElement(val);
}
);
this._checkOpens();
}
hideAll() { let top = parentRect.top + parentRect.height + arrowHeight;
let popovers = this.document.querySelectorAll('*[id^="popover-"]'); let left = parentRect.left;
popovers.forEach( let height = popoverRect.height;
val => { let width = Math.max(popoverRect.width, parentRect.width);
this._removeElement(val);
}
);
this._checkOpens();
}
_findPopOver(node) { let margin = 10;
while (node != null) { let showTop = top + height + margin > window.innerHeight;
if (node.id && node.id.startsWith('popover-')) {
return node.id;
}
node = node.parentNode;
}
return null;
}
onDocMouseDown(event) { if (showTop)
let targetId = this._findPopOver(event.target); top = Math.max(parentRect.top - height - arrowHeight, margin);
if (targetId) { if (left + width + margin > window.innerWidth)
this.hideChilds(targetId); left = window.innerWidth - width - margin;
} else {
this.hideAll(); if (showTop)
} arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
arrowStyle.left = `${(parentRect.left - left) + parentRect.width / 2}px`;
style.top = `${top}px`;
style.left = `${left}px`;
style.width = `${width}px`;
if (height + margin * 2 + arrowHeight > window.innerHeight)
style.height = `${window.innerHeight - margin * 2 - arrowHeight}px`;
} }
onDocKeyDown(event) { onDocKeyDown(event) {
if (event.keyCode === 27) { if (event.defaultPrevented) return;
let targetId = this._findPopOver(this.lastTarget);
if (targetId) { if (event.keyCode == 27) { // Esc
this.hideChilds(targetId); event.preventDefault();
} else { this.hide();
this.hideAll();
}
this.lastTarget = null;
} }
} }
onPopoverMouseDown(event) { onMouseDown(event) {
this.lastTarget = event.target; this.lastMouseEvent = event;
}
onBackgroundMouseDown(event) {
if (event != this.lastMouseEvent)
this.hide();
}
onFocusIn(event) {
this.lastFocusEvent = event;
}
onDocFocusIn(event) {
if (event !== this.lastFocusEvent)
this.hide();
} }
} }
Popover.$inject = ['$document', '$compile', '$transitions']; Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];
ngModule.service('vnPopover', Popover); ngModule.component('vnPopover', {
template: require('./popover.html'),
controller: Popover,
transclude: true,
bindings: {
onOpen: '&?',
onClose: '&?'
}
});
class PopoverService {
constructor($document, $compile, $transitions, $rootScope) {
this.$compile = $compile;
this.$rootScope = $rootScope;
this.$document = $document;
this.stack = [];
}
show(child, parent, $scope) {
let element = this.$compile('<vn-popover/>')($scope || this.$rootScope)[0];
let popover = element.$ctrl;
popover.parent = parent;
popover.child = child;
popover.show();
popover.onClose = () => {
this.$document[0].body.removeChild(element);
if ($scope) $scope.$destroy();
};
this.$document[0].body.appendChild(element);
return popover;
}
showComponent(componentTag, $scope, parent) {
let $newScope = $scope.$new();
let childElement = this.$compile(`<${componentTag}/>`)($newScope)[0];
this.show(childElement, parent, $newScope);
return childElement;
}
}
PopoverService.$inject = ['$document', '$compile', '$transitions', '$rootScope'];
ngModule.service('vnPopover', PopoverService);

View File

@ -1,9 +0,0 @@
.vn-popover {
position: fixed;
box-shadow: 0 0 .4em rgba(1,1,1,.4);
background-color: white;
z-index: 100;
border-radius: .1em;
top: 0;
left: 0;
}

View File

@ -0,0 +1,43 @@
.vn-popover {
display: none;
z-index: 10;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transform: translateY(-.6em);
transition-property: opacity, transform;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
&.shown {
transform: translateY(0);
opacity: 1;
}
& > .popover {
position: absolute;
display: flex;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
& > .arrow {
width: 1em;
height: 1em;
margin: -.5em;
background-color: white;
box-shadow: 0 .1em .4em rgba(1, 1, 1, .4);
position: absolute;
transform: rotate(45deg);
z-index: 0;
}
& > .content {
width: 100%;
border-radius: .1em;
overflow: auto;
background-color: white;
z-index: 1;
}
}
}

View File

@ -0,0 +1,6 @@
vn-textarea {
& > .mdl-textfield {
width: initial;
display: block;
}
}

View File

@ -1,5 +1,6 @@
import ngModule from '../../module'; import ngModule from '../../module';
import template from './textarea.html'; import template from './textarea.html';
import './style.scss';
directive.$inject = ['vnTemplate']; directive.$inject = ['vnTemplate'];
export function directive(vnTemplate) { export function directive(vnTemplate) {

View File

@ -1,7 +1,4 @@
vn-textfield { vn-textfield {
div {
outline: none; //remove chrome outline
}
.mdl-chip__action { .mdl-chip__action {
position: absolute; position: absolute;
width: auto; width: auto;

View File

@ -1,6 +1,7 @@
import './id'; import './id';
import './focus'; import './focus';
import './dialog'; import './dialog';
import './popover';
import './validation'; import './validation';
import './acl'; import './acl';
import './on-error-src'; import './on-error-src';

View File

@ -0,0 +1,26 @@
import ngModule from '../module';
import Popover from '../components/popover/popover';
import {kebabToCamel} from '../lib/string';
/**
* Directive used to open a popover.
*
* @return {Object} The directive
*/
export function directive() {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$element.on('click', function(event) {
let popoverKey = kebabToCamel($attrs.vnPopover);
let popover = $scope[popoverKey];
if (popover instanceof Popover) {
popover.parent = $element[0];
popover.show();
}
event.preventDefault();
});
}
};
}
ngModule.directive('vnPopover', directive);

View File

@ -1,7 +1,7 @@
{ {
"module": "item", "module": "item",
"name": "Items", "name": "Items",
"icon": "/static/images/icon_item.png", "icon": "inbox",
"validations" : true, "validations" : true,
"routes": [ "routes": [
{ {

View File

@ -23,12 +23,9 @@
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="/item/api/Intrastats" url="/item/api/Intrastats"
label="Intrastat" label="Intrastat"
show-field="description"
value-field="id" value-field="id"
field="$ctrl.item.intrastatFk" show-field="description"
order="description ASC" field="$ctrl.item.intrastatFk">
filter-search="{where: {description: {regexp: 'search'}} }">
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -30,10 +30,9 @@
show-field="description" show-field="description"
value-field="id" value-field="id"
field="$ctrl.item.intrastatFk" field="$ctrl.item.intrastatFk"
order="description ASC" where="{or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}"
filter-search= "{where: {or: [{id: {regexp: 'search'}}, {description: {regexp: 'search'}}]}}"
initial-data="$ctrl.item.intrastat"> initial-data="$ctrl.item.intrastat">
<tpl-item>{{$parent.$parent.item.id}} : {{$parent.$parent.item.description}}</tpl-item> <tpl-item>{{id}} : {{description}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield> <vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield>
</vn-horizontal> </vn-horizontal>

View File

@ -1,7 +1,7 @@
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.item" data="$ctrl.item"
form = "form"> form="form">
</vn-watcher> </vn-watcher>
<form name="form" ng-submit="$ctrl.submit()"> <form name="form" ng-submit="$ctrl.submit()">
<vn-card pad-large> <vn-card pad-large>
@ -9,13 +9,12 @@
<vn-horizontal ng-repeat="itemNiche in $ctrl.niches track by $index"> <vn-horizontal ng-repeat="itemNiche in $ctrl.niches track by $index">
<vn-autocomplete <vn-autocomplete
vn-three vn-three
initial-data = "itemNiche.warehouse" data="$ctrl.warehouses"
field = "itemNiche.warehouseFk" show-field="name"
data = "$ctrl.warehouses" value-field="id"
show-field = "name" initial-data="itemNiche.warehouse"
value-field = "id" field="itemNiche.warehouseFk"
label = "Warehouse" label="Warehouse"
order = "name ASC"
vn-acl="buyer,replenisher"> vn-acl="buyer,replenisher">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield <vn-textfield
@ -39,7 +38,7 @@
margin-medium-left margin-medium-left
orange orange
icon="add_circle" icon="add_circle"
ng-if = "itemNiche.showAddIcon" ng-if="itemNiche.showAddIcon"
ng-click="$ctrl.addNiche()"> ng-click="$ctrl.addNiche()">
</vn-icon> </vn-icon>
</vn-one> </vn-one>

View File

@ -14,7 +14,6 @@
data="tags.model" data="tags.model"
show-field="name" show-field="name"
label="Tag" label="Tag"
order="name ASC"
vn-acl="buyer"> vn-acl="buyer">
</vn-autocomplete> </vn-autocomplete>
<vn-textfield <vn-textfield

View File

@ -6,3 +6,4 @@ locator: []
production: [] production: []
salix: [] salix: []
route: [] route: []
ticket: []

View File

@ -1,7 +1,7 @@
{ {
"module": "production", "module": "production",
"name": "Production", "name": "Production",
"icon": "/static/images/icon_production.png", "icon": "local_florist",
"validations" : false, "validations" : false,
"routes": [ "routes": [
{ {

View File

@ -6,29 +6,33 @@
<vn-icon <vn-icon
id="apps" id="apps"
icon="apps" icon="apps"
vn-popover="apps-menu"
translate-attr="{title: 'Applications'}"> translate-attr="{title: 'Applications'}">
</vn-icon> </vn-icon>
<ul id="apps-menu" for="apps" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small> <vn-menu vn-id="apps-menu">
<li ng-repeat="mod in ::$ctrl.modules" class="mdl-menu__item" ui-sref="{{::mod.route.state}}"> <ul pad-small>
<vn-icon ng-if="::mod.icon && !mod.icon.startsWith('/')" icon="{{::mod.icon}}"></vn-icon> <li ng-repeat="mod in ::$ctrl.modules" ui-sref="{{::mod.route.state}}">
<img ng-if="::mod.icon && mod.icon.startsWith('/')" ng-src="{{::mod.icon}}" /> <vn-icon icon="{{::mod.icon}}"></vn-icon>
<span translate="{{::mod.name}}"></span> <span translate>{{::mod.name}}</span>
</li> </li>
</ul> </ul>
</vn-menu>
<vn-icon <vn-icon
id="lang" id="lang"
icon="language" icon="language"
vn-popover="langs-menu"
translate-attr="{title: 'Change language'}"> translate-attr="{title: 'Change language'}">
</vn-icon> </vn-icon>
<ul id="langs-menu" for="lang" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small> <vn-menu vn-id="langs-menu">
<li <ul pad-small>
ng-repeat="lang in ::$ctrl.langs" <li
name="{{::lang}}" ng-repeat="lang in ::$ctrl.langs"
class="mdl-menu__item" name="{{::lang}}"
ng-click="$ctrl.onChangeLangClick(lang)"> ng-click="$ctrl.onChangeLangClick(lang)">
<span>{{::lang}}</span> <span>{{::lang}}</span>
</li> </li>
</ul> </ul>
</vn-menu>
<vn-icon <vn-icon
id="logout" id="logout"
icon="exit_to_app" icon="exit_to_app"

View File

@ -14,26 +14,30 @@ vn-main-menu {
color: #FF9300; color: #FF9300;
} }
} }
li.mdl-menu__item { .vn-popover ul {
background-color: #FF9300; list-style-type: none;
margin-bottom: 8px; margin: 0;
color: white; color: white;
img {
max-width: 18px; li {
vertical-align: middle; background-color: #FF9300;
margin-top: -3px; margin-bottom: .6em;
cursor: pointer;
padding: .8em;
border-radius: .1em;
min-width: 8em;
& > vn-icon {
padding-right: .3em;
vertical-align: middle;
}
&:hover {
background-color: #FF9300;
opacity: 0.7 !important;
}
&:last-child {
margin-bottom: 0;
}
} }
i {
float: left;
padding-top: 13px;
margin-right: 3px;
}
}
li.mdl-menu__item:hover {
background-color: #FF9300;
opacity: 0.7 !important;
}
li.mdl-menu__item:last-child {
margin-bottom: 0;
} }
} }

View File

@ -3,6 +3,21 @@
@import "colors"; @import "colors";
@import "border"; @import "border";
a:focus,
input:focus,
button:focus
{
outline: none;
}
button::-moz-focus-inner,
input[type=submit]::-moz-focus-inner,
input[type=button]::-moz-focus-inner,
input[type=reset]::-moz-focus-inner
{
border: none;
}
.form { .form {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;

View File

@ -8,5 +8,7 @@ export default {
locator: locator:
cb => require.ensure([], () => cb(require('locator'))), cb => require.ensure([], () => cb(require('locator'))),
item: item:
cb => require.ensure([], () => cb(require('item'))) cb => require.ensure([], () => cb(require('item'))),
ticket:
cb => require.ensure([], () => cb(require('ticket')))
}; };

1
client/ticket/index.js Normal file
View File

@ -0,0 +1 @@
export * from './src/ticket';

14
client/ticket/routes.json Normal file
View File

@ -0,0 +1,14 @@
{
"module": "ticket",
"name": "Tickets",
"icon": "receipt",
"validations": false,
"routes": [
{
"url": "/tickets",
"state": "tickets",
"component": "vn-ticket-index",
"acl": ["developer"]
}
]
}

View File

@ -0,0 +1,24 @@
<mg-ajax path="/client/api/Clients/filter" options="mgIndex"></mg-ajax>
<div margin-medium>
<div class="vn-list">
<vn-card>
<vn-horizontal pad-medium>
<vn-searchbar vn-one
index="index"
on-search="$ctrl.search(index)"
ignore-keys = "['page', 'size', 'search']">
</vn-searchbar>
</vn-horizontal>
</vn-card>
<vn-card margin-medium-top>
<vn-ticket-item
ng-repeat="ticket in index.model.instances"
ticket="ticket">
</vn-ticket-item>
</vn-card>
<vn-paging index="index" total="index.model.count"></vn-paging>
</div>
</div>
<a ui-sref="create" fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>
</a>

View File

@ -0,0 +1,14 @@
import ngModule from '../module';
import './item';
export default class Controller {
search(index) {
index.accept();
}
}
Controller.$inject = [];
ngModule.component('vnTicketIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,17 @@
<a
ui-sref="clientCard.basicData({ id: {{::$ctrl.ticket.id}} })"
translate-attr="{title: 'View client'}"
class="vn-list-item">
<vn-horizontal ng-click="$ctrl.onClick($event)">
<vn-one>
<h6>{{::$ctrl.ticket.name}}</h6>
<div><vn-label translate>Id</vn-label> {{::$ctrl.ticket.id}}</div>
</vn-one>
<vn-horizontal class="buttons">
<vn-icon
vn-tooltip="Preview"
icon="icon-preview">
</vn-icon>
</vn-horizontal>
</vn-horizontal>
</a>

View File

@ -0,0 +1,20 @@
import ngModule from '../module';
class Controller {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
preview(event) {
event.preventDefault();
}
}
ngModule.component('vnTicketItem', {
controller: Controller,
template: require('./item.html'),
bindings: {
ticket: '<'
}
});

View File

@ -0,0 +1,3 @@
vn-ticket-item {
display: block;
}

View File

@ -0,0 +1 @@
Tickets: Tickets

View File

@ -0,0 +1 @@
Tickets: Tickets

View File

@ -0,0 +1,5 @@
import {ng} from 'vendor';
import 'core';
const ngModule = ng.module('ticket', ['vnCore']);
export default ngModule;

View File

@ -0,0 +1,4 @@
export * from './module';
import './index/index';

View File

@ -8,7 +8,6 @@ export default {
vnSubmit: 'vn-submit > input', vnSubmit: 'vn-submit > input',
vnTopbar: 'vn-topbar > header', vnTopbar: 'vn-topbar > header',
vnIcon: 'vn-icon', vnIcon: 'vn-icon',
vnMainMenu: 'vn-main-menu > div',
vnModuleContainer: 'vn-module-container > a', vnModuleContainer: 'vn-module-container > a',
vnSearchBar: 'vn-searchbar > form > vn-horizontal', vnSearchBar: 'vn-searchbar > form > vn-horizontal',
vnFloatButton: 'vn-float-button > button', vnFloatButton: 'vn-float-button > button',

View File

@ -23,7 +23,7 @@ Nightmare.action('changeLanguageToEnglish', function(done) {
this.then(done); this.then(done);
} else { } else {
this.click('#lang') this.click('#lang')
.click('#langs-menu > li[name="en"]') .click('vn-main-menu [vn-id="langs-menu"] ul > li[name="en"]')
.then(done); .then(done);
} }
}); });

View File

@ -6,8 +6,8 @@ export default {
globalItems: { globalItems: {
logOutButton: `#logout`, logOutButton: `#logout`,
applicationsMenuButton: `#apps`, applicationsMenuButton: `#apps`,
applicationsMenuVisible: `${components.vnMainMenu} .is-visible > div`, applicationsMenuVisible: `vn-main-menu [vn-id="apps-menu"] ul`,
clientsButton: `${components.vnMainMenu} > div > ul > li:nth-child(1)` clientsButton: `vn-main-menu [vn-id="apps-menu"] ul > li:nth-child(1)`
}, },
moduleAccessView: { moduleAccessView: {
clientsSectionButton: `${components.vnModuleContainer}[ui-sref="clients"]`, clientsSectionButton: `${components.vnModuleContainer}[ui-sref="clients"]`,

View File

@ -6,6 +6,7 @@ const exec = require('child_process').exec;
const PluginError = require('plugin-error'); const PluginError = require('plugin-error');
const argv = require('minimist')(process.argv.slice(2)); const argv = require('minimist')(process.argv.slice(2));
const log = require('fancy-log'); const log = require('fancy-log');
const environment = require('gulp-env');
// Configuration // Configuration
@ -37,7 +38,13 @@ let defaultPort = proxyConf.defaultPort;
// Development // Development
gulp.task('default', () => { gulp.task('default', () => {
return gulp.start('services', 'client'); return gulp.start('environment', 'services', 'client');
});
gulp.task('environment', async () => {
await environment({
file: '.env.json'
});
}); });
gulp.task('client', ['build-clean'], async () => { gulp.task('client', ['build-clean'], async () => {
@ -48,7 +55,7 @@ gulp.task('client', ['build-clean'], async () => {
* Starts all backend services, including the nginx proxy and the database. * Starts all backend services, including the nginx proxy and the database.
*/ */
gulp.task('services', async () => { gulp.task('services', async () => {
await runSequenceP('docker-start', 'services-only', 'nginx'); await runSequenceP('environment', 'docker-start', 'services-only', 'nginx');
}); });
/** /**
@ -137,7 +144,7 @@ gulp.task('install', () => {
// Deployment // Deployment
gulp.task('build', ['clean'], async () => { gulp.task('build', ['clean'], async () => {
await runSequenceP(['routes', 'locales', 'webpack', 'docker-compose', 'nginx-conf']); await runSequenceP(['environment', 'routes', 'locales', 'webpack', 'docker-compose', 'nginx-conf']);
}); });
gulp.task('docker-compose', async () => { gulp.task('docker-compose', async () => {
@ -154,7 +161,9 @@ gulp.task('docker-compose', async () => {
// dockerFile = 'Dockerfile'; // dockerFile = 'Dockerfile';
composeYml.services[service.name] = { composeYml.services[service.name] = {
environment: ['NODE_ENV=${NODE_ENV}'], environment: ['NODE_ENV=${NODE_ENV}' ,'salixHost=${salixHost}', 'salixPort=${salixPort}',
'salixUser=${salixUser}', 'salixPassword=${salixPassword}'
],
container_name: `\${BRANCH_NAME}-${service.name}`, container_name: `\${BRANCH_NAME}-${service.name}`,
image: `${service.name}:\${TAG}`, image: `${service.name}:\${TAG}`,
build: { build: {
@ -480,7 +489,8 @@ gulp.task('docker-wait', callback => {
let conn = mysql.createConnection({ let conn = mysql.createConnection({
host: 'localhost', host: 'localhost',
user: 'root' user: 'root',
password: 'root'
}); });
conn.on('error', () => {}); conn.on('error', () => {});
conn.connect(err => { conn.connect(err => {

View File

@ -44,6 +44,7 @@
"file-loader": "^1.1.6", "file-loader": "^1.1.6",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-concat": "^2.6.0", "gulp-concat": "^2.6.0",
"gulp-env": "^0.4.0",
"gulp-extend": "^0.2.0", "gulp-extend": "^0.2.0",
"gulp-install": "^1.1.0", "gulp-install": "^1.1.0",
"gulp-jasmine": "^3.0.0", "gulp-jasmine": "^3.0.0",

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -2,5 +2,5 @@
for file in changes/*/*.sql; do for file in changes/*/*.sql; do
echo "Importing $file" echo "Importing $file"
mysql -u root < $file mysql -u root -proot < $file
done done

View File

@ -24,8 +24,6 @@ INSERT INTO `account`.`user`(`id`,`name`,`password`,`role`,`active`,`email`)
(109, 'BruceBanner', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceBanner@verdnatura.es'), (109, 'BruceBanner', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceBanner@verdnatura.es'),
(110, 'JessicaJones', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'JessicaJones@verdnatura.es'); (110, 'JessicaJones', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'JessicaJones@verdnatura.es');
INSERT INTO `vn`.`worker`(`workerCode`, `id`, `firstName`, `name`, `userFk`) INSERT INTO `vn`.`worker`(`workerCode`, `id`, `firstName`, `name`, `userFk`)
VALUES VALUES
('LGN', 106, 'David Charles', 'Haller', 106), ('LGN', 106, 'David Charles', 'Haller', 106),
@ -273,6 +271,12 @@ INSERT INTO `vn2008`.`empresa`(`id`, `abbreviation`, `registro`, `gerente_id`, `
(9, 5, 5, NULL, CURDATE(), 105, 'Hulk', 109), (9, 5, 5, NULL, CURDATE(), 105, 'Hulk', 109),
(10, 6, 5, NULL, CURDATE(), 105, 'Jessica Jones', 110); (10, 6, 5, NULL, CURDATE(), 105, 'Jessica Jones', 110);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
( 1, 1 , 1, 'ready' ),
( 2, 2 , 2, 'do it fast please'),
( 3, 3 , 3, '');
INSERT INTO `vn`.`ticketTracking`(`id`, `ticketFk`, `stateFk`, `workerFk`, `created`) INSERT INTO `vn`.`ticketTracking`(`id`, `ticketFk`, `stateFk`, `workerFk`, `created`)
VALUES VALUES
(1, 1, 1, 5, CURDATE()), (1, 1, 1, 5, CURDATE()),
@ -393,6 +397,24 @@ INSERT INTO `vn`.`item`(`id`, `name`,`typeFk`,`size`,`inkFk`,`category`,`stems`,
(4, 'Mark I', 1, 60, 'AMR', 'EXT', 1, 1, 'Iron Mans first armor', 1, 05080000, 1, 2, 0, NULL, 0, 66090, 2), (4, 'Mark I', 1, 60, 'AMR', 'EXT', 1, 1, 'Iron Mans first armor', 1, 05080000, 1, 2, 0, NULL, 0, 66090, 2),
(5, 'Mjolnir', 3, 30, 'AZR', 'EXT', 1, 2, 'Thors hammer!', 2, 06021010, 1, 2, 0, NULL, 0, 67350, 2); (5, 'Mjolnir', 3, 30, 'AZR', 'EXT', 1, 2, 'Thors hammer!', 2, 06021010, 1, 2, 0, NULL, 0, 67350, 2);
INSERT INTO `vn`.`packaging`(`id`, `volume`, `width`, `height`, `depth`, `isPackageReturnable`, `created`, `itemFk`, `price`)
VALUES
(1, 0.00, 10, 10, 0, 0, CURDATE(), 1, 1.50),
(2, 100.00, 20, 20, 0, 0, CURDATE(), 2, 1.00),
('a', 50.00, 30, 30, 0, 1, CURDATE(), 3, 0.00);
INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`, `created`, `pvp`)
VALUES
( 1, 1, 1, 2, CURDATE(), NULL),
( 2, 2, 2, 1, CURDATE(), NULL),
( 3, 3, 'a', 4, CURDATE(), NULL);
INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `price`, `discount`, `reserved`, `isPicked`, `created`)
VALUES
( 1, 1, 1, 'Gem of Time', 5 , 1.5, 0, 0, 0, CURDATE()),
( 2, 1, 1, 'Gem of Time', 2 , 1.5, 0, 0, 0, CURDATE()),
( 3, 2, 2, 'Mjolnir' , 10, 4 , 0, 0, 0, CURDATE());
INSERT INTO `vn`.`itemBarcode`(`id`, `itemFk`, `code`) INSERT INTO `vn`.`itemBarcode`(`id`, `itemFk`, `code`)
VALUES VALUES
(1, 1 ,1 ), (1, 1 ,1 ),

View File

@ -1,6 +1,6 @@
FROM mysql:5.6.37 FROM mysql:5.6.37
ENV MYSQL_ALLOW_EMPTY_PASSWORD yes ENV MYSQL_ROOT_PASSWORD root
ENV TZ GMT-1 ENV TZ GMT-1
WORKDIR /docker-entrypoint-initdb.d WORKDIR /docker-entrypoint-initdb.d

View File

@ -0,0 +1,9 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Ticket', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TicketObservation', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Route', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Sale', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TicketTracking', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TicketState', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('TicketPackaging', '*', '*', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Packaging', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Packaging', '*', 'WRITE', 'ALLOW', 'ROLE', 'logistic');

View File

@ -0,0 +1,18 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`packaging` AS
SELECT
`c`.`Id_Cubo` AS `id`,
`c`.`Volumen` AS `volume`,
`c`.`X` AS `width`,
`c`.`Y` AS `height`,
`c`.`Z` AS `depth`,
`c`.`Retornable` AS `isPackageReturnable`,
`c`.`odbc_date` AS `created`,
`c`.`item_id` AS `itemFk`,
`c`.`pvp` AS `price`
FROM
`vn2008`.`Cubos` `c`;

View File

@ -1,2 +1,2 @@
mysqldump --defaults-file=connect.ini --default-character-set=utf8 --no-data --triggers --routines --events --databases account util vn2008 vn edi bs bi pbx cache salix vncontrol hedera > 01-structure.sql mysqldump --defaults-file=connect.ini --default-character-set=utf8 --no-data --triggers --routines --events --databases account util vn2008 vn edi bs bi pbx cache salix vncontrol hedera stock > 01-structure.sql

View File

@ -4,7 +4,7 @@ let connection = mysql.createConnection({
multipleStatements: true, multipleStatements: true,
host: 'localhost', host: 'localhost',
user: 'root', user: 'root',
password: '', password: 'root',
database: 'salix' database: 'salix'
}); });

View File

@ -1,5 +1,3 @@
let md5 = require('md5');
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethod('createWithUser', { Self.remoteMethod('createWithUser', {
description: 'Creates both client and its web account', description: 'Creates both client and its web account',
@ -18,7 +16,7 @@ module.exports = function(Self) {
} }
}); });
Self.createWithUser = (data, callback) => { Self.createWithUser = async data => {
let firstEmail = data.email ? data.email.split(',')[0] : null; let firstEmail = data.email ? data.email.split(',')[0] : null;
let user = { let user = {
name: data.userName, name: data.userName,
@ -27,34 +25,24 @@ module.exports = function(Self) {
}; };
let Account = Self.app.models.Account; let Account = Self.app.models.Account;
Account.beginTransaction({}, (error, transaction) => { let transaction = await Account.beginTransaction({});
if (error) return callback(error);
Account.create(user, {transaction}, (error, account) => { try {
if (error) { let account = await Account.create(user, {transaction});
transaction.rollback(); let client = {
return callback(error); id: account.id,
} name: data.name,
fi: data.fi,
let client = { socialName: data.socialName,
name: data.name, email: data.email,
fi: data.fi, salesPersonFk: data.salesPersonFk
socialName: data.socialName, };
id: account.id, newClient = await Self.create(client, {transaction});
email: data.email, await transaction.commit();
salesPersonFk: data.salesPersonFk return newClient;
}; } catch (e) {
transaction.rollback();
Self.create(client, {transaction}, (error, newClient) => { throw e;
if (error) { }
transaction.rollback();
return callback(error);
}
transaction.commit();
callback(null, newClient);
});
});
});
}; };
}; };

View File

@ -48,45 +48,39 @@ describe('Client Create', () => {
.catch(catchErrors(done)); .catch(catchErrors(done));
}); });
it('should not be able to create a user if exists', done => { it('should not be able to create a user if exists', async() => {
app.models.Client.findOne({where: {name: 'Charles Xavier'}}) let client = await app.models.Client.findOne({where: {name: 'Charles Xavier'}});
.then(client => { let account = await app.models.Account.findOne({where: {id: client.id}});
app.models.Account.findOne({where: {id: client.id}})
.then(account => {
let formerAccountData = {
name: client.name,
userName: account.name,
email: client.email,
fi: client.fi,
socialName: client.socialName
};
app.models.Client.createWithUser(formerAccountData, (err, client) => { let formerAccountData = {
expect(err.details.codes.name[0]).toEqual('uniqueness'); name: client.name,
done(); userName: account.name,
}); email: client.email,
}); fi: client.fi,
}) socialName: client.socialName
.catch(catchErrors(done)); };
try {
let client = await app.models.Client.createWithUser(formerAccountData);
expect(client).toBeNull();
} catch (err) {
expect(err.details.codes.name[0]).toEqual('uniqueness');
}
}); });
it('should create a new account', done => { it('should create a new account', async() => {
app.models.Client.createWithUser(newAccountData, (error, client) => { let client = await app.models.Client.createWithUser(newAccountData);
if (error) return catchErrors(done)(error); let account = await app.models.Account.findOne({where: {name: newAccountData.userName}});
app.models.Account.findOne({where: {name: newAccountData.userName}})
.then(account => { expect(account.name).toEqual(newAccountData.userName);
expect(account.name).toEqual(newAccountData.userName);
app.models.Client.findOne({where: {name: newAccountData.name}}) client = await app.models.Client.findOne({where: {name: newAccountData.name}});
.then(client => {
expect(client.id).toEqual(account.id); expect(client.id).toEqual(account.id);
expect(client.name).toEqual(newAccountData.name); expect(client.name).toEqual(newAccountData.name);
expect(client.email).toEqual(newAccountData.email); expect(client.email).toEqual(newAccountData.email);
expect(client.fi).toEqual(newAccountData.fi); expect(client.fi).toEqual(newAccountData.fi);
expect(client.socialName).toEqual(newAccountData.socialName); expect(client.socialName).toEqual(newAccountData.socialName);
done();
});
})
.catch(catchErrors(done));
});
}); });
}); });

View File

@ -1,13 +1,6 @@
module.exports = function(Ticket) { module.exports = function(Ticket) {
Ticket.remoteMethod('list', { Ticket.remoteMethod('list', {
description: 'List tickets for production', description: 'List tickets for production',
/* accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'Model id',
http: {source: 'path'}
},*/
returns: { returns: {
arg: 'tickets', arg: 'tickets',
type: 'object' type: 'object'
@ -19,19 +12,8 @@ module.exports = function(Ticket) {
}); });
Ticket.list = function(cb) { Ticket.list = function(cb) {
// list();
};
function list() {
var params = [1, 0]; var params = [1, 0];
var query = 'CALL production_control_source(?, ?)'; var query = 'CALL production_control_source(?, ?)';
var cb = function(error, res) {
if (error) console.log(error);
else console.log(res);
};
Ticket.rawSql(query, params, cb); Ticket.rawSql(query, params, cb);
}; };
}; };

View File

@ -44,10 +44,22 @@ module.exports = function(Self) {
allowBlank: true allowBlank: true
}); });
let validateDni = require('../validations/validateDni'); Self.validateAsync('fi', fiIsValid, {
Self.validateBinded('fi', validateDni, {
message: 'DNI Incorrecto' message: 'DNI Incorrecto'
}); });
let validateDni = require('../validations/validateDni');
async function fiIsValid(err, done) {
let filter = {
fields: ['code'],
where: {id: this.countryFk}
};
let country = await Self.app.models.Country.findOne(filter);
let code = country ? country.code.toLowerCase() : null;
if (!validateDni(this.fi, code))
err();
done();
}
Self.validate('payMethod', hasSalesMan, { Self.validate('payMethod', hasSalesMan, {
message: 'No se puede cambiar la forma de pago si no hay comercial asignado' message: 'No se puede cambiar la forma de pago si no hay comercial asignado'

View File

@ -0,0 +1,76 @@
{
"name": "Ticket",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticket"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"shipped": {
"type": "date",
"required": true
},
"landed": {
"type": "date"
},
"nickname": {
"type": "String"
},
"location": {
"type": "String"
},
"solution": {
"type": "String"
},
"packages": {
"type": "Number"
},
"created": {
"type": "date"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
},
"warehouse": {
"type": "belongsTo",
"model": "Warehouse",
"foreignKey": "warehouseFk"
},
"invoiceOut": {
"type": "belongsTo",
"model": "InvoiceOut",
"foreignKey": "refFk"
},
"address": {
"type": "belongsTo",
"model": "Address",
"foreignKey": "addressFk"
},
"route": {
"type": "belongsTo",
"model": "Route",
"foreignKey": "routeFk"
},
"company": {
"type": "belongsTo",
"model": "Company",
"foreignKey": "companyFk"
},
"agencyMode": {
"type": "belongsTo",
"model": "AgencyMode",
"foreignKey": "agencyModeFk",
"required": true
}
}
}

View File

@ -1,39 +1,39 @@
const validateDni = require('../validateDni'); const validateDni = require('../validateDni');
describe('DNI validation', () => { describe('DNI validation', () => {
it('should return false for invented DNI', () => { it('should return true for any DNI when no country is passed', () => {
let isValid = validateDni('Pepinillos'); let isValid = validateDni('Pepinillos');
expect(isValid).toBeFalsy(); expect(isValid).toBeTruthy();
}); });
describe('Spanish', () => { describe('Spanish', () => {
it('should return true for valid spanish DNI', () => { it('should return true for valid spanish DNI', () => {
let isValid = validateDni('20849756A'); let isValid = validateDni('20849756A', 'es');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return true for spanish DNI with exceeded digits', () => { it('should return false for spanish DNI with exceeded digits', () => {
let isValid = validateDni('208497563239A'); let isValid = validateDni('208497563239A', 'es');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return false for spanish DNI with invalid letter', () => { it('should return false for spanish DNI with invalid letter', () => {
let isValid = validateDni('20243746E'); let isValid = validateDni('20243746E', 'es');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return true for valid spanish CIF', () => { it('should return true for valid spanish CIF', () => {
let isValid = validateDni('B97367486'); let isValid = validateDni('B97367486', 'es');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return false for spanish CIF with invalid letter', () => { it('should return false for spanish CIF with invalid letter', () => {
let isValid = validateDni('A97527786'); let isValid = validateDni('A97527786', 'es');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
@ -41,19 +41,19 @@ describe('DNI validation', () => {
describe('French', () => { describe('French', () => {
it('should return true for valid french DNI', () => { it('should return true for valid french DNI', () => {
let isValid = validateDni('FR1B123456789'); let isValid = validateDni('1B123456789', 'fr');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return true for french DNI with exceeded digits', () => { it('should return false for french DNI with exceeded digits', () => {
let isValid = validateDni('FR1B12345678910'); let isValid = validateDni('1B12345678910', 'fr');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return true for french DNI with bad syntax', () => { it('should return false for french DNI with bad syntax', () => {
let isValid = validateDni('FR1B12345678A'); let isValid = validateDni('1B12345678A', 'fr');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
@ -61,19 +61,19 @@ describe('DNI validation', () => {
describe('Italian', () => { describe('Italian', () => {
it('should return true for valid italian DNI', () => { it('should return true for valid italian DNI', () => {
let isValid = validateDni('IT12345678911'); let isValid = validateDni('12345678911', 'it');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return true for italian DNI with exceeded digits', () => { it('should return false for italian DNI with exceeded digits', () => {
let isValid = validateDni('IT123456789112'); let isValid = validateDni('123456789112', 'it');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return true for italian DNI with bad syntax', () => { it('should return false for italian DNI with bad syntax', () => {
let isValid = validateDni('IT1234567891A'); let isValid = validateDni('1234567891A', 'it');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
@ -81,19 +81,19 @@ describe('DNI validation', () => {
describe('Portuguese', () => { describe('Portuguese', () => {
it('should return true for valid portuguese DNI', () => { it('should return true for valid portuguese DNI', () => {
let isValid = validateDni('PT123456789'); let isValid = validateDni('123456789', 'pt');
expect(isValid).toBeTruthy(); expect(isValid).toBeTruthy();
}); });
it('should return true for portuguese DNI with exceeded digits', () => { it('should return false for portuguese DNI with exceeded digits', () => {
let isValid = validateDni('PT12345678910'); let isValid = validateDni('12345678910', 'pt');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });
it('should return true for portuguese DNI with bad syntax', () => { it('should return false for portuguese DNI with bad syntax', () => {
let isValid = validateDni('PT12345678A'); let isValid = validateDni('12345678A', 'pt');
expect(isValid).toBeFalsy(); expect(isValid).toBeFalsy();
}); });

View File

@ -1,14 +1,12 @@
module.exports = function(fiWithCountry) { module.exports = function(fi, country) {
if (fiWithCountry == null) return true; if (fi == null || country == null)
if (typeof fiWithCountry != 'string') return false; return true;
if (typeof fi != 'string' || typeof country != 'string')
return false;
fiWithCountry = fiWithCountry.toUpperCase(); fi = fi.toUpperCase();
country = country.toLowerCase();
if (!/^[A-Z]{2}/.test(fiWithCountry))
fiWithCountry = `ES${fiWithCountry}`;
let country = fiWithCountry.substring(0, 2).toLowerCase();
let fi = fiWithCountry.substring(2);
let len = fi.length; let len = fi.length;
let validators = { let validators = {
@ -38,7 +36,7 @@ module.exports = function(fiWithCountry) {
let sum = (pairSum + oddSum).toString(); let sum = (pairSum + oddSum).toString();
let units = parseInt(sum.charAt(sum.length - 1)); let units = parseInt(sum.charAt(sum.length - 1));
let control = units != 0 ? 10 - units : 0; let control = units == 0 ? 0 : 10 - units;
let index = 'JABCDEFGHI'.indexOf(lastDigit); let index = 'JABCDEFGHI'.indexOf(lastDigit);
computedDigit = index == -1 ? control.toString() : index; computedDigit = index == -1 ? control.toString() : index;
} else { } else {
@ -67,7 +65,7 @@ module.exports = function(fiWithCountry) {
let validator = validators[country]; let validator = validators[country];
if (!validator) if (!validator)
return false; return true;
return validator.regExp.test(fi) return validator.regExp.test(fi)
&& (!validator.validate || validator.validate()); && (!validator.validate || validator.validate());

View File

@ -4,7 +4,7 @@ module.exports = function(iban) {
iban = iban.toUpperCase(); iban = iban.toUpperCase();
iban = trim(iban); iban = trim(iban);
iban = iban.replace(/\s/g, ""); iban = iban.replace(/\s/g, '');
if (iban.length != 24) { if (iban.length != 24) {
return false; return false;
@ -33,7 +33,7 @@ module.exports = function(iban) {
function module97(iban) { function module97(iban) {
var parts = Math.ceil(iban.length / 7); var parts = Math.ceil(iban.length / 7);
var remainer = ""; var remainer = '';
for (var i = 1; i <= parts; i++) { for (var i = 1; i <= parts; i++) {
remainer = String(parseFloat(remainer + iban.substr((i - 1) * 7, 7)) % 97); remainer = String(parseFloat(remainer + iban.substr((i - 1) * 7, 7)) % 97);
@ -48,6 +48,6 @@ module.exports = function(iban) {
} }
function trim(text) { function trim(text) {
return (text || "").replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, "" ); return (text || '').replace(/^(\s|\u00A0)+|(\s|\u00A0)+$/g, '');
} }
}; };

View File

@ -1,37 +1,41 @@
{ {
"db": { "db": {
"name": "db",
"connector": "memory" "connector": "memory"
}, },
"vn": { "vn": {
"name": "mysql",
"connector": "mysql", "connector": "mysql",
"database": "vn", "database": "vn",
"debug": false, "debug": false,
"host": "localhost", "host": "${salixHost}",
"port": 3306, "port": "${salixPort}",
"username": "root", "username": "${salixUser}",
"password": "", "password": "${salixPassword}",
"connectTimeout": 20000, "connectTimeout": 20000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"salix": { "salix": {
"name": "mysql",
"connector": "mysql", "connector": "mysql",
"database": "salix", "database": "salix",
"debug": false, "debug": false,
"host": "localhost", "host": "${salixHost}",
"port": 3306, "port": "${salixPort}",
"username": "root", "username": "${salixUser}",
"password": "", "password": "${salixPassword}",
"connectTimeout": 20000, "connectTimeout": 20000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"account": { "account": {
"name": "mysql",
"connector": "mysql", "connector": "mysql",
"database": "account", "database": "account",
"debug": false, "debug": false,
"host": "localhost", "host": "${salixHost}",
"port": 3306, "port": "${salixPort}",
"username": "root", "username": "${salixUser}",
"password": "", "password": "${salixPassword}",
"connectTimeout": 20000, "connectTimeout": 20000,
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
@ -39,10 +43,10 @@
"connector": "mysql", "connector": "mysql",
"database": "edi", "database": "edi",
"debug": false, "debug": false,
"host": "localhost", "host": "${salixHost}",
"port": 3306, "port": "${salixPort}",
"username": "root", "username": "${salixUser}",
"password": "", "password": "${salixPassword}",
"connectTimeout": 20000, "connectTimeout": 20000,
"acquireTimeout": 20000 "acquireTimeout": 20000
} }

View File

@ -56,5 +56,17 @@
}, },
"Worker": { "Worker": {
"dataSource": "vn" "dataSource": "vn"
} },
"Ticket": {
"dataSource": "vn"
},
"Route": {
"dataSource": "vn"
},
"State":{
"dataSource": "vn"
},
"TicketState":{
"dataSource": "vn"
}
} }

View File

@ -11,7 +11,7 @@
"port": 3306, "port": 3306,
"database": "vn", "database": "vn",
"user": "root", "user": "root",
"password": "" "password": "root"
}, },
"smtp": { "smtp": {
"host": "localhost", "host": "localhost",

View File

@ -9,7 +9,7 @@
"port": 3306, "port": 3306,
"database": "vn", "database": "vn",
"user": "root", "user": "root",
"password": "" "password": "root"
}, },
"pdf": { "pdf": {
"format": "A4", "format": "A4",

View File

@ -1,19 +0,0 @@
{
"name": "Ticket",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticket"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"forceId": false
},
"shipped": {
"type": "date"
}
}
}

View File

@ -7,20 +7,5 @@
}, },
"MessageInbox": { "MessageInbox": {
"dataSource": "vn" "dataSource": "vn"
},
"Route": {
"dataSource": "vn"
},
"State":{
"dataSource": "vn"
},
"Ticket": {
"dataSource": "vn"
},
"TicketState":{
"dataSource": "vn"
},
"TicketTracking": {
"dataSource": "vn"
} }
} }

View File

@ -0,0 +1,12 @@
FROM node:8.9.4
COPY ticket /app
COPY loopback /loopback
WORKDIR /app
RUN npm install
RUN npm -g install pm2
CMD ["pm2-docker", "./server/server.js"]

View File

@ -0,0 +1,28 @@
{
"name": "ObservationType",
"base": "VnModel",
"options": {
"mysql": {
"table": "observationType"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"description": {
"type": "String",
"required": true
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,45 @@
{
"name": "Packaging",
"base": "VnModel",
"options": {
"mysql": {
"table": "packaging"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"volume": {
"type": "Number"
},
"X": {
"type": "Date"
},
"Y": {
"type": "Number"
},
"Z": {
"type": "Number"
},
"isPackageReturnable": {
"type": "Number"
},
"created": {
"type": "Date"
},
"price": {
"type": "Number"
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Iicket",
"foreignKey": "itemFk"
}
}
}

View File

@ -0,0 +1,51 @@
{
"name": "Sale",
"base": "VnModel",
"options": {
"mysql": {
"table": "sale"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"concept": {
"type": "String"
},
"quantity": {
"type": "Number"
},
"price": {
"type": "Number"
},
"discount": {
"type": "Number"
},
"reserved": {
"type": "Number"
},
"isPicked": {
"type": "Number"
},
"created": {
"type": "date"
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk",
"required": true
},
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk",
"required": true
}
}
}

View File

@ -0,0 +1,34 @@
{
"name": "TicketObservation",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticketObservation"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"description": {
"type": "String",
"required": true
}
},
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk",
"required": true
},
"observationType": {
"type": "belongsTo",
"model": "ObservationType",
"foreignKey": "observationTypeFk",
"required": true
}
}
}

View File

@ -0,0 +1,38 @@
{
"name": "TicketPackaging",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticketPackaging"
}
},
"properties": {
"id": {
"id": true,
"type": "Number",
"description": "Identifier"
},
"quantity": {
"type": "Number"
},
"created": {
"type": "Date"
},
"pvp": {
"type": "Number"
}
},
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
},
"packaging": {
"type": "belongsTo",
"model": "Packaging",
"foreignKey": "packagingFk"
}
}
}

View File

@ -30,7 +30,7 @@
}, },
"worker": { "worker": {
"type": "belongsTo", "type": "belongsTo",
"model": "worker", "model": "Worker",
"foreignKey": "workerFk" "foreignKey": "workerFk"
} }
} }

View File

@ -0,0 +1,16 @@
{
"name": "vn-ticket",
"version": "1.0.0",
"main": "server/server.js",
"scripts": {
"lint": "eslint .",
"start": "node .",
"posttest": "npm run lint && nsp check"
},
"repository": {
"type": "git",
"url": "https://git.verdnatura.es/salix"
},
"license": "GPL-3.0",
"description": "vn-ticket"
}

View File

@ -0,0 +1,20 @@
{
"TicketObservation": {
"dataSource": "vn"
},
"ObservationType": {
"dataSource": "vn"
},
"Sale": {
"dataSource": "vn"
},
"TicketTracking": {
"dataSource": "vn"
},
"TicketPackaging": {
"dataSource": "vn"
},
"Packaging": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,5 @@
var vnLoopback = require('../../loopback/server/server.js');
var app = module.exports = vnLoopback.loopback();
vnLoopback.boot(app, __dirname, module);

View File

@ -13,6 +13,9 @@ if (process.argv[2] === '--v') {
var Jasmine = require('jasmine'); var Jasmine = require('jasmine');
var jasmine = new Jasmine(); var jasmine = new Jasmine();
var SpecReporter = require('jasmine-spec-reporter').SpecReporter; var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
let environment = require('gulp-env');
environment(".env.json");
jasmine.loadConfig({ jasmine.loadConfig({
spec_dir: 'services', spec_dir: 'services',