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

This commit is contained in:
Carlos Jimenez 2018-01-02 08:06:47 +01:00
commit 8c6131322f
169 changed files with 2236 additions and 633 deletions

View File

@ -1,5 +1,5 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss';
export default class Controller { export default class Controller {
constructor() { constructor() {
this.client = null; this.client = null;

View File

@ -1,12 +0,0 @@
vn-main-block {
display:block;
max-width: 1920px;
width:100%;
margin: 0 auto;
.left-block {
min-width: 18em;
padding-left: 1em;
padding-bottom: 1em;
}
}

View File

@ -12,7 +12,7 @@ export default class App {
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
} }
show(message) { show(message) {
if (this.snackbar) this.snackbar.show({message: message}); if (this.snackbar) this.snackbar.show({message: message, timeout: 400});
} }
showMessage(message) { showMessage(message) {
this.show(message); this.show(message);

View File

@ -0,0 +1,9 @@
import {module} from '../module';
const isFullEmpty = item => {
return (!item && item !== 0) || (typeof item === 'object' && !Object.keys(item).length);
};
export default isFullEmpty;
export const NAME = 'isFullEmpty';
module.value(NAME, isFullEmpty);

View File

@ -7,13 +7,18 @@ vn-textfield {
width: auto; width: auto;
top: 0px; top: 0px;
right: -6px; right: -6px;
margin: 22px 0px; margin: 21px 0px;
background: transparent; background: white;
opacity: 1;
z-index: 9999; z-index: 9999;
color: #aaa;
} }
.material-icons { .material-icons {
font-size: 18px; font-size: 18px;
float: right; float: right;
margin-right: 5px; margin-right: 5px;
} }
.material-icons:hover {
color: rgba(0,0,0, .87);
}
} }

View File

@ -3,6 +3,7 @@ import Component from '../lib/component';
import getModifiedData from '../lib/modified'; import getModifiedData from '../lib/modified';
import copyObject from '../lib/copy'; import copyObject from '../lib/copy';
import isEqual from '../lib/equals'; import isEqual from '../lib/equals';
import isFullEmpty from '../lib/fullEmpty';
/** /**
* Component that checks for changes on a specific model property and * Component that checks for changes on a specific model property and
@ -95,7 +96,7 @@ export default class Watcher extends Component {
let changedData = getModifiedData(this.data, this.orgData); let changedData = getModifiedData(this.data, this.orgData);
if (this.save) { if (this.save) {
this.save.model = changedData; this.save.model = this.copyInNewObject(changedData);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.save.accept().then( this.save.accept().then(
json => this.writeData({data: json}, resolve), json => this.writeData({data: json}, resolve),
@ -154,7 +155,7 @@ export default class Watcher extends Component {
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
Object.keys(data).forEach( Object.keys(data).forEach(
val => { val => {
if (data[val] !== "" && data[val] !== undefined && data[val] !== null) { if (!isFullEmpty(data[val])) {
if (typeof data[val] === 'object') { if (typeof data[val] === 'object') {
newCopy[val] = this.copyInNewObject(data[val]); newCopy[val] = this.copyInNewObject(data[val]);
} else { } else {

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

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

49
client/item/routes.json Normal file
View File

@ -0,0 +1,49 @@
{
"module": "item",
"name": "Items",
"icon": "/static/images/icon_item.png",
"routes": [
{
"url": "/item",
"state": "item",
"abstract": true,
"component": "ui-view"
},
{
"url": "/list",
"state": "item.index",
"component": "vn-item-list"
}, {
"url": "/create",
"state": "item.create",
"component": "vn-item-create"
}, {
"url": "/:id",
"state": "item.card",
"abstract": true,
"component": "vn-item-card"
}, {
"url" : "/data",
"state": "item.card.data",
"component": "vn-item-data",
"params": {
"item": "$ctrl.item"
},
"menu": {
"description": "Basic data",
"icon": "folder"
}
}, {
"url" : "/image",
"state": "item.card.image",
"component": "vn-item-image",
"params": {
"item": "$ctrl.item"
},
"menu": {
"description": "Images",
"icon": "image"
}
}
]
}

View File

@ -0,0 +1,16 @@
<vn-main-block>
<vn-horizontal>
<vn-auto class="left-block">
<vn-card margin-medium-v>
<a class="item-product-link pad-large text-center" ui-sref="item.index">
<b>{{$ctrl.item.name}}</b>
<img ng-src="http://verdnatura.es/vn-image-data/catalog/200x200/{{$ctrl.item.image}}" />
</a>
</vn-card>
<vn-left-menu></vn-left-menu>
</vn-auto>
<vn-one>
<vn-vertical margin-medium ui-view></vn-vertical>
</vn-one>
</vn-horizontal>
</vn-main-block>

View File

@ -0,0 +1,32 @@
import ngModule from '../module';
class ItemCard {
constructor($http, $state) {
this.$http = $http;
this.$state = $state;
this.item = {};
}
$onInit() {
let filter = {
include: [
{relation: "itemType"},
{relation: "origin"},
{relation: "ink"},
{relation: "producer"},
{relation: "intrastat"}
]
};
this.$http.get(`/item/api/Items/${this.$state.params.id}?filter=${JSON.stringify(filter)}`).then(
res => {
this.item = res.data;
}
);
}
}
ItemCard.$inject = ['$http', '$state'];
ngModule.component('vnItemCard', {
template: require('./item-card.html'),
controller: ItemCard
});

View File

@ -0,0 +1,66 @@
<mg-ajax path="/item/api/Items" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.item"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;">
<vn-card>
<vn-vertical pad-large>
<vn-title>Create item</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Size" field="$ctrl.item.size" vn-focus></vn-textfield>
<vn-textfield vn-five label="Category" field="$ctrl.item.category"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Stems" field="$ctrl.item.stems"></vn-textfield>
<vn-textfield vn-five label="Description" field="$ctrl.item.description"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/ItemTypes"
label="Type"
show-field="name"
value-field="id"
field="$ctrl.item.typeFk"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Inks"
label="Ink"
show-field="name"
value-field="id"
field="$ctrl.item.inkFk"
>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/Origins"
label="Origin"
show-field="name"
value-field="id"
field="$ctrl.item.originFk"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Producers"
label="Producer"
show-field="name"
value-field="id"
field="$ctrl.item.producerFk"
>
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Create"></vn-submit>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,18 @@
import ngModule from '../module';
class ItemCreate {
constructor() {
this.item = {};
}
onSubmit() {
this.$.watcher.submit().then(
json => this.$state.go('item.card.basic', {id: json.data.id})
);
}
}
ngModule.component('vnItemCreate', {
template: require('./item-create.html'),
controller: ItemCreate
});

View File

@ -0,0 +1,84 @@
<mg-ajax
path="/item/api/Items/{{patch.params.id}}"
options="vnPatch"
override="{filter: {include: [{relation: 'itemType'}, {relation: 'origin'}, {relation: 'ink'}, {relation: 'producer'}]}}"
>
</mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.item"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" ng-cloak>
<vn-card>
<vn-vertical pad-large>
<vn-title>Basic data</vn-title>
<vn-horizontal>
<vn-textfield vn-three label="Name" field="$ctrl.item.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Size" field="$ctrl.item.size"></vn-textfield>
<vn-textfield vn-one label="Stems" field="$ctrl.item.stems"></vn-textfield>
<vn-textfield vn-one label="Category" field="$ctrl.item.category"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Description" field="$ctrl.item.description"></vn-textfield>
<vn-autocomplete vn-one
url="/item/api/Intrastats"
label="Intrastat"
show-field="description"
value-field="id"
field="$ctrl.item.intrastatFk"
initial-data="$ctrl.item.intrastat"
order = "description DESC"
filter-search="{where: {description: {regexp: 'search'}}}"
>
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/ItemTypes"
label="Type"
show-field="name"
value-field="id"
field="$ctrl.item.typeFk"
initial-data="$ctrl.item.itemType"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Inks"
label="Ink"
show-field="name"
value-field="id"
field="$ctrl.item.inkFk"
initial-data="$ctrl.item.ink"
>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/Origins"
label="Origin"
show-field="name"
value-field="id"
field="$ctrl.item.originFk"
initial-data="$ctrl.item.origin"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Producers"
label="Producer"
show-field="name"
value-field="id"
field="$ctrl.item.producer"
>
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,8 @@
import ngModule from '../module';
ngModule.component('vnItemData', {
template: require('./item-data.html'),
bindings: {
item: '<'
}
});

View File

@ -0,0 +1,58 @@
<div pad-large style="min-width: 30em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield vn-one label="Id" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-three label="Description" model="$ctrl.filter.description"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Category" type="number" model="$ctrl.filter.category"></vn-textfield>
<vn-textfield vn-one label="Size" type="number" model="$ctrl.filter.itemSize"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/ItemTypes"
label="Type"
show-field="name"
value-field="id"
field="$ctrl.filter.typeFk"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Inks"
label="Ink"
show-field="name"
value-field="id"
field="$ctrl.filter.inkFk"
>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/Origins"
label="Origin"
show-field="name"
value-field="id"
field="$ctrl.filter.originFk"
>
</vn-autocomplete>
<vn-autocomplete vn-one
url="/item/api/Producers"
label="Producer"
show-field="name"
value-field="id"
field="$ctrl.filter.producerFk"
>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,16 @@
import ngModule from '../module';
class ItemFilterPanel {
constructor() {
this.onSubmit = () => {};
}
onSearch() {
this.onSubmit(this.filter);
}
}
ngModule.component('vnItemFilterPanel', {
template: require('./filter-panel.html'),
controller: ItemFilterPanel
});

View File

@ -0,0 +1,5 @@
{
"Ink": "Tinta",
"Origin": "Origen",
"Producer": "Productor"
}

7
client/item/src/item.js Normal file
View File

@ -0,0 +1,7 @@
export * from './module';
import './list/list';
import './filter-panel/filter-panel';
import './create/item-create';
import './card/item-card';
import './data/item-data';

View File

@ -0,0 +1,14 @@
<vn-horizontal pad-medium border-solid-bottom>
<vn-auto margin-medium-right ng-if="$ctrl.item.image">
<img ng-src="http://verdnatura.es/vn-image-data/catalog/200x200/{{$ctrl.item.image}}" />
</vn-auto>
<vn-one>
<div><span translate>Id</span>: <b>{{$ctrl.item.id}}</b></div>
<div><span translate>Name</span>: <b>{{$ctrl.item.name}}</b></div>
<div><span translate>Description</span>: <b>{{$ctrl.item.description}}</b></div>
<div><span translate>Size</span>: <b>{{$ctrl.item.size}}</b></div>
<div><span translate>Type</span>: <b>{{$ctrl.item.itemType.name}}</b></div>
</vn-one>
</vn-horizontal>

View File

@ -0,0 +1,8 @@
import ngModule from '../module';
ngModule.component('vnItemProduct', {
template: require('./item-product.html'),
bindings: {
item: '<'
}
});

View File

@ -0,0 +1,26 @@
<mg-ajax path="/item/api/Items/filter" options="mgIndex"></mg-ajax>
<div margin-medium>
<div style="max-width: 40em; margin: 0 auto;">
<vn-card>
<vn-horizontal pad-medium>
<vn-searchbar vn-one
index="index"
on-search="$ctrl.search(index)"
advanced="true"
popover="vn-item-filter-panel"
ignore-keys = "['page', 'size', 'search']"
>
</vn-searchbar>
</vn-horizontal>
</vn-card>
<vn-card margin-medium-top>
<a class="item-product-link" ng-repeat="item in index.model.instances" ui-sref="item.card.data({ id: {{item.id}} })" >
<vn-item-product title="View item" item="item"></vn-item-product>
</a>
</vn-card>
<vn-paging index="index" total="index.model.count"></vn-paging>
</div>
<a ui-sref="item.create" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
</div>

View File

@ -0,0 +1,17 @@
import ngModule from '../module';
import './item-product';
import './style.css';
class ItemList {
constructor() {
this.model = {};
}
search(index) {
index.accept();
}
}
ngModule.component('vnItemList', {
template: require('./list.html'),
controller: ItemList
});

View File

@ -0,0 +1,20 @@
vn-item-product {
display: block;
}
a.item-product-link {
display: block;
text-decoration: none;
color: inherit;
}
a.item-product-link:hover {
color: white;
background-color: #424242;
}
vn-item-product img {
max-width: 150px;
}
.vn-item-product-name {
font-family: vn-font-bold;
}

View File

@ -0,0 +1,8 @@
{
"Items": "Artículos",
"Item": "Artículo",
"Category": "Categoría",
"Description": "Descripción",
"Size": "Tamaño",
"Type": "Tipo"
}

View File

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

View File

@ -5,5 +5,6 @@
"client": [], "client": [],
"production": [], "production": [],
"route": [], "route": [],
"locator": [] "locator": [],
"item": []
} }

View File

@ -13,6 +13,7 @@
"Production" : "Producción", "Production" : "Producción",
"Modules access" : "Acceso a módulos", "Modules access" : "Acceso a módulos",
"Locator": "Localizador", "Locator": "Localizador",
"Items": "Artículos",
"name": "Nombre", "name": "Nombre",
"credit": "Crédito", "credit": "Crédito",
"phone": "Teléfono", "phone": "Teléfono",

View File

@ -43,3 +43,14 @@ export const locator = () => {
}; };
core.splitingRegister.register('locator', locator); core.splitingRegister.register('locator', locator);
export const item = () => {
return new Promise(resolve => {
require.ensure([], () => {
require('item');
resolve('item');
}, 'item');
});
};
core.splitingRegister.register('item', item);

View File

@ -99,3 +99,17 @@ html [pointer], .pointer{
html [noDrop], .noDrop{ html [noDrop], .noDrop{
cursor: no-drop; cursor: no-drop;
} }
vn-main-block {
display:block;
max-width: 1920px;
width:100%;
margin: 0 auto;
.left-block {
max-width: 20em;
min-width: 18em;
padding-left: 1em;
padding-bottom: 1em;
}
}

View File

@ -87,7 +87,19 @@ services:
expose: expose:
- "3006" - "3006"
ports: ports:
- "3006:3006" - "3006:3006"
item:
environment:
- NODE_ENV=${NODE_ENV}
container_name: "${BRANCH_NAME}-item"
image: "item:${TAG}"
build:
context: ./services
dockerfile: /item/Dockerfile
expose:
- "3007"
ports:
- "3007:3007"
nginx: nginx:
container_name: "${BRANCH_NAME}-nginx" container_name: "${BRANCH_NAME}-nginx"
image: "nginx:${TAG}" image: "nginx:${TAG}"

15
services/item/Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM node:6.9.1
COPY item /app
COPY loopback /loopback
WORKDIR /app
RUN npm install
RUN npm -g install pm2
CMD ["pm2-docker", "./server/server.js"]
EXPOSE 3007

View File

@ -0,0 +1,31 @@
module.exports = Self => {
Self.installMethod('filter', filterParams);
function filterParams(params) {
let filter = {
where: {},
skip: (params.page - 1) * params.size,
limit: params.size,
order: params.order || 'relevancy DESC',
include: {
relation: "itemType",
scope: {
fields: ["id", "name"]
}
}
};
delete params.page;
delete params.size;
delete params.order;
if (params.itemSize) {
filter.where.size = params.itemSize;
delete params.itemSize;
}
Object.assign(filter.where, params);
return filter;
}
};

View File

@ -0,0 +1,23 @@
{
"name": "Ink",
"base": "VnModel",
"options": {
"mysql": {
"table": "ink",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
},
"showOrder": {
"type": "number"
}
}
}

View File

@ -0,0 +1,32 @@
{
"name": "Intrastat",
"base": "VnModel",
"options": {
"mysql": {
"table": "intrastat",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"description": {
"type": "String"
}
},
"relations": {
"taxGroup": {
"type": "belongsTo",
"model": "TaxGroup",
"foreignKey": "taxGroupFk"
},
"taxCode": {
"type": "belongsTo",
"model": "TaxCode",
"foreignKey": "taxCodeFk"
}
}
}

View File

@ -0,0 +1,23 @@
{
"name": "ItemType",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemType",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
},
"life": {
"type": "Number"
}
}
}

View File

@ -0,0 +1,3 @@
module.exports = function(Self) {
require('../methods/item/filter.js')(Self);
};

View File

@ -0,0 +1,74 @@
{
"name": "Item",
"base": "VnModel",
"options": {
"mysql": {
"table": "item",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
},
"size": {
"type": "Number"
},
"category": {
"type": "String"
},
"stems": {
"type": "Number"
},
"description": {
"type": "String"
},
"isOnOffer": {
"type": "Boolean"
},
"isBargain": {
"type": "Boolean"
},
"comment": {
"type": "String"
},
"relevancy": {
"type": "Number"
},
"image": {
"type": "String"
}
},
"relations": {
"itemType": {
"type": "belongsTo",
"model": "ItemType",
"foreignKey": "typeFk"
},
"ink": {
"type": "belongsTo",
"model": "Ink",
"foreignKey": "inkFk"
},
"origin": {
"type": "belongsTo",
"model": "Origin",
"foreignKey": "originFk"
},
"producer": {
"type": "belongsTo",
"model": "Producer",
"foreignKey": "producerFk"
},
"intrastat": {
"type": "belongsTo",
"model": "Intrastat",
"foreignKey": "intrastatFk"
}
}
}

View File

@ -0,0 +1,23 @@
{
"name": "Origin",
"base": "VnModel",
"options": {
"mysql": {
"table": "origin",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "String"
},
"name": {
"type": "String"
}
}
}

View File

@ -0,0 +1,20 @@
{
"name": "Producer",
"base": "VnModel",
"options": {
"mysql": {
"table": "producer",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
}
}
}

View File

@ -0,0 +1,47 @@
{
"name": "TaxCode",
"base": "VnModel",
"options": {
"mysql": {
"table": "taxCode",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"dated": {
"type": "date"
},
"code": {
"type": "String"
},
"rate": {
"type": "number"
},
"equalizationTax": {
"type": "number"
},
"type": {
"type": "String"
},
"isActive": {
"type": "Boolean"
}
},
"relations": {
"taxType": {
"type": "belongsTo",
"model": "TaxType",
"foreignKey": "taxTypeFk"
},
"link": {
"type": "belongsTo",
"model": "Link",
"foreignKey": "linkFk"
}
}
}

View File

@ -0,0 +1,23 @@
{
"name": "TaxGroup",
"base": "VnModel",
"options": {
"mysql": {
"table": "taxGroup",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"description": {
"type": "String"
},
"code": {
"type": "String"
}
}
}

View File

@ -0,0 +1,36 @@
{
"name": "TaxType",
"base": "VnModel",
"options": {
"mysql": {
"table": "taxType",
"database": "vn"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"nickname": {
"type": "String"
},
"serial": {
"type": "String"
},
"TIPOOPE": {
"type": "String"
},
"description": {
"type": "String"
}
},
"relations": {
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
}
}
}

View File

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

View File

@ -0,0 +1,3 @@
{
"port": 3007
}

View File

@ -0,0 +1,57 @@
{
"user": {
"dataSource": "salix"
},
"AccessToken": {
"dataSource": "salix",
"relations": {
"user": {
"type": "belongsTo",
"model": "user",
"foreignKey": "userId"
}
}
},
"ACL": {
"dataSource": "salix"
},
"RoleMapping": {
"dataSource": "salix"
},
"Role": {
"dataSource": "salix"
},
"Account": {
"dataSource": "salix"
},
"Item": {
"dataSource": "vn"
},
"ItemType": {
"dataSource": "vn"
},
"Ink": {
"dataSource": "vn"
},
"Origin": {
"dataSource": "vn"
},
"Producer": {
"dataSource": "vn"
},
"Intrastat": {
"dataSource": "vn"
},
"TaxGroup": {
"dataSource": "vn"
},
"TaxCode": {
"dataSource": "vn"
},
"TaxType": {
"dataSource": "vn"
},
"Country": {
"dataSource": "salix"
}
}

View File

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

View File

@ -1,15 +1,20 @@
var path = require('path'); var path = require('path');
var fs = require('fs'); let defaultFile = 'datasources.json';
var config = {};
let devConfigPath = path.join(__dirname, '/config/datasources.development.json'); function getFile(fileName) {
let configPath = path.join(__dirname, '/config/datasources.json'); return require(path.join(__dirname, `/config/${fileName}`));
}
try { try {
config = Object.assign(require(configPath), require(devConfigPath)); let envFile = 'datasources.development.json';
if (process.env.NODE_ENV === 'test')
envFile = 'datasources.test.json';
config = getFile(envFile);
} catch (e) { } catch (e) {
if (e.code == 'MODULE_NOT_FOUND') if (e.code == 'MODULE_NOT_FOUND')
config = require(configPath); config = getFile(defaultFile);
} }
config.proxy = require('../../nginx/config.json'); config.proxy = require('../../nginx/config.json');

View File

@ -4,14 +4,14 @@
"debug": false, "debug": false,
"defaultLanguage": "es", "defaultLanguage": "es",
"senderMail": "noreply@localhost", "senderMail": "noreply@localhost",
"senderName": "" "senderName": "VerdNatura"
}, },
"mysql": { "mysql": {
"host": "localhost", "host": "localhost",
"port": 3306, "port": 3306,
"user": "reports", "user": "root",
"password": "", "password": "",
"database": "" "database": "vn"
}, },
"smtp": { "smtp": {
"host": "localhost", "host": "localhost",

View File

@ -12,15 +12,17 @@ module.exports = {
* Load mail config. * Load mail config.
*/ */
init: function() { init: function() {
this.transporter = nodemailer.createTransport(config.smtp); this.transporter = nodemailer.createTransport(config.smtp);
this.transporter.verify(function(error, success) { this.transporter.verify(function(error, success) {
if (error) { if (error) {
throw new Error(error); console.error(error);
} else if (config.app.debug) { } else if (config.app.debug) {
console.log('SMTP connection stablished'); console.log('SMTP connection stablished');
} }
}); });
}, },
/** /**

View File

@ -4,10 +4,15 @@ var config = require('../config.js');
var mail = require('../mail.js'); var mail = require('../mail.js');
var template = require('../template.js'); var template = require('../template.js');
var httpRequest = require('request'); var httpRequest = require('request');
var auth = require('../auth.js');
// Auth middleware
var requestToken = function(request, response, next) {
auth.init(request, response, next);
};
// Printer setup // Printer setup
router.post('/printer-setup/:clientId', function(request, response) { router.get('/printer-setup/:clientId', requestToken, function(request, response) {
mail.sendWithTemplate('printer-setup', {clientId: request.params.clientId}, error => { mail.sendWithTemplate('printer-setup', {clientId: request.params.clientId}, error => {
if (error) if (error)
return response.status(400).json({message: error.message}); return response.status(400).json({message: error.message});
@ -17,7 +22,7 @@ router.post('/printer-setup/:clientId', function(request, response) {
}); });
// Printer setup preview // Printer setup preview
router.get('/printer-setup/:clientId', function(request, response) { router.get('/printer-setup/:clientId/preview', requestToken, function(request, response) {
template.get('printer-setup', {clientId: request.params.clientId, isPreview: true}, (error, result) => { template.get('printer-setup', {clientId: request.params.clientId, isPreview: true}, (error, result) => {
if (error) if (error)
return response.status(400).json({message: error.message}); return response.status(400).json({message: error.message});
@ -27,7 +32,7 @@ router.get('/printer-setup/:clientId', function(request, response) {
}); });
// Client welcome // Client welcome
router.post('/client-welcome/:clientId', function(request, response) { router.get('/client-welcome/:clientId', requestToken, function(request, response) {
mail.sendWithTemplate('client-welcome', {clientId: request.params.clientId}, error => { mail.sendWithTemplate('client-welcome', {clientId: request.params.clientId}, error => {
if (error) if (error)
return response.status(400).json({message: error.message}); return response.status(400).json({message: error.message});
@ -37,7 +42,7 @@ router.post('/client-welcome/:clientId', function(request, response) {
}); });
// Client welcome preview // Client welcome preview
router.get('/client-welcome/:clientId', function(request, response) { router.get('/client-welcome/:clientId/preview', requestToken, function(request, response) {
template.get('client-welcome', {clientId: request.params.clientId, isPreview: true}, (error, result) => { template.get('client-welcome', {clientId: request.params.clientId, isPreview: true}, (error, result) => {
if (error) if (error)
return response.status(400).json({message: error.message}); return response.status(400).json({message: error.message});
@ -47,8 +52,13 @@ router.get('/client-welcome/:clientId', function(request, response) {
}); });
// Client SEPA CORE // Client SEPA CORE
router.post('/sepa-core/:clientId', function(request, response) { router.get('/sepa-core/:companyId/:clientId', requestToken, function(request, response) {
let path = `${request.proxyHost}/print/manuscript/sepa-core/${request.params.clientId}`; let params = {
clientId: request.params.clientId,
companyId: request.params.companyId
};
let path = `${request.proxyHost}/print/manuscript/sepa-core/${params.companyId}/${params.clientId}`;
let options = { let options = {
url: path, url: path,
method: 'GET', method: 'GET',
@ -59,25 +69,75 @@ router.post('/sepa-core/:clientId', function(request, response) {
let httpStream = httpRequest(options, function(error, httpResponse, body) { let httpStream = httpRequest(options, function(error, httpResponse, body) {
if (error || httpResponse.statusCode != 200) if (error || httpResponse.statusCode != 200)
return response.status(400).json({message: error.message}); return response.status(400).json({message: error});
}); });
if (httpStream) if (httpStream)
mail.sendWithTemplate('sepa-core', { params.attachments = [{filename: 'sepa-core.pdf', content: httpStream}];
clientId: request.params.clientId,
attachments: [{filename: 'sepa-core.pdf', content: httpStream}] mail.sendWithTemplate('sepa-core', params, error => {
}, error => { if (error)
if (error) return response.status(400).json({message: error.message});
return response.status(400).json({message: error.message});
return response.json();
return response.json();
}); });
}); });
// Client SEPA CORE preview // Client SEPA CORE preview
router.get('/sepa-core/:clientId', function(request, response) { router.get('/sepa-core/:companyId/:clientId/preview', requestToken, function(request, response) {
template.get('sepa-core', { 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.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-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, clientId: request.params.clientId,
companyId: request.params.companyId,
token: request.user.token, token: request.user.token,
isPreview: true isPreview: true
}, (error, result) => { }, (error, result) => {
@ -88,6 +148,85 @@ router.get('/sepa-core/:clientId', function(request, response) {
}); });
}); });
// 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 // Single user notification
/* router.post('/:recipient/noticeUserSend', function(request, response) { /* router.post('/:recipient/noticeUserSend', function(request, response) {
var params = { var params = {
@ -146,34 +285,4 @@ router.get('/sepa-core/:clientId', function(request, response) {
mail.sendWithTemplate('notification-notice', params, result => { mail.sendWithTemplate('notification-notice', params, result => {
return response.json(result); return response.json(result);
}); });
}); */ }); */
// Payment method changes
router.post('/payment-update/:clientId', 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.post('/client-deactivate/:clientId', 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;

View File

@ -1,5 +1,7 @@
var express = require('express'); var express = require('express');
var router = new express.Router(); var router = new express.Router();
var fs = require('fs');
var path = require('path');
// Mailer default page // Mailer default page
router.get('/', function(request, response) { router.get('/', function(request, response) {
@ -9,4 +11,20 @@ router.get('/', function(request, response) {
// Notifications // Notifications
router.use('/notification', require('./route/notification.js')); 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() {
readStream.pipe(response);
});
});
});
module.exports = router; module.exports = router;

View File

@ -15,7 +15,7 @@ module.exports = {
get: function(template, params, cb) { get: function(template, params, cb) {
var templatePath = path.join(__dirname, 'template', `${template}`, `index.html`); var templatePath = path.join(__dirname, 'template', `${template}`, `index.html`);
var classPath = path.join(__dirname, 'template', `${template}`, `${template}.js`); var classPath = path.join(__dirname, 'template', `${template}`, `${template}.js`);
var stylePath = path.join(__dirname, 'template', `${template}`, 'static', 'css', 'style.css'); var stylePath = path.join(__dirname, 'template', `${template}`, 'style.css');
fs.stat(templatePath, (error, stat) => { fs.stat(templatePath, (error, stat) => {
if (error) if (error)
@ -65,6 +65,8 @@ module.exports = {
return cb(error); return cb(error);
instance._ = result.locale; instance._ = result.locale;
instance.isPreview = params.isPreview;
getDataCb(null, result); getDataCb(null, result);
}); });
}); });
@ -173,14 +175,17 @@ module.exports = {
// Template default attachments // Template default attachments
for (var i = 0; i < tplAttachments.length; i++) { for (var i = 0; i < tplAttachments.length; i++) {
let name = tplAttachments[i].replace('src="cid:', '').replace('"', ''); let src = tplAttachments[i].replace('src="cid:', '').replace('"', '').split('/');
let attachmentTpl = src[0];
let attachment = src[1];
if (isPreview) { if (isPreview) {
let attachmentPath = `/mailer/static/images/${name}`; let attachmentPath = `/mailer/static/${attachmentTpl}/${attachment}`;
body = body.replace(tplAttachments[i], `src="${attachmentPath}"`); body = body.replace(tplAttachments[i], `src="${attachmentPath}"`);
} else { } else {
let attachmentPath = path.join(__dirname, '../static', 'images', name); let attachmentPath = path.join(__dirname, 'template', `${attachmentTpl}`, 'image', attachment);
attachments.push({filename: name, path: attachmentPath, cid: name}); let attachmentName = attachmentTpl + '/' + attachment;
attachments.push({filename: attachmentName, path: attachmentPath, cid: attachmentName});
} }
} }

View File

@ -14,10 +14,11 @@ module.exports = class ClientWelcome {
c.email recipient c.email recipient
FROM client c FROM client c
JOIN account.user u ON u.id = c.id JOIN account.user u ON u.id = c.id
LEFT JOIN worker w ON w.id = c.workerFk LEFT JOIN worker w ON w.id = c.salesPersonFk
LEFT JOIN account.user wu ON wu.id = w.userFk LEFT JOIN account.user wu ON wu.id = w.userFk
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;
database.pool.query(query, [params.clientId], (error, result) => { database.pool.query(query, [params.clientId], (error, result) => {
if (error || result.length == 0) if (error || result.length == 0)
return cb(new Error('No template data found')); return cb(new Error('No template data found'));

View File

@ -22,8 +22,9 @@
<p>{{_.dear}}</p> <p>{{_.dear}}</p>
<p>{{{_.bodyDescription}}}</p> <p>{{{_.bodyDescription}}}</p>
<p> <p>
<div>{{_.clientNumber}} <strong>{{clientId}}</strong></div>
<div>{{_.user}} <strong>{{userName}}</strong></div> <div>{{_.user}} <strong>{{userName}}</strong></div>
<div>{{_.password}} <strong>********</strong> {{_.passwordResetText}}</div> <div>{{_.password}} <strong>********</strong> {{{_.passwordResetText}}}</div>
</p> </p>
<h1>{{_.sectionHowToBuyTitle}}</h1> <h1>{{_.sectionHowToBuyTitle}}</h1>

View File

@ -3,9 +3,10 @@
"title": "¡LE DAMOS LA BIENVENIDA!", "title": "¡LE DAMOS LA BIENVENIDA!",
"dear": "Estimado cliente,", "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:", "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:", "user": "Usuario:",
"password": "Contraseña:", "password": "Contraseña:",
"passwordResetText": "(Va a recibir un correo para establecer la contraseña)", "passwordResetText": "(<a href=\"https://verdnatura.es\">Haga clic en \"¿Has olvidado tu contraseña?\"</a>)",
"sectionHowToBuyTitle": "Cómo hacer un pedido", "sectionHowToBuyTitle": "Cómo hacer un pedido",
"sectionHowToBuyDescription": "Para realizar un pedido en nuestra web, debe configurarlo indicando:", "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.", "sectionHowToBuyRequeriment1": "Si quiere recibir el pedido (por agencia o por nuestro propio reparto) o si lo prefiere recoger en alguno de nuestros almacenes.",

View File

@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 205 B

View File

@ -0,0 +1,4 @@
<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>

After

Width:  |  Height:  |  Size: 461 B

View File

@ -5,7 +5,8 @@ var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class Footer { module.exports = class Footer {
getData(params, cb) { getData(params, cb) {
let query = `SELECT let query = `SELECT
socialName socialName,
LOWER(ct.code) countryCode
FROM client c FROM client c
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -2,10 +2,10 @@
<div class="buttons"> <div class="buttons">
<a href="https://www.verdnatura.es" target="_blank"><div class="btn"> <a href="https://www.verdnatura.es" target="_blank"><div class="btn">
<span class="text">{{_.actionButton}}</span> <span class="text">{{_.actionButton}}</span>
<span class="icon"><img src="cid:action.png"/></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"> </div></a><a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank"><div class="btn">
<span class="text">{{_.infoButton}}</span> <span class="text">{{_.infoButton}}</span>
<span class="icon"><img src="cid:info.png"/></span> <span class="icon"><img src="cid:footer/info.png"/></span>
</div></a> </div></a>
</div> </div>
<!-- Action button block --> <!-- Action button block -->
@ -13,22 +13,22 @@
<!-- Networks block --> <!-- Networks block -->
<div class="footer"> <div class="footer">
<a href="https://www.facebook.com/Verdnatura" target="_blank"> <a href="https://www.facebook.com/Verdnatura" target="_blank">
<img src="cid:facebook.png" alt="Facebook"/> <img src="cid:footer/facebook.png" alt="Facebook"/>
</a> </a>
<a href="https://www.twitter.com/Verdnatura" target="_blank"> <a href="https://www.twitter.com/Verdnatura" target="_blank">
<img src="cid:twitter.png" alt="Twitter"/> <img src="cid:footer/twitter.png" alt="Twitter"/>
</a> </a>
<a href="https://www.youtube.com/Verdnatura" target="_blank"> <a href="https://www.youtube.com/Verdnatura" target="_blank">
<img src="cid:youtube.png" alt="Youtube"/> <img src="cid:footer/youtube.png" alt="Youtube"/>
</a> </a>
<a href="https://www.pinterest.com/Verdnatura" target="_blank"> <a href="https://www.pinterest.com/Verdnatura" target="_blank">
<img src="cid:pinterest.png" alt="Pinterest"/> <img src="cid:footer/pinterest.png" alt="Pinterest"/>
</a> </a>
<a href="https://www.instagram.com/Verdnatura" target="_blank"> <a href="https://www.instagram.com/Verdnatura" target="_blank">
<img src="cid:instagram.png" alt="Instagram"/> <img src="cid:footer/instagram.png" alt="Instagram"/>
</a> </a>
<a href="https://www.linkedin.com/company/verdnatura" target="_blank"> <a href="https://www.linkedin.com/company/verdnatura" target="_blank">
<img src="cid:linkedin.png" alt="Linkedin"/> <img src="cid:footer/linkedin.png" alt="Linkedin"/>
</a> </a>
</div> </div>
<!-- Networks block end --> <!-- Networks block end -->

View File

@ -5,7 +5,8 @@ var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class Header { module.exports = class Header {
getData(params, cb) { getData(params, cb) {
let query = `SELECT let query = `SELECT
c.name AS clientName c.name AS clientName,
LOWER(ct.code) countryCode
FROM client c FROM client c
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,3 +1,3 @@
<div> <div>
<a href="https://www.verdnatura.es"/><img src="cid:header.png" alt="VerdNatura"/></a> <a href="https://www.verdnatura.es"/><img src="cid:header/logo.png" alt="VerdNatura"/></a>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,87 @@
<!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>{{_.termLimits}}</p>
<p>
{{_.payMethod}}
<ol>
<li>{{_.payMethodOption1}}</li>
<li>{{_.payMethodOption2}}</li>
</ol>
</p>
<p>
{{_.legalActions}}
<ol type="a">
<li>{{_.legalActionsOption1}}</li>
<li>{{_.legalActionsOption2}}</li>
<li>{{_.legalActionsOption3}}</li>
</ol>
</p>
<p>{{_.contact}}</p>
<p>{{_.waitingForNews}}</p>
<p>{{_.conclusion}}</p>
<p>
<div class="row">
<div class="text">{{bankName}}</div>
<div class="control">{{iban}}</div>
<div class="description">
<div class="line"><span>{{_.accountTransferData}}</span></div>
</div>
</div>
</p>
{{#isPreview}}
<a href="/print/manuscript/letter-debtor/{{companyId}}/{{clientId}}/preview?token={{token}}" target="_blank" title="Ver adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/preview.svg" alt="Ver adjunto"/>
</div>
<span>Ver adjunto</span>
</div>
</a>
<a href="/print/manuscript/letter-debtor/{{companyId}}/{{clientId}}?token={{token}}" target="_blank" title="Descargar adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/download.svg" alt="Descargar adjunto"/>
</div>
<span>Descargar PDF</span>
</div>
</a>
{{/isPreview}}
</div>
<!-- Mail body block end -->
<!-- Footer block -->
{{$.footer}}
<!-- Footer block end -->
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
var path = require('path');
var database = require(path.join(__dirname, '../../database.js'));
var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class LetterDebtorNd {
getData(params, cb) {
let query = `SELECT
sa.iban,
be.name AS bankName,
LOWER(ct.code) countryCode,
c.email recipient
FROM client c
JOIN company AS cny
JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk
JOIN bankEntity be ON be.id = sa.bankEntityFk
JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ? AND cny.id = ?`;
this.clientId = params.clientId;
this.companyId = params.companyId;
this.token = params.token;
database.pool.query(query, [params.clientId, params.companyId], (error, result) => {
if (error || result.length == 0)
return cb(new Error('No template data found'));
Object.assign(this, result[0]);
cb();
});
}
get previewAttachments() {
if (this.isPreview)
return `<a href="/print/manuscript/letter-debtor/${this.companyId}/${this.clientId}/preview?token=${this.token}" target="_blank" title="Ver extracto.pdf">` +
'<div class="attachment"><div class="attachment-icon"><img src="cid:attachment.png" alt="Descargar adjunto"/></div>' +
'<span>extracto.pdf</span></div></a>';
}
};

View File

@ -0,0 +1,18 @@
{
"subject": "Reiteración de aviso por saldo deudor",
"title": "AVISO REITERADO",
"dear": "Estimado cliente,",
"bodyDescription": "Nos dirigimos a Vd. nuevamente para informarle que sigue pendiente su deuda con nuestra empresa, tal y como puede comprobar en el extracto adjunto.",
"termLimits": "Dado que los plazos de pago acordados están ampliamente superados, no procede mayor dilación en la liquidación del importe adeudado.",
"payMethod": "Para ello dispone de las siguientes formas de pago:",
"payMethodOption1": "Pago online desde nuestra web",
"payMethodOption2": "Ingreso o transferencia al número de cuenta que detallamos al pie de esta carta, indicando el número de cliente.",
"legalActions": "En caso de no ser atendido este apremio de pago, nos veremos obligados a iniciar las acciones legales que procedan, entre las que están:",
"legalActionsOption1": "Inclusión en ficheros negativos sobre solvencia patrimonial y crédito.",
"legalActionsOption2": "Reclamación judicial",
"legalActionsOption3": "Cesión de deuda a una empresa de gestión de cobro",
"contact": "Para consultas, puede ponerse en contacto con nosotros en el 96 324 21 00.",
"waitingForNews": "En espera de sus noticias",
"conclusion": "Gracias por su atención.",
"accountTransferData": "Datos para transferencia bancaria"
}

View File

@ -0,0 +1,69 @@
<!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>{{_.viewExtract}}</p>
<p>{{_.validData}}</p>
<p>{{_.payMethod}}</p>
<p>{{_.conclusion}}</p>
<p>
<div class="row">
<div class="text">{{bankName}}</div>
<div class="control">{{iban}}</div>
<div class="description">
<div class="line"><span>{{_.accountTransferData}}</span></div>
</div>
</div>
</p>
{{#isPreview}}
<a href="/print/manuscript/letter-debtor/{{companyId}}/{{clientId}}/preview?token={{token}}" target="_blank" title="Ver adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/preview.svg" alt="Ver adjunto"/>
</div>
<span>Ver adjunto</span>
</div>
</a>
<a href="/print/manuscript/letter-debtor/{{companyId}}/{{clientId}}?token={{token}}" target="_blank" title="Descargar adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/download.svg" alt="Descargar adjunto"/>
</div>
<span>Descargar PDF</span>
</div>
</a>
{{/isPreview}}
</div>
<!-- Mail body block end -->
<!-- Footer block -->
{{$.footer}}
<!-- Footer block end -->
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
var path = require('path');
var database = require(path.join(__dirname, '../../database.js'));
var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class LetterDebtorSt {
getData(params, cb) {
let query = `SELECT
sa.iban,
be.name AS bankName,
LOWER(ct.code) countryCode,
c.email recipient
FROM client c
JOIN company AS cny
JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk
JOIN bankEntity be ON be.id = sa.bankEntityFk
JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ? AND cny.id = ?`;
this.clientId = params.clientId;
this.companyId = params.companyId;
this.token = params.token;
database.pool.query(query, [params.clientId, params.companyId], (error, result) => {
if (error || result.length == 0)
return cb(new Error('No template data found'));
Object.assign(this, result[0]);
cb();
});
}
get previewAttachments() {
if (this.isPreview)
return `<a href="/print/manuscript/letter-debtor/${this.companyId}/${this.clientId}/preview?token=${this.token}" target="_blank" title="Ver extracto.pdf">` +
'<div class="attachment"><div class="attachment-icon"><img src="cid:attachment.png" alt="Descargar adjunto"/></div>' +
'<span>extracto.pdf</span></div></a>';
}
};

View File

@ -0,0 +1,11 @@
{
"subject": "Aviso inicial por saldo deudor",
"title": "AVISO INICIAL",
"dear": "Estimado cliente,",
"bodyDescription": "Por el presente escrito le comunicamos que, según nuestros datos contables, su cuenta tiene un saldo pendiente de liquidar.",
"viewExtract": "Le solicitamos compruebe que el extracto adjunto corresponde con los datos de que Vd. dispone. Nuestro departamento de administración le aclarará gustosamente cualquier duda que pueda tener, e igualmente le facilitará cualquier documento que solicite.",
"validData": "Si al comprobar los datos aportados resultaran correctos, le rogamos proceda a regularizar su situación.",
"payMethod": "Si no desea desplazarse personalmente hasta nuestras oficinas, puede realizar el pago mediante transferencia bancaria a la cuenta que figura al pie del comunicado, indicando su número de cliente, o bien puede realizar el pago online desde nuestra página web.",
"conclusion": "De antemano le agradecemos su amable colaboración.",
"accountTransferData": "Datos para transferencia bancaria"
}

View File

@ -4,8 +4,7 @@ var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class PaymentUpdate { module.exports = class PaymentUpdate {
getData(params, cb) { getData(params, cb) {
let query = `SELECT let query = `SELECT
c.id clientId,
pm.id payMethodFk, pm.id payMethodFk,
pm.name payMethodName, pm.name payMethodName,
c.dueDay, c.dueDay,
@ -13,9 +12,12 @@ module.exports = class PaymentUpdate {
LOWER(ct.code) countryCode, LOWER(ct.code) countryCode,
c.email recipient c.email recipient
FROM client c FROM client c
JOIN payMethod pm ON pm.id = c.paymentMethodFk JOIN payMethod pm ON pm.id = c.payMethodFk
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;
this.clientId = params.clientId;
database.pool.query(query, [params.clientId], (error, result) => { database.pool.query(query, [params.clientId], (error, result) => {
if (error || result.length == 0) if (error || result.length == 0)
return cb(new Error('No template data found')); return cb(new Error('No template data found'));

View File

@ -4,19 +4,21 @@ var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class PrinterSetup { module.exports = class PrinterSetup {
getData(params, cb) { getData(params, cb) {
let query = `SELECT let query = `SELECT
c.id clientId,
CONCAT(w.name, ' ', w.firstName) name, CONCAT(w.name, ' ', w.firstName) name,
w.phone AS phone, w.phone AS phone,
CONCAT(u.name, '@verdnatura.es') AS email, CONCAT(u.name, '@verdnatura.es') AS email,
LOWER(ct.code) countryCode, LOWER(ct.code) countryCode,
c.email recipient c.email recipient
FROM client c FROM client c
LEFT JOIN worker w ON w.id = c.workerFk LEFT JOIN worker w ON w.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = w.userFk LEFT JOIN account.user u ON u.id = w.userFk
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;
this.clientId = params.clientId;
this.isPreview = params.isPreview;
database.pool.query(query, [params.clientId], (error, result) => { database.pool.query(query, [params.clientId], (error, result) => {
if (error || result.length == 0) if (error || result.length == 0)
return cb(new Error('No template data found')); return cb(new Error('No template data found'));

View File

@ -22,7 +22,26 @@
<p>{{_.dear}}</p> <p>{{_.dear}}</p>
<p>{{_.bodyDescription}}</p> <p>{{_.bodyDescription}}</p>
<p>{{_.conclusion}}</p> <p>{{_.conclusion}}</p>
{{{previewAttachments}}}
{{#isPreview}}
<a href="/print/manuscript/sepa-core/{{companyId}}/{{clientId}}/preview?token={{token}}" target="_blank" title="Ver adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/preview.svg" alt="Ver adjunto"/>
</div>
<span>Ver adjunto</span>
</div>
</a>
<a href="/print/manuscript/sepa-core/{{companyId}}/{{clientId}}?token={{token}}" target="_blank" title="Descargar adjunto">
<div class="attachment">
<div class="attachment-icon">
<img src="cid:default/download.svg" alt="Descargar adjunto"/>
</div>
<span>Descargar PDF</span>
</div>
</a>
{{/isPreview}}
</div> </div>
<!-- Mail body block end --> <!-- Mail body block end -->

View File

@ -5,19 +5,19 @@ var format = require(path.join(__dirname, '../../util/format.js'));
module.exports = class SepaCore { module.exports = class SepaCore {
getData(params, cb) { getData(params, cb) {
let query = `SELECT let query = `SELECT
c.id clientId,
CONCAT(w.name, ' ', w.firstName) name, CONCAT(w.name, ' ', w.firstName) name,
w.phone AS phone, w.phone AS phone,
CONCAT(u.name, '@verdnatura.es') AS email, CONCAT(u.name, '@verdnatura.es') AS email,
LOWER(ct.code) countryCode, LOWER(ct.code) countryCode,
c.email recipient c.email recipient
FROM client c FROM client c
LEFT JOIN worker w ON w.id = c.workerFk LEFT JOIN worker w ON w.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = w.userFk LEFT JOIN account.user u ON u.id = w.userFk
JOIN country ct ON ct.id = c.countryFk JOIN country ct ON ct.id = c.countryFk
WHERE c.id = ?`; WHERE c.id = ?`;
this.isPreview = params.isPreview; this.clientId = params.clientId;
this.companyId = params.companyId;
this.token = params.token; this.token = params.token;
database.pool.query(query, [params.clientId], (error, result) => { database.pool.query(query, [params.clientId], (error, result) => {
@ -29,11 +29,4 @@ module.exports = class SepaCore {
cb(); cb();
}); });
} }
get previewAttachments() {
if (this.isPreview)
return `<a href="/print/manuscript/sepa-core/${this.clientId}/?token=${this.token}" target="_blank" title="Ver sepa-core.pdf">` +
'<div class="attachment"><div class="attachment-icon"><img src="cid:attachment.png" alt="Descargar adjunto"/></div>' +
'<span>sepa-core.pdf</span></div></a>';
}
}; };

View File

@ -13,13 +13,8 @@ app.use(bodyParser.urlencoded({extended: true}));
app.use('/static', express.static(path.join(__dirname, '../static'))); app.use('/static', express.static(path.join(__dirname, '../static')));
// Auth middleware
var requestToken = function(request, response, next) {
auth.init(request, response, next);
};
// Load routes // Load routes
app.use('/', requestToken, require('../application/router.js')); app.use('/', require('../application/router.js'));
app.start = function() { app.start = function() {
var listener = app.listen(config.app.port, function() { var listener = app.listen(config.app.port, function() {

View File

@ -1,137 +1,221 @@
img { body {
margin: 0 padding: 0;
} margin: 0
}
p {
text-align: justify img {
} margin: 0
}
.wrapper {
background-color: #EEE p {
} text-align: justify
}
.container {
font-family: arial, sans-serif; .wrapper {
max-width: 600px; background-color: #EEE
min-width: 320px; }
font-size: 16px;
margin: 0 auto; .container {
color: #555 font-family: arial, sans-serif;
} max-width: 600px;
min-width: 320px;
.title { font-size: 16px;
background-color: #95d831; margin: 0 auto;
text-align: center; color: #555
padding: 35px 0 }
}
.title {
.title h1 { background-color: #95d831;
font-size: 32px; text-align: center;
color: #333; padding: 35px 0
margin: 0 }
}
.title h1 {
.body { font-size: 32px;
background-color:#FFF; color: #333;
padding: 20px margin: 0
} }
.body a { .body {
color: #8dba25 background-color:#FFF;
} padding: 20px
}
.body h1 {
color: #999 .body a {
} color: #8dba25
}
.body h3 {
font-size: 16px .body h1 {
} color: #999
}
.panel {
border: 1px solid #DDD; .body h3 {
margin-bottom: 10px; font-size: 16px
padding:10px }
}
.panel {
.row { border: 1px solid #DDD;
margin-bottom: 15px; margin-bottom: 10px;
overflow: hidden; position: relative;
content: ''; padding:10px
clear: both }
}
.row {
.row .text { margin-bottom: 15px;
margin-bottom: 5px overflow: hidden
} }
.row .control { .row .text {
-webkit-box-sizing: border-box; margin-bottom: 5px
-moz-box-sizing: border-box; }
box-sizing: border-box
} .row .control {
-webkit-box-sizing: border-box;
.row .description { -moz-box-sizing: border-box;
font-size: 8px; box-sizing: border-box
color: #999 }
}
.row .text, .row .control {
.row .v-align { overflow: hidden
padding-top: 5px; }
line-height: 21px
} .row .description {
position: relative;
.row:last-child { padding-top: 2px;
margin-bottom: 0 overflow: hidden;
} font-size: 11px;
display: block;
.row.inline .text { color: #999
margin-bottom: 0; }
width: 40%;
float: left .row .line {
} border-bottom: 1px solid #DDD;
border-right: 1px solid #DDD;
.row.inline .control { border-left: 1px solid #DDD;
font-weight: bold; margin-top: 10px;
padding-left: 20px; color: #999;
color: #000; padding: 5px
width: 60%; }
float: left
} .row .description span {
background-color: #FFF;
.box { margin: -5px 0 0 50px;
border-top: 1px solid #CCC; display: block;
border-right: 1px solid #CCC; padding: 5px;
border-bottom: 1px solid #CCC; float: left
font-weight: bold; }
text-align: center;
padding-top: 4px; .row:last-child {
width: 25px; margin-bottom: 0
height: 21px; }
color: #000;
float: left .row.inline .text {
} margin-bottom: 0;
width: 40%;
.row .control .box:first-child { float: left
border-left: 1px solid #CCC; }
}
.row.inline .control {
.attachment { font-weight: bold;
overflow: hidden; padding-left: 20px;
margin-top: 10px color: #000;
} width: 60%;
float: left
.attachment:after { }
content: ' ';
display: block; .row.inline .description {
clear: both position: static;
} overflow: visible
}
.attachment-icon {
float: left .box {
} border-top: 1px solid #CCC;
border-right: 1px solid #CCC;
.attachment span { border-bottom: 1px solid #CCC;
padding: 16px 0 0 10px; font-weight: bold;
float: left text-align: center;
padding-top: 4px;
width: 25px;
height: 21px;
color: #000;
float: left
}
.box.crossed {
font-weight: 100;
font-size: 16px
}
.row .control .box:first-child {
border-left: 1px solid #CCC;
}
.font.small {
font-size: 10px
}
.font.verticalAlign {
height: 27px;
line-height: 27px
}
.font.centered {
height: 27px;
line-height: 27px;
text-align: center
}
.verticalText {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
position: absolute;
text-align: center;
font-size: .65em;
width: 200px;
right: -115px;
top: 50%
}
.attachment {
overflow: hidden;
margin-top: 10px
}
.attachment-icon {
float: left
}
.attachment span {
padding: 16px 0 0 10px;
float: left
}
.columns {
overflow: hidden
}
.columns .size100 {
width: 100%;
float: left
}
.columns .size75 {
width: 75%;
float: left
}
.columns .size50 {
width: 50%;
float: left
}
.columns .size33 {
width: 33.33%;
float: left
}
.columns .size25 {
width: 25%;
float: left
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -46,6 +46,9 @@ http {
location ~ ^/print(?:/(.*))?$ { location ~ ^/print(?:/(.*))?$ {
proxy_pass http://127.0.0.1:3006/$1$is_args$args; proxy_pass http://127.0.0.1:3006/$1$is_args$args;
} }
location ~ ^/item(?:/(.*))?$ {
proxy_pass http://127.0.0.1:3007/$1$is_args$args;
}
# Este tiene que ser el último # Este tiene que ser el último
location ~ ^(?:/(.*))?$ { location ~ ^(?:/(.*))?$ {
proxy_pass http://127.0.0.1:3001/$1$is_args$args; proxy_pass http://127.0.0.1:3001/$1$is_args$args;

View File

@ -41,6 +41,9 @@ http {
location ~ ^/print(?:/(.*))?$ { location ~ ^/print(?:/(.*))?$ {
proxy_pass http://print:3006/$1$is_args$args; proxy_pass http://print:3006/$1$is_args$args;
} }
location ~ ^/item(?:/(.*))?$ {
proxy_pass http://item:3007/$1$is_args$args;
}
# Este tiene que ser el último # Este tiene que ser el último
location ~ ^(?:/(.*))?$ { location ~ ^(?:/(.*))?$ {
proxy_pass http://salix:3001/$1$is_args$args; proxy_pass http://salix:3001/$1$is_args$args;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,15 +1,20 @@
var path = require('path'); var path = require('path');
var fs = require('fs'); let defaultFile = 'datasources.json';
var config = {};
let devConfigPath = path.join(__dirname, '/config/datasources.development.json'); function getFile(fileName) {
let configPath = path.join(__dirname, '/config/datasources.json'); return require(path.join(__dirname, `/config/${fileName}`));
}
try { try {
config = Object.assign(require(configPath), require(devConfigPath)); let envFile = 'datasources.development.json';
if (process.env.NODE_ENV === 'test')
envFile = 'datasources.test.json';
config = getFile(envFile);
} catch (e) { } catch (e) {
if (e.code == 'MODULE_NOT_FOUND') if (e.code == 'MODULE_NOT_FOUND')
config = require(configPath); config = getFile(defaultFile);
} }
config.proxy = require('../../nginx/config.json'); config.proxy = require('../../nginx/config.json');

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