Merge
|
@ -37,5 +37,8 @@
|
|||
},
|
||||
"WorkerTeamCollegues": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Sip": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "Sip",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "pbx.sip"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"mysql": {
|
||||
"columnName": "extension"
|
||||
}
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"mysql": {
|
||||
"columnName": "secret"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"mysql": {
|
||||
"columnName": "caller_id"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "user_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,13 +19,4 @@ services:
|
|||
- NODE_ENV
|
||||
volumes:
|
||||
- /containers/salix:/etc/salix
|
||||
- /mnt/storage/pdfs:/var/lib/salix/pdfs
|
||||
mailer:
|
||||
image: registry.verdnatura.es/salix-mailer:${TAG}
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: services/mailer
|
||||
environment:
|
||||
- NODE_ENV
|
||||
volumes:
|
||||
- /containers/vn-mailer:/etc/vn-mailer
|
||||
- /mnt/storage/pdfs:/var/lib/salix/pdfs
|
|
@ -145,10 +145,10 @@ describe('Item regularize path', () => {
|
|||
expect(url.hash).toEqual('#!/ticket/index');
|
||||
});
|
||||
|
||||
it('should search for the ticket with id 22 once again', async() => {
|
||||
it('should search for the ticket with id 23 once again', async() => {
|
||||
const result = await nightmare
|
||||
.wait(selectors.ticketsIndex.searchTicketInput)
|
||||
.type(selectors.ticketsIndex.searchTicketInput, 'id:22')
|
||||
.type(selectors.ticketsIndex.searchTicketInput, 'id:23')
|
||||
.click(selectors.ticketsIndex.searchButton)
|
||||
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
|
||||
.countElement(selectors.ticketsIndex.searchResult);
|
||||
|
@ -158,7 +158,7 @@ describe('Item regularize path', () => {
|
|||
|
||||
it(`should now click on the search result to access to the ticket summary`, async() => {
|
||||
const url = await nightmare
|
||||
.waitForTextInElement(selectors.ticketsIndex.searchResult, '22')
|
||||
.waitForTextInElement(selectors.ticketsIndex.searchResult, '23')
|
||||
.waitToClick(selectors.ticketsIndex.searchResult)
|
||||
.waitForURL('/summary')
|
||||
.parsedUrl();
|
||||
|
|
|
@ -2,21 +2,84 @@
|
|||
<vn-horizontal class="header">
|
||||
<vn-auto>
|
||||
<vn-icon icon="keyboard_arrow_left" class="pointer"
|
||||
ng-click="$ctrl.movePrevious()"
|
||||
ng-click="$ctrl.movePrevious($ctrl.skip)"
|
||||
ng-show="$ctrl.displayControls"
|
||||
</vn-icon>
|
||||
</vn-auto>
|
||||
<vn-one>
|
||||
<strong>{{$ctrl.defaultDate | date: 'dd/MM/yyyy'}}</strong>
|
||||
<strong>
|
||||
<span translate>{{$ctrl.defaultDate | date: 'MMMM'}}</span>
|
||||
<span>{{$ctrl.defaultDate | date: 'yyyy'}}</span>
|
||||
</strong>
|
||||
</vn-one>
|
||||
<vn-auto>
|
||||
<vn-icon icon="keyboard_arrow_right" class="pointer"
|
||||
ng-click="$ctrl.moveNext()"
|
||||
ng-click="$ctrl.moveNext($ctrl.skip)"
|
||||
ng-show="$ctrl.displayControls"
|
||||
</vn-icon>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="body">
|
||||
<section ng-repeat="day in $ctrl.days" class="day {{day.color}}" ng-click="$ctrl.select($index)">
|
||||
<span>{{::day.number}}</span>
|
||||
</section>
|
||||
<vn-horizontal>
|
||||
<!-- <vn-auto>
|
||||
<vn-vertical class="body">
|
||||
<section class="day">
|
||||
<span></span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>1</span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>2</span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>3</span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>4</span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>5</span>
|
||||
</section>
|
||||
<section class="day">
|
||||
<span>6</span>
|
||||
</section>
|
||||
</vn-vertical>
|
||||
</vn-auto>
|
||||
<vn-one> -->
|
||||
<vn-vertical class="body">
|
||||
<vn-horizontal class="weekdays">
|
||||
<section class="day" ng-click="$ctrl.selectAll(1)">
|
||||
<span>L</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(2)">
|
||||
<span>M</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(3)">
|
||||
<span>X</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(4)">
|
||||
<span>J</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(5)">
|
||||
<span>V</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(6)">
|
||||
<span>S</span>
|
||||
</section>
|
||||
<section class="day" ng-click="$ctrl.selectAll(0)">
|
||||
<span>D</span>
|
||||
</section>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="days">
|
||||
<section ng-repeat="day in $ctrl.days" class="day {{day.color}}"
|
||||
ng-click="$ctrl.select($index)">
|
||||
<span ng-if="day.event" vn-tooltip="{{day.event.title}}">
|
||||
{{::day.date | date: 'd'}}
|
||||
</span>
|
||||
<span ng-if="!day.event">{{::day.date | date: 'd'}}</span>
|
||||
</section>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
</div>
|
|
@ -9,8 +9,10 @@ import './style.scss';
|
|||
export default class Calendar extends Component {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
|
||||
this.events = [];
|
||||
this.defaultDate = new Date();
|
||||
this.displayControls = true;
|
||||
this.skip = 1;
|
||||
}
|
||||
|
||||
get defaultDate() {
|
||||
|
@ -19,67 +21,218 @@ export default class Calendar extends Component {
|
|||
|
||||
set defaultDate(value) {
|
||||
this._defaultDate = value;
|
||||
this.buildDays();
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
get lastDay() {
|
||||
let year = this.defaultDate.getYear();
|
||||
// Month starts from zero, so we increment one month
|
||||
let month = this.defaultDate.getMonth() + 1;
|
||||
|
||||
return new Date(year, month, 0).getDate();
|
||||
get currentMonth() {
|
||||
return this.defaultDate;
|
||||
}
|
||||
|
||||
buildDays() {
|
||||
let firstMonthDay = new Date(this.defaultDate);
|
||||
firstMonthDay.setDate(1);
|
||||
get events() {
|
||||
return this._events;
|
||||
}
|
||||
|
||||
let weekday = firstMonthDay.getDay();
|
||||
set events(value) {
|
||||
if (!value) return;
|
||||
|
||||
let year = this.defaultDate.getYear();
|
||||
let month = this.defaultDate.getMonth();
|
||||
let previousLastMonthDay = new Date(year, month, 0);
|
||||
value.map(event => {
|
||||
event.date = new Date(event.date);
|
||||
});
|
||||
|
||||
this._events = value;
|
||||
|
||||
if (value.length && this.defaultDate)
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
get nextMonth() {
|
||||
const newDate = new Date(this.currentMonth);
|
||||
newDate.setMonth(this.currentMonth.getMonth() + 1);
|
||||
|
||||
return newDate;
|
||||
}
|
||||
|
||||
get previousMonth() {
|
||||
const newDate = new Date(this.currentMonth);
|
||||
newDate.setMonth(this.currentMonth.getMonth() - 1);
|
||||
|
||||
return newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first day of month from a given date
|
||||
*
|
||||
* @param {Date} date - Origin date
|
||||
* @return {Integer}
|
||||
*/
|
||||
firstDay(date) {
|
||||
const newDate = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(), 1);
|
||||
|
||||
this.applyOffset(newDate);
|
||||
|
||||
return newDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last day of month from a given date
|
||||
*
|
||||
* @param {Date} date - Origin date
|
||||
* @return {Integer}
|
||||
*/
|
||||
lastDay(date) {
|
||||
const newDate = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth() + 1, 0);
|
||||
|
||||
this.applyOffset(newDate);
|
||||
|
||||
return newDate;
|
||||
}
|
||||
|
||||
applyOffset(date) {
|
||||
date.setTime(date.getTime() - date.getTimezoneOffset() * 60000);
|
||||
}
|
||||
|
||||
repaint() {
|
||||
const firstWeekday = this.firstDay(this.currentMonth).getDay();
|
||||
const previousLastDay = this.lastDay(this.previousMonth).getDate();
|
||||
const currentLastDay = this.lastDay(this.currentMonth).getDate();
|
||||
const maxFields = 42; // Max field limit
|
||||
|
||||
let weekdayOffset = firstWeekday > 0 ? firstWeekday : 7;
|
||||
let dayPrevious = previousLastDay - (weekdayOffset - 2);
|
||||
let dayCurrent = 1;
|
||||
let dayNext = 1;
|
||||
|
||||
this.days = [];
|
||||
let day = 1;
|
||||
|
||||
let previousMonthDay = previousLastMonthDay.getDate() - (weekday - 2);
|
||||
let nextMonthDay = 1;
|
||||
|
||||
for (let d = 1; d <= 35; d++) {
|
||||
if (d < weekday) {
|
||||
let monthDay = new Date(previousLastMonthDay);
|
||||
monthDay.setDate(previousMonthDay);
|
||||
this.days.push({number: previousMonthDay, date: monthDay, color: 'gray'});
|
||||
previousMonthDay++;
|
||||
} else if (d >= weekday && day <= this.lastDay) {
|
||||
let monthDay = new Date(this.defaultDate);
|
||||
monthDay.setDate(day);
|
||||
this.days.push({number: day, date: monthDay});
|
||||
day++;
|
||||
} else if (d >= weekday && day > this.lastDay) {
|
||||
this.days.push({number: nextMonthDay, color: 'gray'});
|
||||
nextMonthDay++;
|
||||
for (let fieldIndex = 1; fieldIndex <= maxFields; fieldIndex++) {
|
||||
if (fieldIndex < weekdayOffset) {
|
||||
this.addDay(this.previousMonth, dayPrevious, 'gray');
|
||||
dayPrevious++;
|
||||
} else if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
|
||||
this.addDay(this.currentMonth, dayCurrent);
|
||||
dayCurrent++;
|
||||
} else if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
|
||||
this.addDay(this.nextMonth, dayNext, 'gray');
|
||||
dayNext++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveNext() {
|
||||
let next = this.defaultDate.getMonth() + 1;
|
||||
addDay(date, day, color = '') {
|
||||
const curDate = new Date();
|
||||
curDate.setHours(0, 0, 0, 0);
|
||||
|
||||
this.applyOffset(curDate);
|
||||
|
||||
const newDate = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(), day);
|
||||
|
||||
this.applyOffset(newDate);
|
||||
|
||||
let event = this.events.find(event => {
|
||||
return event.date >= newDate && event.date <= newDate;
|
||||
});
|
||||
|
||||
/* if (curDate >= newDate && curDate <= newDate)
|
||||
color = 'orange'; */
|
||||
|
||||
/* if (newDate.getMonth() === this.currentMonth.getMonth() && newDate.getDay() == 6)
|
||||
color = 'light-blue'; */
|
||||
|
||||
if (newDate.getMonth() === this.currentMonth.getMonth() && newDate.getDay() == 0)
|
||||
color = 'red';
|
||||
|
||||
if (event)
|
||||
color = event.color;
|
||||
|
||||
this.days.push({date: newDate, color, event});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new calendar event
|
||||
*
|
||||
* @param {Date} date - Day to add event
|
||||
* @param {String} color - [green, blue, orange, red]
|
||||
* @param {String} title - Tooltip description
|
||||
* @param {Boolean} isRemovable - True if is removable by users
|
||||
*/
|
||||
addEvent(date, color, title = '', isRemovable = true) {
|
||||
const event = this.events.findIndex(event => {
|
||||
return event.date >= date && event.date <= date;
|
||||
});
|
||||
|
||||
if (event == -1)
|
||||
this.events.push({date, color, title, isRemovable});
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
removeEvent(date) {
|
||||
const event = this.events.findIndex(event => {
|
||||
return event.date >= date && event.date <= date;
|
||||
});
|
||||
|
||||
if (event > -1)
|
||||
this.events.splice(event, 1);
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to next month
|
||||
*
|
||||
* @param {Integer} skip - Months to skip at once
|
||||
*/
|
||||
moveNext(skip = 1) {
|
||||
let next = this.defaultDate.getMonth() + skip;
|
||||
this.defaultDate.setMonth(next);
|
||||
this.buildDays();
|
||||
this.repaint();
|
||||
|
||||
this.emit('moveNext');
|
||||
}
|
||||
|
||||
movePrevious() {
|
||||
let previous = this.defaultDate.getMonth() - 1;
|
||||
/**
|
||||
* Moves to previous month
|
||||
*
|
||||
* @param {Integer} skip - Months to skip at once
|
||||
*/
|
||||
movePrevious(skip = 1) {
|
||||
let previous = this.defaultDate.getMonth() - skip;
|
||||
this.defaultDate.setMonth(previous);
|
||||
this.buildDays();
|
||||
this.repaint();
|
||||
|
||||
this.emit('movePrevious');
|
||||
}
|
||||
|
||||
/**
|
||||
* Day selection event
|
||||
*
|
||||
* @param {Integer} index - Index from days array
|
||||
*/
|
||||
select(index) {
|
||||
this.emit('selection', {value: this.days[index]});
|
||||
let day = this.days[index];
|
||||
day.index = index;
|
||||
|
||||
this.emit('selection', {values: [day]});
|
||||
}
|
||||
|
||||
selectAll(weekday) {
|
||||
let selected = [];
|
||||
for (let i in this.days) {
|
||||
const day = this.days[i];
|
||||
const date = day.date;
|
||||
day.index = i;
|
||||
if (date.getDay() === weekday && date.getMonth() == this.defaultDate.getMonth())
|
||||
selected.push(day);
|
||||
}
|
||||
this.emit('selection', {values: selected});
|
||||
}
|
||||
}
|
||||
|
||||
Calendar.$inject = ['$element', '$scope', '$attrs'];
|
||||
|
@ -89,7 +242,12 @@ ngModule.component('vnCalendar', {
|
|||
controller: Calendar,
|
||||
bindings: {
|
||||
model: '<',
|
||||
events: '<?',
|
||||
defaultDate: '<?',
|
||||
onSelection: '&?'
|
||||
onSelection: '&?',
|
||||
onMoveNext: '&?',
|
||||
onMovePrevious: '&?',
|
||||
displayControls: '<?',
|
||||
skip: '<?'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,21 +4,31 @@ vn-calendar {
|
|||
width: 100%;
|
||||
|
||||
.header vn-one {
|
||||
text-align: center
|
||||
text-align: center;
|
||||
padding: 0.2em 0
|
||||
}
|
||||
|
||||
.body {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
.days {
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
& > .day {
|
||||
.weekdays {
|
||||
border-bottom: 1px solid $hover;
|
||||
border-top: 1px solid $hover;
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
.day {
|
||||
box-sizing: border-box;
|
||||
padding: 0.1em;
|
||||
width: 14.2857143%;
|
||||
line-height: 1.5em;
|
||||
|
||||
span {
|
||||
transition: background-color 0.3s;
|
||||
text-align: center;
|
||||
font-size: 0.8vw;
|
||||
border-radius: 50%;
|
||||
|
@ -26,16 +36,105 @@ vn-calendar {
|
|||
padding: 0.2em;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
& > .day:hover span {
|
||||
.day:hover span {
|
||||
background-color: #DDD
|
||||
}
|
||||
|
||||
& > .day.gray {
|
||||
.day.gray {
|
||||
color: $secondary-font-color
|
||||
}
|
||||
|
||||
.day.orange {
|
||||
font-weight: bold;
|
||||
color: $main-01;
|
||||
}
|
||||
|
||||
.day.orange-circle {
|
||||
color: $main-font-color;
|
||||
& > span {
|
||||
background-color: $main-01
|
||||
}
|
||||
}
|
||||
|
||||
.day.orange-circle:hover {
|
||||
& > span {
|
||||
background-color: $main-01-05
|
||||
}
|
||||
}
|
||||
|
||||
.day.light-orange {
|
||||
color: $main-01-05
|
||||
}
|
||||
|
||||
.day.green {
|
||||
font-weight: bold;
|
||||
color: $main-02;
|
||||
}
|
||||
|
||||
.day.green-circle {
|
||||
color: $main-font-color;
|
||||
& > span {
|
||||
background-color: $main-02
|
||||
}
|
||||
}
|
||||
|
||||
.day.green-circle:hover {
|
||||
& > span {
|
||||
background-color: $main-02-05
|
||||
}
|
||||
}
|
||||
|
||||
.day.light-green {
|
||||
font-weight: bold;
|
||||
color: $main-02-05
|
||||
}
|
||||
|
||||
.day.blue {
|
||||
font-weight: bold;
|
||||
color: $main-03;
|
||||
}
|
||||
|
||||
.day.blue-circle {
|
||||
color: $main-font-color;
|
||||
& > span {
|
||||
background-color: $main-03
|
||||
}
|
||||
}
|
||||
|
||||
.day.blue-circle:hover {
|
||||
& > span {
|
||||
background-color: $main-03-05
|
||||
}
|
||||
}
|
||||
|
||||
.day.light-blue {
|
||||
font-weight: bold;
|
||||
color: $main-03-05
|
||||
}
|
||||
|
||||
.day.red {
|
||||
font-weight: bold;
|
||||
color: $alert-01
|
||||
}
|
||||
|
||||
.day.red-circle {
|
||||
color: $main-font-color;
|
||||
& > span {
|
||||
background-color: $alert-01
|
||||
}
|
||||
}
|
||||
|
||||
.day.red-circle:hover {
|
||||
& > span {
|
||||
background-color: $alert-01-05
|
||||
}
|
||||
}
|
||||
|
||||
.day.light-red {
|
||||
font-weight: bold;
|
||||
color: $alert-01-05;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class Controller extends Input {
|
|||
onChange() {
|
||||
this._field = this.input.checked == true;
|
||||
this.$.$applyAsync();
|
||||
this.emit('change');
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$element', '$scope', '$attrs'];
|
||||
|
@ -47,6 +48,7 @@ ngModule.component('vnCheck', {
|
|||
},
|
||||
bindings: {
|
||||
field: '=?',
|
||||
onChange: '&?',
|
||||
label: '@?',
|
||||
disabled: '<?',
|
||||
rule: '@?'
|
||||
|
|
|
@ -13,4 +13,4 @@ vn-check {
|
|||
color: $secondary-font-color;
|
||||
font-size: 20px !important
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,5 +42,6 @@ import './input-time';
|
|||
import './fetched-tags';
|
||||
import './log';
|
||||
import './treeview';
|
||||
import './treeview/child';
|
||||
import './calendar';
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<ul ng-if="$ctrl.items">
|
||||
<li ng-repeat="item in $ctrl.items" ng-class="{'selected' : item.selected, 'included': item.included}">
|
||||
<vn-horizontal>
|
||||
<vn-auto class="actions">
|
||||
<vn-icon icon="keyboard_arrow_up" ng-if="item.childs.length"
|
||||
ng-click="$ctrl.toggle(item, $event)">
|
||||
</vn-icon>
|
||||
<vn-icon icon="keyboard_arrow_down" ng-if="!item.childs"
|
||||
ng-click="$ctrl.toggle(item, $event)">
|
||||
</vn-icon>
|
||||
</vn-auto>
|
||||
<vn-one class="description">
|
||||
<vn-horizontal>
|
||||
<vn-check vn-auto field="item.selected"
|
||||
on-change="$ctrl.select(item)">
|
||||
</vn-check>
|
||||
<vn-one ng-dblclick="$ctrl.toggle(item)" class="text unselectable">
|
||||
{{::item.name}}
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
<vn-treeview-child items="item.childs"></vn-treeview-child>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,27 @@
|
|||
import ngModule from '../../module';
|
||||
import Component from '../../lib/component';
|
||||
|
||||
class Controller extends Component {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
}
|
||||
|
||||
toggle(item) {
|
||||
this.treeview.onToggle(item);
|
||||
}
|
||||
|
||||
select(item) {
|
||||
this.treeview.onSelection(item);
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnTreeviewChild', {
|
||||
template: require('./child.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
items: '<'
|
||||
},
|
||||
require: {
|
||||
treeview: '^vnTreeview'
|
||||
}
|
||||
});
|
|
@ -1,13 +1 @@
|
|||
<div>
|
||||
<ul>
|
||||
<li ng-repeat="item in $ctrl.data">
|
||||
<a href="">
|
||||
<vn-icon icon="keyboard_arrow_down"></vn-icon>
|
||||
<span>{{::item.name}}</span>
|
||||
|
||||
<section style="float:right">
|
||||
icons</section>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<vn-treeview-child items="$ctrl.data"></vn-treeview-child>
|
||||
|
|
|
@ -8,20 +8,137 @@ import './style.scss';
|
|||
* @property {String} position The relative position to the parent
|
||||
*/
|
||||
export default class Treeview extends Component {
|
||||
constructor($element, $scope, $timeout) {
|
||||
constructor($element, $scope) {
|
||||
super($element, $scope);
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this.model.data;
|
||||
$onInit() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
set selection(value) {
|
||||
this._selection = value;
|
||||
refresh() {
|
||||
this.model.refresh().then(() => {
|
||||
this.data = this.model.data;
|
||||
this.repaintAll();
|
||||
});
|
||||
}
|
||||
|
||||
repaintAll() {
|
||||
let oldData = this.data;
|
||||
oldData.forEach(node => {
|
||||
this.repaintAsc(node);
|
||||
this.repaintDesc(node);
|
||||
});
|
||||
}
|
||||
|
||||
repaintNode(node) {
|
||||
this.repaintAsc(node);
|
||||
this.repaintDesc(node);
|
||||
}
|
||||
|
||||
repaintAsc(node) {
|
||||
if (!node.parent) return;
|
||||
|
||||
const parent = node.parent;
|
||||
if ((node.selected || node.included) && !parent.selected) {
|
||||
parent.included = true;
|
||||
parent.hasCheckedChilds = true;
|
||||
} else if (!this.hasCheckedChilds(parent) && !this.hasCheckedParents(node))
|
||||
parent.included = false;
|
||||
|
||||
// FIXME - Propagate hasCheckedCHilds
|
||||
if (!node.selected && this.hasCheckedParents(node)) {
|
||||
node.included = true;
|
||||
parent.hasCheckedChilds = false;
|
||||
}
|
||||
|
||||
if (!this.hasCheckedChilds(node))
|
||||
node.hasCheckedChilds = false;
|
||||
|
||||
this.repaintAsc(parent);
|
||||
}
|
||||
|
||||
repaintDesc(node) {
|
||||
/* if (node.hasCheckedChilds)
|
||||
node.included = false; */
|
||||
|
||||
if (!node.selected && this.hasCheckedChilds(node)) {
|
||||
node.hasCheckedChilds = true;
|
||||
node.included = true;
|
||||
} else if (!node.selected && node.childs && !this.hasCheckedChilds(node))
|
||||
node.hasCheckedChilds = false;
|
||||
|
||||
|
||||
const childs = node.childs || [];
|
||||
for (let i = 0; i < childs.length; i++) {
|
||||
childs[i].included = false;
|
||||
|
||||
if ((node.selected || node.included && this.hasCheckedParents(childs[i])) && !childs[i].selected)
|
||||
childs[i].included = true;
|
||||
|
||||
this.repaintDesc(childs[i]);
|
||||
}
|
||||
|
||||
if (!node.selected && node.hasCheckedChilds)
|
||||
node.included = true;
|
||||
}
|
||||
|
||||
hasCheckedChilds(node) {
|
||||
if (!node.childs) return false;
|
||||
|
||||
const childs = node.childs;
|
||||
for (let i = 0; i < childs.length; i++) {
|
||||
if (childs[i].selected || this.hasCheckedChilds(childs[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
hasCheckedParents(node) {
|
||||
if (!node.parent) return false;
|
||||
|
||||
const parent = node.parent;
|
||||
if (parent.selected || this.hasCheckedParents(parent))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onSelection(item) {
|
||||
item.selected = !item.selected;
|
||||
|
||||
if (item.selected && item.included)
|
||||
item.included = false;
|
||||
|
||||
if (this.hasCheckedChilds(item))
|
||||
item.hasCheckedChilds = true;
|
||||
else if (this.childs)
|
||||
item.hasCheckedChilds = false;
|
||||
|
||||
this.emit('selection', {item});
|
||||
}
|
||||
|
||||
onToggle(item) {
|
||||
if (item.childs && item.childs.length == 0)
|
||||
return;
|
||||
|
||||
if (item.childs)
|
||||
item.childs = undefined;
|
||||
else {
|
||||
this.model.applyFilter({}, {parentFk: item.id}).then(() => {
|
||||
item.childs = this.model.data;
|
||||
item.childs.forEach(child => {
|
||||
child.parent = item;
|
||||
});
|
||||
this.repaintNode(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Treeview.$inject = ['$element', '$scope', '$attrs'];
|
||||
Treeview.$inject = ['$element', '$scope'];
|
||||
|
||||
ngModule.component('vnTreeview', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "colors";
|
||||
|
||||
vn-treeview {
|
||||
ul {
|
||||
margin: 0;
|
||||
|
@ -5,15 +7,43 @@ vn-treeview {
|
|||
|
||||
li {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
.actions {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 0.5em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > ul > li {
|
||||
|
||||
li ul {
|
||||
padding: 0 1.8em;
|
||||
}
|
||||
|
||||
li > vn-horizontal:hover {
|
||||
background-color: $hover
|
||||
}
|
||||
|
||||
li.selected > vn-horizontal > .description .text,
|
||||
li.included > vn-horizontal > .description .text {
|
||||
font-weight: bold;
|
||||
color: $main-01;
|
||||
}
|
||||
|
||||
li.included {
|
||||
& > vn-horizontal > .description > vn-horizontal > vn-check {
|
||||
.mdl-checkbox .mdl-checkbox__box-outline, {
|
||||
border: 2px solid $main-01-05;
|
||||
}
|
||||
fieldset[disabled] .mdl-checkbox .mdl-checkbox__box-outline, .mdl-checkbox.is-disabled .mdl-checkbox__box-outline {
|
||||
border: 2px solid rgba(0,0,0,.26);
|
||||
}
|
||||
.mdl-checkbox .mdl-checkbox__tick-outline {
|
||||
background: $main-01-05;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,17 @@ Value should be %s characters long: El valor debe ser de %s carácteres de longi
|
|||
Value should have a length between %s and %s: El valor debe tener una longitud de entre %s y %s
|
||||
Value should have at least %s characters: El valor debe tener al menos %s carácteres
|
||||
Value should have at most %s characters: El valor debe tener un máximo de %s carácteres
|
||||
General search: Busqueda general
|
||||
General search: Busqueda general
|
||||
January: Enero
|
||||
February: Febrero
|
||||
March: Marzo
|
||||
April: Abril
|
||||
May: Mayo
|
||||
June: Junio
|
||||
July: Julio
|
||||
August: Agosto
|
||||
September: Septiembre
|
||||
October: Octubre
|
||||
November: Noviembre
|
||||
December: Diciembre
|
||||
Has delivery: Hay reparto
|
|
@ -0,0 +1,35 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('getByWarehouse', {
|
||||
description: 'Returns an array of labour holidays from an specified warehouse',
|
||||
accessType: '',
|
||||
accepts: [{
|
||||
arg: 'warehouseFk',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
}],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getByWarehouse`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getByWarehouse = warehouseFk => {
|
||||
let beginningYear = new Date();
|
||||
beginningYear.setMonth(0);
|
||||
beginningYear.setDate(1);
|
||||
beginningYear.setHours(0, 0, 0, 0);
|
||||
|
||||
return Self.rawSql(
|
||||
`SELECT lh.dated, lhl.description, lht.name, w.id
|
||||
FROM vn.labourHoliday lh
|
||||
JOIN vn.workCenter w ON w.id = lh.workcenterFk
|
||||
LEFT JOIN vn.labourHolidayLegend lhl ON lhl.id = lh.labourHolidayLegendFk
|
||||
LEFT JOIN vn.labourHolidayType lht ON lht.id = lh.labourHolidayTypeFk
|
||||
WHERE w.warehouseFk = ? AND lh.dated >= ?`, [warehouseFk, beginningYear]
|
||||
);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('removeByDate', {
|
||||
description: 'Removes one or more delivery dates for a zone',
|
||||
accessType: '',
|
||||
accepts: [{
|
||||
arg: 'zoneFk',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'dates',
|
||||
type: ['Date'],
|
||||
required: true,
|
||||
}],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/removeByDate`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.removeByDate = (zoneFk, dates) => {
|
||||
return Self.destroyAll({zoneFk, delivered: {inq: dates}});
|
||||
/* return Self.rawSql(`
|
||||
DELETE FROM vn.zoneCalendar
|
||||
WHERE zoneFk = ? AND delivered IN(?)`, [zoneFk, dates]); */
|
||||
};
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('toggleIsIncluded', {
|
||||
description: 'Toggle include to delivery',
|
||||
accessType: '',
|
||||
accepts: [{
|
||||
arg: 'zoneFk',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'geoFk',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
}],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/toggleIsIncluded`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.toggleIsIncluded = async(zoneFk, geoFk) => {
|
||||
const isIncluded = await Self.findOne({
|
||||
where: {zoneFk, geoFk}
|
||||
});
|
||||
|
||||
if (isIncluded)
|
||||
return Self.destroyAll({zoneFk, geoFk});
|
||||
else
|
||||
return Self.upsert({zoneFk, geoFk});
|
||||
};
|
||||
};
|
|
@ -0,0 +1,94 @@
|
|||
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('getLeaves', {
|
||||
description: 'Returns the first shipped and landed possible for params',
|
||||
accessType: '',
|
||||
accepts: [{
|
||||
arg: 'filter',
|
||||
type: 'Object',
|
||||
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
||||
http: {source: 'query'}
|
||||
},
|
||||
{
|
||||
arg: 'zoneFk',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'parentFk',
|
||||
type: 'Number',
|
||||
default: 1,
|
||||
required: false,
|
||||
}],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getLeaves`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getLeaves = async(filter, zoneFk, parentFk = 1) => {
|
||||
let conn = Self.dataSource.connector;
|
||||
let stmts = [];
|
||||
|
||||
stmts.push(new ParameterizedSQL(
|
||||
`SELECT lft, rgt, depth + 1 INTO @lft, @rgt, @depth
|
||||
FROM zoneTreeview WHERE id = ?`, [parentFk]));
|
||||
|
||||
stmts.push(`DROP TEMPORARY TABLE IF EXISTS tChilds`);
|
||||
|
||||
let stmt = new ParameterizedSQL(
|
||||
`CREATE TEMPORARY TABLE tChilds
|
||||
ENGINE = MEMORY
|
||||
SELECT id, lft, rgt
|
||||
FROM zoneTreeview pt`);
|
||||
stmt.merge(conn.makeSuffix(filter));
|
||||
|
||||
if (!filter.where) {
|
||||
stmt.merge(`WHERE pt.lft > @lft AND pt.rgt < @rgt
|
||||
AND pt.depth = @depth`);
|
||||
}
|
||||
|
||||
stmts.push(stmt);
|
||||
|
||||
stmts.push(`DROP TEMPORARY TABLE IF EXISTS tZones`);
|
||||
stmts.push(new ParameterizedSQL(
|
||||
`CREATE TEMPORARY TABLE tZones
|
||||
(INDEX (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT t.id
|
||||
FROM tChilds t
|
||||
JOIN zoneTreeview zt
|
||||
ON zt.lft > t.lft AND zt.rgt < t.rgt
|
||||
JOIN zoneIncluded zi
|
||||
ON zi.geoFk = zt.id AND zi.zoneFk = ?
|
||||
GROUP BY t.id`, [zoneFk]));
|
||||
|
||||
const resultIndex = stmts.push(new ParameterizedSQL(
|
||||
`SELECT
|
||||
pt.id,
|
||||
pt.name,
|
||||
pt.lft,
|
||||
pt.rgt,
|
||||
pt.depth,
|
||||
pt.sons,
|
||||
ti.id IS NOT NULL hasCheckedChilds,
|
||||
zi.geoFk IS NOT NULL AS selected
|
||||
FROM zoneTreeview pt
|
||||
LEFT JOIN vn.zoneIncluded zi
|
||||
ON zi.geoFk = pt.id AND zi.zoneFk = ?
|
||||
JOIN tChilds c ON c.id = pt.id
|
||||
LEFT JOIN tZones ti ON ti.id = pt.id
|
||||
ORDER BY selected DESC, name`, [zoneFk])) - 1;
|
||||
|
||||
const sql = ParameterizedSQL.join(stmts, ';');
|
||||
const result = await Self.rawStmt(sql);
|
||||
|
||||
return result[resultIndex];
|
||||
};
|
||||
};
|
|
@ -1,23 +1,39 @@
|
|||
{
|
||||
"Agency": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"AgencyMode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"DeliveryMethod": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Zone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneCalendar": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneGeo": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneIncluded": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
"Agency": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"AgencyMode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"DeliveryMethod": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Zone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneGeo": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneCalendar": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneIncluded": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneTreeview": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"LabourHoliday": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"LabourHolidayLegend": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"LabourHolidayType": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "LabourHolidayLegend",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "labourHolidayLegend"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"description": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "LabourHolidayType",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "labourHolidayType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"name": {
|
||||
"type": "String"
|
||||
},
|
||||
"rgb": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/labour-holiday/getByWarehouse')(Self);
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "LabourHoliday",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "labourHoliday"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"labourHolidayLegendFk": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"labourHolidayTypeFk": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"dated": {
|
||||
"type": "Date"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"legend": {
|
||||
"type": "belongsTo",
|
||||
"model": "LabourHolidayLegend",
|
||||
"foreignKey": "labourHolidayLegendFk"
|
||||
},
|
||||
"type": {
|
||||
"type": "belongsTo",
|
||||
"model": "LabourHolidayType",
|
||||
"foreignKey": "labourHolidayTypeFk"
|
||||
},
|
||||
"workCenter": {
|
||||
"type": "belongsTo",
|
||||
"model": "WorkCenter",
|
||||
"foreignKey": "workCenterFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "WorkCenter",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "work-center"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"warehouse": {
|
||||
"type": "belongsTo",
|
||||
"model": "Warehouse",
|
||||
"foreignKey": "warehouseFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/zone-calendar/removeByDate')(Self);
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "ZoneCalendar"
|
||||
"table": "zoneCalendar"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
@ -12,6 +12,7 @@
|
|||
"type": "Number"
|
||||
},
|
||||
"delivered": {
|
||||
"id": true,
|
||||
"type": "Date"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/zone-included/toggleIsIncluded')(Self);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/zone-treeview/getLeaves')(Self);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "ZoneTreeview",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "zoneTreeview"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"name": {
|
||||
"type": "String"
|
||||
},
|
||||
"lft": {
|
||||
"type": "Number"
|
||||
},
|
||||
"rgt": {
|
||||
"type": "Number"
|
||||
},
|
||||
"depth": {
|
||||
"type": "Number"
|
||||
},
|
||||
"sons": {
|
||||
"type": "Number"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<mg-ajax path="/agency/api/Zones/{{patch.params.id}}" options="vnPatch"></mg-ajax>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.zone"
|
||||
form="form"
|
||||
save="patch">
|
||||
</vn-watcher>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()">
|
||||
<vn-card pad-large>
|
||||
<vn-title>Basic data</vn-title>
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-two vn-focus
|
||||
label="Name"
|
||||
field="$ctrl.zone.name">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
field="$ctrl.zone.warehouseFk"
|
||||
url="/agency/api/Warehouses"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
label="Warehouse">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
field="$ctrl.zone.agencyModeFk"
|
||||
url="/agency/api/AgencyModes"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
label="Agency">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-input-number
|
||||
vn-two
|
||||
min="0"
|
||||
step="1"
|
||||
label="Traveling days"
|
||||
field="$ctrl.zone.travelingDays">
|
||||
</vn-input-number>
|
||||
<vn-input-time
|
||||
vn-two
|
||||
label="Estimated hour (ETD)"
|
||||
field="$ctrl.zone.hour">
|
||||
</vn-input-time>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-input-number vn-one
|
||||
label="Price"
|
||||
field="$ctrl.zone.price"
|
||||
min="0.00"
|
||||
step="0.50">
|
||||
</vn-input-number>
|
||||
<vn-input-number vn-one
|
||||
label="Bonus"
|
||||
field="$ctrl.zone.bonus"
|
||||
min="0.00"
|
||||
step="0.50">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit label="Save"></vn-submit>
|
||||
</vn-button-bar>
|
||||
</form>
|
|
@ -8,7 +8,6 @@ class Controller {
|
|||
|
||||
onSubmit() {
|
||||
this.$scope.watcher.submit().then(() => {
|
||||
this.$state.go('zone.card.location');
|
||||
this.card.reload();
|
||||
});
|
||||
}
|
||||
|
@ -16,7 +15,7 @@ class Controller {
|
|||
|
||||
Controller.$inject = ['$scope', '$state'];
|
||||
|
||||
ngModule.component('vnZoneEdit', {
|
||||
ngModule.component('vnZoneBasicData', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
|
@ -1,21 +1,29 @@
|
|||
<!-- <vn-crud-model
|
||||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/order/api/ItemCategories"
|
||||
data="categories">
|
||||
</vn-crud-model> -->
|
||||
url="/agency/api/ZoneCalendars"
|
||||
fields="['zoneFk', 'delivered']"
|
||||
link="{zoneFk: $ctrl.$stateParams.id}"
|
||||
data="$ctrl.data"
|
||||
primary-key="zoneFk" auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-horizontal>
|
||||
<vn-vertical vn-one>
|
||||
<vn-card >
|
||||
<vn-card>
|
||||
<vn-vertical>
|
||||
<vn-vertical pad-small>
|
||||
<vn-calendar
|
||||
default-date="$ctrl.defaultDate"
|
||||
on-selection="$ctrl.onSelection(value)">
|
||||
<vn-calendar vn-id="stMonth" events="$ctrl.events" skip="2"
|
||||
on-selection="$ctrl.onSelection(stMonth, values)"
|
||||
on-move-next="$ctrl.onMoveNext(ndMonth)"
|
||||
on-move-previous="$ctrl.onMovePrevious(ndMonth)">
|
||||
</vn-calendar>
|
||||
</vn-vertical>
|
||||
<vn-vertical pad-small>
|
||||
<vn-calendar vn-id="ndMonth" events="$ctrl.events" skip="2"
|
||||
display-controls="false"
|
||||
on-selection="$ctrl.onSelection(ndMonth, values)"
|
||||
default-date="$ctrl.ndMonthDate">
|
||||
</vn-calendar>
|
||||
</vn-vertical>
|
||||
<!-- <vn-vertical pad-small>
|
||||
<vn-calendar default-date="$ctrl.defaultNexDate"></vn-calendar>
|
||||
</vn-vertical> -->
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
</vn-vertical>
|
||||
|
|
|
@ -1,19 +1,144 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
class Controller {
|
||||
constructor($scope) {
|
||||
constructor($scope, $stateParams, $http) {
|
||||
this.$stateParams = $stateParams;
|
||||
this.$scope = $scope;
|
||||
this.defaultDate = new Date();
|
||||
this.defaultNexDate = new Date(this.defaultDate);
|
||||
this.defaultNexDate.setMonth(this.defaultNexDate.getMonth() + 1);
|
||||
this.$http = $http;
|
||||
this.stMonthDate = new Date();
|
||||
this.ndMonthDate = new Date();
|
||||
this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1);
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
onSelection(value) {
|
||||
console.log(value);
|
||||
$postLink() {
|
||||
this.stMonth = this.$scope.stMonth;
|
||||
this.ndMonth = this.$scope.ndMonth;
|
||||
}
|
||||
|
||||
get zone() {
|
||||
return this._zone;
|
||||
}
|
||||
|
||||
set zone(value) {
|
||||
this._zone = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
let query = '/agency/api/LabourHolidays/getByWarehouse';
|
||||
this.$http.get(query, {params: {warehouseFk: value.warehouseFk}}).then(res => {
|
||||
if (!res.data) return;
|
||||
const events = [];
|
||||
res.data.forEach(holiday => {
|
||||
events.push({
|
||||
date: holiday.dated,
|
||||
color: 'blue-circle',
|
||||
title: holiday.description || holiday.name,
|
||||
isRemovable: false
|
||||
});
|
||||
});
|
||||
|
||||
this.events = this.events.concat(events);
|
||||
});
|
||||
}
|
||||
|
||||
get data() {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
const events = [];
|
||||
value.forEach(event => {
|
||||
events.push({
|
||||
date: event.delivered,
|
||||
color: 'green-circle',
|
||||
title: 'Has delivery',
|
||||
isRemovable: true
|
||||
});
|
||||
});
|
||||
|
||||
this.events = this.events.concat(events);
|
||||
}
|
||||
|
||||
onSelection(calendar, values) {
|
||||
let totalEvents = 0;
|
||||
values.forEach(day => {
|
||||
const exists = this.events.findIndex(event => {
|
||||
return event.date >= day.date && event.date <= day.date
|
||||
&& event.isRemovable;
|
||||
});
|
||||
|
||||
if (exists > -1) totalEvents++;
|
||||
});
|
||||
|
||||
if (totalEvents > (values.length / 2))
|
||||
this.removeEvents(values);
|
||||
else
|
||||
this.addEvents(values);
|
||||
}
|
||||
|
||||
addEvents(days) {
|
||||
days.forEach(day => {
|
||||
const event = this.events.find(event => {
|
||||
return event.date >= day.date && event.date <= day.date;
|
||||
});
|
||||
|
||||
if (event)
|
||||
return false;
|
||||
|
||||
this.$scope.model.insert({
|
||||
zoneFk: this.zone.id,
|
||||
delivered: day.date
|
||||
});
|
||||
|
||||
this.stMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
|
||||
this.stMonth.repaint();
|
||||
this.ndMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
|
||||
this.ndMonth.repaint();
|
||||
});
|
||||
this.$scope.model.save();
|
||||
}
|
||||
|
||||
removeEvents(days) {
|
||||
let dates = [];
|
||||
days.forEach(day => {
|
||||
const event = this.events.find(event => {
|
||||
return event.date >= day.date && event.date <= day.date;
|
||||
});
|
||||
|
||||
if (event && !event.isRemovable)
|
||||
return false;
|
||||
|
||||
// FIXME - Date offset
|
||||
let date = new Date(day.date);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
dates.push(date);
|
||||
|
||||
this.stMonth.removeEvent(day.date);
|
||||
this.stMonth.repaint();
|
||||
this.ndMonth.removeEvent(day.date);
|
||||
this.ndMonth.repaint();
|
||||
});
|
||||
|
||||
if (dates.length == 0) return;
|
||||
const params = {zoneFk: this.zone.id, dates};
|
||||
this.$http.post('/agency/api/zoneCalendars/removeByDate', params);
|
||||
}
|
||||
|
||||
onMoveNext(calendar) {
|
||||
calendar.moveNext(2);
|
||||
}
|
||||
|
||||
onMovePrevious(calendar) {
|
||||
calendar.movePrevious(2);
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$scope'];
|
||||
Controller.$inject = ['$scope', '$stateParams', '$http'];
|
||||
|
||||
ngModule.component('vnZoneCalendar', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<vn-main-block>
|
||||
<vn-vertical margin-medium>
|
||||
<vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor>
|
||||
<vn-horizontal>
|
||||
<vn-one ui-view></vn-one>
|
||||
<vn-auto class="right-block">
|
||||
<vn-zone-calendar zone="$ctrl.zone"></vn-zone-calendar>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
<vn-horizontal>
|
||||
<vn-auto class="left-block">
|
||||
<vn-zone-descriptor zone="$ctrl.zone"></vn-zone-descriptor>
|
||||
<vn-left-menu></vn-left-menu>
|
||||
</vn-auto>
|
||||
<vn-one>
|
||||
<vn-vertical margin-medium ui-view></vn-vertical>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
</vn-main-block>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Agency', () => {
|
||||
xdescribe('Agency', () => {
|
||||
describe('Component vnZoneCard', () => {
|
||||
let $scope;
|
||||
let controller;
|
||||
|
|
|
@ -14,7 +14,7 @@ export default class Controller {
|
|||
|
||||
onSubmit() {
|
||||
this.$scope.watcher.submit().then(res => {
|
||||
this.$state.go('zone.card.basicData', {id: res.data.id});
|
||||
this.$state.go('zone.card.location', {id: res.data.id});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import './index';
|
||||
import watcher from 'core/mocks/watcher';
|
||||
|
||||
describe('Agency', () => {
|
||||
xdescribe('Agency', () => {
|
||||
describe('Component vnZoneCreate', () => {
|
||||
let $scope;
|
||||
let $state;
|
||||
|
|
|
@ -17,30 +17,28 @@
|
|||
<div class="body">
|
||||
<vn-one>
|
||||
<vn-label-value label="Id"
|
||||
value="{{::$ctrl.zone.id}}">
|
||||
value="{{$ctrl.zone.id}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Name"
|
||||
value="{{::$ctrl.zone.name}}">
|
||||
value="{{$ctrl.zone.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Warehouse"
|
||||
value="{{::$ctrl.zone.warehouse.name}}">
|
||||
value="{{$ctrl.zone.warehouse.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Agency"
|
||||
value="{{::$ctrl.zone.agencyMode.name}}">
|
||||
value="{{$ctrl.zone.agencyMode.name}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-label-value label="Estimated hour (ETD)"
|
||||
value="{{::$ctrl.zone.hour | date: 'HH:mm'}}">
|
||||
value="{{$ctrl.zone.hour | date: 'HH:mm'}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Traveling days"
|
||||
value="{{::$ctrl.zone.travelingDays}}">
|
||||
value="{{$ctrl.zone.travelingDays}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Price"
|
||||
value="{{::$ctrl.zone.price | currency: '€': 2}}">
|
||||
value="{{$ctrl.zone.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Bonus"
|
||||
value="{{::$ctrl.zone.price | currency: '€': 2}}">
|
||||
value="{{$ctrl.zone.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,6 @@ class Controller {
|
|||
this.$state = $state;
|
||||
this.$http = $http;
|
||||
this.moreOptions = [
|
||||
{callback: this.editZone, name: 'Settings'},
|
||||
{callback: this.deleteZone, name: 'Delete'}
|
||||
];
|
||||
}
|
||||
|
@ -19,10 +18,6 @@ class Controller {
|
|||
this.$scope.deleteZone.show();
|
||||
}
|
||||
|
||||
editZone() {
|
||||
this.$state.go('zone.card.edit', {zone: this.zone});
|
||||
}
|
||||
|
||||
returnDialog(response) {
|
||||
if (response === 'ACCEPT') {
|
||||
this.$http.delete(`/agency/api/Zones/${this.zone.id}`).then(() => {
|
||||
|
|
|
@ -6,6 +6,6 @@ import './card';
|
|||
import './descriptor';
|
||||
import './search-panel';
|
||||
import './create';
|
||||
import './edit';
|
||||
import './basic-data';
|
||||
import './location';
|
||||
import './calendar';
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
<vn-td number>{{::zone.price | currency:'€':2}}</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon-button
|
||||
ng-click="$ctrl.preview(zone)"
|
||||
ng-click="$ctrl.preview($event, zone)"
|
||||
vn-tooltip="Preview"
|
||||
icon="desktop_windows">
|
||||
</vn-icon-button>
|
||||
|
@ -54,6 +54,13 @@
|
|||
</vn-card>
|
||||
<vn-pagination model="model"></vn-pagination>
|
||||
</div>
|
||||
<vn-dialog
|
||||
vn-id="summary"
|
||||
class="dialog-summary">
|
||||
<tpl-body>
|
||||
<vn-zone-summary zone="$ctrl.zoneSelected"></vn-zone-summary>
|
||||
</tpl-body>
|
||||
</vn-dialog>
|
||||
|
||||
<a ui-sref="zone.create" vn-tooltip="New zone" vn-bind="+" fixed-bottom-right>
|
||||
<vn-float-button icon="add"></vn-float-button>
|
||||
|
|
|
@ -26,8 +26,8 @@ export default class Controller {
|
|||
preview(event, zone) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.$scope.summary.zone = zone;
|
||||
this.$scope.dialog.show();
|
||||
this.zoneSelected = zone;
|
||||
this.$scope.summary.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Agency', () => {
|
||||
xdescribe('Agency', () => {
|
||||
describe('Component vnZoneIndex', () => {
|
||||
let $componentController;
|
||||
let controller;
|
||||
|
|
|
@ -6,7 +6,7 @@ Price: Precio
|
|||
Create: Crear
|
||||
Delete: Eliminar
|
||||
Settings: Ajustes
|
||||
Delivery days: Días de envío
|
||||
Locations: Localizaciones
|
||||
Enter a new search: Introduce una nueva búsqueda
|
||||
Delete zone: Eliminar zona
|
||||
Are you sure you want to delete this zone?: ¿Estás seguro de querer eliminar esta zona?
|
||||
|
@ -15,4 +15,4 @@ Zones: Zonas
|
|||
List: Listado
|
||||
Summary: Vista previa
|
||||
New zone: Nueva zona
|
||||
Edit zone: Editar zona
|
||||
Basic data: Datos básicos
|
|
@ -1,18 +1,26 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/agency/api/ZoneGeos"
|
||||
data="geos">
|
||||
url="/agency/api/ZoneTreeviews/getLeaves"
|
||||
filter="::$ctrl.filter"
|
||||
params="{zoneFk: $ctrl.$stateParams.id, parentFk: 1}">
|
||||
</vn-crud-model>
|
||||
|
||||
<vn-vertical vn-one>
|
||||
<vn-card pad-large>
|
||||
<vn-title>Delivery days</vn-title>
|
||||
<vn-searchbar
|
||||
panel="vn-calendar-search-panel"
|
||||
model="model"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
<vn-treeview model="model"></vn-treeview>
|
||||
</vn-card>
|
||||
</vn-vertical>
|
||||
<vn-horizontal>
|
||||
<vn-vertical vn-one>
|
||||
<vn-card pad-large>
|
||||
<vn-title>Locations</vn-title>
|
||||
<vn-searchbar
|
||||
model="model"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||
on-search="$ctrl.onSearch()"
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
<vn-treeview vn-id="treeview" model="model"
|
||||
on-selection="$ctrl.onSelection(item)">
|
||||
</vn-treeview>
|
||||
</vn-card>
|
||||
</vn-vertical>
|
||||
<vn-auto class="right-block">
|
||||
<vn-zone-calendar zone="::$ctrl.zone"></vn-zone-calendar>
|
||||
</vn-auto>
|
||||
</vn-horizontal>
|
|
@ -1,12 +1,37 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
class Controller {
|
||||
constructor($scope) {
|
||||
constructor($scope, $http, $stateParams) {
|
||||
this.$stateParams = $stateParams;
|
||||
this.$scope = $scope;
|
||||
this.$http = $http;
|
||||
this.searchValue = '';
|
||||
this.filter = {};
|
||||
}
|
||||
|
||||
onSearch() {
|
||||
this.$scope.$$postDigest(() => {
|
||||
this.$scope.treeview.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
exprBuilder(param, value) {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return {name: {like: `%${value}%`}};
|
||||
}
|
||||
}
|
||||
|
||||
onSelection(item) {
|
||||
const path = '/agency/api/ZoneIncludeds/toggleIsIncluded';
|
||||
const params = {geoFk: item.id, zoneFk: this.zone.id};
|
||||
this.$http.post(path, params).then(() => {
|
||||
this.$scope.treeview.repaintNode(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$scope'];
|
||||
Controller.$inject = ['$scope', '$http', '$stateParams'];
|
||||
|
||||
ngModule.component('vnZoneLocation', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -32,23 +32,26 @@
|
|||
"description": "Detail"
|
||||
},
|
||||
{
|
||||
"url": "/location",
|
||||
"url": "/location?q",
|
||||
"state": "zone.card.location",
|
||||
"component": "vn-zone-location",
|
||||
"description": "Location",
|
||||
"description": "Locations",
|
||||
"params": {
|
||||
"zone": "$ctrl.zone"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "/edit",
|
||||
"state": "zone.card.edit",
|
||||
"component": "vn-zone-edit",
|
||||
"description": "Edit zone",
|
||||
"url": "/basic-data",
|
||||
"state": "zone.card.basicData",
|
||||
"component": "vn-zone-basic-data",
|
||||
"description": "Basic data",
|
||||
"params": {
|
||||
"zone": "$ctrl.zone"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menu": []
|
||||
"menu": [
|
||||
{"state": "zone.card.basicData", "icon": "settings"},
|
||||
{"state": "zone.card.location", "icon": "my_location"}
|
||||
]
|
||||
}
|
|
@ -1,30 +1,37 @@
|
|||
<vn-card class="summary">
|
||||
<vn-vertical pad-medium>
|
||||
<vn-horizontal>
|
||||
<vn-one margin-medium>
|
||||
<vn-vertical name="basicData">
|
||||
<h5 translate>Basic data</h5>
|
||||
<vn-label-value label="Name"
|
||||
value="{{$ctrl.zone.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Warehouse"
|
||||
value="{{$ctrl.zone.warehouse.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Agency"
|
||||
value="{{$ctrl.zone.agencyMode.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Estimated hour (ETD)"
|
||||
value="{{$ctrl.zone.hour | date: 'HH:mm'}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Price"
|
||||
value="{{$ctrl.zone.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Bonus"
|
||||
value="{{$ctrl.zone.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
</vn-vertical>
|
||||
<vn-auto>
|
||||
<h5 text-center pad-small-v class="title">{{$ctrl.summary.name}}</h5>
|
||||
</vn-auto>
|
||||
<vn-horizontal pad-medium>
|
||||
<vn-one>
|
||||
<vn-label-value label="Id"
|
||||
value="{{::$ctrl.summary.id}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Name"
|
||||
value="{{::$ctrl.summary.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Warehouse"
|
||||
value="{{::$ctrl.summary.warehouse.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Agency"
|
||||
value="{{::$ctrl.summary.agencyMode.name}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-label-value label="Estimated hour (ETD)"
|
||||
value="{{::$ctrl.summary.hour | date: 'HH:mm'}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Traveling days"
|
||||
value="{{::$ctrl.summary.travelingDays}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Price"
|
||||
value="{{::$ctrl.summary.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Bonus"
|
||||
value="{{::$ctrl.summary.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one margin-medium></vn-one>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
|
@ -18,8 +18,17 @@ class Controller {
|
|||
}
|
||||
|
||||
getSummary() {
|
||||
this.$http.get(`/agency/api/Zones/${this.zone.id}`).then(response => {
|
||||
this.summary = response.data;
|
||||
let filter = {
|
||||
include: [
|
||||
{relation: 'warehouse', fields: ['name']},
|
||||
{relation: 'agencyMode', fields: ['name']}
|
||||
],
|
||||
where: {id: this.zone.id}
|
||||
};
|
||||
filter = encodeURIComponent(JSON.stringify((filter)));
|
||||
this.$http.get(`/agency/api/Zones/findOne?filter=${filter}`).then(res => {
|
||||
if (res && res.data)
|
||||
this.summary = res.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,9 +36,9 @@ export default class Controller {
|
|||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
? {id: value}
|
||||
: {client: {regexp: value}};
|
||||
: {client: {like: `%${value}%`}};
|
||||
case 'client':
|
||||
return {[param]: {regexp: value}};
|
||||
return {[param]: {like: `%${value}%`}};
|
||||
case 'created':
|
||||
return {created: {between: [value, value]}};
|
||||
case 'id':
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class Controller {
|
|||
case 'name':
|
||||
case 'socialName':
|
||||
case 'city':
|
||||
return {[param]: {regexp: `%${value}%`}};
|
||||
return {[param]: {like: `%${value}%`}};
|
||||
case 'id':
|
||||
case 'fi':
|
||||
case 'postcode':
|
||||
|
|
|
@ -168,11 +168,11 @@
|
|||
<h4 translate>Financial information</h4>
|
||||
<vn-label-value label="Risk"
|
||||
value="{{$ctrl.summary.debt.debt | currency:'€':2}}"
|
||||
ng-class="{bold: $ctrl.summary.debt.debt > $ctrl.summary.credit}">
|
||||
ng-class="{alert: $ctrl.summary.debt.debt > $ctrl.summary.credit}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Credit"
|
||||
value="{{$ctrl.summary.credit | currency:'€':2 }} "
|
||||
ng-class="{bold: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
|
||||
ng-class="{alert: $ctrl.summary.credit > $ctrl.summary.creditInsurance ||
|
||||
($ctrl.summary.credit && $ctrl.summary.creditInsurance == null)}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Secured credit"
|
||||
|
@ -183,7 +183,7 @@
|
|||
</vn-label-value>
|
||||
<vn-label-value label="Balance due"
|
||||
value="{{$ctrl.summary.defaulters[0].amount | currency:'€':2}}"
|
||||
ng-class="{bold: $ctrl.summary.defaulters[0].amount}">
|
||||
ng-class="{alert: $ctrl.summary.defaulters[0].amount}">
|
||||
</vn-label-value>
|
||||
<vn-vertical ng-if="$ctrl.summary.recovery.started">
|
||||
<vn-label-value label="Recovery since"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
@import 'colors';
|
||||
|
||||
vn-client-summary {
|
||||
.bold {
|
||||
font-family: vn-font-bold;
|
||||
.alert span {
|
||||
color: $alert-01 !important
|
||||
}
|
||||
}
|
|
@ -18,8 +18,9 @@ class Controller {
|
|||
addStowaway(index) {
|
||||
let params = {id: this.possibleStowaways[index].id, shipFk: this.ticket.id};
|
||||
this.$http.post(`/api/Stowaways/`, params)
|
||||
.then(() => {
|
||||
this.card.reload();
|
||||
.then(res => {
|
||||
this.ticket.ship.push = res.data[0];
|
||||
this.hide;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -39,8 +40,5 @@ ngModule.component('vnAddStowaway', {
|
|||
controller: Controller,
|
||||
bindings: {
|
||||
ticket: '<'
|
||||
},
|
||||
require: {
|
||||
card: '^vnTicketCard'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,17 +10,38 @@ class Controller {
|
|||
this.$translate = $translate;
|
||||
this.moreOptions = [
|
||||
{callback: this.showAddTurnDialog, name: 'Add turn', show: true},
|
||||
{callback: this.showDeleteTicketDialog, name: 'Delete ticket', show: true}/* ,
|
||||
{callback: this.showDeleteTicketDialog, name: 'Delete ticket', show: true},
|
||||
{callback: this.showAddStowaway, name: 'Add stowaway', show: true},
|
||||
{callback: this.showRemoveStowaway, name: 'Remove stowaway', show: false}
|
||||
*/
|
||||
{callback: this.showRemoveStowaway, name: 'Remove stowaway', show: () => this.shouldShowRemoveStowaway()},
|
||||
/* callback: this.showChangeShipped, name: 'Change shipped hour', show: true} */
|
||||
];
|
||||
}
|
||||
|
||||
// Change shipped hour
|
||||
showChangeShipped() {
|
||||
if (!this.isEditable) {
|
||||
this.vnApp.showError(this.$translate.instant('This ticket can\'t be modified'));
|
||||
return;
|
||||
}
|
||||
this.newShipped = new Date(this.ticket.shipped);
|
||||
this.$scope.changeShippedDialog.show();
|
||||
}
|
||||
|
||||
changeShipped(response) {
|
||||
console.log(response);
|
||||
if (response === 'ACCEPT') {
|
||||
let params = {shipped: this.newShipped};
|
||||
this.$http.patch(`/ticket/api/Tickets/${this.ticket.id}/`, params).then(() => {
|
||||
this.$state.go('ticket.index');
|
||||
this.vnApp.showSuccess(this.$translate.instant('Shipped hour updated'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldShowRemoveStowaway() {
|
||||
if (!this._ticket)
|
||||
return false;
|
||||
return (this._ticket.stowaway || this._ticket.ship.length > 1);
|
||||
return (this._ticket.stowaway || (this._ticket.ship && this._ticket.ship.length > 1));
|
||||
}
|
||||
|
||||
onMoreChange(callback) {
|
||||
|
@ -81,7 +102,6 @@ class Controller {
|
|||
|
||||
showAddStowaway() {
|
||||
this.$scope.addStowaway.show();
|
||||
console.log(this.$state.getCurrentPath());
|
||||
}
|
||||
|
||||
showRemoveStowaway() {
|
||||
|
@ -110,16 +130,16 @@ class Controller {
|
|||
tooltip: 'Ship'
|
||||
};
|
||||
}
|
||||
/*
|
||||
if (value.ship.length == 1) {
|
||||
|
||||
if (value.ship && value.ship.length == 1) {
|
||||
links.btnThree = {
|
||||
icon: 'icon-polizon',
|
||||
state: `ticket.card.summary({id: ${value.ship[0].id}})`,
|
||||
tooltip: 'Stowaway'
|
||||
};
|
||||
} else if (value.ship.length > 1)
|
||||
} else if (value.ship && value.ship.length > 1)
|
||||
this.shipStowaways = value.ship;
|
||||
*/
|
||||
|
||||
this._quicklinks = links;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,15 @@ class Controller {
|
|||
deleteStowaway(response) {
|
||||
if (response === 'ACCEPT') {
|
||||
this.$http.delete(`/api/Stowaways/${this.stowawayToDelete.id}`).then(res => {
|
||||
this.card.reload();
|
||||
if (this.ticket.stowaway)
|
||||
delete this.ticket.stowaway;
|
||||
else {
|
||||
for (let i = 0; i < this.ticket.ship.length; i++) {
|
||||
if (this.ticket.ship[i].id === this.stowawayToDelete.id)
|
||||
delete this.ticket.ship[i];
|
||||
}
|
||||
}
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -71,8 +79,5 @@ ngModule.component('vnRemoveStowaway', {
|
|||
controller: Controller,
|
||||
bindings: {
|
||||
ticket: '<'
|
||||
},
|
||||
require: {
|
||||
card: '^vnTicketCard'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ module.exports = Self => {
|
|||
|
||||
let possibleStowaways = await Self.app.models.Ticket.find({
|
||||
where: {
|
||||
id: {neq: ticketFk},
|
||||
clientFk: ship.clientFk,
|
||||
addressFk: ship.addressFk,
|
||||
agencyModeFk: ship.agencyModeFk,
|
||||
|
|
|
@ -7,9 +7,9 @@ class Controller {
|
|||
this.filter = {
|
||||
include: [
|
||||
{relation: 'warehouse', scope: {fields: ['name']}},
|
||||
// {relation: 'ship'},
|
||||
{relation: 'ship'},
|
||||
{relation: 'agencyMode', scope: {fields: ['name']}},
|
||||
// {relation: 'stowaway'},
|
||||
{relation: 'stowaway'},
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
},
|
||||
"ref": {
|
||||
"type": "String"
|
||||
},
|
||||
"totalEntries": {
|
||||
"type": "Number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
|
|
@ -35,10 +35,15 @@ export default class Controller {
|
|||
return {id: value};
|
||||
case 'ref':
|
||||
return {[param]: {regexp: value}};
|
||||
case 'shipped':
|
||||
return {shipped: {lte: value}};
|
||||
case 'landed':
|
||||
return {landed: {gte: value}};
|
||||
case 'id':
|
||||
case 'agencyFk':
|
||||
case 'warehouseOutFk':
|
||||
case 'warehouseInFk':
|
||||
case 'totalEntries':
|
||||
return {[param]: value};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
label="Reference"
|
||||
model="filter.ref">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Total entries"
|
||||
model="filter.totalEntries">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
|
@ -29,6 +34,18 @@
|
|||
value-field="id">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Shipped"
|
||||
model="filter.shipped">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Landed"
|
||||
model="filter.landed">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
label="Warehouse Out"
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
ALTER TABLE `vn`.`ticketService`
|
||||
ADD COLUMN `ticketFk` INT(11) NOT NULL AFTER `taxClassFk`;
|
||||
|
||||
|
||||
ALTER TABLE `vn`.`ticketService`
|
||||
ADD INDEX `fgn_ticketFk_idx` (`ticketFk` ASC);
|
||||
ALTER TABLE `vn`.`ticketService`
|
||||
ADD CONSTRAINT `fgn_ticketFk`
|
||||
FOREIGN KEY (`ticketFk`)
|
||||
REFERENCES `vn2008`.`Tickets` (`Id_Ticket`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
|
@ -0,0 +1,34 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `logAddWithUser`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `logAddWithUser`(vOriginFk INT, vUserId INT, vActionCode VARCHAR(45), vEntity VARCHAR(45), vDescription TEXT)
|
||||
BEGIN
|
||||
/**
|
||||
* Guarda las acciones realizadas por el usuario
|
||||
*
|
||||
* @param vOriginFk Id del registro de origen
|
||||
* @param vActionCode Código de la acción {insert | delete | update}
|
||||
* @param vEntity Nombre que hace referencia a la tabla.
|
||||
* @param descripcion Descripción de la acción realizada por el usuario
|
||||
*/
|
||||
DECLARE vTableName VARCHAR(255) DEFAULT CONCAT(IFNULL(vEntity, ''), 'Log');
|
||||
|
||||
SET @sqlQuery = CONCAT(
|
||||
'INSERT INTO vn.', vTableName, ' SET originFk = ?, userFk = ?, action = ?, description = ?'
|
||||
);
|
||||
SET @originFk = vOriginFk;
|
||||
SET @userFk = vUserId;
|
||||
SET @action = vActionCode;
|
||||
SET @description = vDescription;
|
||||
|
||||
PREPARE stmt FROM @sqlQuery;
|
||||
EXECUTE stmt USING @originFk, @userFk, @action, @description;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sqlQuery = NULL;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `logAdd`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `logAdd`(vOriginFk INT, vActionCode VARCHAR(45), vEntity VARCHAR(45), vDescription TEXT)
|
||||
BEGIN
|
||||
/**
|
||||
* Guarda las acciones realizadas por el usuario
|
||||
*
|
||||
* @param vOriginFk Id del registro de origen
|
||||
* @param vActionCode Código de la acción {insert | delete | update}
|
||||
* @param vEntity Nombre que hace referencia a la tabla.
|
||||
* @param descripcion Descripción de la acción realizada por el usuario
|
||||
*/
|
||||
|
||||
CALL logAddWithUser(vOriginFk, account.userGetId(), vActionCode, vEntity, vDescription);
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `ticketCreate`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCreate`(
|
||||
vClientId INT
|
||||
,vShipped DATE
|
||||
,vWarehouseId INT
|
||||
,vCompanyFk INT
|
||||
,vAddressFk INT
|
||||
,vAgencyType INT
|
||||
,vRouteFk INT
|
||||
,vlanded DATE
|
||||
,OUT vNewTicket INT)
|
||||
BEGIN
|
||||
CALL `ticketCreateWithUser`(vClientId, vShipped, vWarehouseId, vCompanyFk, vAddressFk, vAgencyType, vRouteFk, vlanded, account.userGetId(), vNewTicket);
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
USE `vn`;
|
||||
DROP procedure IF EXISTS `ticketCreateWithUser`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCreateWithUser`(
|
||||
vClientId INT
|
||||
,vShipped DATE
|
||||
,vWarehouseId INT
|
||||
,vCompanyFk INT
|
||||
,vAddressFk INT
|
||||
,vAgencyType INT
|
||||
,vRouteFk INT
|
||||
,vlanded DATE
|
||||
,vUserId INT
|
||||
,OUT vNewTicket INT)
|
||||
BEGIN
|
||||
|
||||
DECLARE vClientOrnamentales INT DEFAULT 5270;
|
||||
DECLARE vCompanyOrn INT DEFAULT 1381;
|
||||
DECLARE vProvinceName VARCHAR(255);
|
||||
|
||||
SELECT p.name INTO vProvinceName
|
||||
FROM vn.client c
|
||||
JOIN province p ON p.id = c.provinceFk
|
||||
WHERE c.id = vClientId;
|
||||
|
||||
IF vProvinceName IN ('SANTA CRUZ DE TENERIFE', 'LAS PALMAS DE GRAN CANARIA') AND vClientId <> vClientOrnamentales THEN
|
||||
SET vCompanyFk = vCompanyOrn;
|
||||
END IF;
|
||||
|
||||
IF NOT vAddressFk OR vAddressFk IS NULL THEN
|
||||
SELECT id INTO vAddressFk
|
||||
FROM address
|
||||
WHERE clientFk = vClientId AND isDefaultAddress;
|
||||
END IF;
|
||||
|
||||
INSERT INTO vn2008.Tickets (
|
||||
Id_Cliente,
|
||||
Fecha,
|
||||
Id_Consigna,
|
||||
Id_Agencia,
|
||||
Alias,
|
||||
warehouse_id,
|
||||
Id_Ruta,
|
||||
empresa_id,
|
||||
landing
|
||||
)
|
||||
SELECT
|
||||
vClientId,
|
||||
vShipped,
|
||||
a.id,
|
||||
IF(vAgencyType, vAgencyType, a.agencyModeFk),
|
||||
a.nickname,
|
||||
vWarehouseId,
|
||||
IF(vRouteFk,vRouteFk,NULL),
|
||||
vCompanyFk,
|
||||
vlanded
|
||||
FROM address a
|
||||
JOIN agencyMode am ON am.id = a.agencyModeFk
|
||||
WHERE a.id = vAddressFk;
|
||||
|
||||
SET vNewTicket = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO ticketObservation(ticketFk, observationTypeFk, description)
|
||||
SELECT vNewTicket, ao.observationTypeFk, ao.description
|
||||
FROM addressObservation ao
|
||||
JOIN address a ON a.id = ao.addressFk
|
||||
WHERE a.id = vAddressFk;
|
||||
|
||||
CALL logAddWithUser(vNewTicket, vUserId, 'insert', 'ticket', CONCAT('Ha creado el ticket', ' ', vNewTicket));
|
||||
|
||||
IF (SELECT isCreatedAsServed FROM vn.client WHERE id = vClientId ) <> FALSE THEN
|
||||
INSERT INTO vncontrol.inter(state_id, Id_Ticket, Id_Trabajador)
|
||||
SELECT id, vNewTicket, getWorker()
|
||||
FROM state
|
||||
WHERE `code` = 'DELIVERED';
|
||||
END IF;
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
USE `hedera`;
|
||||
DROP procedure IF EXISTS `orderConfirm`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `hedera`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `orderConfirm`(vOrder INT)
|
||||
BEGIN
|
||||
/**
|
||||
* Confirms an order, creating each of its tickets on
|
||||
* the corresponding date and store.
|
||||
*
|
||||
* @param vOrder The order identifier
|
||||
*/
|
||||
CALL orderConfirmWithUser(vOrder, account.userGetId());
|
||||
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
|
@ -0,0 +1,233 @@
|
|||
USE `hedera`;
|
||||
DROP procedure IF EXISTS `orderConfirmWithUser`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `hedera`$$
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `orderConfirmWithUser`(vOrder INT, vUserId INT)
|
||||
BEGIN
|
||||
/**
|
||||
* Confirms an order, creating each of its tickets on
|
||||
* the corresponding date, store and user
|
||||
*
|
||||
* @param vOrder The order identifier
|
||||
*@param vUser The user identifier
|
||||
*/
|
||||
DECLARE vOk BOOL;
|
||||
DECLARE vDone BOOL DEFAULT FALSE;
|
||||
DECLARE vWarehouse INT;
|
||||
DECLARE vShipment DATETIME;
|
||||
DECLARE vTicket INT;
|
||||
DECLARE vNotes VARCHAR(255);
|
||||
DECLARE vItem INT;
|
||||
DECLARE vConcept VARCHAR(30);
|
||||
DECLARE vAmount INT;
|
||||
DECLARE vPrice DECIMAL(10,2);
|
||||
DECLARE vSale INT;
|
||||
DECLARE vRate INT;
|
||||
DECLARE vRowId INT;
|
||||
DECLARE vDelivery DATE;
|
||||
DECLARE vAddress INT;
|
||||
DECLARE vAgency INT;
|
||||
DECLARE vIsConfirmed BOOL;
|
||||
DECLARE vClientId INT;
|
||||
DECLARE vCompanyId INT;
|
||||
DECLARE vAgencyModeId INT;
|
||||
|
||||
DECLARE TICKET_FREE INT DEFAULT 2;
|
||||
DECLARE SYSTEM_WORKER INT DEFAULT 20;
|
||||
|
||||
DECLARE cDates CURSOR FOR
|
||||
SELECT ah.shipped, r.warehouse_id
|
||||
FROM `order` o
|
||||
JOIN order_row r ON r.order_id = o.id
|
||||
LEFT JOIN tmp.agencyHourGetShipped ah ON ah.warehouseFk = r.warehouse_id
|
||||
WHERE o.id = vOrder AND r.amount != 0
|
||||
GROUP BY warehouse_id;
|
||||
|
||||
DECLARE cRows CURSOR FOR
|
||||
SELECT r.id, r.item_id, a.Article, r.amount, r.price, r.rate
|
||||
FROM order_row r
|
||||
JOIN vn2008.Articles a ON a.Id_Article = r.item_id
|
||||
WHERE r.amount != 0
|
||||
AND r.warehouse_id = vWarehouse
|
||||
AND r.order_id = vOrder
|
||||
ORDER BY r.rate DESC;
|
||||
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND
|
||||
SET vDone = TRUE;
|
||||
|
||||
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
||||
BEGIN
|
||||
ROLLBACK;
|
||||
RESIGNAL;
|
||||
END;
|
||||
|
||||
-- Carga los datos del pedido
|
||||
|
||||
SELECT o.date_send, o.address_id, o.note, a.agency_id,
|
||||
o.confirmed, cs.Id_Cliente, o.company_id, o.agency_id
|
||||
INTO vDelivery, vAddress, vNotes, vAgency,
|
||||
vIsConfirmed, vClientId, vCompanyId, vAgencyModeId
|
||||
FROM hedera.`order` o
|
||||
JOIN vn2008.Agencias a ON a.Id_Agencia = o.agency_id
|
||||
JOIN vn2008.Consignatarios cs ON cs.Id_Consigna = o.address_id
|
||||
WHERE id = vOrder;
|
||||
|
||||
-- Comprueba que el pedido no está confirmado
|
||||
|
||||
IF vIsConfirmed THEN
|
||||
CALL util.throw ('ORDER_ALREADY_CONFIRMED');
|
||||
END IF;
|
||||
|
||||
-- Comprueba que el pedido no está vacío
|
||||
|
||||
SELECT COUNT(*) > 0 INTO vOk
|
||||
FROM order_row WHERE order_id = vOrder AND amount > 0;
|
||||
|
||||
IF !vOk THEN
|
||||
CALL util.throw ('ORDER_EMPTY');
|
||||
END IF;
|
||||
|
||||
-- Carga las fechas de salida de cada almacén
|
||||
|
||||
CALL vn.agencyHourGetShipped (vDelivery, vAddress, vAgency);
|
||||
|
||||
-- Trabajador que realiza la acción
|
||||
|
||||
IF vUserId IS NULL THEN
|
||||
SELECT employeeFk INTO vUserId FROM orderConfig;
|
||||
END IF;
|
||||
|
||||
-- Crea los tickets del pedido
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
OPEN cDates;
|
||||
|
||||
lDates:
|
||||
LOOP
|
||||
SET vTicket = NULL;
|
||||
SET vDone = FALSE;
|
||||
FETCH cDates INTO vShipment, vWarehouse;
|
||||
|
||||
IF vDone THEN
|
||||
LEAVE lDates;
|
||||
END IF;
|
||||
|
||||
-- Busca un ticket existente que coincida con los parametros del nuevo pedido
|
||||
|
||||
SELECT Id_Ticket INTO vTicket
|
||||
FROM vn2008.Tickets t
|
||||
LEFT JOIN vn.ticketState tls on tls.ticket = t.Id_Ticket
|
||||
JOIN `order` o
|
||||
ON o.address_id = t.Id_Consigna
|
||||
AND vWarehouse = t.warehouse_id
|
||||
AND o.agency_id = t.Id_Agencia
|
||||
AND t.landing = o.date_send
|
||||
AND vShipment = DATE(t.Fecha)
|
||||
WHERE o.id = vOrder
|
||||
AND t.Factura IS NULL
|
||||
AND IFNULL(tls.alertLevel,0) = 0
|
||||
AND t.Id_Cliente <> 1118
|
||||
LIMIT 1;
|
||||
|
||||
-- Crea el ticket en el caso de no existir uno adecuado
|
||||
|
||||
IF vTicket IS NULL
|
||||
THEN
|
||||
CALL vn.ticketCreateWithUser(
|
||||
vClientId,
|
||||
IFNULL(vShipment, CURDATE()),
|
||||
vWarehouse,
|
||||
vCompanyId,
|
||||
vAddress,
|
||||
vAgencyModeId,
|
||||
NULL,
|
||||
vDelivery,
|
||||
vUserId,
|
||||
vTicket
|
||||
);
|
||||
ELSE
|
||||
INSERT INTO vncontrol.inter
|
||||
SET Id_Ticket = vTicket,
|
||||
Id_Trabajador = SYSTEM_WORKER,
|
||||
state_id = TICKET_FREE;
|
||||
END IF;
|
||||
|
||||
INSERT IGNORE INTO vn2008.order_Tickets
|
||||
SET order_id = vOrder,
|
||||
Id_Ticket = vTicket;
|
||||
|
||||
-- Añade las notas
|
||||
|
||||
IF vNotes IS NOT NULL AND vNotes != ''
|
||||
THEN
|
||||
INSERT INTO vn2008.ticket_observation (Id_Ticket, observation_type_id, text)
|
||||
VALUES (vTicket, 4/*comercial*/ , vNotes)
|
||||
ON DUPLICATE KEY UPDATE text = CONCAT(VALUES(text),'. ', text);
|
||||
END IF;
|
||||
|
||||
-- Añade los movimientos y sus componentes
|
||||
|
||||
OPEN cRows;
|
||||
|
||||
lRows:
|
||||
LOOP
|
||||
SET vDone = FALSE;
|
||||
FETCH cRows INTO vRowId, vItem, vConcept, vAmount, vPrice, vRate;
|
||||
|
||||
IF vDone THEN
|
||||
LEAVE lRows;
|
||||
END IF;
|
||||
|
||||
INSERT INTO vn2008.Movimientos
|
||||
SET
|
||||
Id_Article = vItem,
|
||||
Id_Ticket = vTicket,
|
||||
Concepte = vConcept,
|
||||
Cantidad = vAmount,
|
||||
Preu = vPrice,
|
||||
CostFixat = 0,
|
||||
PrecioFijado = TRUE;
|
||||
|
||||
SET vSale = LAST_INSERT_ID();
|
||||
|
||||
INSERT INTO vn2008.Movimientos_componentes (Id_Movimiento, Id_Componente, Valor)
|
||||
SELECT vSale, cm.component_id, cm.price
|
||||
FROM order_component cm
|
||||
JOIN bi.tarifa_componentes tc ON tc.Id_Componente = cm.component_id
|
||||
WHERE cm.order_row_id = vRowId
|
||||
GROUP BY vSale, cm.component_id;
|
||||
|
||||
UPDATE order_row SET Id_Movimiento = vSale
|
||||
WHERE id = vRowId;
|
||||
|
||||
END LOOP;
|
||||
|
||||
CLOSE cRows;
|
||||
|
||||
-- Fija el Costfixat
|
||||
|
||||
UPDATE vn2008.Movimientos m
|
||||
JOIN (SELECT SUM(mc.Valor) sum_valor,mc.Id_Movimiento
|
||||
FROM vn2008.Movimientos_componentes mc
|
||||
JOIN bi.tarifa_componentes tc USING(Id_Componente)
|
||||
JOIN bi.tarifa_componentes_series tcs on tcs.tarifa_componentes_series_id = tc.tarifa_componentes_series_id AND tcs.base
|
||||
JOIN vn2008.Movimientos m ON m.Id_Movimiento = mc.Id_Movimiento
|
||||
WHERE m.Id_Ticket = vTicket
|
||||
GROUP BY mc.Id_Movimiento) mc ON mc.Id_Movimiento = m.Id_Movimiento
|
||||
SET m.CostFixat = sum_valor;
|
||||
END LOOP;
|
||||
|
||||
CLOSE cDates;
|
||||
|
||||
DELETE FROM basketOrder WHERE orderFk = vOrder;
|
||||
UPDATE `order` SET confirmed = TRUE, confirm_date = NOW()
|
||||
WHERE id = vOrder;
|
||||
|
||||
COMMIT;
|
||||
|
||||
END$$
|
||||
|
||||
DELIMITER ;
|
||||
|
|
@ -345,27 +345,28 @@ INSERT INTO `vn`.`invoiceOut`(`id`,`ref`, `serial`, `amount`, `issued`,`clientFk
|
|||
|
||||
INSERT INTO `vn`.`ticket`(`id`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `created`)
|
||||
VALUES
|
||||
(1 , 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY) , DATE_ADD(CURDATE(), INTERVAL -15 DAY) , 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY) ),
|
||||
(2 , 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -10 DAY) , DATE_ADD(CURDATE(), INTERVAL -10 DAY) , 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY) ),
|
||||
(3 , 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL -5 DAY) , DATE_ADD(CURDATE(), INTERVAL -5 DAY) , 102, 'address 22', 122, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY) ),
|
||||
(4 , 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL -4 DAY) , DATE_ADD(CURDATE(), INTERVAL -4 DAY) , 102, 'address 22', 122, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -4 DAY) ),
|
||||
(5 , 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -3 DAY) , DATE_ADD(CURDATE(), INTERVAL -3 DAY) , 103, 'address 23', 123, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -3 DAY) ),
|
||||
(6 , 3, 3, 4, DATE_ADD(CURDATE(), INTERVAL -2 DAY) , DATE_ADD(CURDATE(), INTERVAL -2 DAY) , 103, 'address 23', 123, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -2 DAY) ),
|
||||
(7 , 4, 4, 4, DATE_ADD(CURDATE(), INTERVAL -1 DAY) , DATE_ADD(CURDATE(), INTERVAL -1 DAY) , 104, 'address 24', 124, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -1 DAY) ),
|
||||
(8 , 1, 1, 4, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 104, 'address 24', 124, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
|
||||
(9 , 5, 5, 4, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 105, 'address 25', 125, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
|
||||
(10, 6, 5, 5, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 105, 'address 25', 125, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -3 MONTH)),
|
||||
(11, 7, 1, 1, CURDATE() , CURDATE() , 101, 'address 21', 121, NULL, 0, CURDATE() ),
|
||||
(12, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 MONTH)),
|
||||
(13, 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL +2 MONTH), DATE_ADD(CURDATE(), INTERVAL +2 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +2 MONTH)),
|
||||
(14, 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL +3 MONTH), DATE_ADD(CURDATE(), INTERVAL +3 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +3 MONTH)),
|
||||
(15, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL +4 MONTH), DATE_ADD(CURDATE(), INTERVAL +4 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +4 MONTH)),
|
||||
(16, 1, 1, 1, CURDATE() , CURDATE() , 101, 'address 21', 121, NULL, 0, CURDATE() ),
|
||||
(17, 4, 4, 4, CURDATE() , CURDATE() , 106, 'address 26', 126, NULL, 0, CURDATE() ),
|
||||
(18, 4, 4, 4, CURDATE() , CURDATE() , 107, 'address 27', 127, NULL, 0, CURDATE() ),
|
||||
(19, 5, 5, 4, CURDATE() , CURDATE() , 108, 'address 28', 128, NULL, 0, CURDATE() ),
|
||||
(20, 5, 5, 4, CURDATE() , CURDATE() , 108, 'address 28', 128, NULL, 0, CURDATE() ),
|
||||
(21, 5, 5, 4, CURDATE() , CURDATE() , 110, 'address 29', 129, NULL, 1, CURDATE() );
|
||||
(1 , 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -15 DAY) , DATE_ADD(CURDATE(), INTERVAL -15 DAY) , 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY) ),
|
||||
(2 , 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL -10 DAY) , DATE_ADD(CURDATE(), INTERVAL -10 DAY) , 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY) ),
|
||||
(3 , 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL -5 DAY) , DATE_ADD(CURDATE(), INTERVAL -5 DAY) , 102, 'address 22', 122, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY) ),
|
||||
(4 , 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL -4 DAY) , DATE_ADD(CURDATE(), INTERVAL -4 DAY) , 102, 'address 22', 122, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -4 DAY) ),
|
||||
(5 , 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL -3 DAY) , DATE_ADD(CURDATE(), INTERVAL -3 DAY) , 103, 'address 23', 123, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -3 DAY) ),
|
||||
(6 , 3, 3, 4, DATE_ADD(CURDATE(), INTERVAL -2 DAY) , DATE_ADD(CURDATE(), INTERVAL -2 DAY) , 103, 'address 23', 123, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -2 DAY) ),
|
||||
(7 , 4, 4, 4, DATE_ADD(CURDATE(), INTERVAL -1 DAY) , DATE_ADD(CURDATE(), INTERVAL -1 DAY) , 104, 'address 24', 124, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -1 DAY) ),
|
||||
(8 , 1, 1, 4, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 104, 'address 24', 124, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 MONTH) ),
|
||||
(9 , 5, 5, 4, DATE_ADD(CURDATE(), INTERVAL -2 MONTH), DATE_ADD(CURDATE(), INTERVAL -2 MONTH), 105, 'address 25', 125, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH) ),
|
||||
(10, 6, 5, 5, DATE_ADD(CURDATE(), INTERVAL -3 MONTH), DATE_ADD(CURDATE(), INTERVAL -3 MONTH), 105, 'address 25', 125, NULL, 0, DATE_ADD(CURDATE(), INTERVAL -3 MONTH) ),
|
||||
(11, 7, 1, 1, CURDATE() , CURDATE() , 101, 'address 21', 121, NULL, 0, CURDATE() ),
|
||||
(12, 1, 1, 1, DATE_ADD(CURDATE(), INTERVAL +1 MONTH), DATE_ADD(CURDATE(), INTERVAL +1 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 MONTH) ),
|
||||
(13, 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL +2 MONTH), DATE_ADD(CURDATE(), INTERVAL +2 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +2 MONTH) ),
|
||||
(14, 2, 2, 2, DATE_ADD(CURDATE(), INTERVAL +3 MONTH), DATE_ADD(CURDATE(), INTERVAL +3 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +3 MONTH) ),
|
||||
(15, 3, 3, 3, DATE_ADD(CURDATE(), INTERVAL +4 MONTH), DATE_ADD(CURDATE(), INTERVAL +4 MONTH), 101, 'address 21', 121, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +4 MONTH) ),
|
||||
(16, 1, 1, 1, CURDATE(), CURDATE(), 101, 'address 21', 121, NULL, 0, CURDATE() ),
|
||||
(17, 4, 4, 4, CURDATE(), CURDATE(), 106, 'address 26', 126, NULL, 0, CURDATE() ),
|
||||
(18, 4, 4, 4, CURDATE(), CURDATE(), 107, 'address 27', 127, NULL, 0, CURDATE() ),
|
||||
(19, 5, 5, 4, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), 108, 'address 28', 128, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 DAY) ),
|
||||
(20, 5, 5, 4, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), 108, 'address 28', 128, NULL, 0, DATE_ADD(CURDATE(), INTERVAL +1 DAY) ),
|
||||
(21, 5, 5, 4, CURDATE(), CURDATE(), 110, 'address 29', 129, NULL, 1, CURDATE() ),
|
||||
(22, 5, 5, 4, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), 108, 'address 28', 128, NULL, 1, DATE_ADD(CURDATE(), INTERVAL +1 DAY) );
|
||||
|
||||
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
|
||||
VALUES
|
||||
|
@ -394,8 +395,9 @@ INSERT INTO `vn`.`ticketTracking`(`id`, `ticketFk`, `stateFk`, `workerFk`, `crea
|
|||
(17, 17, 1 , 19, CURDATE()),
|
||||
(18, 18, 1 , 19, CURDATE()),
|
||||
(19, 19, 13, 19, CURDATE()),
|
||||
(20, 20, 15, 19, CURDATE()),
|
||||
(21, 21, 3 , 19, CURDATE());
|
||||
(20, 20, 13, 19, CURDATE()),
|
||||
(21, 21, 3 , 19, CURDATE()),
|
||||
(23, 21, 13, 19, CURDATE());
|
||||
|
||||
INSERT INTO `vn`.`stowaway`(`id`, `shipFk`, `created`)
|
||||
VALUES
|
||||
|
@ -537,22 +539,24 @@ INSERT INTO `vn`.`ticketPackaging`(`id`, `ticketFk`, `packagingFk`, `quantity`,
|
|||
|
||||
INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `price`, `discount`, `reserved`, `isPicked`, `created`)
|
||||
VALUES
|
||||
(1, 1, 1 , 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(2, 2, 1 , 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(3, 1, 1 , 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(4, 4, 1 , 'Object4 Armor2 2', 20, 3.06, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(5, 1, 2 , 'Object1 Gem1 5', 10, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
|
||||
(6, 1, 3 , 'Object1 Gem1 5', 15, 6.50, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY)),
|
||||
(7, 2, 11, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
|
||||
(8, 4, 11, 'Object4 Armor2 2', 10, 3.26, 0, 0, 0, CURDATE()),
|
||||
(9, 1, 16, 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, CURDATE()),
|
||||
(10, 2, 16, 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, CURDATE()),
|
||||
(11, 1, 16, 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, CURDATE()),
|
||||
(12, 4, 16, 'Object4 Armor2 2', 20, 3.06, 0, 0, 0, CURDATE()),
|
||||
(13, 2, 8, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
|
||||
(14, 1, 8, 'Object1 Gem1 5', 10, 2.30, 0, 0, 0, CURDATE()),
|
||||
(15, 1, 19, 'Object1 Gem1 5', 10, 1.50, 0, 0, 0, CURDATE()),
|
||||
(16, 2, 20, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE());
|
||||
(1, 1, 1 , 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(2, 2, 1 , 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(3, 1, 1 , 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(4, 4, 1 , 'Object4 Armor2 2', 20, 3.06, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -15 DAY)),
|
||||
(5, 1, 2 , 'Object1 Gem1 5', 10, 9.10, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -10 DAY)),
|
||||
(6, 1, 3 , 'Object1 Gem1 5', 15, 6.50, 0, 0, 0, DATE_ADD(CURDATE(), INTERVAL -5 DAY)),
|
||||
(7, 2, 11, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
|
||||
(8, 4, 11, 'Object4 Armor2 2', 10, 3.26, 0, 0, 0, CURDATE()),
|
||||
(9, 1, 16, 'Object1 Gem1 5', 5, 9.10, 0, 0, 0, CURDATE()),
|
||||
(10, 2, 16, 'Object2 Gem2 3', 10, 1.07, 0, 0, 0, CURDATE()),
|
||||
(11, 1, 16, 'Object1 Gem1 5', 2, 9.10, 0, 0, 0, CURDATE()),
|
||||
(12, 4, 16, 'Object4 Armor2 2', 20, 3.06, 0, 0, 0, CURDATE()),
|
||||
(13, 2, 8, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
|
||||
(14, 1, 8, 'Object1 Gem1 5', 10, 2.30, 0, 0, 0, CURDATE()),
|
||||
(15, 1, 19, 'Object1 Gem1 5', 10, 1.50, 0, 0, 0, CURDATE()),
|
||||
(16, 2, 20, 'Object2 Gem2 3', 15, 1.30, 0, 0, 0, CURDATE()),
|
||||
(17, 2, 22, 'Object2 Gem2 3', 30, 2.30, 0, 0, 0, CURDATE()),
|
||||
(18, 4, 22, 'Object4 Armor2 2', 20, 3.00, 0, 0, 0, CURDATE());
|
||||
|
||||
INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`)
|
||||
VALUES
|
||||
|
@ -637,7 +641,18 @@ INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
|
|||
(16, 21, 0.002),
|
||||
(16, 28, 5.6),
|
||||
(16, 29, -4.6),
|
||||
(16, 39, 0.01);
|
||||
(16, 39, 0.01),
|
||||
(17, 15, 0.058),
|
||||
(17, 21, 0.002),
|
||||
(17, 28, 5.6),
|
||||
(17, 29, -4.6),
|
||||
(17, 39, 0.01),
|
||||
(18, 15, 0.051),
|
||||
(18, 22, -0.001),
|
||||
(18, 28, 20.72),
|
||||
(18, 29, -19.72),
|
||||
(18, 37, 2),
|
||||
(18, 39, 0.01);
|
||||
|
||||
INSERT INTO `vn`.`saleTracking`(`saleFk`, `isChecked`, `created`, `originalQuantity`, `workerFk`, `actionFk`, `id`, `stateFk`)
|
||||
VALUES
|
||||
|
@ -1064,4 +1079,11 @@ INSERT INTO `vn`.`ticketService`(`id`, `description`, `quantity`, `price`, `taxC
|
|||
VALUES
|
||||
(1, 'delivery charge', 1, 2.00, 1, 1),
|
||||
(2, 'training course', 1, 10.00, 1, 2),
|
||||
(3, 'delivery charge', 1, 5.50, 1, 11);
|
||||
(3, 'delivery charge', 1, 5.50, 1, 11);
|
||||
|
||||
INSERT INTO `pbx`.`sip`(`user_id`, `extension`, `secret`, `caller_id`)
|
||||
VALUES
|
||||
(1, 1010, '123456', 'employee'),
|
||||
(3, 1101, '123456', 'agency'),
|
||||
(5, 1102, '123456', 'administrative'),
|
||||
(9, 1201, '123456', 'developer');
|
|
@ -1 +0,0 @@
|
|||
node_modules
|
|
@ -1,6 +0,0 @@
|
|||
FROM node:lts-slim
|
||||
|
||||
RUN npm -g install pm2
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
CMD ["pm2-docker", "./server/server.js"]
|
|
@ -1,75 +0,0 @@
|
|||
var database = require('./database.js');
|
||||
let config = require('./config.js');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Initialize auth.
|
||||
* @param {Object} request Request object
|
||||
* @param {Object} response Response object
|
||||
* @param {Object} next Next object
|
||||
*/
|
||||
init: function(request, response, next) {
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.next = next;
|
||||
|
||||
this.validateToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate auth token.
|
||||
*/
|
||||
validateToken: function() {
|
||||
let query = 'SELECT userId, ttl, created FROM salix.AccessToken WHERE id = ?';
|
||||
|
||||
database.pool.query(query, [this.getToken()], (error, result) => {
|
||||
let token = result[0];
|
||||
|
||||
if (error || result.length == 0)
|
||||
return this.response.status(401).send({message: 'Invalid token'});
|
||||
|
||||
if (this.isTokenExpired(token.created, token.ttl))
|
||||
return this.response.status(401).send({message: 'Token expired'});
|
||||
|
||||
// Set proxy host
|
||||
let proxy = config.proxy;
|
||||
|
||||
if (!proxy)
|
||||
proxy = {
|
||||
host: 'localhost',
|
||||
port: 80
|
||||
};
|
||||
|
||||
this.request.proxyHost = `http://${proxy.host}:${proxy.port}`;
|
||||
this.request.user = {
|
||||
id: token.userId,
|
||||
token: this.getToken()
|
||||
};
|
||||
this.next();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get request token.
|
||||
* @return {String} Token
|
||||
*/
|
||||
getToken: function() {
|
||||
return this.request.headers.authorization || this.request.query.token;
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the token has expired.
|
||||
* @param {String} created Creation date
|
||||
* @param {Integer} ttl Ttl seconds
|
||||
* @return {Boolean} %true if the token has expired
|
||||
*/
|
||||
isTokenExpired: function(created, ttl) {
|
||||
let date = new Date(created);
|
||||
let currentDate = new Date();
|
||||
|
||||
date.setSeconds(date.getSeconds() + ttl);
|
||||
|
||||
if (currentDate > date)
|
||||
return true;
|
||||
}
|
||||
};
|
|
@ -1,34 +0,0 @@
|
|||
require('require-yaml');
|
||||
const fs = require('fs-extra');
|
||||
const packageJson = require('../package.json');
|
||||
let configPath = `/etc/${packageJson.name}`;
|
||||
let env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
|
||||
|
||||
let config = require('./config/datasources.json');
|
||||
let configFiles = [
|
||||
`${configPath}/datasources.json`,
|
||||
`${configPath}/datasources.${env}.json`
|
||||
];
|
||||
|
||||
for (let configFile of configFiles) {
|
||||
if (fs.existsSync(configFile))
|
||||
Object.assign(config, require(configFile));
|
||||
}
|
||||
|
||||
let proxyConf = {};
|
||||
let proxyFiles = [
|
||||
'../../nginx/config.yml',
|
||||
`${configPath}/config.yml`,
|
||||
`${configPath}/config.${env}.yml`
|
||||
];
|
||||
|
||||
for (let proxyFile of proxyFiles) {
|
||||
if (fs.existsSync(proxyFile))
|
||||
Object.assign(proxyConf, require(proxyFile));
|
||||
}
|
||||
|
||||
config.proxy = proxyConf;
|
||||
config.package = packageJson;
|
||||
config.env = env;
|
||||
|
||||
module.exports = config;
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"app": {
|
||||
"port": 3000,
|
||||
"debug": false,
|
||||
"defaultLanguage": "es",
|
||||
"senderMail": "noreply@localhost",
|
||||
"senderName": "MySender"
|
||||
},
|
||||
"mysql": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"database": "vn",
|
||||
"user": "root",
|
||||
"password": "root"
|
||||
},
|
||||
"smtp": {
|
||||
"host": "localhost",
|
||||
"port": 465,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "noreply",
|
||||
"pass": ""
|
||||
},
|
||||
"tls": {
|
||||
"rejectUnauthorized": false
|
||||
},
|
||||
"pool": true
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
var mysql = require('mysql');
|
||||
let config = require('./config.js');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Pool instance
|
||||
*/
|
||||
pool: null,
|
||||
|
||||
/**
|
||||
* Start database pool
|
||||
*/
|
||||
init: function() {
|
||||
this.pool = mysql.createPool(config.mysql);
|
||||
|
||||
this.pool.getConnection(function(error, connection) {
|
||||
if (error) {
|
||||
throw new Error('Can\'t connect to database: ' + error.code);
|
||||
} else if (config.app.debug) {
|
||||
console.log('Database connection stablished');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set test environment mail.
|
||||
*/
|
||||
testEmail: function() {
|
||||
this.pool.query('SELECT fakeEmail as email FROM vn.config', function(error, qryRs) {
|
||||
config.smtp.testEmail = qryRs[0].email;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
var fs = require('fs');
|
||||
var config = require('./config.js');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns template locale
|
||||
* @param {String} template - Template name
|
||||
* @param {String} countryCode - Language code
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
load: function(template, countryCode, cb) {
|
||||
var localeFile = path.join(__dirname, 'template', `${template}`, 'locale', `${countryCode}.json`);
|
||||
var defaultLocaleFile = path.join(__dirname, 'template', `${template}`, 'locale', `${config.app.defaultLanguage}.json`);
|
||||
|
||||
fs.stat(localeFile, (error, stats) => {
|
||||
if (error) {
|
||||
fs.stat(defaultLocaleFile, (error, stats) => {
|
||||
if (error)
|
||||
return cb(new Error('Translation not found for template ' + template));
|
||||
|
||||
cb(null, {locale: require(defaultLocaleFile)});
|
||||
});
|
||||
} else {
|
||||
cb(null, {locale: require(localeFile)});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse locale text
|
||||
* @param {String} text - Locale text
|
||||
* @param {Object} params - Locale params
|
||||
* @return {String} - Returns parsed text
|
||||
*/
|
||||
parseText: function(text, params) {
|
||||
for (var key in params) {
|
||||
text = text.replace(`%${key}%`, params[key]);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
};
|
|
@ -1,112 +0,0 @@
|
|||
var nodemailer = require('nodemailer');
|
||||
var config = require('./config.js');
|
||||
var template = require('./template.js');
|
||||
var database = require('./database.js');
|
||||
|
||||
/**
|
||||
* Mail module
|
||||
*/
|
||||
module.exports = {
|
||||
transporter: null,
|
||||
/**
|
||||
* Load mail config.
|
||||
*/
|
||||
init: function() {
|
||||
this.transporter = nodemailer.createTransport(config.smtp);
|
||||
|
||||
this.transporter.verify(function(error, success) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
} else if (config.app.debug) {
|
||||
console.log('SMTP connection stablished');
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Send email.
|
||||
* @param {Object} recipient - Mail destinatary
|
||||
* @param {String} subject - Subject
|
||||
* @param {String} body - Mail body
|
||||
* @param {Object} attachments - Mail attachments
|
||||
* @param {Object} params - Params
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
send: function(recipient, subject, body, attachments, params, cb) {
|
||||
let mailOptions = {
|
||||
from: '"' + config.app.senderName + '" <' + config.app.senderMail + '>',
|
||||
to: recipient,
|
||||
subject: subject,
|
||||
html: body,
|
||||
attachments
|
||||
};
|
||||
|
||||
if (config.env != 'production')
|
||||
mailOptions.to = config.app.senderMail;
|
||||
|
||||
this.transporter.sendMail(mailOptions, (error, info) => {
|
||||
try {
|
||||
let status = (error ? error.message : 'OK');
|
||||
this.log(params.sender, params.recipient, recipient, subject, body, params.message, status);
|
||||
|
||||
if (error)
|
||||
return cb(new Error('Email not sent: ' + error));
|
||||
|
||||
if (config.app.debug)
|
||||
console.log('Mail sent ' + info.messageId + ' [' + info.response + ']');
|
||||
|
||||
cb();
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send email with template.
|
||||
* @param {String} tplName - Template name
|
||||
* @param {Object} params - Params object
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
sendWithTemplate: function(tplName, params, cb) {
|
||||
template.get(tplName, params, (error, result) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
// Custom attachments
|
||||
if (params.attachments)
|
||||
params.attachments.forEach(function(attachment) {
|
||||
result.attachments.push(attachment);
|
||||
});
|
||||
|
||||
this.send(result.recipient, result.subject, result.body, result.attachments, params, error => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add mail log
|
||||
* @param {Integer} senderId - Sender id
|
||||
* @param {Integer} recipientId - Recipient id
|
||||
* @param {String} sender - Sender email
|
||||
* @param {String} subject - Mail subject
|
||||
* @param {String} body - Mail body
|
||||
* @param {String} plainTextBody - Mail plain text body
|
||||
* @param {String} status - Mail status
|
||||
*/
|
||||
log: function(senderId, recipientId, sender, subject, body, plainTextBody, status) {
|
||||
let qry = `INSERT INTO mail(senderFk, recipientFk, sender, replyTo, subject, body, plainTextBody, sent, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
let qryParams = [senderId, recipientId, sender, config.app.senderMail, subject, body, plainTextBody, 1, status];
|
||||
|
||||
database.pool.query(qry, qryParams, function(error, result) {
|
||||
if (config.app.debug && error)
|
||||
console.log('Mail log: ' + error);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,288 +0,0 @@
|
|||
var express = require('express');
|
||||
var router = new express.Router();
|
||||
var config = require('../config.js');
|
||||
var mail = require('../mail.js');
|
||||
var template = require('../template.js');
|
||||
var httpRequest = require('request');
|
||||
var auth = require('../auth.js');
|
||||
|
||||
// Auth middleware
|
||||
var requestToken = function(request, response, next) {
|
||||
auth.init(request, response, next);
|
||||
};
|
||||
|
||||
// Printer setup
|
||||
router.get('/printer-setup/:clientId', requestToken, function(request, response) {
|
||||
mail.sendWithTemplate('printer-setup', {clientId: request.params.clientId}, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// Printer setup preview
|
||||
router.get('/printer-setup/:clientId/preview', requestToken, function(request, response) {
|
||||
template.get('printer-setup', {clientId: request.params.clientId, isPreview: true}, (error, result) => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
response.send(result.body);
|
||||
});
|
||||
});
|
||||
|
||||
// Client welcome
|
||||
router.get('/client-welcome/:clientId', requestToken, function(request, response) {
|
||||
mail.sendWithTemplate('client-welcome', {clientId: request.params.clientId}, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// Client welcome preview
|
||||
router.get('/client-welcome/:clientId/preview', requestToken, function(request, response) {
|
||||
template.get('client-welcome', {clientId: request.params.clientId, isPreview: true}, (error, result) => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
response.send(result.body);
|
||||
});
|
||||
});
|
||||
|
||||
// Client SEPA CORE
|
||||
router.get('/sepa-core/:companyId/:clientId', requestToken, function(request, response) {
|
||||
let params = {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId
|
||||
};
|
||||
|
||||
let path = `${request.proxyHost}/print/manuscript/sepa-core/${params.companyId}/${params.clientId}`;
|
||||
let options = {
|
||||
url: path,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': request.headers.authorization
|
||||
}
|
||||
}
|
||||
|
||||
let httpStream = httpRequest(options, function(error, httpResponse, body) {
|
||||
if (error || httpResponse.statusCode != 200)
|
||||
return response.status(400).json({message: error});
|
||||
});
|
||||
|
||||
if (httpStream)
|
||||
params.attachments = [{filename: 'sepa-core.pdf', content: httpStream}];
|
||||
|
||||
mail.sendWithTemplate('sepa-core', params, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// Client SEPA CORE preview
|
||||
router.get('/sepa-core/:companyId/:clientId/preview', requestToken, function(request, response) {
|
||||
let params = {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId,
|
||||
token: request.user.token,
|
||||
isPreview: true
|
||||
};
|
||||
|
||||
template.get('sepa-core', params, (error, result) => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
response.send(result.body);
|
||||
});
|
||||
});
|
||||
|
||||
// First debtor letter
|
||||
router.get('/letter-debtor-st/:companyId/:clientId', requestToken, function(request, response) {
|
||||
let params = {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId,
|
||||
token: request.user.token
|
||||
};
|
||||
|
||||
let path = `${request.proxyHost}/print/manuscript/letter-debtor/${params.companyId}/${params.clientId}`;
|
||||
let options = {
|
||||
url: path,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': request.user.token
|
||||
}
|
||||
}
|
||||
|
||||
let httpStream = httpRequest(options, function(error, httpResponse, body) {
|
||||
if (error || httpResponse.statusCode != 200)
|
||||
return response.status(400).json({message: error});
|
||||
});
|
||||
|
||||
if (httpStream)
|
||||
params.attachments = [{filename: 'extracto.pdf', content: httpStream}];
|
||||
|
||||
mail.sendWithTemplate('letter-debtor-st', params, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// First debtor letter preview
|
||||
router.get('/letter-debtor-st/:companyId/:clientId/preview', requestToken, function(request, response) {
|
||||
template.get('letter-debtor-st', {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId,
|
||||
token: request.user.token,
|
||||
isPreview: true
|
||||
}, (error, result) => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
response.send(result.body);
|
||||
});
|
||||
});
|
||||
|
||||
// Second debtor letter
|
||||
router.get('/letter-debtor-nd/:companyId/:clientId', requestToken, function(request, response) {
|
||||
let params = {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId,
|
||||
token: request.user.token
|
||||
};
|
||||
|
||||
let path = `${request.proxyHost}/print/manuscript/letter-debtor/${params.companyId}/${params.clientId}`;
|
||||
let options = {
|
||||
url: path,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': request.headers.authorization
|
||||
}
|
||||
}
|
||||
|
||||
let httpStream = httpRequest(options, function(error, httpResponse, body) {
|
||||
if (error || httpResponse.statusCode != 200)
|
||||
return response.status(400).json({message: error});
|
||||
});
|
||||
|
||||
if (httpStream)
|
||||
params.attachments = [{filename: 'extracto.pdf', content: httpStream}];
|
||||
|
||||
mail.sendWithTemplate('letter-debtor-nd', params, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// Second debtor letter preview
|
||||
router.get('/letter-debtor-nd/:companyId/:clientId/preview', requestToken, function(request, response) {
|
||||
template.get('letter-debtor-nd', {
|
||||
clientId: request.params.clientId,
|
||||
companyId: request.params.companyId,
|
||||
token: request.user.token,
|
||||
isPreview: true
|
||||
}, (error, result) => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
response.send(result.body);
|
||||
});
|
||||
});
|
||||
|
||||
// Payment method changes
|
||||
router.get('/payment-update/:clientId', requestToken, function(request, response) {
|
||||
mail.sendWithTemplate('payment-update', {clientId: request.params.clientId}, error => {
|
||||
if (error)
|
||||
return response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
// Send notification to alias creditInsurance on client deactivate
|
||||
router.get('/client-deactivate/:clientId', requestToken, function(request, response) {
|
||||
var params = {
|
||||
alias: 'creditInsurance',
|
||||
code: 'clientDeactivate',
|
||||
bodyParams: {
|
||||
clientId: request.params.clientId
|
||||
}
|
||||
};
|
||||
|
||||
mail.sendWithTemplate('notification-alias', params, error => {
|
||||
if (error)
|
||||
response.status(400).json({message: error.message});
|
||||
|
||||
return response.json();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
// Single user notification
|
||||
/* router.post('/:recipient/noticeUserSend', function(request, response) {
|
||||
var params = {
|
||||
recipient: request.params.recipient,
|
||||
sender: request.body.sender,
|
||||
category: request.body.category,
|
||||
message: request.body.message
|
||||
};
|
||||
|
||||
if (params.sender == params.recipient)
|
||||
return;
|
||||
|
||||
let query = `SELECT COUNT(*) isEnabled
|
||||
FROM noticeSubscription s
|
||||
JOIN noticeCategory c ON c.id = s.noticeCategoryFk AND c.isEnabled
|
||||
WHERE c.keyName = ? AND s.userFk = ?`;
|
||||
|
||||
database.pool.query(query, [params.category, params.recipient, params.sender], (error, result) => {
|
||||
mail.sendWithTemplate('notification-notice', params, result => {
|
||||
return response.json(result);
|
||||
});
|
||||
});
|
||||
}); */
|
||||
|
||||
// Send notification to all user subscribed to category
|
||||
/* router.post('/:category/noticeCategorySend', function(request, response) {
|
||||
var params = {
|
||||
sender: request.body.sender,
|
||||
category: request.params.category,
|
||||
message: request.body.message
|
||||
};
|
||||
|
||||
let query = `SELECT s.userFk id FROM noticeSubscription s JOIN noticeCategory
|
||||
c ON c.id = s.noticeCategoryFk AND c.isEnabled WHERE c.keyName = ? AND s.userFk != ?`;
|
||||
|
||||
database.pool.query(query, [params.category, params.sender], (error, result) => {
|
||||
result.forEach(function(user) {
|
||||
params.recipient = user.id;
|
||||
|
||||
mail.sendWithTemplate('notification-notice', params, result => {
|
||||
return response.json(result);
|
||||
});
|
||||
}, this);
|
||||
});
|
||||
}); */
|
||||
|
||||
// Send system notification
|
||||
/* router.post('/:recipient/noticeSystemSend', function(request, response) {
|
||||
var params = {
|
||||
recipient: request.params.recipient,
|
||||
sender: settings.smtp().auth.id,
|
||||
category: request.body.category,
|
||||
message: request.body.message
|
||||
};
|
||||
|
||||
mail.sendWithTemplate('notification-notice', params, result => {
|
||||
return response.json(result);
|
||||
});
|
||||
}); */
|
|
@ -1,49 +0,0 @@
|
|||
var express = require('express');
|
||||
var router = new express.Router();
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
// Mailer default page
|
||||
router.get('/', function(request, response) {
|
||||
response.json({});
|
||||
});
|
||||
|
||||
// Notifications
|
||||
router.use('/notification', require('./route/notification.js'));
|
||||
|
||||
// Serve static images
|
||||
router.use('/static/:template/:image', function(request, response) {
|
||||
let imagePath = path.join(__dirname, '/template/', request.params.template, '/image/', request.params.image);
|
||||
|
||||
fs.stat(imagePath, function(error) {
|
||||
if (error)
|
||||
return response.json({message: 'Image not found'});
|
||||
|
||||
let readStream = fs.createReadStream(imagePath);
|
||||
|
||||
readStream.on('open', function() {
|
||||
let contentType = getContentType(imagePath);
|
||||
|
||||
if (contentType)
|
||||
response.setHeader('Content-type', getContentType(imagePath));
|
||||
|
||||
readStream.pipe(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getContentType(path) {
|
||||
let types = {
|
||||
png: 'image/png',
|
||||
svg: 'image/svg+xml',
|
||||
gif: 'image/gif',
|
||||
jpeg: 'image/jpeg',
|
||||
jpg: 'image/jpeg'
|
||||
};
|
||||
|
||||
let extension = path.split('.')[1];
|
||||
|
||||
return types[extension];
|
||||
}
|
||||
|
||||
module.exports = router;
|
|
@ -1,234 +0,0 @@
|
|||
var fs = require('fs');
|
||||
var mustache = require('mustache');
|
||||
var locale = require('./locale.js');
|
||||
var inlineCss = require('inline-css');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Get template.
|
||||
* @param {String} template - Template name
|
||||
* @param {Object} countryCode - Language code
|
||||
* @param {Object} params - Params
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
get: function(template, params, cb) {
|
||||
var templatePath = path.join(__dirname, 'template', `${template}`, `index.html`);
|
||||
var classPath = path.join(__dirname, 'template', `${template}`, `${template}.js`);
|
||||
var stylePath = path.join(__dirname, 'template', `${template}`, 'style.css');
|
||||
|
||||
fs.stat(templatePath, (error, stat) => {
|
||||
if (error)
|
||||
return cb(new Error('Template ' + template + ' not found'));
|
||||
|
||||
let TemplateClass = require(classPath);
|
||||
let instance = new TemplateClass();
|
||||
|
||||
let getRenderedStyles = (error, body) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
this.renderStyles(stylePath, body, (error, body) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
// Check if has a subject param
|
||||
params.subject = params.subject || instance.subject;
|
||||
|
||||
if (params.subject == undefined) {
|
||||
// Try to find a subject from Html source
|
||||
let title = body.match(new RegExp('<title>(.*?)</title>', 'i'));
|
||||
|
||||
if (title)
|
||||
params.subject = title[1];
|
||||
}
|
||||
|
||||
this.getAttachments(template, body, params.isPreview, (error, result) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
cb(null, {recipient: instance.recipient, subject: params.subject, body: result.body, attachments: result.attachments});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let getDataCb = () => {
|
||||
this.render(templatePath, instance, (error, result) => getRenderedStyles(error, result));
|
||||
};
|
||||
|
||||
instance.getData(params, (error, result) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
locale.load(template, instance.countryCode, (error, result) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
instance._ = result.locale;
|
||||
instance.isPreview = params.isPreview;
|
||||
|
||||
getDataCb(null, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render template
|
||||
* @param {String} path - Template path
|
||||
* @param {Object} data - Params
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
render: function(path, data, cb) {
|
||||
fs.readFile(path, 'utf8', (error, body) => {
|
||||
// Find matching sub-templates
|
||||
let regexp = new RegExp(/\{\{\$\.(.*?)\}\}/, 'ig');
|
||||
let subTpl = body.match(regexp);
|
||||
|
||||
if (!subTpl) {
|
||||
mustache.parse(body);
|
||||
return cb(null, mustache.render(body, data));
|
||||
}
|
||||
|
||||
let parentBody = body;
|
||||
this.renderSub(parentBody, subTpl, data, regexp, (error, body) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
mustache.parse(body);
|
||||
cb(null, mustache.render(body, data));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render sub-template
|
||||
* @param {String} body - Raw body
|
||||
* @param {Object} subTpl - Sub-template name
|
||||
* @param {Object} data - Params
|
||||
* @param {Object} regexp - Regexp
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
renderSub: function(body, subTpl, data, regexp, cb) {
|
||||
let index = 1;
|
||||
|
||||
subTpl.forEach(keyName => {
|
||||
subTplName = keyName.replace(regexp, '$1');
|
||||
|
||||
this.get(subTplName, data, (error, result) => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
|
||||
let subTplBody = result.body;
|
||||
body = body.replace(keyName, subTplBody);
|
||||
|
||||
if (index === subTpl.length)
|
||||
cb(null, body);
|
||||
|
||||
index++;
|
||||
});
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Render template style.
|
||||
* @param {String} path - Stylesheet path
|
||||
* @param {String} body - Rendered html
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
renderStyles: function(stylePath, html, cb) {
|
||||
// Common components
|
||||
let comPath = path.join(__dirname, '../', 'static', 'css', 'component.css');
|
||||
|
||||
fs.readFile(comPath, 'utf8', (error, comCss) => {
|
||||
fs.stat(stylePath, error => {
|
||||
if (error)
|
||||
return cb(new Error('Template stylesheet not found'));
|
||||
|
||||
fs.readFile(stylePath, 'utf8', (error, css) => {
|
||||
let style = '<style>' + comCss + css + '</style>';
|
||||
let body = style + html;
|
||||
let options = {url: ' '};
|
||||
|
||||
inlineCss(body, options)
|
||||
.then(function(body) {
|
||||
cb(null, body);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get template attachments
|
||||
* @param {String} template - Template name
|
||||
* @param {String} body - template body
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
getAttachments: function(template, body, isPreview, cb) {
|
||||
let attachments = [];
|
||||
let tplAttachments = body.match(new RegExp('src="cid:(.*?)"', 'ig'));
|
||||
|
||||
if (!tplAttachments)
|
||||
tplAttachments = {};
|
||||
|
||||
// Template default attachments
|
||||
for (var i = 0; i < tplAttachments.length; i++) {
|
||||
let src = tplAttachments[i].replace('src="cid:', '').replace('"', '').split('/');
|
||||
let attachmentTpl = src[0];
|
||||
let attachment = src[1];
|
||||
|
||||
if (isPreview) {
|
||||
let attachmentPath = `/mailer/static/${attachmentTpl}/${attachment}`;
|
||||
body = body.replace(tplAttachments[i], `src="${attachmentPath}"`);
|
||||
} else {
|
||||
let attachmentPath = path.join(__dirname, 'template', `${attachmentTpl}`, 'image', attachment);
|
||||
let attachmentName = attachmentTpl + '/' + attachment;
|
||||
attachments.push({filename: attachmentName, path: attachmentPath, cid: attachmentName});
|
||||
}
|
||||
}
|
||||
|
||||
if (isPreview)
|
||||
return cb(null, {body: body, attachments: attachments});
|
||||
|
||||
// Template attachment files
|
||||
let attachmentsPath = path.join(__dirname, 'template', `${template}`, 'attachment.json');
|
||||
|
||||
fs.stat(attachmentsPath, (error, stats) => {
|
||||
if (error)
|
||||
return cb(null, {body: body, attachments: attachments});
|
||||
|
||||
let attachObj = require(attachmentsPath);
|
||||
|
||||
for (var i = 0; i < attachObj.length; i++) {
|
||||
let filename = attachObj[i];
|
||||
let attachmentPath = path.join(__dirname, 'template', `${template}`, 'attachment', filename);
|
||||
|
||||
attachments.push({filename: filename, path: attachmentPath, cid: filename});
|
||||
}
|
||||
|
||||
this.checkAttachments(attachments, error => {
|
||||
if (error)
|
||||
return cb(error);
|
||||
cb(null, {body: body, attachments: attachments});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check all template attachments
|
||||
* @param {Object} attachments - Attachments object
|
||||
* @param {Object} cb - Callback
|
||||
*/
|
||||
checkAttachments: function(attachments, cb) {
|
||||
for (var i = 0; i < attachments.length; i++) {
|
||||
var attachment = attachments[i];
|
||||
fs.stat(attachment.path, error => {
|
||||
if (error)
|
||||
return cb(new Error(`Could not load attachment file ${attachment.path}`));
|
||||
});
|
||||
}
|
||||
cb();
|
||||
}
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
[]
|
|
@ -1,46 +0,0 @@
|
|||
var path = require('path');
|
||||
var database = require(path.join(__dirname, '../../database.js'));
|
||||
var format = require(path.join(__dirname, '../../util/format.js'));
|
||||
|
||||
module.exports = class ClientWelcome {
|
||||
getData(params, cb) {
|
||||
let query = `SELECT
|
||||
c.id clientId,
|
||||
CONCAT(w.name, ' ', w.firstName) name,
|
||||
w.phone AS phone,
|
||||
CONCAT(wu.name, '@verdnatura.es') AS email,
|
||||
u.name AS userName,
|
||||
LOWER(ct.code) countryCode,
|
||||
c.email recipient
|
||||
FROM client c
|
||||
JOIN account.user u ON u.id = c.id
|
||||
LEFT JOIN worker w ON w.id = c.salesPersonFk
|
||||
LEFT JOIN account.user wu ON wu.id = w.userFk
|
||||
JOIN country ct ON ct.id = c.countryFk
|
||||
WHERE c.id = ?`;
|
||||
|
||||
database.pool.query(query, [params.clientId], (error, result) => {
|
||||
if (error || result.length == 0)
|
||||
return cb(new Error('No template data found'));
|
||||
|
||||
Object.assign(this, result[0]);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
get salesPersonName() {
|
||||
if (this.name)
|
||||
return `<div>${this._.salesPersonNameText}: <strong>${this.name}</strong></div>`;
|
||||
}
|
||||
|
||||
get salesPersonPhone() {
|
||||
if (this.phone)
|
||||
return `<div>${this._.salesPersonPhoneText}: <strong>${format.phone(this.phone)}</strong></div>`;
|
||||
}
|
||||
|
||||
get salesPersonEmail() {
|
||||
if (this.email)
|
||||
return `<div>${this._.salesPersonEmailText}: ` +
|
||||
`<strong><a href="mailto:${this.email}" target="_blank" style="color:#8dba25">${this.email}</strong></div>`;
|
||||
}
|
||||
};
|
|
@ -1,67 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{_.subject}}</title>
|
||||
<meta charset="utf8"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<!-- Header block -->
|
||||
{{$.header}}
|
||||
<!-- Header block end -->
|
||||
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{_.title}}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<!-- Mail body block -->
|
||||
<div class="body">
|
||||
<p>{{_.dear}}</p>
|
||||
<p>{{{_.bodyDescription}}}</p>
|
||||
<p>
|
||||
<div>{{_.clientNumber}} <strong>{{clientId}}</strong></div>
|
||||
<div>{{_.user}} <strong>{{userName}}</strong></div>
|
||||
<div>{{_.password}} <strong>********</strong> {{{_.passwordResetText}}}</div>
|
||||
</p>
|
||||
|
||||
<h1>{{_.sectionHowToBuyTitle}}</h1>
|
||||
<p>{{_.sectionHowToBuyDescription}}</p>
|
||||
<ol>
|
||||
<li>{{_.sectionHowToBuyRequeriment1}}</li>
|
||||
<li>{{_.sectionHowToBuyRequeriment2}}</li>
|
||||
<li>{{_.sectionHowToBuyRequeriment3}}</li>
|
||||
</ol>
|
||||
<p>{{_.sectionHowToBuyStock}}</p>
|
||||
<p>{{_.sectionHowToBuyDelivery}}</p>
|
||||
|
||||
<h1>{{_.sectionHowToPayTitle}}</h1>
|
||||
<p>{{_.sectionHowToPayDescription}}</p>
|
||||
<ul>
|
||||
<li>{{{_.sectionHowToPayOption1}}}</li>
|
||||
<li>{{{_.sectionHowToPayOption2}}}</li>
|
||||
</ul>
|
||||
|
||||
<h1>{{_.sectionToConsiderTitle}}</h1>
|
||||
<p>{{_.sectionToConsiderDescription}}</p>
|
||||
|
||||
<h3>{{_.sectionClaimsPolicyTitle}}</h3>
|
||||
<p>{{_.sectionClaimsPolicyDescription}}</p>
|
||||
<p>{{{_.doubtsText}}}</p>
|
||||
<p>
|
||||
{{{salesPersonName}}}
|
||||
{{{salesPersonPhone}}}
|
||||
{{{salesPersonEmail}}}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Mail body block end -->
|
||||
|
||||
<!-- Footer block -->
|
||||
{{$.footer}}
|
||||
<!-- Footer block end -->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"subject": "¡Le damos la bienvenida!",
|
||||
"title": "¡LE DAMOS LA BIENVENIDA!",
|
||||
"dear": "Estimado cliente,",
|
||||
"bodyDescription": "Sus datos para poder comprar en la web de verdnatura (<a href=\"https://www.verdnatura.es\" title=\"Visitar Verdnatura\" target=\"_blank\">https://www.verdnatura.es</a>) o en nuestras aplicaciones para <a href=\"https://goo.gl/3hC2mG\" title=\"App Store\" target=\"_blank\">iOS</a> y <a href=\"https://goo.gl/8obvLc\" title=\"Google Play\" target=\"_blank\">Android</a> (<a href=\"https://www.youtube.com/watch?v=gGfEtFm8qkw\" target=\"_blank\"><strong>Ver tutorial de uso</strong></a>), son:",
|
||||
"clientNumber": "Identificador de cliente:",
|
||||
"user": "Usuario:",
|
||||
"password": "Contraseña:",
|
||||
"passwordResetText": "(<a href=\"https://verdnatura.es\">Haga clic en \"¿Has olvidado tu contraseña?\"</a>)",
|
||||
"sectionHowToBuyTitle": "Cómo hacer un pedido",
|
||||
"sectionHowToBuyDescription": "Para realizar un pedido en nuestra web, debe configurarlo indicando:",
|
||||
"sectionHowToBuyRequeriment1": "Si quiere recibir el pedido (por agencia o por nuestro propio reparto) o si lo prefiere recoger en alguno de nuestros almacenes.",
|
||||
"sectionHowToBuyRequeriment2": "La fecha en la que quiera recibir el pedido (se preparará el día anterior).",
|
||||
"sectionHowToBuyRequeriment3": "La dirección de entrega o el almacén donde quiera recoger el pedido.",
|
||||
"sectionHowToBuyStock": "En nuestra web y aplicaciones puedes visualizar el stock disponible de flor cortada, verdes, plantas, complementos y artificial. Tenga en cuenta que dicho stock puede variar en función de la fecha seleccionada al configurar el pedido. Es importante CONFIRMAR los pedidos para que la mercancía quede reservada.",
|
||||
"sectionHowToBuyDelivery": "El reparto se realiza de lunes a sábado según la zona en la que se encuentre. Por regla general, los pedidos que se entregan por agencia, deben estar confirmados y pagados antes de las 17h del día en que se preparan (el día anterior a recibirlos), aunque esto puede variar si el pedido se envía a través de nuestro reparto y según la zona.",
|
||||
"sectionHowToPayTitle": "Cómo pagar",
|
||||
"sectionHowToPayDescription": "Las formas de pago admitidas en Verdnatura son:",
|
||||
"sectionHowToPayOption1": "Con <strong>tarjeta</strong> a través de nuestra plataforma web (al confirmar el pedido).",
|
||||
"sectionHowToPayOption2": "Mediante <strong>giro bancario mensual</strong>, modalidad que hay que solicitar y tramitar.",
|
||||
"sectionToConsiderTitle": "Cosas a tener en cuenta",
|
||||
"sectionToConsiderDescription": "Verdnatura vende EXCLUSIVAMENTE a profesionales, por lo que debe remitirnos el Modelo 036 ó 037, para comprobar que está dado/a de alta en el epígrafe correspondiente al comercio de flores.",
|
||||
"sectionClaimsPolicyTitle": "POLÍTICA DE RECLAMACIONES",
|
||||
"sectionClaimsPolicyDescription": "Verdnatura aceptará las reclamaciones que se realicen dentro de los dos días naturales siguientes a la recepción del pedido (incluyendo el mismo día de la recepción). Pasado este plazo no se aceptará ninguna reclamación.",
|
||||
"doubtsText": "Cualquier duda que le surja, no dude en consultarla, <strong>¡estamos para atenderle!</strong>",
|
||||
"salesPersonNameText": "Soy tu comercial y mi nombre es",
|
||||
"salesPersonPhoneText": "Teléfono y whatsapp",
|
||||
"salesPersonEmailText": "Dirección de e-mail"
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 205 B |
|
@ -1,4 +0,0 @@
|
|||
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11.5 9C10.12 9 9 10.12 9 11.5s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5S12.88 9 11.5 9zM20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-3.21 14.21l-2.91-2.91c-.69.44-1.51.7-2.39.7C9.01 16 7 13.99 7 11.5S9.01 7 11.5 7 16 9.01 16 11.5c0 .88-.26 1.69-.7 2.39l2.91 2.9-1.42 1.42z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 461 B |
|
@ -1,21 +0,0 @@
|
|||
var path = require('path');
|
||||
var database = require(path.join(__dirname, '../../database.js'));
|
||||
var format = require(path.join(__dirname, '../../util/format.js'));
|
||||
|
||||
module.exports = class Footer {
|
||||
getData(params, cb) {
|
||||
let query = `SELECT
|
||||
socialName,
|
||||
LOWER(ct.code) countryCode
|
||||
FROM client c
|
||||
JOIN country ct ON ct.id = c.countryFk
|
||||
WHERE c.id = ?`;
|
||||
database.pool.query(query, [params.clientId], (error, result) => {
|
||||
if (error || result.length == 0)
|
||||
return cb(new Error('No template data found'));
|
||||
|
||||
Object.assign(this, result[0]);
|
||||
cb();
|
||||
});
|
||||
}
|
||||
};
|
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.4 KiB |
|
@ -1,42 +0,0 @@
|
|||
<!-- Action button block -->
|
||||
<div class="buttons">
|
||||
<a href="https://www.verdnatura.es" target="_blank"><div class="btn">
|
||||
<span class="text">{{_.actionButton}}</span>
|
||||
<span class="icon"><img src="cid:footer/action.png"/></span>
|
||||
</div></a><a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank"><div class="btn">
|
||||
<span class="text">{{_.infoButton}}</span>
|
||||
<span class="icon"><img src="cid:footer/info.png"/></span>
|
||||
</div></a>
|
||||
</div>
|
||||
<!-- Action button block -->
|
||||
|
||||
<!-- Networks block -->
|
||||
<div class="footer">
|
||||
<a href="https://www.facebook.com/Verdnatura" target="_blank">
|
||||
<img src="cid:footer/facebook.png" alt="Facebook"/>
|
||||
</a>
|
||||
<a href="https://www.twitter.com/Verdnatura" target="_blank">
|
||||
<img src="cid:footer/twitter.png" alt="Twitter"/>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/Verdnatura" target="_blank">
|
||||
<img src="cid:footer/youtube.png" alt="Youtube"/>
|
||||
</a>
|
||||
<a href="https://www.pinterest.com/Verdnatura" target="_blank">
|
||||
<img src="cid:footer/pinterest.png" alt="Pinterest"/>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/Verdnatura" target="_blank">
|
||||
<img src="cid:footer/instagram.png" alt="Instagram"/>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/verdnatura" target="_blank">
|
||||
<img src="cid:footer/linkedin.png" alt="Linkedin"/>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Networks block end -->
|
||||
|
||||
<!-- Privacy block -->
|
||||
<div class="privacy">
|
||||
<p>{{_.fiscalAddress}}</p>
|
||||
<p>{{_.privacy}}</p>
|
||||
<p>{{_.privacyLaw}}</p>
|
||||
</div>
|
||||
<!-- Privacy block end -->
|