refactor
gitea/salix/1466-print_refactor This commit looks good Details

This commit is contained in:
Joan Sanchez 2019-10-29 07:46:44 +01:00
parent 0eaba04e52
commit 443aa56fed
132 changed files with 2419 additions and 774 deletions

View File

@ -18,6 +18,7 @@ export default class Controller {
this.$.balanceCreateDialog.show(); this.$.balanceCreateDialog.show();
}, name: 'Payment on account...', always: true} }, name: 'Payment on account...', always: true}
]; ];
console.log(this.$stateParams);
} }
setBalanceCreateDialog() { setBalanceCreateDialog() {

20
package-lock.json generated
View File

@ -4769,7 +4769,7 @@
"dot-prop": { "dot-prop": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
"requires": { "requires": {
"is-obj": "^1.0.0" "is-obj": "^1.0.0"
} }
@ -4937,7 +4937,7 @@
}, },
"jsonfile": { "jsonfile": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -7485,7 +7485,7 @@
}, },
"kind-of": { "kind-of": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=",
"dev": true "dev": true
}, },
@ -9120,7 +9120,7 @@
}, },
"jasmine-core": { "jasmine-core": {
"version": "2.99.1", "version": "2.99.1",
"resolved": "http://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
"integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
"dev": true "dev": true
}, },
@ -11194,7 +11194,7 @@
"dependencies": { "dependencies": {
"commander": { "commander": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "http://registry.npmjs.org/commander/-/commander-1.0.4.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-1.0.4.tgz",
"integrity": "sha1-Xt6xruI8T7VBprcNaSq+8ZZpotM=", "integrity": "sha1-Xt6xruI8T7VBprcNaSq+8ZZpotM=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -13041,7 +13041,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "1.1.14", "version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -13053,7 +13053,7 @@
}, },
"string_decoder": { "string_decoder": {
"version": "0.10.31", "version": "0.10.31",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true "dev": true
}, },
@ -13500,7 +13500,7 @@
"dependencies": { "dependencies": {
"jsesc": { "jsesc": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true "dev": true
} }
@ -14321,7 +14321,7 @@
}, },
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -15885,7 +15885,7 @@
"touch": { "touch": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", "integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
"dev": true, "dev": true,
"requires": { "requires": {
"nopt": "~1.0.10" "nopt": "~1.0.10"

View File

@ -1,3 +1,7 @@
/**
* Email only stylesheet
*
*/
body { body {
background-color: #EEE background-color: #EEE
} }

View File

@ -1,3 +1,7 @@
/**
* CSS layout elements
*
*/
.container { .container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif; font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px font-size: 16px

View File

@ -1,3 +1,7 @@
/**
* CSS misc classes
*
*/
.uppercase { .uppercase {
text-transform: uppercase text-transform: uppercase
} }

View File

@ -1,3 +1,7 @@
/**
* Report only stylesheet
*
*/
body { body {
zoom: 0.55 zoom: 0.55
} }

View File

@ -1,25 +0,0 @@
[
{"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": "email", "name": "claim-pickup-order"},
{"type": "email", "name": "sepa-core"},
{"type": "email", "name": "client-lcr"},
{"type": "email", "name": "driver-route"},
{"type": "email", "name": "delivery-note"},
{"type": "report", "name": "rpt-delivery-note"},
{"type": "report", "name": "rpt-claim-pickup-order"},
{"type": "report", "name": "rpt-letter-debtor"},
{"type": "report", "name": "rpt-sepa-core"},
{"type": "report", "name": "rpt-receipt"},
{"type": "report", "name": "rpt-zone"},
{"type": "report", "name": "rpt-route"},
{"type": "report", "name": "rpt-lcr"},
{"type": "report", "name": "rpt-item-label"},
{"type": "static", "name": "email-header"},
{"type": "static", "name": "email-footer"},
{"type": "static", "name": "report-header"},
{"type": "static", "name": "report-footer"}
]

103
print/core/component.js Normal file
View File

@ -0,0 +1,103 @@
const Vue = require('vue');
const VueI18n = require('vue-i18n');
const renderer = require('vue-server-renderer').createRenderer();
Vue.use(VueI18n);
const fs = require('fs');
const yaml = require('js-yaml');
const juice = require('juice');
const path = require('path');
const config = require('./config');
class Component {
constructor(name) {
this.name = name;
}
get path() {
return `./components/${this.name}`;
}
get template() {
const templatePath = `${this.path}/${this.name}.html`;
const fullPath = path.resolve(__dirname, templatePath);
return fs.readFileSync(fullPath, 'utf8');
}
get locale() {
const mergedLocale = {messages: {}};
const localePath = path.resolve(__dirname, `${this.path}/locale`);
if (!fs.existsSync(localePath))
return mergedLocale;
const localeDir = fs.readdirSync(localePath);
localeDir.forEach(locale => {
const fullPath = path.join(localePath, '/', locale);
const yamlLocale = fs.readFileSync(fullPath, 'utf8');
const jsonLocale = yaml.safeLoad(yamlLocale);
const localeName = locale.replace('.yml', '');
mergedLocale.messages[localeName] = jsonLocale;
});
return mergedLocale;
}
get stylesheet() {
let mergedStyles = '';
const stylePath = path.resolve(__dirname, `${this.path}/assets/css`);
if (!fs.existsSync(stylePath))
return mergedStyles;
const styleDir = fs.readdirSync(stylePath);
styleDir.forEach(fileName => {
const fullPath = path.join(stylePath, '/', fileName);
const contents = fs.readFileSync(fullPath, 'utf8');
mergedStyles += contents;
});
return mergedStyles;
}
get attachments() {
const attachmentsPath = `${this.path}/attachments.json`;
const fullPath = path.resolve(__dirname, attachmentsPath);
if (!fs.existsSync(fullPath))
return [];
return require(fullPath);
}
build() {
const component = require(`${this.path}/${this.name}`);
component.i18n = this.locale;
component.attachments = this.attachments;
component.template = juice.inlineContent(this.template, this.stylesheet, {
inlinePseudoElements: true
});
return component;
}
async render() {
const component = this.build();
const i18n = new VueI18n(config.i18n);
const app = new Vue({
i18n: i18n,
render: h => h(component, {
props: this.args
})
});
return renderer.renderToString(app);
}
}
module.exports = Component;

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: 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

@ -0,0 +1,27 @@
[
{
"filename": "facebook.png",
"path": "/assets/images/facebook.png",
"cid": "facebook.png"
}, {
"filename": "twitter.png",
"path": "/assets/images/twitter.png",
"cid": "twitter.png"
}, {
"filename": "youtube.png",
"path": "/assets/images/youtube.png",
"cid": "youtube.png"
}, {
"filename": "pinterest.png",
"path": "/assets/images/pinterest.png",
"cid": "pinterest.png"
}, {
"filename": "instagram.png",
"path": "/assets/images/instagram.png",
"cid": "instagram.png"
}, {
"filename": "linkedin.png",
"path": "/assets/images/linkedin.png",
"cid": "linkedin.png"
}
]

View File

@ -4,13 +4,13 @@
<a href="https://www.verdnatura.es" target="_blank"> <a href="https://www.verdnatura.es" target="_blank">
<div class="btn"> <div class="btn">
<span class="text">{{ $t('buttons.webAcccess')}}</span> <span class="text">{{ $t('buttons.webAcccess')}}</span>
<span class="icon"><img :src="embeded['/assets/images/action.png']"/></span> <span class="icon"><img :src="/assets/images/action.png"/></span>
</div> </div>
</a> </a>
<a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank"> <a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank">
<div class="btn"> <div class="btn">
<span class="text">{{ $t('buttons.info')}}</span> <span class="text">{{ $t('buttons.info')}}</span>
<span class="icon"><img :src="embeded['/assets/images/info.png']"/></span> <span class="icon"><img :src="/assets/images/info.png"/></span>
</div> </div>
</a> </a>
</section> --> </section> -->
@ -19,22 +19,22 @@
<!-- Networks block --> <!-- Networks block -->
<section class="networks"> <section class="networks">
<a href="https://www.facebook.com/Verdnatura" target="_blank"> <a href="https://www.facebook.com/Verdnatura" target="_blank">
<img :src="embeded['/assets/images/facebook.png']" alt="Facebook"/> <img :src="getSrc('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="embeded['/assets/images/twitter.png']" alt="Twitter"/> <img :src="getSrc('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="embeded['/assets/images/youtube.png']" alt="Youtube"/> <img :src="getSrc('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="embeded['/assets/images/pinterest.png']" alt="Pinterest"/> <img :src="getSrc('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="embeded['/assets/images/instagram.png']" alt="Instagram"/> <img :src="getSrc('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="embeded['/assets/images/linkedin.png']" alt="Linkedin"/> <img :src="getSrc('linkedin.png')" alt="Linkedin"/>
</a> </a>
</section> </section>
<!-- Networks block end --> <!-- Networks block end -->

View File

@ -0,0 +1,4 @@
module.exports = {
name: 'email-footer',
props: ['isPreview', 'locale']
};

View File

@ -0,0 +1,19 @@
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 has recibido este mensaje
por error, te rogamos lo comuniques al remitente y borres 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, te comunicamos que los datos personales que facilites
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.

View File

@ -0,0 +1,19 @@
buttons:
webAcccess: Visitez notre site 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 - Ce message est privé et confidentiel et doit être utilisé.exclusivamente
por la persona destinataria del mismo. Si has recibido este mensajepor error,
te rogamos lo comuniques al remitente y borres dicho mensaje y cualquier documentoadjunto
que pudiera contener. Verdnatura Levante SL no renuncia a la confidencialidad
ni aningún privilegio por causa de transmisión errónea o mal funcionamiento. Igualmente
no se haceresponsable 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,te comunicamos que los datos personales que facilites
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 porescrito al domicilio social de la entidad. La finalidad del fichero
es la gestión administrativa, contabilidad, y facturación.

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,7 @@
[
{
"filename": "email-logo.png",
"path": "/assets/images/email-logo.png",
"cid": "email-logo.png"
}
]

View File

@ -0,0 +1,5 @@
<header>
<a href="https://www.verdnatura.es" target="_blank">
<img :src="getSrc('email-logo.png')" alt="VerdNatura"/>
</a>
</header>

View File

@ -0,0 +1,4 @@
module.exports = {
name: 'email-header',
props: ['isPreview']
};

View File

@ -0,0 +1,10 @@
numPages: Página {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE SL - Pasaporte Fitosanitario R.P. Generalitat
Valenciana - Nº Comerciante: ES17462130'
privacy: 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.

View File

@ -0,0 +1,10 @@
numPages: Page {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE SL - Passeport Phytosanitaire R.P. Generalitat
Valenciana - Numéro d''opérateur: ES17462130'
privacy: Conformément aux dispositions de la loi organique 15/1999 sur la protection
des données personnelles, nous vous informons que les données personnelles que
vous fournissez seront incluses dans des dossiers. VERDNATURA LEVANTE S.L., vous
pouvez à tout moment, exercer les droits d'accès, de rectification, d'annulation
et d'opposition, en communiquant par écrit au siège social de la société. Le dossier
a pour objet la gestion administrative, la comptabilité et la facturation.

View File

@ -0,0 +1,10 @@
numPages: Página {{page}} de {{pages}}
law:
phytosanitary: 'VERDNATURA LEVANTE S.L - Passaporte Fitossanitário R.P. Generalitat
Valenciana - Nº Comerciante: ES17462130'
privacy: Em cumprimento do disposto na lei Orgânica 15/1999, de Protecção de Dados
de Carácter Pessoal, comunicamos que os dados pessoais que facilite se incluirão
nos ficheiros automatizados de VERDNATURA LEVANTE S.L., podendo em todo momento
exercer os direitos de acesso, rectificação, cancelação e oposição, comunicando
por escrito ao domicílio social da entidade. A finalidade do ficheiro é a gestão
administrativa, contabilidade e facturação.

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -0,0 +1,5 @@
company:
fiscalAddress: VERDNATURA LEVANTE S.L., B97367486 Avda. Espioca, 100, 46460 Silla
- www.verdnatura.es - clientes@verdnatura.es
registry: 'CIF: B97367486 Registro Mercantil de Valencia, Tomo 8041, Libro 5334,
Folio 160, Sección 8, Hoja V 102076'

View File

@ -0,0 +1,5 @@
company:
fiscalAddress: VERDNATURA LEVANTE S.L., B97367486 Avda. Espioca, 100, 46460 Silla
- www.verdnatura.es - clientes@verdnatura.es
registry: 'CIF: B97367486 Registro Mercantil de Valencia, Tomo 8041, Libro 5334,
Folio 160, Sección 8, Hoja V 102076'

View File

@ -0,0 +1 @@
// Import global directives

View File

@ -1,26 +1,92 @@
const Vue = require('vue'); const path = require('path');
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 smtp = require('./smtp');
const fallbackLocale = 'es'; const Component = require('./component');
const Report = require('./report');
if (!process.env.OPENSSL_CONF) if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/'; process.env.OPENSSL_CONF = '/etc/ssl/';
Vue.use(VueI18n); class Email extends Component {
constructor(name, args) {
super(name);
module.exports = { this.args = args;
}
get path() {
return `../templates/email/${this.name}`;
}
get subject() {
return null;
}
async send() {
const instance = this.build();
const rendered = await this.render();
const attachments = [];
const getAttachments = async(componentPath, files) => {
for (file of files) {
const fileCopy = Object.assign({}, file);
if (fileCopy.cid) {
const templatePath = `${componentPath}/${file.path}`;
const fullFilePath = path.resolve(__dirname, templatePath);
fileCopy.path = path.resolve(__dirname, fullFilePath);
} else {
const reportName = fileCopy.filename.replace('.pdf', '');
const report = new Report(reportName, this.args);
fileCopy.content = await report.toPdfStream();
}
attachments.push(fileCopy);
}
};
if (instance.components) {
const components = instance.components;
for (let componentName in components) {
const component = components[componentName];
const componentPath = `../components/${componentName}`;
await getAttachments(componentPath, component.attachments);
}
}
if (this.attachments)
await getAttachments(this.path, this.attachments);
/*
this.attachments.forEach(file => {
const fileCopy = Object.assign({}, file);
if (fileCopy.path) {
const templatePath = `${this.path}/${file.path}`;
const fullFilePath = path.resolve(__dirname, templatePath);
fileCopy.path = path.resolve(__dirname, fullFilePath);
}
attachments.push(fileCopy);
}); */
const options = {
to: this.args.recipient,
subject: 'Test',
html: rendered,
attachments: attachments
};
return smtp.send(options);
}
}
module.exports = Email;
/* module.exports = {
path: `${appPath}/report`, path: `${appPath}/report`,
/**
* Renders a report component
*
* @param {String} name - Report name
* @param {Object} ctx - Request context
*/
async render(name, ctx) { async render(name, ctx) {
const component = require(`${this.path}/${name}`); const component = require(`${this.path}/${name}`);
const result = await this.preFetch(component, ctx); const result = await this.preFetch(component, ctx);
@ -39,12 +105,6 @@ module.exports = {
}); });
}, },
/**
* Prefetch all component data from asyncData method
*
* @param {Object} orgComponent - Component object
* @param {Object} ctx - Request context
*/
async preFetch(orgComponent, ctx) { async preFetch(orgComponent, ctx) {
let component = Object.create(orgComponent); let component = Object.create(orgComponent);
let mergedData = {attachments: []}; let mergedData = {attachments: []};
@ -150,3 +210,4 @@ module.exports = {
return smtp.send(options); return smtp.send(options);
}, },
}; };
*/

View File

@ -0,0 +1,7 @@
const Vue = require('vue');
const currency = {
methods: {
},
};
Vue.mixin(currency);

View File

@ -0,0 +1 @@
// Import global filters

View File

@ -0,0 +1,15 @@
const Vue = require('vue');
const imageSrc = {
methods: {
getSrc(image) {
let src = `cid:${image}`;
if (this.isPreview === 'true')
src = `/api/assets/${this.$options.name}/images/${image}`;
return src;
}
},
};
Vue.mixin(imageSrc);

View File

@ -0,0 +1,2 @@
// Import global mixins
require('./image-src');

View File

@ -1,66 +1,20 @@
const Vue = require('vue');
const VueI18n = require('vue-i18n');
const renderer = require('vue-server-renderer').createRenderer();
const fs = require('fs'); const fs = require('fs');
const pdf = require('html-pdf'); const pdf = require('html-pdf');
const juice = require('juice');
const path = require('path');
const config = require('./config'); const config = require('./config');
const reportsPath = '../templates/reports'; const Component = require('./component');
if (!process.env.OPENSSL_CONF) if (!process.env.OPENSSL_CONF)
process.env.OPENSSL_CONF = '/etc/ssl/'; process.env.OPENSSL_CONF = '/etc/ssl/';
Vue.use(VueI18n); class Report extends Component {
class Report {
constructor(name, args) { constructor(name, args) {
this.name = name; super(name);
this.args = args; this.args = args;
} }
get path() { get path() {
return `${reportsPath}/${this.name}`; return `../templates/reports/${this.name}`;
}
get template() {
const templatePath = `${this.path}/${this.name}.html`;
const fullPath = path.resolve(__dirname, templatePath);
return fs.readFileSync(fullPath, 'utf8');
}
get locale() {
}
get style() {
}
async render() {
const localePath = `${this.path}/locale`;
const stylePath = `${this.path}/assets/css/index`;
const stylesheet = require(stylePath);
const component = require(this.path);
component.i18n = require(localePath);
component.template = juice.inlineContent(this.template, stylesheet, {
inlinePseudoElements: true
});
const i18n = new VueI18n(config.i18n);
const app = new Vue({
i18n: i18n,
render: h => h(component, {
props: this.args
})
});
return renderer.renderToString(app);
} }
async toPdfStream() { async toPdfStream() {

View File

@ -1,13 +1,25 @@
const Report = require('./report');
const emailEngine = require('./emailEngine');
const express = require('express'); const express = require('express');
const path = require('path'); const path = require('path');
const vue = require('vue');
const fs = require('fs'); const fs = require('fs');
const Report = require('./report');
const Email = require('./email');
const templatesPath = path.resolve(__dirname, '../templates'); const templatesPath = path.resolve(__dirname, '../templates');
const componentsPath = path.resolve(__dirname, './components');
module.exports = app => { module.exports = app => {
/**
* Serve component static files
*/
const componentsDir = fs.readdirSync(componentsPath);
componentsDir.forEach(componentName => {
const componentDir = path.join(componentsPath, '/', componentName);
const assetsDir = `${componentDir}/assets`;
app.use(`/api/assets/${componentName}`, express.static(assetsDir));
});
/** /**
* Serve static files * Serve static files
*/ */
@ -20,7 +32,7 @@ module.exports = app => {
const templateDir = path.join(templatesPath, '/', directory, '/', templateName); const templateDir = path.join(templatesPath, '/', directory, '/', templateName);
const assetsDir = `${templateDir}/assets`; const assetsDir = `${templateDir}/assets`;
app.use(`/api/assets`, express.static(assetsDir)); app.use(`/api/assets/${templateName}`, express.static(assetsDir));
}); });
}); });
@ -28,19 +40,34 @@ module.exports = app => {
const args = Object.assign({}, req.body, req.query); const args = Object.assign({}, req.body, req.query);
const report = new Report(req.params.name, args); const report = new Report(req.params.name, args);
const stream = await report.toPdfStream(); const stream = await report.toPdfStream();
res.setHeader('Content-type', 'application/pdf'); res.setHeader('Content-type', 'application/pdf');
stream.pipe(res); stream.pipe(res);
}); });
/* app.get(`/api/email/${name}`, (request, response) => { app.get(`/api/email/:name`, async(req, res) => {
emailEngine.render(name, request).then(rendered => { const args = req.query;
response.send(rendered.html); const requiredArgs = ['userId'];
}).catch(e => {
next(e); const hasRequiredArgs = requiredArgs.every(arg => {
return args[arg];
}); });
if (!hasRequiredArgs)
res.json({message: 'userId'});
const email = new Email(req.params.name, args);
if (args.isPreview === 'true') {
const rendered = await email.render();
res.send(rendered);
} else {
await email.send();
res.status(200).json({message: 'Sent'});
}
}); });
/*
app.post(`/api/email/${name}`, (request, response, next) => { app.post(`/api/email/${name}`, (request, response, next) => {
emailEngine.toEmail(name, request).then(() => { emailEngine.toEmail(name, request).then(() => {
response.status(200).json({status: 200}); response.status(200).json({status: 200});

View File

@ -1,5 +1,5 @@
const nodemailer = require('nodemailer'); const nodemailer = require('nodemailer');
const config = require('./config.js'); const config = require('./config');
module.exports = { module.exports = {
init() { init() {
@ -11,7 +11,7 @@ module.exports = {
options.from = `${config.app.senderName} <${config.app.senderMail}>`; options.from = `${config.app.senderName} <${config.app.senderMail}>`;
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
if (!config.smtp.user) if (!config.smtp.auth.user)
return Promise.resolve(true); return Promise.resolve(true);
options.to = config.app.senderMail; options.to = config.app.senderMail;

View File

@ -1,7 +0,0 @@
module.exports = {
methods: {
uFirst: (text) => {
return text;
},
},
};

View File

@ -25,6 +25,14 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
}, },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"asn1": { "asn1": {
"version": "0.2.4", "version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
@ -309,6 +317,11 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"extend": { "extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -519,6 +532,15 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
}, },
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsbn": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@ -997,6 +1019,11 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI="
}, },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sqlstring": { "sqlstring": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",

View File

@ -15,6 +15,7 @@
"dependencies": { "dependencies": {
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"html-pdf": "^2.2.0", "html-pdf": "^2.2.0",
"js-yaml": "^3.13.1",
"juice": "^5.0.1", "juice": "^5.0.1",
"mysql2": "^1.6.5", "mysql2": "^1.6.5",
"nodemailer": "^4.7.0", "nodemailer": "^4.7.0",

View File

@ -4,11 +4,16 @@ module.exports = app => {
process.env.OPENSSL_CONF = '/etc/ssl/'; process.env.OPENSSL_CONF = '/etc/ssl/';
// Init database instance // Init database instance
require('./lib/database').init(); require('./core/database').init();
// Init SMTP Instance // Init SMTP Instance
require('./lib/smtp').init(); require('./core/smtp').init();
require('./lib/router')(app); require('./core/router')(app);
require('./core/mixins');
require('./core/filters');
require('./core/directives');
// require('./core/components/email-header/email-header');
}; };

View File

@ -0,0 +1,44 @@
/**
* Email only stylesheet
*
*/
body {
background-color: #EEE
}
.container {
max-width: 600px;
min-width: 320px;
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
}

View File

@ -1,7 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${appPath}/common/css/misc.css`])
.mergeStyles();

View File

@ -0,0 +1,233 @@
/**
* CSS layout elements
*
*/
.container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px
}
.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
}
.clearfix {
overflow: hidden;
display: block;
clear: both
}
.panel {
position: relative;
margin-bottom: 15px;
padding-top: 10px;
break-inside: avoid;
break-before: always;
break-after: always;
}
.panel .header {
background-color: #FFF;
padding: 2.5px 10px;
position: absolute;
font-weight: bold;
top: 0px;
left: 17.5px;
}
.panel .body {
border: 1px solid #CCC;
overflow: hidden;
padding: 20px
}
.panel .body h3 {
margin-top: 0
}
.panel.dark .header {
border: 1px solid #808080;
background-color: #FFF;
}
.panel.dark .body {
border: 1px solid #808080;
background-color: #c0c0c0
}
.field {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-top: 1px solid #CCC;
float: left
}
.field span {
border-right: 1px solid #CCC;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: table-cell;
vertical-align: middle;
text-align: center;
font-weight: bold
}
.field.square span {
height: 35.4px;
width: 35.4px
}
.emptyField {
border-bottom: 1px dotted grey;
min-height: 1em;
display: block
}
.field.rectangle span {
height: 2em;
width: 8em
}
.pull-left {
float: left !important
}
.pull-right {
float: right !important
}
.vertical-text {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
position: absolute;
text-align: center;
font-size: .65em;
right: -108px;
width: 200px;
top: 50%
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.row-oriented, .column-oriented {
text-align: left;
width: 100%
}
.column-oriented {
margin-bottom: 15px
}
.column-oriented td,
.column-oriented th {
padding: 5px 10px
}
.column-oriented thead {
background-color: #e5e5e5
}
.column-oriented thead tr {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #e5e5e5
}
.column-oriented tfoot {
border-top: 2px solid #808080;
}
.column-oriented tfoot tr:first-child td {
padding-top: 20px !important;
}
.column-oriented .description {
border-bottom: 1px solid #DDD;
font-size: 0.8em
}
.panel .row-oriented td, .panel .row-oriented th {
padding: 10px 0
}
.row-oriented > tbody > tr > td {
width: 30%
}
.row-oriented > tbody > tr > th {
padding-left: 30px;
width: 70%
}
.row-oriented .description {
padding: 0 !important;
font-size: 0.6em;
color: #888
}
.line {
border-bottom: 1px solid #DDD;
border-right: 1px solid #DDD;
border-left: 1px solid #DDD;
position: relative;
margin-left: -1px;
margin-right: 1px;
margin-top: 10px;
color: #999;
padding: 5px 0
}
.line .vertical-aligned {
position: absolute;
text-align: center;
width: 100%;
}
.line span {
background-color: #FFF;
padding: 5px
}
.signature {
width: 100%
}
.signature section {
height: 150px
}
.signature p {
margin-right: 50%;
margin-top: 140px
}

View File

@ -0,0 +1,47 @@
/**
* CSS misc classes
*
*/
.uppercase {
text-transform: uppercase
}
.justified {
text-align: justify
}
.centered {
text-align: center
}
.align-right {
text-align: right
}
.align-left {
text-align: left
}
.number {
text-align: right
}
.font.gray {
color: #555
}
.font.light-gray {
color: #888
}
.font.small {
font-size: 0.65em
}
.font.bold {
font-weight: bold
}
.non-page-break {
page-break-inside: avoid;
}

View File

@ -0,0 +1,3 @@
[{
"filename": "claim-pickup-order.pdf"
}]

View File

@ -6,7 +6,7 @@
<body> <body>
<section class="container"> <section class="container">
<!-- Header component --> <!-- Header component -->
<email-header></email-header> <email-header :is-preview="isPreview"></email-header>
<!-- End header component --> <!-- End header component -->
<section class="main"> <section class="main">
<!-- Title block --> <!-- Title block -->
@ -15,6 +15,8 @@
</div> </div>
<!-- Title block end --> <!-- Title block end -->
{{$t('testing')}}
<p>{{$t('description.dear')}},</p> <p>{{$t('description.dear')}},</p>
<p>{{$t('description.instructions')}}</p> <p>{{$t('description.instructions')}}</p>
@ -27,9 +29,14 @@
</ol> </ol>
<p>{{$t('sections.howToBuy.stock')}}</p> <p>{{$t('sections.howToBuy.stock')}}</p>
<p>{{$t('sections.howToBuy.delivery')}}</p> --> <p>{{$t('sections.howToBuy.delivery')}}</p> -->
<section>
<section v-for="attachment in attachments">
<a href="/api/report/claim-pickup-order?userId=106&claimId=5" target="_blank">Ver PDF</a>
</section>
</section>
</section> </section>
<!-- Footer component --> <!-- Footer component -->
<email-footer :locale="locale"></email-footer> <email-footer :is-preview="isPreview" :locale="locale"></email-footer>
<!-- End footer component --> <!-- End footer component -->
</section> </section>
</body> </body>

View File

@ -0,0 +1,39 @@
const db = require(`${appPath}/core/database`);
const Component = require(`${appPath}/core/component`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
const attachments = require('./attachments.json');
module.exports = {
name: 'claim-pickup-order',
/* async serverPrefetch() {
this.client = await this.fetchClient(this.clientId);
},*/
created() {
if (this.locale)
this.$i18n.locale = this.locale;
},
data() {
return {
attachments
};
},
methods: {
fetchClient(claimId) {
return db.findOne(`
SELECT
c.id,
u.lang locale,
c.email recipient
FROM claim cl
JOIN client c ON c.id = cl.clientFk
JOIN account.user u ON u.id = c.id
WHERE cl.id = ?`, [claimId]);
},
},
components: {
'email-header': emailHeader.build(),
'email-footer': emailFooter.build()
},
props: ['claimId', 'isPreview']
};

View File

@ -1,53 +0,0 @@
const UserException = require(`${appPath}/lib/exceptions/userException`);
const reportEngine = require(`${appPath}/lib/reportEngine`);
const database = require(`${appPath}/lib/database`);
const emailHeader = require('../email-header');
const emailFooter = require('../email-footer');
module.exports = {
name: 'claim-pickup-order',
async asyncData(ctx, params) {
const promises = [];
const data = {
isPreview: ctx.method === 'GET',
};
if (!params.claimFk)
throw new UserException('No claim id specified');
promises.push(reportEngine.toPdf('rpt-claim-pickup-order', ctx));
promises.push(this.methods.fetchClient(params.claimFk));
return Promise.all(promises).then(result => {
const stream = result[0];
const [[client]] = result[1];
Object.assign(data, client);
Object.assign(data, {attachments: [{filename: 'claim-pickup-order.pdf', content: stream}]});
return data;
});
},
created() {
if (this.locale)
this.$i18n.locale = this.locale;
},
methods: {
fetchClient(claimFk) {
return database.pool.query(`
SELECT
c.id,
u.lang locale,
c.email recipient
FROM claim cl
JOIN client c ON c.id = cl.clientFk
JOIN account.user u ON u.id = c.id
WHERE cl.id = ?`, [claimFk]);
},
},
components: {
emailHeader,
emailFooter,
},
};

View File

@ -1,25 +0,0 @@
module.exports = {
messages: {
es: {
subject: 'Orden de recogida',
title: 'Orden de recogida',
description: {
dear: 'Estimado cliente',
instructions: 'Aqui tienes tu orden de recogida.'
},
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.',
}
}
},
},
};

View File

@ -0,0 +1,23 @@
subject: Orden de recogida
title: Orden de recogida
description:
dear: Estimado cliente
instructions: Aqui tienes tu orden de recogida.
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.

View File

@ -0,0 +1 @@
../../../../../common/css/email.css

View File

@ -1,7 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${appPath}/common/css/misc.css`])
.mergeStyles();

