added print service part 1 #1001
|
@ -0,0 +1,3 @@
|
|||
module.exports = function(app) {
|
||||
require('../../../services/print/server.js')(app);
|
||||
};
|
|
@ -35,7 +35,7 @@ export default class Controller {
|
|||
}
|
||||
|
||||
notifyChanges() {
|
||||
this.$http.get(`/mailer/notification/payment-update/${this.client.id}`).then(
|
||||
this.$http.get(`/email/payment-update`, {clientFk: this.client.id}).then(
|
||||
() => this.vnApp.showMessage(this.$translate.instant('Notification sent!'))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"app": {
|
||||
"port": 3000,
|
||||
"defaultLanguage": "es",
|
||||
"senderMail": "nocontestar@verdnatura.es",
|
||||
"senderName": "Verdnatura"
|
||||
},
|
||||
"mysql": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"database": "vn",
|
||||
"user": "root",
|
||||
"password": "root"
|
||||
},
|
||||
"smtp": {
|
||||
"host": "localhost",
|
||||
"port": 465,
|
||||
"secure": true,
|
||||
"auth": {
|
||||
"user": "",
|
||||
"pass": ""
|
||||
},
|
||||
"tls": {
|
||||
"rejectUnauthorized": false
|
||||
},
|
||||
"pool": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{"type": "email", "name": "client-welcome"},
|
||||
{"type": "email", "name": "printer-setup"},
|
||||
{"type": "email", "name": "payment-update"},
|
||||
{"type": "email", "name": "letter-debtor-st"},
|
||||
{"type": "email", "name": "letter-debtor-nd"},
|
||||
{"type": "report", "name": "delivery-note"},
|
||||
{"type": "report", "name": "invoice"},
|
||||
{"type": "static", "name": "email-header"},
|
||||
{"type": "static", "name": "email-footer"}
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
const fs = require('fs-extra');
|
||||
let env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
|
||||
|
||||
let configPath = `/etc/`;
|
||||
let config = require('../config/print.json');
|
||||
let configFiles = [
|
||||
`${configPath}/print.json`,
|
||||
`${configPath}/print.${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.env = env;
|
||||
|
||||
module.exports = config;
|
|
@ -0,0 +1,9 @@
|
|||
const mysql = require('mysql2/promise');
|
||||
const config = require('./config.js');
|
||||
|
||||
module.exports = {
|
||||
init() {
|
||||
if (!this.pool)
|
||||
this.pool = mysql.createPool(config.mysql);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,144 @@
|
|||
const Vue = require('vue');
|
||||
const VueI18n = require('vue-i18n');
|
||||
const renderer = require('vue-server-renderer').createRenderer();
|
||||
const fs = require('fs-extra');
|
||||
const juice = require('juice');
|
||||
const smtp = require('./smtp');
|
||||
const i18n = new VueI18n({
|
||||
locale: 'es',
|
||||
});
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
||||
if (!process.env.OPENSSL_CONF)
|
||||
process.env.OPENSSL_CONF = '/etc/ssl/';
|
||||
|
||||
module.exports = {
|
||||
|
||||
path: `${appPath}/reports`,
|
||||
|
||||
/**
|
||||
* Renders a report component
|
||||
*
|
||||
* @param {String} name - Report name
|
||||
* @param {Object} ctx - Request context
|
||||
*/
|
||||
async render(name, ctx) {
|
||||
const component = require(`${this.path}/${name}`);
|
||||
const prefetchedData = await this.preFetch(component, ctx);
|
||||
|
||||
const app = new Vue({
|
||||
i18n,
|
||||
render: h => h(component),
|
||||
});
|
||||
|
||||
return renderer.renderToString(app).then(renderedHtml => {
|
||||
return {
|
||||
html: renderedHtml,
|
||||
data: prefetchedData,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Prefetch all component data from asyncData method
|
||||
*
|
||||
* @param {Object} component - Component object
|
||||
* @param {Object} ctx - Request context
|
||||
*/
|
||||
async preFetch(component, ctx) {
|
||||
const preFetchData = {attachments: []};
|
||||
let params = {};
|
||||
let data = {};
|
||||
|
||||
if (Object.keys(ctx.body).length > 0)
|
||||
params = ctx.body;
|
||||
|
||||
if (Object.keys(ctx.query).length > 0)
|
||||
params = ctx.query;
|
||||
|
||||
await this.attachAssets(component);
|
||||
|
||||
if (component.hasOwnProperty('data'))
|
||||
data = component.data();
|
||||
|
||||
if (component.hasOwnProperty('asyncData')) {
|
||||
const asyncData = await component.asyncData(ctx, params);
|
||||
|
||||
if (asyncData.locale) {
|
||||
const locale = component.i18n.messages[asyncData.locale];
|
||||
preFetchData.subject = locale.subject;
|
||||
}
|
||||
|
||||
if (asyncData.recipient)
|
||||
preFetchData.recipient = asyncData.recipient;
|
||||
|
||||
const mergedData = {...data, ...asyncData};
|
||||
component.data = function data() {
|
||||
return mergedData;
|
||||
};
|
||||
}
|
||||
|
||||
if (data && data.hasOwnProperty('attachments')) {
|
||||
const fileNames = data.attachments;
|
||||
fileNames.forEach(attachment => {
|
||||
const componentPath = `${this.path}/${component.name}`;
|
||||
let fileSrc = componentPath + attachment;
|
||||
|
||||
if (attachment.slice(0, 4) === 'http' || attachment.slice(0, 4) === 'https')
|
||||
fileSrc = attachment;
|
||||
|
||||
const fileName = attachment.split('/').pop();
|
||||
preFetchData.attachments.push({
|
||||
filename: fileName,
|
||||
path: fileSrc,
|
||||
cid: attachment,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (component.components) {
|
||||
const components = component.components;
|
||||
const promises = [];
|
||||
|
||||
Object.keys(components).forEach(component => {
|
||||
promises.push(this.preFetch(components[component], ctx));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(results => {
|
||||
results.forEach(result => {
|
||||
result.attachments.forEach(atth => {
|
||||
preFetchData.attachments.push(atth);
|
||||
});
|
||||
});
|
||||
return preFetchData;
|
||||
});
|
||||
}
|
||||
|
||||
return preFetchData;
|
||||
},
|
||||
|
||||
async attachAssets(component) {
|
||||
const localePath = `${this.path}/${component.name}/locale.js`;
|
||||
const templatePath = `${this.path}/${component.name}/index.html`;
|
||||
const stylePath = `${this.path}/${component.name}/assets/css/style.css`;
|
||||
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
const css = await fs.readFile(stylePath, 'utf8');
|
||||
|
||||
component.i18n = require(localePath);
|
||||
component.template = juice.inlineContent(template, css);
|
||||
},
|
||||
|
||||
async toEmail(name, ctx) {
|
||||
const rendered = await this.render(name, ctx);
|
||||
const data = rendered.data;
|
||||
const options = {
|
||||
to: data.recipient,
|
||||
subject: data.subject,
|
||||
html: rendered.html,
|
||||
attachments: data.attachments,
|
||||
};
|
||||
return smtp.send(options);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
module.exports = (app) => {
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(`Caught exception: ${err}`);
|
||||
});
|
||||
|
||||
process.on('warning', (err) => {
|
||||
console.error(`My warning err`);
|
||||
});
|
||||
|
||||
app.use(function(error, request, response, next) {
|
||||
if (!error.httpStatusCode)
|
||||
return next(error);
|
||||
|
||||
response.status(error.httpStatusCode);
|
||||
response.json({
|
||||
httpStatusCode: error.httpStatusCode,
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = class UserException extends Error {
|
||||
/**
|
||||
* UserException
|
||||
* @param {String} message - Error message
|
||||
*/
|
||||
constructor(message) {
|
||||
super(message);
|
||||
|
||||
this.httpStatusCode = 400;
|
||||
this.name = 'UserException';
|
||||
}
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
methods: {
|
||||
uFirst: (text) => {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,95 @@
|
|||
const Vue = require('vue');
|
||||
const VueI18n = require('vue-i18n');
|
||||
const renderer = require('vue-server-renderer').createRenderer();
|
||||
const fs = require('fs-extra');
|
||||
const pdf = require('phantom-html2pdf');
|
||||
const juice = require('juice');
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
||||
if (!process.env.OPENSSL_CONF)
|
||||
process.env.OPENSSL_CONF = '/etc/ssl/';
|
||||
|
||||
module.exports = {
|
||||
|
||||
path: `${appPath}/reports`,
|
||||
|
||||
/**
|
||||
* Renders a report component
|
||||
*
|
||||
* @param {String} name - Report name
|
||||
* @param {Object} ctx - Request context
|
||||
*/
|
||||
async render(name, ctx) {
|
||||
const component = require(`${this.path}/${name}`);
|
||||
|
||||
await this.preFetch(component, ctx);
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: 'es',
|
||||
});
|
||||
const app = new Vue({
|
||||
i18n,
|
||||
render: h => h(component),
|
||||
});
|
||||
|
||||
return renderer.renderToString(app);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prefetch all component data from asyncData method
|
||||
*
|
||||
* @param {Object} component - Component object
|
||||
* @param {Object} ctx - Request context
|
||||
*/
|
||||
async preFetch(component, ctx) {
|
||||
let data = {};
|
||||
|
||||
await this.attachAssets(component);
|
||||
|
||||
if (component.hasOwnProperty('data'))
|
||||
data = component.data();
|
||||
|
||||
if (component.hasOwnProperty('asyncData')) {
|
||||
const fetch = await component.asyncData(ctx, ctx.body);
|
||||
const mergedData = {...data, ...fetch};
|
||||
|
||||
component.data = function data() {
|
||||
return mergedData;
|
||||
};
|
||||
}
|
||||
|
||||
if (component.components) {
|
||||
const components = component.components;
|
||||
const promises = [];
|
||||
|
||||
Object.keys(components).forEach(component => {
|
||||
promises.push(this.preFetch(components[component], ctx));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
},
|
||||
|
||||
async attachAssets(component) {
|
||||
const localePath = `${this.path}/${component.name}/locale.js`;
|
||||
const templatePath = `${this.path}/${component.name}/index.html`;
|
||||
const stylePath = `${this.path}/${component.name}/assets/css/style.css`;
|
||||
|
||||
const template = await fs.readFile(templatePath, 'utf8');
|
||||
const css = await fs.readFile(stylePath, 'utf8');
|
||||
|
||||
component.i18n = require(localePath);
|
||||
component.template = juice.inlineContent(template, css);
|
||||
},
|
||||
|
||||
async toPdf(name, ctx) {
|
||||
const options = {
|
||||
html: await this.render(name, ctx),
|
||||
};
|
||||
const result = await pdf.convert(options);
|
||||
const stream = await result.toStream();
|
||||
|
||||
return stream;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
const reportEngine = require('./reportEngine.js');
|
||||
const emailEngine = require('./emailEngine');
|
||||
const express = require('express');
|
||||
const routes = require(`../config/routes.json`);
|
||||
|
||||
module.exports = app => {
|
||||
this.path = `${appPath}/reports`;
|
||||
|
||||
/**
|
||||
* Enables a report
|
||||
*
|
||||
* @param {String} name - Report state path
|
||||
*/
|
||||
function registerReport(name) {
|
||||
if (!name) throw new Error('Report name required');
|
||||
|
||||
app.get(`/report/${name}`, (request, response, next) => {
|
||||
response.setHeader('Content-Disposition', `attachment; filename="${name}.pdf"`);
|
||||
response.setHeader('Content-type', 'application/pdf');
|
||||
|
||||
reportEngine.toPdf(name, request).then(stream => {
|
||||
stream.pipe(response);
|
||||
}).catch(e => {
|
||||
next(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a email
|
||||
*
|
||||
* @param {String} name - Report state path
|
||||
*/
|
||||
function registerEmail(name) {
|
||||
if (!name) throw new Error('Email name required');
|
||||
|
||||
app.get(`/email/${name}`, (request, response, next) => {
|
||||
emailEngine.render(name, request).then(rendered => {
|
||||
response.send(rendered.html);
|
||||
}).catch(e => {
|
||||
next(e);
|
||||
});
|
||||
});
|
||||
|
||||
app.post(`/email/${name}`, (request, response, next) => {
|
||||
emailEngine.toEmail(name, request).then(() => {
|
||||
response.status(200).json({status: 200});
|
||||
}).catch(e => {
|
||||
next(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
routes.forEach(route => {
|
||||
if (route.type === 'email')
|
||||
registerEmail(route.name);
|
||||
else if (route.type === 'report')
|
||||
registerReport(route.name);
|
||||
|
||||
const staticPath = this.path + `/${route.name}/assets`;
|
||||
app.use(`/assets`, express.static(staticPath));
|
||||
});
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
const nodemailer = require('nodemailer');
|
||||
const config = require('./config.js');
|
||||
|
||||
module.exports = {
|
||||
init() {
|
||||
if (!this.transporter)
|
||||
this.transporter = nodemailer.createTransport(config.smtp);
|
||||
},
|
||||
|
||||
send(options) {
|
||||
options.from = `${config.app.senderName} <${config.app.senderMail}>`;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production')
|
||||
options.to = config.app.senderMail;
|
||||
|
||||
return this.transporter.sendMail(options);
|
||||
},
|
||||
|
||||
log() {
|
||||
|
||||
},
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "vn-print",
|
||||
"version": "2.0.0",
|
||||
"description": "Print service",
|
||||
"main": "server/server.js",
|
||||
"scripts": {
|
||||
"start": "node server/server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.verdnatura.es/salix"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"fs-extra": "^7.0.1",
|
||||
"juice": "^5.0.1",
|
||||
"mysql2": "^1.6.1",
|
||||
"nodemailer": "^4.7.0",
|
||||
"phantom": "^6.0.3",
|
||||
"phantom-html2pdf": "^4.0.1",
|
||||
"vue": "^2.5.17",
|
||||
"vue-i18n": "^8.3.1",
|
||||
"vue-server-renderer": "^2.5.17"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
body {
|
||||
background-color: #EEE
|
||||
}
|
||||
|
||||
.container {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: #FFF;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: #8dba25
|
||||
}
|
||||
|
||||
.main h1 {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.main h3 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #95d831;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 35px 0
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="container">
|
||||
<email-header></email-header>
|
||||
<section class="main">
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<p>{{$t('dearClient')}},</p>
|
||||
<p v-html="$t('clientData')"></p>
|
||||
|
||||
<p>
|
||||
<div>{{$t('clientId')}}: <strong>{{ id }}</strong></div>
|
||||
<div>{{$t('user')}}: <strong>{{userName}}</strong></div>
|
||||
<div>{{$t('password')}}: <strong>********</strong>
|
||||
(<a href="https://verdnatura.es">{{$t('passwordResetText')}}</a>)
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<h1>{{$t('sections.howToBuy.title')}}</h1>
|
||||
<p>{{$t('sections.howToBuy.description')}}</p>
|
||||
<ol>
|
||||
<li v-for="requeriment in $t('sections.howToBuy.requeriments')">
|
||||
<span v-html="requeriment"></span>
|
||||
</li>
|
||||
</ol>
|
||||
<p>{{$t('sections.howToBuy.stock')}}</p>
|
||||
<p>{{$t('sections.howToBuy.delivery')}}</p>
|
||||
|
||||
<h1>{{$t('sections.howToPay.title')}}</h1>
|
||||
<p>{{$t('sections.howToPay.description')}}</p>
|
||||
<ul>
|
||||
<li v-for="option in $t('sections.howToPay.options')">
|
||||
<span v-html="option"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>{{$t('sections.toConsider.title')}}</h1>
|
||||
<p>{{$t('sections.toConsider.description')}}</p>
|
||||
|
||||
<h3>{{$t('sections.claimsPolicy.title')}}</h3>
|
||||
<p>{{$t('sections.claimsPolicy.description')}}</p>
|
||||
|
||||
<p v-html="$t('help')"></p>
|
||||
<p>
|
||||
<section v-if="salesPersonName">
|
||||
{{$t('salesPersonName')}}: <strong>{{salesPersonName}}</strong>
|
||||
</section>
|
||||
<section v-if="salesPersonPhone">
|
||||
{{$t('salesPersonPhone')}}: <strong>{{salesPersonPhone}}</strong>
|
||||
</section>
|
||||
<section v-if="salesPersonEmail">
|
||||
{{$t('salesPersonEmail')}}:
|
||||
<strong><a v-bind:href="`mailto: ${salesPersonEmail}`" target="_blank">{{salesPersonEmail}}</strong>
|
||||
</section>
|
||||
</p>
|
||||
</section>
|
||||
<email-footer></email-footer>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,46 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const emailHeader = require('../email-header');
|
||||
const emailFooter = require('../email-footer');
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'client-welcome',
|
||||
async asyncData(ctx, params) {
|
||||
const data = {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
|
||||
if (!params.clientFk)
|
||||
throw new UserException('No client id specified');
|
||||
|
||||
return this.methods.fetchClientData(params.clientFk)
|
||||
.then(([result]) => {
|
||||
return Object.assign(data, result[0]);
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.$i18n.locale = this.locale;
|
||||
},
|
||||
methods: {
|
||||
fetchClientData(clientFk) {
|
||||
return database.pool.query(`
|
||||
SELECT
|
||||
c.id,
|
||||
u.lang locale,
|
||||
u.name AS userName,
|
||||
c.email recipient,
|
||||
CONCAT(w.name, ' ', w.firstName) salesPersonName,
|
||||
w.phone AS salesPersonPhone,
|
||||
CONCAT(wu.name, '@verdnatura.es') AS salesPersonEmail
|
||||
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
|
||||
WHERE c.id = ?`, [clientFk]);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'email-header': emailHeader,
|
||||
'email-footer': emailFooter,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,55 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
subject: 'Bienvenido a Verdnatura',
|
||||
title: '¡Te damos la bienvenida!',
|
||||
dearClient: 'Estimado cliente',
|
||||
clientData: `Tus datos para poder comprar en la web de Verdnatura
|
||||
(<a href="https://www.verdnatura.es" title="Visitar Verdnatura" target="_blank" style="color: #8dba25">https://www.verdnatura.es</a>)
|
||||
o en nuestras aplicaciones para <a href="https://goo.gl/3hC2mG" title="App Store" target="_blank" style="color: #8dba25">iOS</a> y
|
||||
<a href="https://goo.gl/8obvLc" title="Google Play" target="_blank" style="color: #8dba25">Android</a>
|
||||
(<a href="https://www.youtube.com/watch?v=gGfEtFm8qkw" target="_blank" style="color: #8dba25"><strong>Ver tutorial de uso</strong></a>), son`,
|
||||
clientId: 'Identificador de cliente',
|
||||
user: 'Usuario',
|
||||
password: 'Contraseña',
|
||||
passwordResetText: 'Haz clic en "¿Has olvidado tu contraseña?"',
|
||||
sections: {
|
||||
howToBuy: {
|
||||
title: 'Cómo hacer un pedido',
|
||||
description: `Para realizar un pedido en nuestra web,
|
||||
debes configurarlo indicando:`,
|
||||
requeriments: [
|
||||
'Si quieres recibir el pedido (por agencia o por nuestro propio reparto) o si lo prefieres recoger en alguno de nuestros almacenes.',
|
||||
'La fecha en la que quieres recibir el pedido (se preparará el día anterior).',
|
||||
'La dirección de entrega o el almacén donde quieres recoger el pedido.'],
|
||||
stock: 'En nuestra web y aplicaciones puedes visualizar el stock disponible de flor cortada, verdes, plantas, complementos y artificial. Ten 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.',
|
||||
delivery: 'El reparto se realiza de lunes a sábado según la zona en la que te encuentres. 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.',
|
||||
},
|
||||
howToPay: {
|
||||
title: 'Cómo pagar',
|
||||
description: 'Las formas de pago admitidas en Verdnatura son:',
|
||||
options: [
|
||||
'Con <strong>tarjeta</strong> a través de nuestra plataforma web (al confirmar el pedido).',
|
||||
'Mediante <strong>giro bancario mensual</strong>, modalidad que hay que solicitar y tramitar.',
|
||||
],
|
||||
},
|
||||
toConsider: {
|
||||
title: 'Cosas a tener en cuenta',
|
||||
description: `Verdnatura vende EXCLUSIVAMENTE a profesionales, por lo que debes
|
||||
remitirnos el Modelo 036 ó 037, para comprobar que está
|
||||
dado/a de alta en el epígrafe correspondiente al comercio de flores.`,
|
||||
},
|
||||
claimsPolicy: {
|
||||
title: 'POLÍTICA DE RECLAMACIONES',
|
||||
description: `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.`,
|
||||
},
|
||||
},
|
||||
help: 'Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para atenderte!</strong>',
|
||||
salesPersonName: 'Soy tu comercial y mi nombre es',
|
||||
salesPersonPhone: 'Teléfono y whatsapp',
|
||||
salesPersonEmail: 'Dirección de e-mail',
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
.blue {
|
||||
color: blue
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<span class="red">{{$t('clientId')}}: {{ id1 }} {{ id2 }}</span>
|
||||
<span class="blue">heey</span>
|
||||
</div>
|
|
@ -0,0 +1,36 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'delivery-note',
|
||||
async asyncData(ctx, params) {
|
||||
console.log(params);
|
||||
const promises = [];
|
||||
const dataIndex = promises.push(this.methods.fetchData()) - 1;
|
||||
const itemsIndex = promises.push(this.methods.fetchItems()) - 1;
|
||||
|
||||
return Promise.all(promises).then(result => {
|
||||
const [[data]] = result[dataIndex];
|
||||
const [[items]] = result[itemsIndex];
|
||||
|
||||
return {
|
||||
id1: data.id,
|
||||
id2: items.id,
|
||||
};
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchData() {
|
||||
return database.pool.query('SELECT 1 AS id');
|
||||
},
|
||||
|
||||
fetchItems() {
|
||||
return database.pool.query('SELECT 2 AS id');
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
clientId: 'Id cliente',
|
||||
},
|
||||
},
|
||||
}
|
||||
;
|
|
@ -0,0 +1,62 @@
|
|||
footer {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto
|
||||
}
|
||||
|
||||
.buttons {
|
||||
background-color: #FFF;
|
||||
text-align: center;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.buttons a {
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.buttons .btn {
|
||||
background-color: #333;
|
||||
min-width: 300px;
|
||||
height: 72px;
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.buttons .btn .text {
|
||||
display: inline-block;
|
||||
padding-top: 22px
|
||||
}
|
||||
|
||||
.buttons .btn .icon {
|
||||
background-color: #95d831;
|
||||
text-align: center;
|
||||
padding-top: 22px;
|
||||
float: right;
|
||||
height: 50px;
|
||||
width: 70px
|
||||
}
|
||||
|
||||
.networks {
|
||||
background-color: #555;
|
||||
text-align: center;
|
||||
padding: 20px 0
|
||||
}
|
||||
|
||||
.networks a {
|
||||
text-decoration: none;
|
||||
margin-right: 5px
|
||||
}
|
||||
|
||||
.networks a img {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.privacy {
|
||||
padding: 20px 0;
|
||||
font-size: 10px;
|
||||
font-weight: 100
|
||||
}
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,49 @@
|
|||
<footer>
|
||||
<!-- Action button block -->
|
||||
<!-- <section class="buttons">
|
||||
<a href="https://www.verdnatura.es" target="_blank">
|
||||
<div class="btn">
|
||||
<span class="text">{{ $t('buttons.webAcccess')}}</span>
|
||||
<span class="icon"><img src="/assets/images/action.png"/></span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank">
|
||||
<div class="btn">
|
||||
<span class="text">{{ $t('buttons.info')}}</span>
|
||||
<span class="icon"><img src="/assets/images/info.png"/></span>
|
||||
</div>
|
||||
</a>
|
||||
</section> -->
|
||||
<!-- Action button block -->
|
||||
|
||||
<!-- Networks block -->
|
||||
<section class="networks">
|
||||
<a href="https://www.facebook.com/Verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/facebook.png']" alt="Facebook"/>
|
||||
</a>
|
||||
<a href="https://www.twitter.com/Verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/twitter.png']" alt="Twitter"/>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/Verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/youtube.png']" alt="Youtube"/>
|
||||
</a>
|
||||
<a href="https://www.pinterest.com/Verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/pinterest.png']" alt="Pinterest"/>
|
||||
</a>
|
||||
<a href="https://www.instagram.com/Verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/instagram.png']" alt="Instagram"/>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/verdnatura" target="_blank">
|
||||
<img :src="embeded['/assets/images/linkedin.png']" alt="Linkedin"/>
|
||||
</a>
|
||||
</section>
|
||||
<!-- Networks block end -->
|
||||
|
||||
<!-- Privacy block -->
|
||||
<section class="privacy">
|
||||
<p>{{$t('privacy.fiscalAddress')}}</p>
|
||||
<p>{{$t('privacy.disclaimer')}}</p>
|
||||
<p>{{$t('privacy.law')}}</p>
|
||||
</section>
|
||||
<!-- Privacy block end -->
|
||||
</footer>
|
|
@ -0,0 +1,28 @@
|
|||
module.exports = {
|
||||
name: 'email-footer',
|
||||
asyncData(ctx, params) {
|
||||
return {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const embeded = [];
|
||||
this.attachments.map((attachment) => {
|
||||
const src = this.isPreview ? attachment : `cid:${attachment}`;
|
||||
embeded[attachment] = src;
|
||||
});
|
||||
this.embeded = embeded;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachments: [
|
||||
'/assets/images/facebook.png',
|
||||
'/assets/images/twitter.png',
|
||||
'/assets/images/youtube.png',
|
||||
'/assets/images/pinterest.png',
|
||||
'/assets/images/instagram.png',
|
||||
'/assets/images/linkedin.png',
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
buttons: {
|
||||
webAcccess: 'Visita nuestra Web',
|
||||
info: 'Ayúdanos a mejorar',
|
||||
},
|
||||
privacy: {
|
||||
fiscalAddress: 'VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla · www.verdnatura.es · clientes@verdnatura.es',
|
||||
disclaimer: `- 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.`,
|
||||
law: '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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
header {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 100%
|
||||
}
|
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,5 @@
|
|||
<header>
|
||||
<a href="https://www.verdnatura.es" target="_blank"/>
|
||||
<img :src="embeded['/assets/images/logo.png']" alt="VerdNatura"/>
|
||||
</a>
|
||||
</header>
|
|
@ -0,0 +1,22 @@
|
|||
module.exports = {
|
||||
name: 'email-header',
|
||||
asyncData(ctx, params) {
|
||||
return {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const embeded = [];
|
||||
this.attachments.map((attachment) => {
|
||||
const src = this.isPreview ? attachment : `cid:${attachment}`;
|
||||
embeded[attachment] = src;
|
||||
});
|
||||
this.embeded = embeded;
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
attachments: ['/assets/images/logo.png'],
|
||||
};
|
||||
},
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {clientName: 'Nombre cliente'},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
body {
|
||||
background-color: #EEE
|
||||
}
|
||||
|
||||
.container {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: #FFF;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: #8dba25
|
||||
}
|
||||
|
||||
.main h1 {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.main h3 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #95d831;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 35px 0
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="container">
|
||||
<!-- Header component -->
|
||||
<email-header></email-header>
|
||||
<!-- End header component -->
|
||||
|
||||
<section class="main">
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<h1>{{ $t('sections.introduction.title') }},</h1>
|
||||
<p>{{ $t('sections.introduction.description') }}</p>
|
||||
<p>{{ $t('sections.introduction.terms') }}</p>
|
||||
|
||||
<p>
|
||||
{{ $t('sections.payMethod.description') }}:
|
||||
<ol>
|
||||
<li v-for="option in $t('sections.payMethod.options')">
|
||||
{{ option }}
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ $t('sections.legalAction.description') }}:
|
||||
<ol type="a">
|
||||
<li v-for="option in $t('sections.legalAction.options')">
|
||||
{{ option }}
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<p v-html="$t('contactPhone')"></p>
|
||||
<p v-html="$t('conclusion')"></p>
|
||||
|
||||
<p>
|
||||
<div class="row">
|
||||
<div class="text">{{bankName}}</div>
|
||||
<div class="control">{{iban}}</div>
|
||||
<div class="description">
|
||||
<div class="line"><span>{{$t('transferAccount') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</section>
|
||||
<!-- Footer component -->
|
||||
<email-footer></email-footer>
|
||||
<!-- End footer component -->
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,56 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const emailHeader = require('../email-header');
|
||||
const emailFooter = require('../email-footer');
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'letter-debtor-nd',
|
||||
async asyncData(ctx, params) {
|
||||
const data = {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
|
||||
if (!params.clientFk)
|
||||
throw new UserException('No client id specified');
|
||||
|
||||
return this.methods.fetchClientData(params.clientFk, params.companyFk)
|
||||
.then(([result]) => {
|
||||
return Object.assign(data, result[0]);
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.$i18n.locale = this.locale;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachments: ['http://localhost:8080/report/delivery-note'],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchClientData(clientFk, companyFk) {
|
||||
return database.pool.query(`
|
||||
SELECT
|
||||
u.lang locale,
|
||||
c.email recipient,
|
||||
c.dueDay,
|
||||
c.iban,
|
||||
sa.iban,
|
||||
be.name AS bankName
|
||||
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 account.user u ON u.id = c.id
|
||||
WHERE c.id = ? AND cny.id = ?`, [clientFk, companyFk]);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
accountAddress: function() {
|
||||
return this.iban.slice(-4);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'email-header': emailHeader,
|
||||
'email-footer': emailFooter,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
subject: 'Reiteración de aviso por saldo deudor',
|
||||
title: 'Aviso reiterado',
|
||||
sections: {
|
||||
introduction: {
|
||||
title: 'Estimado cliente',
|
||||
description: `Nos dirigimos a Vd. nuevamente para informarle que sigue
|
||||
pendiente su deuda con nuestra empresa, tal y como puede comprobar en el extracto adjunto.`,
|
||||
terms: `Dado que los plazos de pago acordados están ampliamente superados, no procede mayor dilación en la liquidación del importe adeudado.`,
|
||||
},
|
||||
payMethod: {
|
||||
description: 'Para ello dispone de las siguientes formas de pago',
|
||||
options: [
|
||||
'Pago online desde nuestra web.',
|
||||
'Ingreso o transferencia al número de cuenta que detallamos al pie de esta carta, indicando el número de cliente.',
|
||||
],
|
||||
},
|
||||
legalAction: {
|
||||
description: `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`,
|
||||
options: [
|
||||
'Inclusión en ficheros negativos sobre solvencia patrimonial y crédito.',
|
||||
'Reclamación judicial.',
|
||||
'Cesión de deuda a una empresa de gestión de cobro.',
|
||||
],
|
||||
},
|
||||
},
|
||||
contactPhone: 'Para consultas, puede ponerse en contacto con nosotros en el <strong>96 324 21 00</strong>.',
|
||||
conclusion: 'En espera de sus noticias. <br/> Gracias por su atención.',
|
||||
transferAccount: 'Datos para transferencia bancaria',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
body {
|
||||
background-color: #EEE
|
||||
}
|
||||
|
||||
.container {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: #FFF;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: #8dba25
|
||||
}
|
||||
|
||||
.main h1 {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.main h3 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #95d831;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 35px 0
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="container">
|
||||
<!-- Header component -->
|
||||
<email-header></email-header>
|
||||
<!-- End header component -->
|
||||
|
||||
<section class="main">
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<h1>{{ $t('sections.introduction.title') }},</h1>
|
||||
<p>{{ $t('sections.introduction.description') }}</p>
|
||||
|
||||
<p>{{ $t('checkExtract') }}</p>
|
||||
<p>{{ $t('checkValidData') }}</p>
|
||||
<p>{{ $t('payMethod') }}</p>
|
||||
<p>{{ $t('conclusion') }}</p>
|
||||
|
||||
<p>
|
||||
<div class="row">
|
||||
<div class="text">{{bankName}}</div>
|
||||
<div class="control">{{iban}}</div>
|
||||
<div class="description">
|
||||
<div class="line"><span>{{$t('transferAccount') }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</section>
|
||||
<!-- Footer component -->
|
||||
<email-footer></email-footer>
|
||||
<!-- End footer component -->
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,61 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const emailHeader = require('../email-header');
|
||||
const emailFooter = require('../email-footer');
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'letter-debtor-st',
|
||||
async asyncData(ctx, params) {
|
||||
const data = {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
|
||||
if (!params.clientFk)
|
||||
throw new UserException('No client id specified');
|
||||
|
||||
if (!params.companyFk)
|
||||
throw new UserException('No company id specified');
|
||||
|
||||
return this.methods.fetchClientData(params.clientFk, params.companyFk)
|
||||
.then(([[result]]) => {
|
||||
if (!result) throw new UserException('Client data not found');
|
||||
|
||||
return Object.assign(data, result);
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.$i18n.locale = this.locale;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachments: ['http://localhost:5000/report/delivery-note'],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchClientData(clientFk, companyFk) {
|
||||
return database.pool.query(`
|
||||
SELECT
|
||||
u.lang locale,
|
||||
c.email recipient,
|
||||
c.dueDay,
|
||||
c.iban,
|
||||
sa.iban,
|
||||
be.name AS bankName
|
||||
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 account.user u ON u.id = c.id
|
||||
WHERE c.id = ? AND cny.id = ?`, [clientFk, companyFk]);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
accountAddress: function() {
|
||||
return this.iban.slice(-4);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'email-header': emailHeader,
|
||||
'email-footer': emailFooter,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
subject: 'Aviso inicial por saldo deudor',
|
||||
title: 'Aviso inicial',
|
||||
sections: {
|
||||
introduction: {
|
||||
title: 'Estimado cliente',
|
||||
description: `Por el presente escrito te comunicamos que, según nuestros
|
||||
datos contables, tu cuenta tiene un saldo pendiente de liquidar.`,
|
||||
},
|
||||
},
|
||||
checkExtract: `Te solicitamos compruebes 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.`,
|
||||
checkValidData: `Si al comprobar los datos aportados resultaran correctos,
|
||||
te rogamos procedas a regularizar tu situación.`,
|
||||
payMethod: `Si no deseas desplazarte personalmente hasta nuestras oficinas,
|
||||
puedes realizar el pago mediante transferencia bancaria a la cuenta que figura al pie del comunicado,
|
||||
indicando tu número de cliente, o bien puedes realizar el pago online desde nuestra página web.`,
|
||||
conclusion: 'De antemano te agradecemos tu amable colaboración.',
|
||||
transferAccount: 'Datos para transferencia bancaria',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
body {
|
||||
background-color: #EEE
|
||||
}
|
||||
|
||||
.container {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: #FFF;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: #8dba25
|
||||
}
|
||||
|
||||
.main h1 {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.main h3 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #95d831;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 35px 0
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="container">
|
||||
<!-- Header component -->
|
||||
<email-header></email-header>
|
||||
<!-- End header component -->
|
||||
|
||||
<section class="main">
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<h1>{{ $t('sections.introduction.title') }},</h1>
|
||||
<p v-html="`${$t('sections.introduction.description')}:`"></p>
|
||||
|
||||
<p>
|
||||
<section>
|
||||
<span>{{ $t('sections.pay.method') }}:</span>
|
||||
<strong>{{ payMethodName }}</strong>
|
||||
</section>
|
||||
<section v-if="payMethodFk != 5">
|
||||
<span>{{ $t('sections.pay.day') }}:</span>
|
||||
<strong>{{ $t('sections.pay.dueDay', [dueDay]) }}</strong>
|
||||
</section>
|
||||
</p>
|
||||
|
||||
<p v-if="payMethodFk == 4" v-html="$t('sections.pay.accountImplicates', [accountAddress])"></p>
|
||||
<p v-else-if="payMethodFk == 5">
|
||||
{{ $t('sections.pay.cardImplicates') }}
|
||||
</p>
|
||||
|
||||
<p>{{ $t('notifyAnError') }}</p>
|
||||
</section>
|
||||
<!-- Footer component -->
|
||||
<email-footer></email-footer>
|
||||
<!-- End footer component -->
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const emailHeader = require('../email-header');
|
||||
const emailFooter = require('../email-footer');
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'payment-update',
|
||||
async asyncData(ctx, params) {
|
||||
const data = {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
|
||||
if (!params.clientFk)
|
||||
throw new UserException('No client id specified');
|
||||
|
||||
return this.methods.fetchClientData(params.clientFk)
|
||||
.then(([result]) => {
|
||||
return Object.assign(data, result[0]);
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.$i18n.locale = this.locale;
|
||||
},
|
||||
methods: {
|
||||
fetchClientData(clientFk) {
|
||||
return database.pool.query(`
|
||||
SELECT
|
||||
u.lang locale,
|
||||
c.email recipient,
|
||||
c.dueDay,
|
||||
c.iban,
|
||||
pm.id payMethodFk,
|
||||
pm.name payMethodName
|
||||
FROM client c
|
||||
JOIN payMethod pm ON pm.id = c.payMethodFk
|
||||
JOIN account.user u ON u.id = c.id
|
||||
WHERE c.id = ?`, [clientFk]);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
accountAddress: function() {
|
||||
return this.iban.slice(-4);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'email-header': emailHeader,
|
||||
'email-footer': emailFooter,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
subject: 'Cambios en las condiciones de pago',
|
||||
title: 'Cambios en las condiciones',
|
||||
sections: {
|
||||
introduction: {
|
||||
title: 'Estimado cliente',
|
||||
description: `Le informamos que han cambiado las condiciones de pago de su cuenta.
|
||||
<br/>A continuación le indicamos las nuevas condiciones`,
|
||||
},
|
||||
pay: {
|
||||
method: 'Método de pago',
|
||||
day: 'Día de pago',
|
||||
dueDay: '{0} de cada mes',
|
||||
cardImplicates: `Su modo de pago actual implica que deberá abonar el
|
||||
importe de los pedidos realizados en el mismo día para que se puedan enviar.`,
|
||||
accountImplicates: `Su modo de pago actual implica que se le pasará un cargo a la
|
||||
cuenta terminada en <strong>"{0}"</strong> por el importe pendiente, al vencimiento establecido en las condiciones.`,
|
||||
},
|
||||
},
|
||||
notifyAnError: `En el caso de detectar algún error en los datos indicados
|
||||
o para cualquier aclaración, debes dirigirte a tu comercial.`,
|
||||
},
|
||||
fr: {
|
||||
subject: 'Changement des C.G.V',
|
||||
title: 'Changement des C.G.V',
|
||||
sections: {
|
||||
introduction: {
|
||||
title: 'Chèr client',
|
||||
description: `Nous vous informons que les conditions de paiement ont changé.
|
||||
<br/>Voici les nouvelles conditions`,
|
||||
},
|
||||
pay: {
|
||||
method: 'Méthode de paiement',
|
||||
day: 'Date paiement',
|
||||
dueDay: '{0} de chaque mois',
|
||||
cardImplicates: `Avec votre mode de règlement vous devrez
|
||||
payer le montant des commandes avant son départ.`,
|
||||
accountImplicates: `Avec ce mode de règlement nous vous passerons un prélèvement automatique dans votre compte bancaire
|
||||
se termine dans <strong>"{0}"</strong> our le montant dû, au date à terme établi en nos conditions.`,
|
||||
},
|
||||
},
|
||||
notifyAnError: `Pour tout renseignement contactez votre commercial.`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
body {
|
||||
background-color: #EEE
|
||||
}
|
||||
|
||||
.container {
|
||||
font-family: arial, sans-serif;
|
||||
max-width: 600px;
|
||||
min-width: 320px;
|
||||
font-size: 16px;
|
||||
margin: 0 auto;
|
||||
color: #555
|
||||
}
|
||||
|
||||
.main {
|
||||
background-color: #FFF;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.main a {
|
||||
color: #8dba25
|
||||
}
|
||||
|
||||
.main h1 {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.main h3 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: #95d831;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 35px 0
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin: 0
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<section class="container">
|
||||
<!-- Header component -->
|
||||
<email-header></email-header>
|
||||
<!-- End header component -->
|
||||
|
||||
<section class="main">
|
||||
<!-- Title block -->
|
||||
<div class="title">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
</div>
|
||||
<!-- Title block end -->
|
||||
|
||||
<p>{{$t('description.dear')}},</p>
|
||||
<p>{{$t('description.instructions')}}</p>
|
||||
<p v-html="$t('description.followGuide')"></p>
|
||||
<p v-html="$t('description.downloadFrom')"></p>
|
||||
|
||||
<h1>{{$t('sections.QLabel.title')}}</h1>
|
||||
<p>{{$t('sections.QLabel.description')}}:</p>
|
||||
<ol>
|
||||
<li v-for="step in $t('sections.QLabel.steps')">
|
||||
<span v-html="step"></span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<!-- Help section -->
|
||||
<h1>{{$t('sections.help.title')}}</h1>
|
||||
<p>{{$t('sections.help.description')}}</p>
|
||||
<p v-html="$t('sections.help.remoteSupport')"></p>
|
||||
<!-- End help section-->
|
||||
|
||||
<p>
|
||||
<section v-if="salesPersonName">
|
||||
{{$t('salesPersonName')}}: <strong>{{salesPersonName}}</strong>
|
||||
</section>
|
||||
<section v-if="salesPersonPhone">
|
||||
{{$t('salesPersonPhone')}}: <strong>{{salesPersonPhone}}</strong>
|
||||
</section>
|
||||
<section v-if="salesPersonEmail">
|
||||
{{$t('salesPersonEmail')}}:
|
||||
<strong><a v-bind:href="`mailto: salesPersonEmail`" target="_blank">{{salesPersonEmail}}</strong>
|
||||
</section>
|
||||
</p>
|
||||
</section>
|
||||
<!-- Footer component -->
|
||||
<email-footer></email-footer>
|
||||
<!-- End footer component -->
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,51 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const emailHeader = require('../email-header');
|
||||
const emailFooter = require('../email-footer');
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
|
||||
module.exports = {
|
||||
name: 'printer-setup',
|
||||
async asyncData(ctx, params) {
|
||||
const data = {
|
||||
isPreview: ctx.method === 'GET',
|
||||
};
|
||||
|
||||
if (!params.clientFk)
|
||||
throw new UserException('No client id specified');
|
||||
|
||||
return this.methods.fetchClientData(params.clientFk)
|
||||
.then(([result]) => {
|
||||
return Object.assign(data, result[0]);
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.$i18n.locale = this.locale;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
attachments: ['/assets/files/model.ezp'],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchClientData(clientFk) {
|
||||
return database.pool.query(`
|
||||
SELECT
|
||||
c.id,
|
||||
u.lang locale,
|
||||
u.name AS userName,
|
||||
c.email recipient,
|
||||
CONCAT(w.name, ' ', w.firstName) salesPersonName,
|
||||
w.phone AS salesPersonPhone,
|
||||
CONCAT(wu.name, '@verdnatura.es') AS salesPersonEmail
|
||||
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
|
||||
WHERE c.id = ?`, [clientFk]);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'email-header': emailHeader,
|
||||
'email-footer': emailFooter,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
subject: 'Instalación y configuración de impresora',
|
||||
title: '¡Gracias por tu confianza!',
|
||||
description: {
|
||||
dear: 'Estimado cliente',
|
||||
instructions: 'Sigue las intrucciones especificadas en este correo para llevar a cabo la instalación de la impresora.',
|
||||
followGuide: `Puedes utilizar como guía, el video del montaje del ribon y la cinta
|
||||
<a href="https://www.youtube.com/watch?v=qhb0kgQF3o8" title="Youtube" target="_blank" style="color:#8dba25">https://www.youtube.com/watch?v=qhb0kgQF3o8</a>.
|
||||
También necesitarás el QLabel, el programa para imprimir las cintas.`,
|
||||
downloadFrom: `Puedes descargarlo desde este enlace
|
||||
<a href="http://ww.godexintl.com/es1/download/downloads/Download/2996" title="Descargar QLabel"
|
||||
target="_blank" style="color:#8dba25">http://ww.godexintl.com/es1/download/downloads/Download/2996</a>`,
|
||||
},
|
||||
sections: {
|
||||
QLabel: {
|
||||
title: 'Utilización de QLabel',
|
||||
description: 'Para utilizar el programa de impresión de cintas sigue estos pasos',
|
||||
steps: [
|
||||
'Abre el programa QLabel',
|
||||
'Haz clic en el icono de la barra superior con forma de "carpeta"',
|
||||
'Selecciona el archivo llamado "model.ezp" adjunto en este correo',
|
||||
'Haz clic <strong>encima del texto</strong> con el boton secundario del ratón',
|
||||
'Elige la primera opcion "setup"',
|
||||
'Cambia el texto para imprimir',
|
||||
'Haz clic en el boton "Ok"',
|
||||
'Desplázate con el raton para ver la medida máxima',
|
||||
'Haz clic <strong>encima del texto</strong> con el botón secundario del ratón',
|
||||
'Elige la primera opcion "Setup printer"',
|
||||
'Haz clic en la primera pestalla "Label Setup"',
|
||||
'Modifica la propidad "Paper Height"',
|
||||
'Haz clic en el boton "Ok"',
|
||||
'Haz clic sobre el icono de la impresora',
|
||||
'Haz clic en "Print"',
|
||||
],
|
||||
},
|
||||
help: {
|
||||
title: '¿Necesitas ayuda?',
|
||||
description: `Si necesitas ayuda, descárgate nuestro programa de soporte para poder conectarnos
|
||||
remotamente a tu equipo y hacerte la instalación. Proporciónanos un horario de contacto para atenderte, y contactaremos contigo.`,
|
||||
remoteSupport: `Puedes descargarte el programa desde este enlace
|
||||
<a href="http://soporte.verdnatura.es" title="Soporte Verdnatura" target="_blank" style="color:#8dba25">http://soporte.verdnatura.es</a>.`,
|
||||
},
|
||||
},
|
||||
help: 'Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para atenderte!</strong>',
|
||||
salesPersonName: 'Soy tu comercial y mi nombre es',
|
||||
salesPersonPhone: 'Teléfono y whatsapp',
|
||||
salesPersonEmail: 'Dirección de e-mail',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
const database = require('./lib/database');
|
||||
const smtp = require('./lib/smtp');
|
||||
|
||||
module.exports = app => {
|
||||
global.appPath = __dirname;
|
||||
|
||||
process.env.OPENSSL_CONF = '/etc/ssl/';
|
||||
|
||||
// Init database instance
|
||||
database.init();
|
||||
// Init SMTP Instance
|
||||
smtp.init();
|
||||
|
||||
require('./lib/router')(app);
|
||||
require('./lib/errorHandler')(app);
|
||||
};
|
||||
|
||||
|