Compare commits

...

1 Commits

Author SHA1 Message Date
Joan Sanchez 0cd5f852e2 Updated to vue3
gitea/salix/pipeline/head There was a failure building this commit Details
2022-11-11 14:15:17 +01:00
32 changed files with 2751 additions and 937 deletions

View File

@ -0,0 +1,23 @@
const db = require('vn-print/core/database');
function useDatabase() {
function rawSql(query, params, connection) {
return db.rawSql(query, params, connection);
}
function findOne(query, params) {
return db.findOne(query, params);
}
function findValue(query, params) {
return db.findValue(query, params);
}
return {
rawSql,
findOne,
findValue
};
}
module.exports = {useDatabase};

View File

@ -9,7 +9,8 @@
"i18n": {
"locale": "es",
"fallbackLocale": "es",
"silentTranslationWarn": false
"silentTranslationWarn": false,
"allowComposition": true
},
"pdf": {
"format": "A4",

View File

@ -1,13 +1,14 @@
const Vue = require('vue');
const VueI18n = require('vue-i18n');
const renderer = require('vue-server-renderer').createRenderer();
Vue.use(VueI18n);
const {createSSRApp, h} = require('vue');
const {createI18n} = require('vue-i18n');
const {renderToString} = require('vue/server-renderer');
const fs = require('fs');
const yaml = require('js-yaml');
// const yaml = require('js-yaml');
const juice = require('juice');
const path = require('path');
const config = require('./config');
const messages = require('vn-print/i18n');
class Component {
constructor(name) {
@ -25,53 +26,53 @@ class Component {
return fs.readFileSync(fullPath, 'utf8');
}
get locale() {
if (!this._locale)
this._locale = this.getLocales();
// get locale() {
// if (!this._locale)
// this._locale = this.getLocales();
return this._locale;
}
// return this._locale;
// }
getLocales() {
const mergedLocales = {messages: {}};
const localePath = path.resolve(__dirname, `${this.path}/locale`);
// getLocales() {
// const mergedLocales = {messages: {}};
// const localePath = path.resolve(__dirname, `${this.path}/locale`);
if (!fs.existsSync(localePath))
return mergedLocales;
// if (!fs.existsSync(localePath))
// return mergedLocales;
const localeDir = fs.readdirSync(localePath);
for (const locale of localeDir) {
const fullPath = path.join(localePath, '/', locale);
const yamlLocale = fs.readFileSync(fullPath, 'utf8');
const jsonLocale = yaml.safeLoad(yamlLocale);
const localeName = locale.replace('.yml', '');
// const localeDir = fs.readdirSync(localePath);
// for (const locale of localeDir) {
// const fullPath = path.join(localePath, '/', locale);
// const yamlLocale = fs.readFileSync(fullPath, 'utf8');
// const jsonLocale = yaml.safeLoad(yamlLocale);
// const localeName = locale.replace('.yml', '');
mergedLocales.messages[localeName] = jsonLocale;
}
// mergedLocales.messages[localeName] = jsonLocale;
// }
return mergedLocales;
}
// return mergedLocales;
// }
async getUserLocale() {
let lang = this.args.lang;
// async getUserLocale() {
// let lang = this.args.lang;
// Fetches user locale from mixing method getLocale()
if (this.args.recipientId) {
const component = await this.component();
lang = await component.getLocale(this.args.recipientId);
}
// // Fetches user locale from mixing method getLocale()
// if (this.args.recipientId) {
// const component = await this.component();
// lang = await component.getLocale(this.args.recipientId);
// }
const messages = this.locale.messages;
const userTranslations = messages[lang];
// const messages = this.locale.messages;
// const userTranslations = messages[lang];
if (!userTranslations) {
const fallbackLang = config.i18n.fallbackLocale;
// if (!userTranslations) {
// const fallbackLang = config.i18n.fallbackLocale;
return messages[fallbackLang];
}
// return messages[fallbackLang];
// }
return userTranslations;
}
// return userTranslations;
// }
get stylesheet() {
let mergedStyles = '';
@ -99,7 +100,7 @@ class Component {
throw new Error(`Template "${this.name}" not found`);
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
@ -118,15 +119,22 @@ class Component {
return this._component;
const component = this.build();
const i18n = new VueI18n(config.i18n);
const i18nOptions = Object.assign({
messages: messages
}, config.i18n);
const i18n = createI18n(i18nOptions);
const props = {...this.args};
this._component = new Vue({
const app = createSSRApp({
i18n: i18n,
render: h => h(component, {
props: props
})
render: () => h(component, props)
});
app.use(i18n);
this._component = app;
return this._component;
}
@ -134,7 +142,7 @@ class Component {
* @return {Promise} Rendered component
*/
async render() {
return renderer.renderToString(
return renderToString(
this.component()
);
}

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/email.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,19 @@
body {
-webkit-text-size-adjust: none;
-ms-text-size-adjust: none;
background-color: #FFF;
font-weight: 400;
color: #555;
margin: 0
}
body {
zoom: 0.70;
}
.title {
margin-bottom: 20px;
font-weight: 100;
font-size: 2.6rem;
margin-top: 0
}

View File

@ -0,0 +1 @@
test

View File

@ -0,0 +1,8 @@
const controller = {
template: '<div><slot/></div>',
setup() {
return {};
}
};
module.exports = controller;

View File

@ -1,8 +0,0 @@
// Import global filters
require('./date');
require('./uppercase');
require('./currency');
require('./percentage');
require('./number');
require('./zerofill');

View File

@ -1,5 +1,5 @@
// Import global mixins
require('./image-src');
require('./user-locale');
require('./prop-validator');
require('./db-helper');
// require('./image-src');
// require('./user-locale');
// require('./prop-validator');
// require('./db-helper');

View File

@ -18,9 +18,9 @@ class Report extends Component {
return `../templates/reports/${this.name}`;
}
async getName() {
return (await this.getUserLocale())['reportName'];
}
// async getName() {
// return (await this.getUserLocale())['reportName'];
// }
async toPdfStream() {
const template = await this.render();

13
print/filters/index.js Normal file
View File

@ -0,0 +1,13 @@
// Import global filters
// require('./date');
// require('./uppercase');
// require('./currency');
// require('./percentage');
// require('./number');
// require('./zerofill');
module.exports = {
toDate: require('./toDate'),
toCurrency: require('./toCurrency')
};

View File

@ -1,5 +1,5 @@
const Vue = require('vue');
const config = require('../config');
const config = require('../core/config');
const defaultLocale = config.i18n.locale;
const percentage = function(value, minFraction = 2, maxFraction = 2, locale = defaultLocale) {

View File

@ -1,5 +1,4 @@
const Vue = require('vue');
const config = require('../config');
const config = require('vn-print/core/config');
const defaultLocale = config.i18n.locale;
const currency = function(value, currency = 'EUR', locale = defaultLocale) {
@ -9,6 +8,4 @@ const currency = function(value, currency = 'EUR', locale = defaultLocale) {
}).format(parseFloat(value));
};
Vue.filter('currency', currency);
module.exports = currency;

View File

@ -1,4 +1,3 @@
const Vue = require('vue');
const strftime = require('strftime');
const date = function(value, specifiers = '%d-%m-%Y') {
@ -6,6 +5,4 @@ const date = function(value, specifiers = '%d-%m-%Y') {
return strftime(specifiers, value);
};
Vue.filter('date', date);
module.exports = date;

5
print/i18n/en/index.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
deliveryNote: {
title: 'test'
}
};

62
print/i18n/es/index.js Normal file
View File

@ -0,0 +1,62 @@
module.exports = {
deliveryNote: {
deliveryNote: 'Albarán',
proforma: 'Proforma',
withoutPrices: 'Albarán',
clientId: 'Cliente',
deliveryAddress: 'Dirección de entrega',
fiscalData: 'Datos fiscales',
saleLines: 'Líneas de pedido',
date: 'Fecha',
reference: 'Ref.',
quantity: 'Cant.',
concept: 'Concepto',
price: 'PVP/u',
discount: 'Dto.',
vat: 'IVA',
amount: 'Importe',
total: 'Total',
subtotal: 'Subtotal',
vatType: 'Tipo de IVA',
digitalSignature: 'Firma digital',
ticket: 'Albarán {0}',
plantPassport: 'Pasaporte fitosanitario',
packages: 'Bultos',
services:
{
title: 'Servicios',
theader: {
quantity: 'Cantidad',
concept: 'Concepto',
price: 'PVP/u',
vat: 'IVA',
amount: 'Importe'
},
tfoot: {subtotal: 'Subtotal'},
warning: `Los embalajes en depósito se facturarán
si no han sido devueltos pasados 30 dias de su entrega.`
},
packagings: {
title: 'Cubos y embalajes',
theader: {
reference: 'Referencia',
quantity: 'Cantidad',
concept: 'Concepto'
}
},
taxes: {
title: 'Desglose impositivo',
theader: {
type: 'Tipo',
taxBase: 'Base imp.',
tax: 'Tasa',
fee: 'Cuota'
},
tfoot: {
subtotal: 'Subtotal',
total: 'Total'
}
},
observations: 'Observaciones'
}
};

7
print/i18n/index.js Normal file
View File

@ -0,0 +1,7 @@
const en = require('./en');
const es = require('./es');
module.exports = {
en: en,
es: es,
};

View File

@ -16,8 +16,6 @@ module.exports = {
require('./core/database').init(app.dataSources);
require('./core/smtp').init();
require('./core/cluster').init();
require('./core/mixins');
require('./core/filters');
const componentsDir = fs.readdirSync(componentsPath);
componentsDir.forEach(componentName => {

2337
print/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,7 @@
"puppeteer-cluster": "^0.23.0",
"qrcode": "^1.4.2",
"strftime": "^0.10.0",
"vue": "^2.6.10",
"vue-i18n": "^8.15.0",
"vue-server-renderer": "^2.6.10"
"vue": "^3.2.42",
"vue-i18n": "^9.2.2"
}
}

View File

@ -9,4 +9,4 @@
<p v-html="$t('conclusion')"></p>
</div>
</div>
</email-body>
</email-body>

View File

@ -2,54 +2,334 @@
<template v-slot:header>
<report-header v-bind="$props" v-bind:company-code="ticket.companyCode"> </report-header>
</template>
<div class="grid-row">
<div class="grid-block">
<div class="columns">
<div class="size50">
<div class="size75 vn-mt-ml">
<h1 class="title uppercase">{{$t(deliverNoteType)}}</h1>
<table class="row-oriented ticket-info">
<tbody>
<tr>
<td class="font gray uppercase">{{$t('clientId')}}</td>
<th>{{client.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t(deliverNoteType)}}</td>
<th>{{ticket.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('date')}}</td>
<th>{{ticket.shipped | date('%d-%m-%Y')}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('packages')}}</td>
<th>{{ticket.packages}}</th>
</tr>
</tbody>
</table>
</div>
</div>
<div class="size50">
<div class="panel">
<div class="header">{{$t('deliveryAddress')}}</div>
<div class="body">
<h3 class="uppercase">{{address.nickname}}</h3>
<div>{{address.street}}</div>
<div>{{address.postalCode}}, {{address.city}} ({{address.province}})</div>
</div>
</div>
<div class="grid-row">
<div class="grid-block">
<div class="columns">
<div class="size50">
<div class="size75 vn-mt-ml">
<h1 class="title uppercase">{{$t(`deliveryNote['${deliverNoteType}']`)}}</h1>
<table class="row-oriented ticket-info">
<tbody>
<tr>
<td class="font gray uppercase">{{$t('deliveryNote.clientId')}}</td>
<th>{{client.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t(`deliveryNote['${deliverNoteType}']`)}}</td>
<th>{{ticket.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('deliveryNote.date')}}</td>
<th>{{ticket.shipped | toDate('%d-%m-%Y')}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('deliveryNote.packages')}}</td>
<th>{{ticket.packages}}</th>
</tr>
</tbody>
</table>
</div>
</div>
<div class="size50">
<div class="panel">
<div class="header">{{$t('deliveryNote.deliveryAddress')}}</div>
<div class="body">
<h3 class="uppercase">{{address.nickname}}</h3>
<div>
{{address.street}}
</div>
<div>
{{address.postalCode}}, {{address.city}} ({{address.province}})
</div>
</div>
</div>
<div class="panel">
<div class="header">{{$t('fiscalData')}}</div>
<div class="body">
<div>{{client.socialName}}</div>
<div>{{client.street}}</div>
<div>{{client.fi}}</div>
<div class="panel">
<div class="header">{{$t('deliveryNote.fiscalData')}}</div>
<div class="body">
<div>
{{client.socialName}}
</div>
<div>
{{client.street}}
</div>
<div>
{{client.fi}}
</div>
</div>
</div>
</div>
</div>
<h2>{{$t('deliveryNote.saleLines')}}</h2>
<table v-if="sales" class="column-oriented">
<thead>
<tr>
<th width="5%">
{{$t('deliveryNote.reference')}}
</th>
<th class="number">
{{$t('deliveryNote.quantity')}}
</th>
<th width="50%">
{{$t('deliveryNote.concept')}}
</th>
<th class="number" v-if="showPrices">
{{$t('deliveryNote.price')}}
</th>
<th class="centered" width="5%" v-if="showPrices">
{{$t('deliveryNote.discount')}}
</th>
<th class="centered" v-if="showPrices">
{{$t('deliveryNote.vat')}}
</th>
<th class="number" v-if="showPrices">
{{$t('deliveryNote.amount')}}
</th>
</tr>
</thead>
<tbody v-for="sale in sales" class="no-page-break">
<tr>
<td width="5%">{{sale.itemFk}}</td>
<td class="number">{{sale.quantity}}</td>
<td width="50%">{{sale.concept}}</td>
<td class="number" v-if="showPrices">
{{toCurrency(sale.price, 'EUR', $i18n.locale)}}
</td>
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) |
percentage}}</td>
<td class="centered" v-if="showPrices">{{sale.vatType}}</td>
<td class="number" v-if="showPrices">
{{toCurrency(sale.price * sale.quantity * (1 - sale.discount / 100), 'EUR', $i18n.locale)}}
</td>
</tr>
<tr class="description font light-gray">
<td colspan="7">
<span v-if="sale.value5">
<strong>{{sale.tag5}}</strong> {{sale.value5}}
</span>
<span v-if="sale.value6">
<strong>{{sale.tag6}}</strong> {{sale.value6}}
</span>
<span v-if="sale.value7">
<strong>{{sale.tag7}}</strong> {{sale.value7}}
</span>
</td>
</tr>
</tbody>
<tfoot v-if="showPrices">
<tr>
<td colspan="6" class="font bold">
<span class="pull-right">{{$t('deliveryNote.subtotal')}}</span>
</td>
<td class="number">{{toCurrency(getSubTotal(), 'EUR', $i18n.locale)}}</td>
</tr>
</tfoot>
</table>
<!-- <div class="columns vn-mb-ml">
<div class="size100 no-page-break" v-if="services.length > 0">
<h2>{{$t('services.title')}}</h2>
<table class="column-oriented">
<thead>
<tr>
<th width="5%"></th>
<th class="number">{{$t('services.theader.quantity')}}</th>
<th width="50%">{{$t('services.theader.concept')}}</th>
<th class="number">{{$t('services.theader.price')}}</th>
<th class="centered" width="5%"></th>
<th class="centered">{{$t('services.theader.vat')}}</th>
<th class="number">{{$t('services.theader.amount')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="service in services">
<td width="5%"></td>
<td class="number">{{service.quantity}}</td>
<td width="50%">{{service.description}}</td>
<td class="number">{{service.price | toCurrency('EUR', $i18n.locale)}}
</td>
<td class="centered" width="5%"></td>
<td class="centered">{{service.taxDescription}}</td>
<td class="number">{{service.price | toCurrency('EUR', $i18n.locale)}}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6" class="font bold">
<span class="pull-right">{{$t('services.tfoot.subtotal')}}</span>
</td>
<td class="number">{{serviceTotal | toCurrency('EUR', $i18n.locale)}}</td>
</tr>
</tfoot>
</table>
<span class="font gray">* {{ $t('services.warning') }}</span>
</div>
</div>
<div class="columns">
<div id="packagings" class="size100 no-page-break" v-if="packagings.length > 0">
<h2>{{$t('packagings.title')}}</h2>
<table class="column-oriented">
<thead>
<tr>
<th>{{$t('packagings.theader.reference')}}</th>
<th class="number">{{$t('packagings.theader.quantity')}}</th>
<th wihth="75%">{{$t('packagings.theader.concept')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="packaging in packagings">
<td>{{packaging.itemFk | zerofill('000000')}}</td>
<td class="number">{{packaging.quantity}}</td>
<td width="85%">{{packaging.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="columns vn-mt-xl" v-if="showPrices">
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
<table class="column-oriented">
<thead>
<tr>
<th colspan="4">{{$t('taxes.title')}}</th>
</tr>
</thead>
<thead class="light">
<tr>
<th width="45%">{{$t('taxes.theader.type')}}</th>
<th width="25%" class="number">{{$t('taxes.theader.taxBase')}}</th>
<th>{{$t('taxes.theader.tax')}}</th>
<th class="number">{{$t('taxes.theader.fee')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="tax in taxes">
<td width="45%">{{tax.name}}</td>
<td width="25%" class="number">
{{tax.Base | currency('EUR', $i18n.locale)}}
</td>
<td>{{tax.vatPercent | percentage}}</td>
<td class="number">{{tax.tax | currency('EUR', $i18n.locale)}}</td>
</tr>
</tbody>
<tfoot>
<tr class="font bold">
<td width="45%">{{$t('subtotal')}}</td>
<td width="20%" class="number">
{{getTotalBase() | currency('EUR', $i18n.locale)}}
</td>
<td></td>
<td class="number">{{getTotalTax()| currency('EUR', $i18n.locale)}}</td>
</tr>
<tr class="font bold">
<td colspan="2">{{$t('total')}}</td>
<td colspan="2" class="number">{{getTotal() | currency('EUR',
$i18n.locale)}}</td>
</tr>
</tfoot>
</table>
</div>
<div id="phytosanitary" class="size50 pull-left no-page-break">
<div class="panel">
<div class="body">
<div class="flag">
<div class="columns">
<div class="size25">
<img v-bind:src="getReportSrc('europe.png')" />
</div>
<div class="size75 flag-text">
<strong>{{$t('plantPassport')}}</strong><br />
</div>
</div>
</div>
<div class="phytosanitary-info">
<div>
<strong>A</strong>
<span>{{getBotanical()}}</span>
</div>
<div>
<strong>B</strong>
<span>ES17462130</span>
</div>
<div>
<strong>C</strong>
<span>{{ticket.id}}</span>
</div>
<div>
<strong>D</strong>
<span>ES</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="size50 pull-left no-page-break">
<div id="signature" class="panel" v-if="signature && signature.id">
<div class="header">{{$t('digitalSignature')}}</div>
<div class="body centered">
<img v-bind:src="dmsPath" />
<div>{{signature.created | date('%d-%m-%Y')}}</div>
</div>
</div>
</div>
</div>
<div class="columns vn-mb-ml" v-if="hasObservations">
<div class="size100 no-page-break">
<h2>{{$t('observations')}}</h2>
<p class="observations">{{ticket.description}}</p>
</div>
</div>
-->
</div>
</div>
</div>
</div>
<!--
</report-header>
<div class="grid-row">
<div class="grid-block">
<div class="columns">
<div class="size50">
<div class="size75 vn-mt-ml">
<h1 class="title uppercase">{{$t(deliverNoteType)}}</h1>
<table class="row-oriented ticket-info">
<tbody>
<tr>
<td class="font gray uppercase">{{$t('clientId')}}</td>
<th>{{client.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t(deliverNoteType)}}</td>
<th>{{ticket.id}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('date')}}</td>
<th>{{ticket.shipped | date('%d-%m-%Y')}}</th>
</tr>
<tr>
<td class="font gray uppercase">{{$t('packages')}}</td>
<th>{{ticket.packages}}</th>
</tr>
</tbody>
</table>
</div>
</div>
<div class="size50">
<div class="panel">
<div class="header">{{$t('deliveryAddress')}}</div>
<div class="body">
<h3 class="uppercase">{{address.nickname}}</h3>
<div>
{{address.street}}
</div>
<div>
{{address.postalCode}}, {{address.city}} ({{address.province}})
</div>
</div>
</div>
<h2>{{$t('saleLines')}}</h2>
<table class="column-oriented">
@ -203,48 +483,217 @@
<div class="size75 flag-text"><strong>{{$t('plantPassport')}}</strong><br /></div>
</div>
</div>
<div class="phytosanitary-info">
<div>
<strong>A</strong>
<span>{{getBotanical()}}</span>
<h2>{{$t('saleLines')}}</h2>
<table class="column-oriented">
<thead>
<tr>
<th width="5%">{{$t('reference')}}</th>
<th class="number">{{$t('quantity')}}</th>
<th width="50%">{{$t('concept')}}</th>
<th class="number" v-if="showPrices">{{$t('price')}}</th>
<th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th>
<th class="centered" v-if="showPrices">{{$t('vat')}}</th>
<th class="number" v-if="showPrices">{{$t('amount')}}</th>
</tr>
</thead>
<tbody v-for="sale in sales" class="no-page-break">
<tr>
<td width="5%">{{sale.itemFk | zerofill('000000')}}</td>
<td class="number">{{sale.quantity}}</td>
<td width="50%">{{sale.concept}}</td>
<td class="number" v-if="showPrices">{{sale.price | currency('EUR',
$i18n.locale)}}</td>
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) |
percentage}}</td>
<td class="centered" v-if="showPrices">{{sale.vatType}}</td>
<td class="number" v-if="showPrices">{{sale.price * sale.quantity * (1 -
sale.discount / 100) | currency('EUR', $i18n.locale)}}</td>
</tr>
<tr class="description font light-gray">
<td colspan="7">
<span v-if="sale.value5">
<strong>{{sale.tag5}}</strong> {{sale.value5}}
</span>
<span v-if="sale.value6">
<strong>{{sale.tag6}}</strong> {{sale.value6}}
</span>
<span v-if="sale.value7">
<strong>{{sale.tag7}}</strong> {{sale.value7}}
</span>
</td>
</tr>
</tbody>
<tfoot v-if="showPrices">
<tr>
<td colspan="6" class="font bold">
<span class="pull-right">{{$t('subtotal')}}</span>
</td>
<td class="number">{{getSubTotal() | currency('EUR', $i18n.locale)}}</td>
</tr>
</tfoot>
</table>
<div class="columns vn-mb-ml">
<div class="size100 no-page-break" v-if="services.length > 0">
<h2>{{$t('services.title')}}</h2>
<table class="column-oriented">
<thead>
<tr>
<th width="5%"></th>
<th class="number">{{$t('services.theader.quantity')}}</th>
<th width="50%">{{$t('services.theader.concept')}}</th>
<th class="number">{{$t('services.theader.price')}}</th>
<th class="centered" width="5%"></th>
<th class="centered">{{$t('services.theader.vat')}}</th>
<th class="number">{{$t('services.theader.amount')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="service in services">
<td width="5%"></td>
<td class="number">{{service.quantity}}</td>
<td width="50%">{{service.description}}</td>
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}
</td>
<td class="centered" width="5%"></td>
<td class="centered">{{service.taxDescription}}</td>
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6" class="font bold">
<span class="pull-right">{{$t('services.tfoot.subtotal')}}</span>
</td>
<td class="number">{{serviceTotal | currency('EUR', $i18n.locale)}}</td>
</tr>
</tfoot>
</table>
<span class="font gray">* {{ $t('services.warning') }}</span>
</div>
<div>
<strong>B</strong>
<span>ES17462130</span>
</div>
<div class="columns">
<div id="packagings" class="size100 no-page-break" v-if="packagings.length > 0">
<h2>{{$t('packagings.title')}}</h2>
<table class="column-oriented">
<thead>
<tr>
<th>{{$t('packagings.theader.reference')}}</th>
<th class="number">{{$t('packagings.theader.quantity')}}</th>
<th wihth="75%">{{$t('packagings.theader.concept')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="packaging in packagings">
<td>{{packaging.itemFk | zerofill('000000')}}</td>
<td class="number">{{packaging.quantity}}</td>
<td width="85%">{{packaging.name}}</td>
</tr>
</tbody>
</table>
</div>
<div>
<strong>C</strong>
<span>{{ticket.id}}</span>
</div>
<div class="columns vn-mt-xl" v-if="showPrices">
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
<table class="column-oriented">
<thead>
<tr>
<th colspan="4">{{$t('taxes.title')}}</th>
</tr>
</thead>
<thead class="light">
<tr>
<th width="45%">{{$t('taxes.theader.type')}}</th>
<th width="25%" class="number">{{$t('taxes.theader.taxBase')}}</th>
<th>{{$t('taxes.theader.tax')}}</th>
<th class="number">{{$t('taxes.theader.fee')}}</th>
</tr>
</thead>
<tbody>
<tr v-for="tax in taxes">
<td width="45%">{{tax.name}}</td>
<td width="25%" class="number">
{{tax.Base | currency('EUR', $i18n.locale)}}
</td>
<td>{{tax.vatPercent | percentage}}</td>
<td class="number">{{tax.tax | currency('EUR', $i18n.locale)}}</td>
</tr>
</tbody>
<tfoot>
<tr class="font bold">
<td width="45%">{{$t('subtotal')}}</td>
<td width="20%" class="number">
{{getTotalBase() | currency('EUR', $i18n.locale)}}
</td>
<td></td>
<td class="number">{{getTotalTax()| currency('EUR', $i18n.locale)}}</td>
</tr>
<tr class="font bold">
<td colspan="2">{{$t('total')}}</td>
<td colspan="2" class="number">{{getTotal() | currency('EUR',
$i18n.locale)}}</td>
</tr>
</tfoot>
</table>
</div>
<div>
<strong>D</strong>
<span>ES</span>
<div id="phytosanitary" class="size50 pull-left no-page-break">
<div class="panel">
<div class="body">
<div class="flag">
<div class="columns">
<div class="size25">
<img v-bind:src="getReportSrc('europe.png')" />
</div>
<div class="size75 flag-text">
<strong>{{$t('plantPassport')}}</strong><br />
</div>
</div>
</div>
<div class="phytosanitary-info">
<div>
<strong>A</strong>
<span>{{getBotanical()}}</span>
</div>
<div>
<strong>B</strong>
<span>ES17462130</span>
</div>
<div>
<strong>C</strong>
<span>{{ticket.id}}</span>
</div>
<div>
<strong>D</strong>
<span>ES</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="size50 pull-left no-page-break">
<div id="signature" class="panel" v-if="signature && signature.id">
<div class="header">{{$t('digitalSignature')}}</div>
<div class="body centered">
<img v-bind:src="dmsPath" />
<div>{{signature.created | date('%d-%m-%Y')}}</div>
</div>
</div>
</div>
</div>
<div class="columns vn-mb-ml" v-if="hasObservations">
<div class="size100 no-page-break">
<h2>{{$t('observations')}}</h2>
<p class="observations">{{ticket.description}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns">
<div class="size50 pull-left no-page-break">
<div id="signature" class="panel" v-if="signature && signature.id">
<div class="header">{{$t('digitalSignature')}}</div>
<div class="body centered">
<img v-bind:src="dmsPath" />
<div>{{signature.created | date('%d-%m-%Y')}}</div>
</div>
</div>
</div>
</div>
<div class="columns vn-mb-ml" v-if="hasObservations">
<div class="size100 no-page-break">
<h2>{{$t('observations')}}</h2>
<p class="observations">{{ticket.description}}</p>
</div>
</div>
</div>
</div>
<template v-slot:footer>
<template v-slot:footer>
<report-footer
id="pageFooter"
v-bind:company-code="ticket.companyCode"
@ -253,5 +702,5 @@
v-bind="$props"
>
</report-footer>
</template>
</template>-->
</report-body>

View File

@ -1,134 +1,21 @@
const config = require(`vn-print/core/config`);
const Component = require(`vn-print/core/component`);
const reportBody = new Component('report-body');
const reportHeader = new Component('report-header');
const reportFooter = new Component('report-footer');
const md5 = require('md5');
const fs = require('fs-extra');
// const config = require(`vn-print/core/config`);
// const Component = require(`vn-print/core/component`);
// const templateBody = new Component('template-body');
// const reportHeader = new Component('report-header');
// const reportFooter = new Component('report-footer');
// const md5 = require('md5');
// const fs = require('fs-extra');
module.exports = {
name: 'delivery-note',
async serverPrefetch() {
this.client = await this.fetchClient(this.id);
this.ticket = await this.fetchTicket(this.id);
this.sales = await this.fetchSales(this.id);
this.address = await this.fetchAddress(this.id);
this.services = await this.fetchServices(this.id);
this.taxes = await this.fetchTaxes(this.id);
this.packagings = await this.fetchPackagings(this.id);
this.signature = await this.fetchSignature(this.id);
const {onServerPrefetch, ref, computed} = require('vue');
const {useDatabase} = require('vn-print/composables/useDatabase');
const reportHeader = require('vn-print/core/components/report-header/report-header');
const reportBody = require('vn-print/core/components/report-body/report-body');
const {toDate, toCurrency} = require('vn-print/filters');
if (!this.ticket)
throw new Error('Something went wrong');
},
data() {
return {totalBalance: 0.00};
},
computed: {
dmsPath() {
if (!this.signature) return;
const hash = md5(this.signature.id.toString()).substring(0, 3);
const file = `${config.storage.root}/${hash}/${this.signature.id}.png`;
if (!fs.existsSync(file)) return null;
const src = fs.readFileSync(file);
const base64 = Buffer.from(src, 'utf8').toString('base64');
return `data:image/png;base64, ${base64}`;
},
deliverNoteType() {
return this.type ? this.type : 'deliveryNote';
},
serviceTotal() {
let total = 0.00;
this.services.forEach(service => {
total += parseFloat(service.price) * service.quantity;
});
return total;
},
showPrices() {
return this.deliverNoteType != 'withoutPrices';
},
footerType() {
const translatedType = this.$t(this.deliverNoteType);
return `${translatedType} ${this.id}`;
},
hasObservations() {
return this.ticket.description !== null;
}
},
methods: {
fetchClient(id) {
return this.findOneFromDef('client', [id]);
},
fetchTicket(id) {
return this.findOneFromDef('ticket', [id]);
},
fetchAddress(id) {
return this.findOneFromDef(`address`, [id]);
},
fetchSignature(id) {
return this.findOneFromDef('signature', [id]);
},
fetchTaxes(id) {
return this.findOneFromDef(`taxes`, [id]);
},
fetchSales(id) {
return this.rawSqlFromDef('sales', [id]);
},
fetchPackagings(id) {
return this.rawSqlFromDef('packagings', [id]);
},
fetchServices(id) {
return this.rawSqlFromDef('services', [id]);
},
getSubTotal() {
let subTotal = 0.00;
this.sales.forEach(sale => {
subTotal += sale.quantity * sale.price * (1 - sale.discount / 100);
});
return subTotal;
},
getTotalBase() {
let totalBase = 0.00;
this.taxes.forEach(tax => {
totalBase += parseFloat(tax.Base);
});
return totalBase;
},
getTotalTax() {
let totalTax = 0.00;
this.taxes.forEach(tax => {
totalTax += parseFloat(tax.tax);
});
return totalTax;
},
getTotal() {
return this.getTotalBase() + this.getTotalTax();
},
getBotanical() {
let phytosanitary = [];
this.sales.forEach(sale => {
if (sale.botanical)
phytosanitary.push(sale.botanical);
});
return phytosanitary.filter((item, index) =>
phytosanitary.indexOf(item) == index
).join(', ');
}
},
const controller = {
components: {
'report-body': reportBody.build(),
'report-header': reportHeader.build(),
'report-footer': reportFooter.build()
reportHeader: reportHeader,
reportBody: reportBody,
},
props: {
id: {
@ -140,5 +27,306 @@ module.exports = {
type: String,
required: false
}
},
setup(props) {
const db = useDatabase();
onServerPrefetch(async() => {
await fetchClient();
await fetchTicket();
await fetchAddress();
await fetchSales();
});
const deliverNoteType = props.type ? props.type : 'deliveryNote';
const showPrices = deliverNoteType != 'withoutPrices';
const hasObservations = computed(() => {
return ticket.value.description !== null;
});
// footerType() {
// const translatedType = this.$t(this.deliverNoteType);
// return `${translatedType} ${this.id}`;
// },
const client = ref();
async function fetchClient() {
client.value = await db.findOne(`
SELECT
c.id,
c.socialName,
c.street,
c.fi
FROM ticket t
JOIN client c ON c.id = t.clientFk
WHERE t.id = ?
`, [props.id]);
}
const ticket = ref();
async function fetchTicket() {
ticket.value = await db.findOne(
`SELECT
t.id,
t.shipped,
c.code companyCode,
t.packages,
tto.description
FROM ticket t
JOIN company c ON c.id = t.companyFk
LEFT JOIN ticketObservation tto
ON tto.ticketFk = t.id
AND tto.observationTypeFk = (SELECT id FROM observationType WHERE code = 'deliveryNote')
WHERE t.id = ?
`, [props.id]);
}
const address = ref();
async function fetchAddress() {
address.value = await db.findOne(
`
SELECT
a.nickname,
a.street,
a.postalCode,
a.city,
p.name province
FROM ticket t
JOIN address a ON a.clientFk = t.clientFk
AND a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
WHERE t.id = ?
`, [props.id]);
}
const sales = ref();
async function fetchSales() {
sales.value = await db.rawSql(
`
SELECT
s.id,
s.itemFk,
s.concept,
s.quantity,
s.price,
s.price - SUM(IF(ctr.id = 6, sc.value, 0)) netPrice,
s.discount,
i.size,
i.stems,
i.category,
it.id itemTypeId,
o.code AS origin,
i.inkFk,
s.ticketFk,
tcl.code vatType,
ib.ediBotanic botanical,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7
FROM vn.sale s
LEFT JOIN saleComponent sc ON sc.saleFk = s.id
LEFT JOIN component cr ON cr.id = sc.componentFk
LEFT JOIN componentType ctr ON ctr.id = cr.typeFk
LEFT JOIN item i ON i.id = s.itemFk
LEFT JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN origin o ON o.id = i.originFk
LEFT JOIN country c ON c.id = o.countryFk
LEFT JOIN supplier sp ON sp.id = t.companyFk
LEFT JOIN itemType it ON it.id = i.typeFk
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = sp.countryFk
LEFT JOIN taxClass tcl ON tcl.id = itc.taxClassFk
LEFT JOIN itemBotanicalWithGenus ib ON ib.itemFk = i.id
AND ic.code = 'plant'
AND ib.ediBotanic IS NOT NULL
WHERE s.ticketFk = ?
GROUP BY s.id
ORDER BY (it.isPackaging), s.concept, s.itemFk
`, [props.id]);
}
function getSubTotal() {
let subTotal = 0.00;
for (const sale of sales.value)
subTotal += sale.quantity * sale.price * (1 - sale.discount / 100);
return subTotal;
}
// getTotalBase() {
// let totalBase = 0.00;
// this.taxes.forEach(tax => {
// totalBase += parseFloat(tax.Base);
// });
// return totalBase;
// },
// getTotalTax() {
// let totalTax = 0.00;
// this.taxes.forEach(tax => {
// totalTax += parseFloat(tax.tax);
// });
// return totalTax;
// },
// getTotal() {
// return this.getTotalBase() + this.getTotalTax();
// },
return {
props,
toDate,
toCurrency,
deliverNoteType,
client,
ticket,
address,
sales,
showPrices,
hasObservations,
getSubTotal
};
}
};
module.exports = controller;
// module.exports = {
// name: 'delivery-note',
// async serverPrefetch() {
// this.client = await this.fetchClient(this.id);
// this.ticket = await this.fetchTicket(this.id);
// this.sales = await this.fetchSales(this.id);
// this.address = await this.fetchAddress(this.id);
// this.services = await this.fetchServices(this.id);
// this.taxes = await this.fetchTaxes(this.id);
// this.packagings = await this.fetchPackagings(this.id);
// this.signature = await this.fetchSignature(this.id);
// if (!this.ticket)
// throw new Error('Something went wrong');
// },
// data() {
// return {totalBalance: 0.00};
// },
// computed: {
// dmsPath() {
// if (!this.signature) return;
// const hash = md5(this.signature.id.toString()).substring(0, 3);
// const file = `${config.storage.root}/${hash}/${this.signature.id}.png`;
// if (!fs.existsSync(file)) return null;
// const src = fs.readFileSync(file);
// const base64 = Buffer.from(src, 'utf8').toString('base64');
// return `data:image/png;base64, ${base64}`;
// },
// deliverNoteType() {
// return this.type ? this.type : 'deliveryNote';
// },
// serviceTotal() {
// let total = 0.00;
// this.services.forEach(service => {
// total += parseFloat(service.price) * service.quantity;
// });
// return total;
// },
// showPrices() {
// return this.deliverNoteType != 'withoutPrices';
// },
// footerType() {
// const translatedType = this.$t(this.deliverNoteType);
// return `${translatedType} ${this.id}`;
// },
// hasObservations() {
// return this.ticket.description !== null;
// }
// },
// methods: {
// fetchClient(id) {
// return this.findOneFromDef('client', [id]);
// },
// fetchTicket(id) {
// return this.findOneFromDef('ticket', [id]);
// },
// fetchAddress(id) {
// return this.findOneFromDef(`address`, [id]);
// },
// fetchSignature(id) {
// return this.findOneFromDef('signature', [id]);
// },
// fetchTaxes(id) {
// return this.findOneFromDef(`taxes`, [id]);
// },
// fetchSales(id) {
// return this.rawSqlFromDef('sales', [id]);
// },
// fetchPackagings(id) {
// return this.rawSqlFromDef('packagings', [id]);
// },
// fetchServices(id) {
// return this.rawSqlFromDef('services', [id]);
// },
// getSubTotal() {
// let subTotal = 0.00;
// this.sales.forEach(sale => {
// subTotal += sale.quantity * sale.price * (1 - sale.discount / 100);
// });
// return subTotal;
// },
// getTotalBase() {
// let totalBase = 0.00;
// this.taxes.forEach(tax => {
// totalBase += parseFloat(tax.Base);
// });
// return totalBase;
// },
// getTotalTax() {
// let totalTax = 0.00;
// this.taxes.forEach(tax => {
// totalTax += parseFloat(tax.tax);
// });
// return totalTax;
// },
// getTotal() {
// return this.getTotalBase() + this.getTotalTax();
// },
// getBotanical() {
// let phytosanitary = [];
// this.sales.forEach(sale => {
// if (sale.botanical)
// phytosanitary.push(sale.botanical);
// });
// return phytosanitary.filter((item, index) =>
// phytosanitary.indexOf(item) == index
// ).join(', ');
// }
// },
// components: {
// 'template-body': templateBody.build(),
// 'report-header': reportHeader.build(),
// 'report-footer': reportFooter.build()
// },
// props: {
// id: {
// type: Number,
// required: true,
// description: 'The ticket id'
// },
// type: {
// type: String,
// required: false
// }
// }
// };