View File

@ -0,0 +1,233 @@
/**
* CSS layout elements
*
*/
.container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px
}
.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
}
.clearfix {
overflow: hidden;
display: block;
clear: both
}
.panel {
position: relative;
margin-bottom: 15px;
padding-top: 10px;
break-inside: avoid;
break-before: always;
break-after: always;
}
.panel .header {
background-color: #FFF;
padding: 2.5px 10px;
position: absolute;
font-weight: bold;
top: 0px;
left: 17.5px;
}
.panel .body {
border: 1px solid #CCC;
overflow: hidden;
padding: 20px
}
.panel .body h3 {
margin-top: 0
}
.panel.dark .header {
border: 1px solid #808080;
background-color: #FFF;
}
.panel.dark .body {
border: 1px solid #808080;
background-color: #c0c0c0
}
.field {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-top: 1px solid #CCC;
float: left
}
.field span {
border-right: 1px solid #CCC;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: table-cell;
vertical-align: middle;
text-align: center;
font-weight: bold
}
.field.square span {
height: 35.4px;
width: 35.4px
}
.emptyField {
border-bottom: 1px dotted grey;
min-height: 1em;
display: block
}
.field.rectangle span {
height: 2em;
width: 8em
}
.pull-left {
float: left !important
}
.pull-right {
float: right !important
}
.vertical-text {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
position: absolute;
text-align: center;
font-size: .65em;
right: -108px;
width: 200px;
top: 50%
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.row-oriented, .column-oriented {
text-align: left;
width: 100%
}
.column-oriented {
margin-bottom: 15px
}
.column-oriented td,
.column-oriented th {
padding: 5px 10px
}
.column-oriented thead {
background-color: #e5e5e5
}
.column-oriented thead tr {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #e5e5e5
}
.column-oriented tfoot {
border-top: 2px solid #808080;
}
.column-oriented tfoot tr:first-child td {
padding-top: 20px !important;
}
.column-oriented .description {
border-bottom: 1px solid #DDD;
font-size: 0.8em
}
.panel .row-oriented td, .panel .row-oriented th {
padding: 10px 0
}
.row-oriented > tbody > tr > td {
width: 30%
}
.row-oriented > tbody > tr > th {
padding-left: 30px;
width: 70%
}
.row-oriented .description {
padding: 0 !important;
font-size: 0.6em;
color: #888
}
.line {
border-bottom: 1px solid #DDD;
border-right: 1px solid #DDD;
border-left: 1px solid #DDD;
position: relative;
margin-left: -1px;
margin-right: 1px;
margin-top: 10px;
color: #999;
padding: 5px 0
}
.line .vertical-aligned {
position: absolute;
text-align: center;
width: 100%;
}
.line span {
background-color: #FFF;
padding: 5px
}
.signature {
width: 100%
}
.signature section {
height: 150px
}
.signature p {
margin-right: 50%;
margin-top: 140px
}

View File

@ -0,0 +1,47 @@
/**
* CSS misc classes
*
*/
.uppercase {
text-transform: uppercase
}
.justified {
text-align: justify
}
.centered {
text-align: center
}
.align-right {
text-align: right
}
.align-left {
text-align: left
}
.number {
text-align: right
}
.font.gray {
color: #555
}
.font.light-gray {
color: #888
}
.font.small {
font-size: 0.65em
}
.font.bold {
font-weight: bold
}
.non-page-break {
page-break-inside: avoid;
}

View File

@ -6,7 +6,7 @@
<body> <body>
<section class="container"> <section class="container">
<!-- Header component --> <!-- Header component -->
<email-header></email-header> <email-header :is-preview="isPreview"></email-header>
<!-- End header component --> <!-- End header component -->
<section class="main"> <section class="main">
<!-- Title block --> <!-- Title block -->
@ -19,8 +19,8 @@
<p v-html="$t('clientData')"></p> <p v-html="$t('clientData')"></p>
<p> <p>
<div>{{$t('clientId')}}: <strong>{{ id }}</strong></div> <div>{{$t('clientId')}}: <strong>{{client.id}}</strong></div>
<div>{{$t('user')}}: <strong>{{userName}}</strong></div> <div>{{$t('user')}}: <strong>{{client.userName}}</strong></div>
<div>{{$t('password')}}: <strong>********</strong> <div>{{$t('password')}}: <strong>********</strong>
(<a href="https://verdnatura.es">{{$t('passwordResetText')}}</a>) (<a href="https://verdnatura.es">{{$t('passwordResetText')}}</a>)
</div> </div>
@ -52,20 +52,21 @@
<p v-html="$t('help')"></p> <p v-html="$t('help')"></p>
<p> <p>
<section v-if="salesPersonName"> <section v-if="client.salesPersonName">
{{$t('salesPersonName')}}: <strong>{{salesPersonName}}</strong> {{$t('salesPersonName')}}: <strong>{{client.salesPersonName}}</strong>
</section> </section>
<section v-if="salesPersonPhone"> <section v-if="client.salesPersonPhone">
{{$t('salesPersonPhone')}}: <strong>{{salesPersonPhone}}</strong> {{$t('salesPersonPhone')}}: <strong>{{client.salesPersonPhone}}</strong>
</section> </section>
<section v-if="salesPersonEmail"> <section v-if="client.salesPersonEmail">
{{$t('salesPersonEmail')}}: {{$t('salesPersonEmail')}}:
<strong><a v-bind:href="`mailto: ${salesPersonEmail}`" target="_blank">{{salesPersonEmail}}</strong> <strong><a v-bind:href="`mailto: ${client.salesPersonEmail}`" target="_blank">{{client.salesPersonEmail}}</strong>
</section> </section>
</p> </p>
</section> </section>
{{isPreview}}
<!-- Footer component --> <!-- Footer component -->
<email-footer :locale="locale"></email-footer> <email-footer :is-preview="isPreview" :locale="client.locale"></email-footer>
<!-- End footer component --> <!-- End footer component -->
</section> </section>
</body> </body>

View File

@ -1,9 +1,15 @@
const database = require(`${appPath}/lib/database`); const db = require(`${appPath}/lib/database`);
const UserException = require(`${appPath}/lib/exceptions/userException`); const Component = require(`${appPath}/lib/component`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'client-welcome', name: 'client-welcome',
async asyncData(ctx, params) { async serverPrefetch() {
this.client = await this.fetchClient(this.clientId);
},
/* async asyncData(ctx, params) {
const data = { const data = {
isPreview: ctx.method === 'GET', isPreview: ctx.method === 'GET',
}; };
@ -17,14 +23,14 @@ module.exports = {
throw new UserException('No client data found'); throw new UserException('No client data found');
return Object.assign(data, result[0]); return Object.assign(data, result[0]);
}); });
}, }, */
created() { created() {
if (this.locale) if (this.locale)
this.$i18n.locale = this.locale; this.$i18n.locale = this.locale;
}, },
methods: { methods: {
fetchClient(clientFk) { fetchClient(clientId) {
return database.pool.query(` return db.findOne(`
SELECT SELECT
c.id, c.id,
u.lang locale, u.lang locale,
@ -37,11 +43,12 @@ module.exports = {
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.salesPersonFk 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
WHERE c.id = ?`, [clientFk]); WHERE c.id = ?`, [clientId]);
}, },
}, },
components: { components: {
'email-header': require('../email-header'), 'email-header': emailHeader.build(),
'email-footer': require('../email-footer'), 'email-footer': emailFooter.build()
}, },
props: ['clientId', 'isPreview']
}; };

View File

@ -1,55 +0,0 @@
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',
},
},
};

