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.TAG = "${env.BUILD_NUMBER}";
env.salixUser="${env.salixUser}";
env.salixPassword="${env.salixPassword}";
switch (branchName){
case branchTest:
env.NODE_ENV = "test";
env.salixHost = "${env.testSalixHost}";
env.salixPort = "${env.testSalixPort}";
break;
case branchProduction:
env.DOCKER_HOST = "tcp://172.16.255.29:2375";
@ -20,7 +24,7 @@ switch (branchName){
node
{
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') {
checkout scm

View File

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

View File

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

View File

@ -30,7 +30,7 @@
<span>{{::observation.description}}</span>
</vn-one>
</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>
</a>
</vn-horizontal>

View File

@ -30,7 +30,7 @@
value-field="id"
select-fields="name"
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-one
initial-data="$ctrl.client.contactChannel"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ describe('Component vnDropDown', () => {
let $element;
let $scope;
let $httpBackend;
let $transitions;
let $q;
let $filter;
let controller;
@ -15,18 +16,25 @@ describe('Component vnDropDown', () => {
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_;
$element = angular.element(`<div>${template}</div>`);
$timeout = _$timeout_;
$transitions = _$transitions_;
$q = _$q_;
$filter = _$filter_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$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});
controller = $componentController('vnDropDown', {$element, $scope, $transclude: null, $timeout, $httpBackend, $translate: null});
controller.$postLink();
controller.parent = angular.element('<vn-parent></vn-parent>')[0];
}));

View File

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

View File

@ -2,8 +2,6 @@ import './textfield/textfield';
import './watcher/watcher';
import './paging/paging';
import './icon/icon';
import './autocomplete/autocomplete';
import './popover/popover';
import './dialog/dialog';
import './confirm/confirm';
import './title/title';
@ -12,7 +10,10 @@ import './spinner/spinner';
import './snackbar/snackbar';
import './tooltip/tooltip';
import './icon-menu/icon-menu';
import './popover/popover';
import './autocomplete/autocomplete';
import './drop-down/drop-down';
import './menu/menu';
import './column-header/column-header';
import './grid-header/grid-header';
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{
cursor: pointer;
}
&:focus, &:active, &:hover{
outline: none;
border: none;
}
.primaryCheckbox {
vn-icon{
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 './style.css';
import Component from '../../lib/component';
import './style.scss';
directive.$inject = ['vnPopover'];
export function directive(vnPopover) {
return {
restrict: 'A',
link: function($scope, $element, $attrs) {
$element.on('click', function(event) {
vnPopover.showComponent($attrs.vnDialog, $scope, $element);
event.preventDefault();
});
}
};
}
ngModule.directive('vnPopover', directive);
export class Popover {
constructor($document, $compile, $transitions) {
this.document = $document[0];
this.$compile = $compile;
/**
* A simple popover.
*/
export default class Popover extends Component {
constructor($element, $scope, $timeout, $transitions) {
super($element, $scope);
this.$timeout = $timeout;
this.$transitions = $transitions;
this.removeScope = false;
this.popOpens = 0;
this._shown = false;
}
_init() {
this.docMouseDownHandler = e => this.onDocMouseDown(e);
this.document.addEventListener('mousedown', this.docMouseDownHandler);
$postLink() {
this.$element.addClass('vn-popover');
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.deregisterCallback = this.$transitions.onStart({},
() => this.hideAll());
this.document.addEventListener('focusin', this.docFocusInHandler);
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.docMouseDownHandler = null;
this.docKeyDownHandler = null;
this.deregisterCallback();
}
show(childElement, parent, popoverId) {
this.childElement = childElement;
let popover = this.document.createElement('div');
this.popOpens++;
this.document.removeEventListener('focusin', this.docFocusInHandler);
if (!popoverId) {
popoverId = 'popover-' + this.popOpens;
popover.id = popoverId;
}
if (this.deregisterCallback)
this.deregisterCallback();
popover.className = 'vn-popover';
popover.addEventListener('mousedown',
e => this.onPopoverMouseDown(e));
popover.appendChild(childElement);
this.popover = popover;
if (this.parent)
this.parent.focus();
let style = popover.style;
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;
if (this.onClose)
this.onClose();
}
showComponent(childComponent, $scope, parent) {
let childElement = this.document.createElement(childComponent);
let id = 'popover-' + this.popOpens;
childElement.id = id;
this.removeScope = true;
this.$compile(childElement)($scope.$new());
this.show(childElement, parent, id);
return childElement;
}
/**
* Repositions the popover to a correct location relative to the parent.
*/
relocate() {
if (!(this.parent && this._shown)) return;
_checkOpens() {
this.popOpens = this.document.querySelectorAll('*[id^="popover-"]').length;
if (this.popOpens === 0) {
this._destroy();
}
}
let style = this.popover.style;
style.width = '';
style.height = '';
_removeElement(val) {
if (!val) return;
let element = angular.element(val);
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);
}
let arrowStyle = this.arrow.style;
arrowStyle.top = '';
arrowStyle.bottom = '';
hide(id) {
let popover = this.document.querySelector(`#${id}`);
if (popover) {
this._removeElement(popover);
}
this._checkOpens();
}
let parentRect = this.parent.getBoundingClientRect();
let popoverRect = this.popover.getBoundingClientRect();
let arrowRect = this.arrow.getBoundingClientRect();
hideChilds(id) {
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();
}
let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;
hideAll() {
let popovers = this.document.querySelectorAll('*[id^="popover-"]');
popovers.forEach(
val => {
this._removeElement(val);
}
);
this._checkOpens();
}
let top = parentRect.top + parentRect.height + arrowHeight;
let left = parentRect.left;
let height = popoverRect.height;
let width = Math.max(popoverRect.width, parentRect.width);
_findPopOver(node) {
while (node != null) {
if (node.id && node.id.startsWith('popover-')) {
return node.id;
}
node = node.parentNode;
}
return null;
}
let margin = 10;
let showTop = top + height + margin > window.innerHeight;
onDocMouseDown(event) {
let targetId = this._findPopOver(event.target);
if (targetId) {
this.hideChilds(targetId);
} else {
this.hideAll();
}
if (showTop)
top = Math.max(parentRect.top - height - arrowHeight, margin);
if (left + width + margin > window.innerWidth)
left = window.innerWidth - width - margin;
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) {
if (event.keyCode === 27) {
let targetId = this._findPopOver(this.lastTarget);
if (targetId) {
this.hideChilds(targetId);
} else {
this.hideAll();
}
this.lastTarget = null;
if (event.defaultPrevented) return;
if (event.keyCode == 27) { // Esc
event.preventDefault();
this.hide();
}
}
onPopoverMouseDown(event) {
this.lastTarget = event.target;
onMouseDown(event) {
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 template from './textarea.html';
import './style.scss';
directive.$inject = ['vnTemplate'];
export function directive(vnTemplate) {

View File

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

View File

@ -1,6 +1,7 @@
import './id';
import './focus';
import './dialog';
import './popover';
import './validation';
import './acl';
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",
"name": "Items",
"icon": "/static/images/icon_item.png",
"icon": "inbox",
"validations" : true,
"routes": [
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,26 +14,30 @@ vn-main-menu {
color: #FF9300;
}
}
li.mdl-menu__item {
background-color: #FF9300;
margin-bottom: 8px;
.vn-popover ul {
list-style-type: none;
margin: 0;
color: white;
img {
max-width: 18px;
vertical-align: middle;
margin-top: -3px;
li {
background-color: #FF9300;
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 "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 {
height: 100%;
box-sizing: border-box;

View File

@ -8,5 +8,7 @@ export default {
locator:
cb => require.ensure([], () => cb(require('locator'))),
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',
vnTopbar: 'vn-topbar > header',
vnIcon: 'vn-icon',
vnMainMenu: 'vn-main-menu > div',
vnModuleContainer: 'vn-module-container > a',
vnSearchBar: 'vn-searchbar > form > vn-horizontal',
vnFloatButton: 'vn-float-button > button',

View File

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

View File

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

View File

@ -6,6 +6,7 @@ const exec = require('child_process').exec;
const PluginError = require('plugin-error');
const argv = require('minimist')(process.argv.slice(2));
const log = require('fancy-log');
const environment = require('gulp-env');
// Configuration
@ -37,7 +38,13 @@ let defaultPort = proxyConf.defaultPort;
// Development
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 () => {
@ -48,7 +55,7 @@ gulp.task('client', ['build-clean'], async () => {
* Starts all backend services, including the nginx proxy and the database.
*/
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
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 () => {
@ -154,7 +161,9 @@ gulp.task('docker-compose', async () => {
// dockerFile = 'Dockerfile';
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}`,
image: `${service.name}:\${TAG}`,
build: {
@ -480,7 +489,8 @@ gulp.task('docker-wait', callback => {
let conn = mysql.createConnection({
host: 'localhost',
user: 'root'
user: 'root',
password: 'root'
});
conn.on('error', () => {});
conn.connect(err => {

View File

@ -44,6 +44,7 @@
"file-loader": "^1.1.6",
"gulp": "^3.9.1",
"gulp-concat": "^2.6.0",
"gulp-env": "^0.4.0",
"gulp-extend": "^0.2.0",
"gulp-install": "^1.1.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
echo "Importing $file"
mysql -u root < $file
mysql -u root -proot < $file
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'),
(110, 'JessicaJones', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'JessicaJones@verdnatura.es');
INSERT INTO `vn`.`worker`(`workerCode`, `id`, `firstName`, `name`, `userFk`)
VALUES
('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),
(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`)
VALUES
(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),
(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`)
VALUES
(1, 1 ,1 ),

View File

@ -1,6 +1,6 @@
FROM mysql:5.6.37
ENV MYSQL_ALLOW_EMPTY_PASSWORD yes
ENV MYSQL_ROOT_PASSWORD root
ENV TZ GMT-1
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,
host: 'localhost',
user: 'root',
password: '',
password: 'root',
database: 'salix'
});

View File

@ -1,5 +1,3 @@
let md5 = require('md5');
module.exports = function(Self) {
Self.remoteMethod('createWithUser', {
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 user = {
name: data.userName,
@ -27,34 +25,24 @@ module.exports = function(Self) {
};
let Account = Self.app.models.Account;
Account.beginTransaction({}, (error, transaction) => {
if (error) return callback(error);
let transaction = await Account.beginTransaction({});
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);
});
});
});
try {
let account = await Account.create(user, {transaction});
let client = {
id: account.id,
name: data.name,
fi: data.fi,
socialName: data.socialName,
email: data.email,
salesPersonFk: data.salesPersonFk
};
newClient = await Self.create(client, {transaction});
await transaction.commit();
return newClient;
} catch (e) {
transaction.rollback();
throw e;
}
};
};

View File

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

View File

@ -1,13 +1,6 @@
module.exports = function(Ticket) {
Ticket.remoteMethod('list', {
description: 'List tickets for production',
/* accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'Model id',
http: {source: 'path'}
},*/
returns: {
arg: 'tickets',
type: 'object'
@ -19,19 +12,8 @@ module.exports = function(Ticket) {
});
Ticket.list = function(cb) {
// list();
};
function list() {
var params = [1, 0];
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);
};
};

View File

@ -44,10 +44,22 @@ module.exports = function(Self) {
allowBlank: true
});
let validateDni = require('../validations/validateDni');
Self.validateBinded('fi', validateDni, {
Self.validateAsync('fi', fiIsValid, {
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, {
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');
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');
expect(isValid).toBeFalsy();
expect(isValid).toBeTruthy();
});
describe('Spanish', () => {
it('should return true for valid spanish DNI', () => {
let isValid = validateDni('20849756A');
let isValid = validateDni('20849756A', 'es');
expect(isValid).toBeTruthy();
});
it('should return true for spanish DNI with exceeded digits', () => {
let isValid = validateDni('208497563239A');
it('should return false for spanish DNI with exceeded digits', () => {
let isValid = validateDni('208497563239A', 'es');
expect(isValid).toBeFalsy();
});
it('should return false for spanish DNI with invalid letter', () => {
let isValid = validateDni('20243746E');
let isValid = validateDni('20243746E', 'es');
expect(isValid).toBeFalsy();
});
it('should return true for valid spanish CIF', () => {
let isValid = validateDni('B97367486');
let isValid = validateDni('B97367486', 'es');
expect(isValid).toBeTruthy();
});
it('should return false for spanish CIF with invalid letter', () => {
let isValid = validateDni('A97527786');
let isValid = validateDni('A97527786', 'es');
expect(isValid).toBeFalsy();
});
@ -41,19 +41,19 @@ describe('DNI validation', () => {
describe('French', () => {
it('should return true for valid french DNI', () => {
let isValid = validateDni('FR1B123456789');
let isValid = validateDni('1B123456789', 'fr');
expect(isValid).toBeTruthy();
});
it('should return true for french DNI with exceeded digits', () => {
let isValid = validateDni('FR1B12345678910');
it('should return false for french DNI with exceeded digits', () => {
let isValid = validateDni('1B12345678910', 'fr');
expect(isValid).toBeFalsy();
});
it('should return true for french DNI with bad syntax', () => {
let isValid = validateDni('FR1B12345678A');
it('should return false for french DNI with bad syntax', () => {
let isValid = validateDni('1B12345678A', 'fr');
expect(isValid).toBeFalsy();
});
@ -61,19 +61,19 @@ describe('DNI validation', () => {
describe('Italian', () => {
it('should return true for valid italian DNI', () => {
let isValid = validateDni('IT12345678911');
let isValid = validateDni('12345678911', 'it');
expect(isValid).toBeTruthy();
});
it('should return true for italian DNI with exceeded digits', () => {
let isValid = validateDni('IT123456789112');
it('should return false for italian DNI with exceeded digits', () => {
let isValid = validateDni('123456789112', 'it');
expect(isValid).toBeFalsy();
});
it('should return true for italian DNI with bad syntax', () => {
let isValid = validateDni('IT1234567891A');
it('should return false for italian DNI with bad syntax', () => {
let isValid = validateDni('1234567891A', 'it');
expect(isValid).toBeFalsy();
});
@ -81,19 +81,19 @@ describe('DNI validation', () => {
describe('Portuguese', () => {
it('should return true for valid portuguese DNI', () => {
let isValid = validateDni('PT123456789');
let isValid = validateDni('123456789', 'pt');
expect(isValid).toBeTruthy();
});
it('should return true for portuguese DNI with exceeded digits', () => {
let isValid = validateDni('PT12345678910');
it('should return false for portuguese DNI with exceeded digits', () => {
let isValid = validateDni('12345678910', 'pt');
expect(isValid).toBeFalsy();
});
it('should return true for portuguese DNI with bad syntax', () => {
let isValid = validateDni('PT12345678A');
it('should return false for portuguese DNI with bad syntax', () => {
let isValid = validateDni('12345678A', 'pt');
expect(isValid).toBeFalsy();
});

View File

@ -1,14 +1,12 @@
module.exports = function(fiWithCountry) {
if (fiWithCountry == null) return true;
if (typeof fiWithCountry != 'string') return false;
module.exports = function(fi, country) {
if (fi == null || country == null)
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 validators = {
@ -38,7 +36,7 @@ module.exports = function(fiWithCountry) {
let sum = (pairSum + oddSum).toString();
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);
computedDigit = index == -1 ? control.toString() : index;
} else {
@ -67,7 +65,7 @@ module.exports = function(fiWithCountry) {
let validator = validators[country];
if (!validator)
return false;
return true;
return validator.regExp.test(fi)
&& (!validator.validate || validator.validate());

View File

@ -4,7 +4,7 @@ module.exports = function(iban) {
iban = iban.toUpperCase();
iban = trim(iban);
iban = iban.replace(/\s/g, "");
iban = iban.replace(/\s/g, '');
if (iban.length != 24) {
return false;
@ -33,7 +33,7 @@ module.exports = function(iban) {
function module97(iban) {
var parts = Math.ceil(iban.length / 7);
var remainer = "";
var remainer = '';
for (var i = 1; i <= parts; i++) {
remainer = String(parseFloat(remainer + iban.substr((i - 1) * 7, 7)) % 97);
@ -48,6 +48,6 @@ module.exports = function(iban) {
}
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": {
"name": "db",
"connector": "memory"
},
"vn": {
"name": "mysql",
"connector": "mysql",
"database": "vn",
"debug": false,
"host": "localhost",
"port": 3306,
"username": "root",
"password": "",
"host": "${salixHost}",
"port": "${salixPort}",
"username": "${salixUser}",
"password": "${salixPassword}",
"connectTimeout": 20000,
"acquireTimeout": 20000
},
"salix": {
"name": "mysql",
"connector": "mysql",
"database": "salix",
"debug": false,
"host": "localhost",
"port": 3306,
"username": "root",
"password": "",
"host": "${salixHost}",
"port": "${salixPort}",
"username": "${salixUser}",
"password": "${salixPassword}",
"connectTimeout": 20000,
"acquireTimeout": 20000
},
"account": {
"name": "mysql",
"connector": "mysql",
"database": "account",
"debug": false,
"host": "localhost",
"port": 3306,
"username": "root",
"password": "",
"host": "${salixHost}",
"port": "${salixPort}",
"username": "${salixUser}",
"password": "${salixPassword}",
"connectTimeout": 20000,
"acquireTimeout": 20000
},
@ -39,10 +43,10 @@
"connector": "mysql",
"database": "edi",
"debug": false,
"host": "localhost",
"port": 3306,
"username": "root",
"password": "",
"host": "${salixHost}",
"port": "${salixPort}",
"username": "${salixUser}",
"password": "${salixPassword}",
"connectTimeout": 20000,
"acquireTimeout": 20000
}

View File

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

View File

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

View File

@ -9,7 +9,7 @@
"port": 3306,
"database": "vn",
"user": "root",
"password": ""
"password": "root"
},
"pdf": {
"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": {
"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": {
"type": "belongsTo",
"model": "worker",
"model": "Worker",
"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 = new Jasmine();
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
let environment = require('gulp-env');
environment(".env.json");
jasmine.loadConfig({
spec_dir: 'services',