diff --git a/print/templates/reports/invoice/assets/css/import.js b/print/templates/reports/invoice/assets/css/import.js new file mode 100644 index 000000000..fd8796c2b --- /dev/null +++ b/print/templates/reports/invoice/assets/css/import.js @@ -0,0 +1,9 @@ +const Stylesheet = require(`${appPath}/core/stylesheet`); + +module.exports = new Stylesheet([ + `${appPath}/common/css/spacing.css`, + `${appPath}/common/css/misc.css`, + `${appPath}/common/css/layout.css`, + `${appPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/invoice/assets/css/style.css b/print/templates/reports/invoice/assets/css/style.css new file mode 100644 index 000000000..cbe894097 --- /dev/null +++ b/print/templates/reports/invoice/assets/css/style.css @@ -0,0 +1,40 @@ +#signature { + padding-right: 10px +} + +#signature img { + -webkit-filter: brightness(0%); + filter: brightness(0%); + margin-bottom: 10px; + max-width: 150px +} + +.description strong { + text-transform: uppercase; +} + +h2 { + font-weight: 100; + color: #555 +} + +.ticket-info { + font-size: 26px +} + +#phytosanitary { + padding-right: 10px +} + +#phytosanitary .flag img { + width: 100% +} + +#phytosanitary .flag .flag-text { + padding-left: 10px; + box-sizing: border-box; +} + +.phytosanitary-info { + margin-top: 10px +} \ No newline at end of file diff --git a/print/templates/reports/invoice/assets/images/europe.png b/print/templates/reports/invoice/assets/images/europe.png new file mode 100644 index 000000000..673be92ae Binary files /dev/null and b/print/templates/reports/invoice/assets/images/europe.png differ diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html new file mode 100644 index 000000000..a1015c28c --- /dev/null +++ b/print/templates/reports/invoice/invoice.html @@ -0,0 +1,276 @@ + + + + + + + + + +
+ + + + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('invoiceData')}}
+
+

{{client.socialName}}

+
+ {{client.street}} +
+
+ {{client.fi}} +
+
+
+
+
+ + +

{{$t('saleLines')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}
+ + {{sale.tag5}} {{sale.value5}} + + + {{sale.tag6}} {{sale.value6}} + + + {{sale.tag7}} {{sale.value7}} + +
+ {{$t('subtotal')}} + {{getSubTotal() | currency('EUR', $i18n.locale)}}
+ + +
+ +
+

{{$t('services')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('vat')}}{{$t('amount')}}
{{service.quantity}}{{service.description}}{{service.price | currency('EUR', $i18n.locale)}}{{service.taxDescription}}{{service.price | currency('EUR', $i18n.locale)}}
+ {{$t('subtotal')}} + {{serviceTotal | currency('EUR', $i18n.locale)}}
+
+ +
+
+ +
+

{{$t('packagings')}}

+ + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}
{{packaging.itemFk | zerofill('000000')}}{{packaging.quantity}}{{packaging.name}}
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxBreakdown')}}
{{$t('type')}} + {{$t('taxBase')}} + {{$t('tax')}}{{$t('fee')}}
{{tax.name}} + {{tax.Base | currency('EUR', $i18n.locale)}} + {{tax.vatPercent | percentage}}{{tax.tax | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} + {{getTotalBase() | currency('EUR', $i18n.locale)}} + {{getTotalTax()| currency('EUR', $i18n.locale)}}
{{$t('total')}}{{getTotal() | currency('EUR', $i18n.locale)}}
+
+ + + + + +
+
+ + + +
+
+
+ + + +
+ + \ No newline at end of file diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js new file mode 100755 index 000000000..ac4f62155 --- /dev/null +++ b/print/templates/reports/invoice/invoice.js @@ -0,0 +1,129 @@ +const config = require(`${appPath}/core/config`); +const Component = require(`${appPath}/core/component`); +const reportHeader = new Component('report-header'); +const reportFooter = new Component('report-footer'); +const md5 = require('md5'); +const fs = require('fs-extra'); + +module.exports = { + name: 'invoice', + async serverPrefetch() { + this.invoice = await this.fetchInvoice(this.invoiceId); + this.client = {}; + this.address = {}; + this.sales = []; + this.services = []; + this.taxes = []; + this.packagings = []; + /* this.client = await this.fetchClient(this.ticketId); + this.ticket = await this.fetchTicket(this.ticketId); + this.sales = await this.fetchSales(this.ticketId); + this.address = await this.fetchAddress(this.ticketId); + this.services = await this.fetchServices(this.ticketId); + this.taxes = await this.fetchTaxes(this.ticketId); + this.packagings = await this.fetchPackagings(this.ticketId); + this.signature = await this.fetchSignature(this.ticketId); + + */ + if (!this.invoice) + 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`; + const src = fs.readFileSync(file); + const base64 = Buffer.from(src, 'utf8').toString('base64'); + + return `data:image/png;base64, ${base64}`; + }, + serviceTotal() { + let total = 0.00; + this.services.forEach(service => { + total += parseFloat(service.price) * service.quantity; + }); + + return total; + } */ + }, + methods: { + fetchInvoice(invoiceId) { + return this.findOneFromDef('invoice', [invoiceId]); + }, + fetchClient(ticketId) { + return this.findOneFromDef('client', [ticketId]); + }, + fetchAddress(ticketId) { + return this.findOneFromDef(`address`, [ticketId]); + }, + fetchSignature(ticketId) { + return this.findOneFromDef('signature', [ticketId]); + }, + fetchTaxes(ticketId) { + return this.findOneFromDef(`taxes`, [ticketId]); + }, + fetchSales(ticketId) { + return this.rawSqlFromDef('sales', [ticketId]); + }, + fetchPackagings(ticketId) { + return this.rawSqlFromDef('packagings', [ticketId]); + }, + fetchServices(ticketId) { + return this.rawSqlFromDef('services', [ticketId]); + }, + + 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: { + 'report-header': reportHeader.build(), + 'report-footer': reportFooter.build() + }, + props: { + invoiceId: { + type: String, + required: true + } + } +}; diff --git a/print/templates/reports/invoice/locale/es.yml b/print/templates/reports/invoice/locale/es.yml new file mode 100644 index 000000000..783babfb7 --- /dev/null +++ b/print/templates/reports/invoice/locale/es.yml @@ -0,0 +1,27 @@ +title: Factura +invoice: Factura +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 +type: Tipo +taxBase: Base imp. +tax: Tasa +fee: Cuota +total: Total +subtotal: Subtotal +taxBreakdown: Desglose impositivo +packagings: Cubos y embalajes +services: Servicios +vatType: Tipo de IVA +digitalSignature: Firma digital +invoiceRef: Factura {0} +plantPassport: Pasaporte fitosanitario \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/address.sql b/print/templates/reports/invoice/sql/address.sql new file mode 100644 index 000000000..86414635a --- /dev/null +++ b/print/templates/reports/invoice/sql/address.sql @@ -0,0 +1,11 @@ +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 = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/client.sql b/print/templates/reports/invoice/sql/client.sql new file mode 100644 index 000000000..5318a98c7 --- /dev/null +++ b/print/templates/reports/invoice/sql/client.sql @@ -0,0 +1,8 @@ +SELECT + c.id, + c.socialName, + c.street, + c.fi +FROM ticket t + JOIN client c ON c.id = t.clientFk +WHERE t.id = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/corrected.sql b/print/templates/reports/invoice/sql/corrected.sql new file mode 100644 index 000000000..4ea56f38d --- /dev/null +++ b/print/templates/reports/invoice/sql/corrected.sql @@ -0,0 +1,5 @@ +SELECT io.amount, io.ref, io.issued, ict.description +FROM vn.invoiceCorrection ic + JOIN vn.invoiceOut io ON io.id = ic.correctedFk + JOIN vn.invoiceCorrectionType ict ON ict.id = ic.invoiceCorrectionTypeFk +where ic.correctingFk = # \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/invoice.sql b/print/templates/reports/invoice/sql/invoice.sql new file mode 100644 index 000000000..3e141d660 --- /dev/null +++ b/print/templates/reports/invoice/sql/invoice.sql @@ -0,0 +1,17 @@ +SELECT + io.issued, + io.clientFk, + io.companyFk, + io.ref, + c.socialName, + c.street postalAddress, + cny.code companyCode, + IF (ios.taxAreaFk IS NOT NULL, CONCAT(cty.code, c.fi), c.fi) fi, + CONCAT(c.postcode,' - ',c.city) postcodeCity +FROM vn.invoiceOut io + JOIN vn.client c ON c.id = io.clientFk + JOIN vn.country cty ON cty.id = c.countryFk + JOIN company cny ON cny.id = io.companyFk + LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial + AND ios.taxAreaFk = 'CEE' +WHERE io.id = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/packagings.sql b/print/templates/reports/invoice/sql/packagings.sql new file mode 100644 index 000000000..75a82a0aa --- /dev/null +++ b/print/templates/reports/invoice/sql/packagings.sql @@ -0,0 +1,9 @@ +SELECT + tp.quantity, + i.name, + p.itemFk +FROM ticketPackaging tp + JOIN packaging p ON p.id = tp.packagingFk + JOIN item i ON i.id = p.itemFk +WHERE tp.ticketFk = ? +ORDER BY itemFk \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/sales.sql b/print/templates/reports/invoice/sql/sales.sql new file mode 100644 index 000000000..d17f6feee --- /dev/null +++ b/print/templates/reports/invoice/sql/sales.sql @@ -0,0 +1,42 @@ +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' +WHERE s.ticketFk = ? +GROUP BY s.id +ORDER BY (it.isPackaging), s.concept, s.itemFk \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/services.sql b/print/templates/reports/invoice/sql/services.sql new file mode 100644 index 000000000..d64e8dc26 --- /dev/null +++ b/print/templates/reports/invoice/sql/services.sql @@ -0,0 +1,8 @@ +SELECT + tc.code taxDescription, + ts.description, + ts.quantity, + ts.price +FROM ticketService ts + JOIN taxClass tc ON tc.id = ts.taxClassFk +WHERE ts.ticketFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/signature.sql b/print/templates/reports/invoice/sql/signature.sql new file mode 100644 index 000000000..2eb83b3ac --- /dev/null +++ b/print/templates/reports/invoice/sql/signature.sql @@ -0,0 +1,8 @@ +SELECT + d.id, + d.created +FROM ticket t + JOIN ticketDms dt ON dt.ticketFk = t.id + JOIN dms d ON d.id = dt.dmsFk + AND d.file LIKE '%.png' +WHERE t.id = ? \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/taxes.sql b/print/templates/reports/invoice/sql/taxes.sql new file mode 100644 index 000000000..576074df7 --- /dev/null +++ b/print/templates/reports/invoice/sql/taxes.sql @@ -0,0 +1,8 @@ +SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent, ios.footNotes + FROM vn.invoiceOutTax iot + JOIN vn.pgc ON pgc.code = iot.pgcFk + LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code + JOIN vn.invoiceOut io ON io.id = iot.invoiceOutFk + LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial + WHERE invoiceOutFk = # + ORDER BY iot.id \ No newline at end of file