View File

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

View File

@ -0,0 +1,44 @@
/**
* Email only stylesheet
*
*/
body {
background-color: #EEE
}
.container {
max-width: 600px;
min-width: 320px;
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
}

View File

@ -1,7 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${appPath}/common/css/misc.css`])
.mergeStyles();

View File

@ -0,0 +1,233 @@
/**
* CSS layout elements
*
*/
.container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px
}
.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
}
.clearfix {
overflow: hidden;
display: block;
clear: both
}
.panel {
position: relative;
margin-bottom: 15px;
padding-top: 10px;
break-inside: avoid;
break-before: always;
break-after: always;
}
.panel .header {
background-color: #FFF;
padding: 2.5px 10px;
position: absolute;
font-weight: bold;
top: 0px;
left: 17.5px;
}
.panel .body {
border: 1px solid #CCC;
overflow: hidden;
padding: 20px
}
.panel .body h3 {
margin-top: 0
}
.panel.dark .header {
border: 1px solid #808080;
background-color: #FFF;
}
.panel.dark .body {
border: 1px solid #808080;
background-color: #c0c0c0
}
.field {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-top: 1px solid #CCC;
float: left
}
.field span {
border-right: 1px solid #CCC;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: table-cell;
vertical-align: middle;
text-align: center;
font-weight: bold
}
.field.square span {
height: 35.4px;
width: 35.4px
}
.emptyField {
border-bottom: 1px dotted grey;
min-height: 1em;
display: block
}
.field.rectangle span {
height: 2em;
width: 8em
}
.pull-left {
float: left !important
}
.pull-right {
float: right !important
}
.vertical-text {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
position: absolute;
text-align: center;
font-size: .65em;
right: -108px;
width: 200px;
top: 50%
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.row-oriented, .column-oriented {
text-align: left;
width: 100%
}
.column-oriented {
margin-bottom: 15px
}
.column-oriented td,
.column-oriented th {
padding: 5px 10px
}
.column-oriented thead {
background-color: #e5e5e5
}
.column-oriented thead tr {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #e5e5e5
}
.column-oriented tfoot {
border-top: 2px solid #808080;
}
.column-oriented tfoot tr:first-child td {
padding-top: 20px !important;
}
.column-oriented .description {
border-bottom: 1px solid #DDD;
font-size: 0.8em
}
.panel .row-oriented td, .panel .row-oriented th {
padding: 10px 0
}
.row-oriented > tbody > tr > td {
width: 30%
}
.row-oriented > tbody > tr > th {
padding-left: 30px;
width: 70%
}
.row-oriented .description {
padding: 0 !important;
font-size: 0.6em;
color: #888
}
.line {
border-bottom: 1px solid #DDD;
border-right: 1px solid #DDD;
border-left: 1px solid #DDD;
position: relative;
margin-left: -1px;
margin-right: 1px;
margin-top: 10px;
color: #999;
padding: 5px 0
}
.line .vertical-aligned {
position: absolute;
text-align: center;
width: 100%;
}
.line span {
background-color: #FFF;
padding: 5px
}
.signature {
width: 100%
}
.signature section {
height: 150px
}
.signature p {
margin-right: 50%;
margin-top: 140px
}

View File

@ -0,0 +1,47 @@
/**
* CSS misc classes
*
*/
.uppercase {
text-transform: uppercase
}
.justified {
text-align: justify
}
.centered {
text-align: center
}
.align-right {
text-align: right
}
.align-left {
text-align: left
}
.number {
text-align: right
}
.font.gray {
color: #555
}
.font.light-gray {
color: #888
}
.font.small {
font-size: 0.65em
}
.font.bold {
font-weight: bold
}
.non-page-break {
page-break-inside: avoid;
}

View File

@ -0,0 +1,4 @@
[{
"filename": "delivery-note.pdf",
"href": "http://localhost:5000/pi"
}]

View File

@ -6,7 +6,7 @@
<body> <body>
<section class="container"> <section class="container">
<!-- Header component --> <!-- Header component -->
<email-header></email-header> <email-header :is-preview="isPreview" :locale="locale"></email-header>
<!-- End header component --> <!-- End header component -->
<section class="main"> <section class="main">
<!-- Title block --> <!-- Title block -->
@ -20,7 +20,7 @@
<p v-html="$t('help')"></p> <p v-html="$t('help')"></p>
</section> </section>
<!-- Footer component --> <!-- Footer component -->
<email-footer :locale="locale"></email-footer> <email-footer :is-preview="isPreview" :locale="locale"></email-footer>
<!-- End footer component --> <!-- End footer component -->
</section> </section>
</body> </body>

View File

@ -1,6 +1,7 @@
const database = require(`${appPath}/lib/database`); const db = require(`${appPath}/lib/database`);
const reportEngine = require(`${appPath}/lib/reportEngine.js`); const Component = require(`${appPath}/lib/component`);
const UserException = require(`${appPath}/lib/exceptions/userException`); const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
module.exports = { module.exports = {
name: 'delivery-note', name: 'delivery-note',
@ -27,12 +28,11 @@ module.exports = {
}); });
}, },
created() { created() {
if (this.locale) this.locale = 'es';
this.$i18n.locale = this.locale;
}, },
methods: { methods: {
fetchTicket(ticketFk) { fetchTicket(ticketId) {
return database.pool.query(` return db.findOne(`
SELECT SELECT
t.id, t.id,
u.lang locale, u.lang locale,
@ -40,11 +40,12 @@ module.exports = {
FROM ticket t FROM ticket t
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN account.user u ON u.id = c.id JOIN account.user u ON u.id = c.id
WHERE t.id = ?`, [ticketFk]); WHERE t.id = ?`, [ticketId]);
}, },
}, },
components: { components: {
'email-header': require('../email-header'), 'email-header': emailHeader.build(),
'email-footer': require('../email-footer'), 'email-footer': emailFooter.build()
}, },
props: ['ticketId', 'isPreview']
}; };

View File

@ -1,11 +0,0 @@
module.exports = {
messages: {
es: {
subject: 'Aquí tienes tu albarán',
title: '¡Este es tu albarán!',
dearClient: 'Estimado cliente',
clientData: `A continuación adjuntamos tu albarán.`,
help: 'Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para atenderte!</strong>'
},
},
};

View File

@ -0,0 +1,6 @@
subject: Aquí tienes tu albarán
title: "¡Este es tu albarán!"
dearClient: Estimado cliente
clientData: A continuación adjuntamos tu albarán.
help: Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para
atenderte!</strong>

View File

@ -1,4 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([`${__dirname}/style.css`])
.mergeStyles();

View File

@ -1,34 +0,0 @@
module.exports = {
name: 'email-footer',
asyncData(ctx) {
return {
isPreview: ctx.method === 'GET',
};
},
created() {
if (this.locale)
this.$i18n.locale = this.locale;
const embeded = [];
this.files.map(file => {
const src = this.isPreview ? `/api/${file}` : `cid:${file}`;
embeded[file] = src;
});
this.embeded = embeded;
},
data() {
return {
files: [
/* '/assets/images/action.png',
'/assets/images/info.png', */
'/assets/images/facebook.png',
'/assets/images/twitter.png',
'/assets/images/youtube.png',
'/assets/images/pinterest.png',
'/assets/images/instagram.png',
'/assets/images/linkedin.png',
],
};
},
props: ['locale']
};

View File

@ -1,42 +0,0 @@
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 has recibido este mensaje
por error, te rogamos lo comuniques al remitente y borres 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,
te comunicamos que los datos personales que facilites 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.`,
},
},
/* fr: {
buttons: {
webAcccess: 'Visitez notre site 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 - Ce message est privé et confidentiel et doit être utilisé.
exclusivamente por la persona destinataria del mismo. Si has recibido este mensaje
por error, te rogamos lo comuniques al remitente y borres 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,
te comunicamos que los datos personales que facilites 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.`,
},
}, */
},
};

