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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,12 +20,3 @@ services:
|
|||
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
|
|
@ -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>
|
||||
<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 {
|
||||
.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: '@?'
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
li ul {
|
||||
padding: 0 1.8em;
|
||||
}
|
||||
|
||||
& > ul > li {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,3 +25,16 @@ Value should have a length between %s and %s: El valor debe tener una longitud d
|
|||
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
|
||||
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];
|
||||
};
|
||||
};
|
|
@ -11,13 +11,29 @@
|
|||
"Zone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneCalendar": {
|
||||
"ZoneGeo": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZoneGeo": {
|
||||
"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 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-vertical>
|
||||
</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-horizontal>
|
||||
<vn-vertical vn-one>
|
||||
<vn-card pad-large>
|
||||
<vn-title>Delivery days</vn-title>
|
||||
<vn-title>Locations</vn-title>
|
||||
<vn-searchbar
|
||||
panel="vn-calendar-search-panel"
|
||||
model="model"
|
||||
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||
on-search="$ctrl.onSearch()"
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
<vn-treeview model="model"></vn-treeview>
|
||||
<vn-treeview vn-id="treeview" model="model"
|
||||
on-selection="$ctrl.onSelection(item)">
|
||||
</vn-treeview>
|
||||
</vn-card>
|
||||
</vn-vertical>
|
||||
</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-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.zone.name}}">
|
||||
value="{{::$ctrl.summary.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Warehouse"
|
||||
value="{{$ctrl.zone.warehouse.name}}">
|
||||
value="{{::$ctrl.summary.warehouse.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Agency"
|
||||
value="{{$ctrl.zone.agencyMode.name}}">
|
||||
value="{{::$ctrl.summary.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.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.zone.price | currency: '€': 2}}">
|
||||
value="{{::$ctrl.summary.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Bonus"
|
||||
value="{{$ctrl.zone.price | currency: '€': 2}}">
|
||||
value="{{::$ctrl.summary.price | currency: '€': 2}}">
|
||||
</vn-label-value>
|
||||
</vn-vertical>
|
||||
</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 ;
|
||||
|
|
@ -352,20 +352,21 @@ INSERT INTO `vn`.`invoiceOut`(`id`,`ref`, `serial`, `amount`, `issued`,`clientFk
|
|||
(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)),
|
||||
(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() );
|
||||
(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
|
||||
|
@ -552,7 +554,9 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
|
|||
(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());
|
||||
(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
|
||||
|
@ -1065,3 +1080,10 @@ INSERT INTO `vn`.`ticketService`(`id`, `description`, `quantity`, `price`, `taxC
|
|||
(1, 'delivery charge', 1, 2.00, 1, 1),
|
||||
(2, 'training course', 1, 10.00, 1, 2),
|
||||
(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 -->
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"actionButton": "Visita nuestra Web",
|
||||
"infoButton": "Ayúdanos a mejorar",
|
||||
"fiscalAddress": "VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla · www.verdnatura.es · clientes@verdnatura.es",
|
||||
"privacy": "- AVISO - Este mensaje es privado y confidencial, y debe ser utilizado exclusivamente por la persona destinataria del mismo. Si usted ha recibido este mensaje por error, le rogamos lo comunique al remitente y borre dicho mensaje y cualquier documento adjunto que pudiera contener. Verdnatura Levante SL no renuncia a la confidencialidad ni a ningún privilegio por causa de transmisión errónea o mal funcionamiento. Igualmente no se hace responsable de los cambios, alteraciones, errores u omisiones que pudieran hacerse al mensaje una vez enviado.",
|
||||
"privacyLaw": "En cumplimiento de lo dispuesto en la Ley Orgánica 15/1999, de Protección de Datos de Carácter Personal, le comunicamos que los datos personales que facilite se incluirán en ficheros automatizados de VERDNATURA LEVANTE S.L., pudiendo en todo momento ejercitar los derechos de acceso, rectificación, cancelación y oposición, comunicándolo por escrito al domicilio social de la entidad. La finalidad del fichero es la gestión administrativa, contabilidad, y facturación."
|
||||
}
|