View File

@ -1,4 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([`${__dirname}/style.css`])
.mergeStyles();

View File

@ -1,5 +0,0 @@
<header>
<a href="https://www.verdnatura.es" target="_blank"/>
<img :src="embeded['/assets/images/email-logo.png']" alt="VerdNatura"/>
</a>
</header>

View File

@ -1,22 +0,0 @@
module.exports = {
name: 'email-header',
asyncData(ctx) {
return {
isPreview: ctx.method === 'GET',
};
},
created() {
const embeded = [];
this.files.map(file => {
const src = this.isPreview ? `/api/${file}` : `cid:${file}`;
embeded[file] = src;
});
this.embeded = embeded;
},
data() {
return {
files: ['/assets/images/email-logo.png'],
};
},
};

View File

@ -1,5 +0,0 @@
module.exports = {
messages: {
es: {},
},
};

View File

@ -0,0 +1,44 @@
/**
* Email only stylesheet
*
*/
body {
background-color: #EEE
}
.container {
max-width: 600px;
min-width: 320px;
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
}

View File

@ -1,7 +0,0 @@
const CssReader = require(`${appPath}/lib/cssReader`);
module.exports = new CssReader([
`${appPath}/common/css/layout.css`,
`${appPath}/common/css/email.css`,
`${appPath}/common/css/misc.css`])
.mergeStyles();

View File

@ -0,0 +1,233 @@
/**
* CSS layout elements
*
*/
.container {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
font-size: 16px
}
.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
}
.clearfix {
overflow: hidden;
display: block;
clear: both
}
.panel {
position: relative;
margin-bottom: 15px;
padding-top: 10px;
break-inside: avoid;
break-before: always;
break-after: always;
}
.panel .header {
background-color: #FFF;
padding: 2.5px 10px;
position: absolute;
font-weight: bold;
top: 0px;
left: 17.5px;
}
.panel .body {
border: 1px solid #CCC;
overflow: hidden;
padding: 20px
}
.panel .body h3 {
margin-top: 0
}
.panel.dark .header {
border: 1px solid #808080;
background-color: #FFF;
}
.panel.dark .body {
border: 1px solid #808080;
background-color: #c0c0c0
}
.field {
border-bottom: 1px solid #CCC;
border-left: 1px solid #CCC;
border-top: 1px solid #CCC;
float: left
}
.field span {
border-right: 1px solid #CCC;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: table-cell;
vertical-align: middle;
text-align: center;
font-weight: bold
}
.field.square span {
height: 35.4px;
width: 35.4px
}
.emptyField {
border-bottom: 1px dotted grey;
min-height: 1em;
display: block
}
.field.rectangle span {
height: 2em;
width: 8em
}
.pull-left {
float: left !important
}
.pull-right {
float: right !important
}
.vertical-text {
-moz-transform: rotate(90deg);
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
position: absolute;
text-align: center;
font-size: .65em;
right: -108px;
width: 200px;
top: 50%
}
table {
border-collapse: collapse;
border-spacing: 0;
}
.row-oriented, .column-oriented {
text-align: left;
width: 100%
}
.column-oriented {
margin-bottom: 15px
}
.column-oriented td,
.column-oriented th {
padding: 5px 10px
}
.column-oriented thead {
background-color: #e5e5e5
}
.column-oriented thead tr {
border-bottom: 1px solid #808080;
border-top: 1px solid #808080;
background-color: #e5e5e5
}
.column-oriented tfoot {
border-top: 2px solid #808080;
}
.column-oriented tfoot tr:first-child td {
padding-top: 20px !important;
}
.column-oriented .description {
border-bottom: 1px solid #DDD;
font-size: 0.8em
}
.panel .row-oriented td, .panel .row-oriented th {
padding: 10px 0
}
.row-oriented > tbody > tr > td {
width: 30%
}
.row-oriented > tbody > tr > th {
padding-left: 30px;
width: 70%
}
.row-oriented .description {
padding: 0 !important;
font-size: 0.6em;
color: #888
}
.line {
border-bottom: 1px solid #DDD;
border-right: 1px solid #DDD;
border-left: 1px solid #DDD;
position: relative;
margin-left: -1px;
margin-right: 1px;
margin-top: 10px;
color: #999;
padding: 5px 0
}
.line .vertical-aligned {
position: absolute;
text-align: center;
width: 100%;
}
.line span {
background-color: #FFF;
padding: 5px
}
.signature {
width: 100%
}
.signature section {
height: 150px
}
.signature p {
margin-right: 50%;
margin-top: 140px
}

View File

@ -0,0 +1,47 @@
/**
* CSS misc classes
*
*/
.uppercase {
text-transform: uppercase
}
.justified {
text-align: justify
}
.centered {
text-align: center
}
.align-right {
text-align: right
}
.align-left {
text-align: left
}
.number {
text-align: right
}
.font.gray {
color: #555
}
.font.light-gray {
color: #888
}
.font.small {
font-size: 0.65em
}
.font.bold {
font-weight: bold
}
.non-page-break {
page-break-inside: avoid;
}

View File

@ -0,0 +1,10 @@
[{
"filename": "model.ezp",
"path": "/assets/files/model.ezp",
"cid": "model.ezp"
},
{
"filename": "port.png",
"path": "/assets/files/port.png",
"cid": "port.png"
}]

View File

@ -1,55 +0,0 @@
const database = require(`${appPath}/lib/database`);
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.fetchClient(params.clientFk)
.then(([result]) => {
if (!result)
throw new UserException('No client data found');
return Object.assign(data, result[0]);
});
},
created() {
if (this.locale)
this.$i18n.locale = this.locale;
},
data() {
return {
files: [
'/assets/files/model.ezp',
'/assets/files/port.png'
],
};
},
methods: {
fetchClient(clientFk) {
return database.pool.query(`
SELECT
c.id,
u.lang locale,
u.name AS userName,
c.email recipient,
CONCAT(w.lastName, ' ', 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': require('../email-header'),
'email-footer': require('../email-footer'),
},
};

View File

@ -1,59 +0,0 @@
module.exports = {
messages: {
es: {
subject: 'Instalación y configuración de impresora',
title: '¡Gracias por tu confianza!',
description: {
dear: 'Estimado cliente',
instructions: 'Sigue las instrucciones especificadas en este correo para llevar a cabo la instalación de la impresora.',
followGuide: `Puedes utilizar como guía, el vídeo 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="https://godex.s3-accelerate.amazonaws.com/gGnOPoojkP6vC1lgmrbEqQ.file?v01" title="Descargar QLabel"
target="_blank" style="color:#8dba25">https://godex.s3-accelerate.amazonaws.com/gGnOPoojkP6vC1lgmrbEqQ.file?v01</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, y haz clic en abrir',
'Ve a "File" -> "Save as" y guárdalo en el escritorio con otro nombre',
'Cierra el Qlabel y abre el archivo que acabamos de guardar',
'Haz clic <strong>encima del texto</strong> con el botón secundario del ratón',
'Elige la primera opción "setup"',
'Cambia el texto para imprimir',
'Haz clic en el botón "Ok"',
'Desplázate con el ratón para ver la medida máxima que ocupa el texto',
'Haz clic <strong>encima del texto</strong> con el botón secundario del ratón',
'Elige la segunda opción "Setup printer"',
'Haz clic en la primera pestaña "Label Setup"',
'Modifica la propiedad "Paper Height" con la medida máxima consultada anteriormente',
`Comprueba el puerto de la impresora, botón de de la derecha
"SETUP PRINTER" y en la parte derecha, igual como la imagen
que adjuntamos, seleccionar la que ponga "USB00x: GODEX"`,
'Haz clic en el botón "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',
},
},
};

View File

@ -0,0 +1,50 @@
subject: Instalación y configuración de impresora
title: "¡Gracias por tu confianza!"
description:
dear: Estimado cliente
instructions: Sigue las instrucciones especificadas en este correo para llevar a
cabo la instalación de la impresora.
followGuide: Puedes utilizar como guía, el vídeo 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='https://godex.s3-accelerate.amazonaws.com/gGnOPoojkP6vC1lgmrbEqQ.file?v01'
title='Descargar QLabel' target='_blank' style='color:#8dba25'>https://godex.s3-accelerate.amazonaws.com/gGnOPoojkP6vC1lgmrbEqQ.file?v01</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, y haz clic
en abrir
- Ve a 'File' -> 'Save as' y guárdalo en el escritorio con otro nombre
- Cierra el Qlabel y abre el archivo que acabamos de guardar
- Haz clic <strong>encima del texto</strong> con el botón secundario del ratón
- Elige la primera opción 'setup'
- Cambia el texto para imprimir
- Haz clic en el botón 'Ok'
- Desplázate con el ratón para ver la medida máxima que ocupa el texto
- Haz clic <strong>encima del texto</strong> con el botón secundario del ratón
- Elige la segunda opción 'Setup printer'
- Haz clic en la primera pestaña 'Label Setup'
- Modifica la propiedad 'Paper Height' con la medida máxima consultada anteriormente
- 'Comprueba el puerto de la impresora, botón de de la derecha ''SETUP PRINTER''
y en la parte derecha, igual como la imagen que adjuntamos, seleccionar la que
ponga ''USB00x: GODEX'''
- Haz clic en el botón '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

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