2989 - Added client deb statement sample
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
parent
63b502b368
commit
bfd6c995ad
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE `vn`.`sample` ADD COLUMN
|
||||
(`datepickerEnabled` TINYINT(1) NOT NULL DEFAULT 0);
|
||||
|
||||
ALTER TABLE `vn`.`sample` MODIFY code VARCHAR(25) charset utf8 NOT NULL;
|
||||
|
||||
INSERT INTO `vn`.`sample` (code, description, isVisible, hasCompany, hasPreview, datepickerEnabled)
|
||||
VALUES ('client-debt-statement', 'Extracto del cliente', 1, 0, 1, 1);
|
|
@ -9,23 +9,26 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"code": {
|
||||
"type": "String"
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "String"
|
||||
"type": "string"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "Boolean"
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasCompany": {
|
||||
"type": "Boolean"
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasPreview": {
|
||||
"type": "Boolean"
|
||||
"type": "boolean"
|
||||
},
|
||||
"datepickerEnabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"scopes": {
|
||||
|
|
|
@ -25,20 +25,6 @@
|
|||
</vn-crud-model>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
label="Recipient"
|
||||
ng-model="$ctrl.clientSample.recipient"
|
||||
info="Its only used when sample is sent">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
label="Reply to"
|
||||
ng-model="$ctrl.clientSample.replyTo"
|
||||
info="To who should the recipient reply?">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-id="sampleType"
|
||||
|
@ -47,8 +33,25 @@
|
|||
fields="['code','hasCompany', 'hasPreview']"
|
||||
data="samplesVisible"
|
||||
show-field="description"
|
||||
label="Sample">
|
||||
label="Sample"
|
||||
required="true">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
label="Recipient"
|
||||
ng-model="$ctrl.clientSample.recipient"
|
||||
info="Its only used when sample is sent"
|
||||
required="true">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Reply to"
|
||||
ng-model="$ctrl.clientSample.replyTo"
|
||||
info="To who should the recipient reply?"
|
||||
required="true">
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal ng-if="sampleType.selection.hasCompany || sampleType.selection.datepickerEnabled">
|
||||
<vn-autocomplete
|
||||
ng-model="$ctrl.companyId"
|
||||
model="ClientSample.companyFk"
|
||||
|
@ -57,6 +60,12 @@
|
|||
label="Company"
|
||||
ng-if="sampleType.selection.hasCompany">
|
||||
</vn-autocomplete>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="From"
|
||||
ng-model="$ctrl.clientSample.from"
|
||||
ng-if="sampleType.selection.datepickerEnabled">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
|
|
|
@ -80,6 +80,12 @@ class Controller extends Section {
|
|||
if (sampleType.hasCompany)
|
||||
params.companyId = this.clientSample.companyFk;
|
||||
|
||||
if (sampleType.datepickerEnabled && !this.clientSample.from)
|
||||
return this.vnApp.showError(this.$t('Choose a date'));
|
||||
|
||||
if (sampleType.datepickerEnabled)
|
||||
params.from = this.clientSample.from;
|
||||
|
||||
let query = `email/${sampleType.code}`;
|
||||
if (isPreview)
|
||||
query = `email/${sampleType.code}/preview`;
|
||||
|
|
|
@ -89,8 +89,7 @@ const dbHelper = {
|
|||
const absolutePath = path.join(__dirname, '../', this.path, 'sql', queryName);
|
||||
return db.getSqlFromDef(absolutePath);
|
||||
},
|
||||
},
|
||||
props: ['tplPath']
|
||||
}
|
||||
};
|
||||
|
||||
Vue.mixin(dbHelper);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
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/email.css`])
|
||||
.mergeStyles();
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"filename": "client-debt-statement.pdf",
|
||||
"component": "client-debt-statement"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html v-bind:lang="$i18n.locale">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<title>{{ $t('subject') }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<table class="grid">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Empty block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block empty"></div>
|
||||
</div>
|
||||
<!-- Header block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block">
|
||||
<email-header v-bind="$props"></email-header>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block vn-pa-ml">
|
||||
<h1>{{ $t('title') }}</h1>
|
||||
<p>{{$t('description.instructions')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Preview block -->
|
||||
<div class="grid-row" v-if="isPreview">
|
||||
<div class="grid-block vn-pa-ml">
|
||||
<attachment v-for="attachment in attachments"
|
||||
v-bind:key="attachment.filename"
|
||||
v-bind:attachment="attachment"
|
||||
v-bind:args="$props">
|
||||
</attachment>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block">
|
||||
<email-footer v-bind="$props"></email-footer>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Empty block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block empty"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
const Component = require(`${appPath}/core/component`);
|
||||
const emailHeader = new Component('email-header');
|
||||
const emailFooter = new Component('email-footer');
|
||||
const attachment = new Component('attachment');
|
||||
const attachments = require('./attachments.json');
|
||||
|
||||
module.exports = {
|
||||
name: 'client-debt-statement',
|
||||
components: {
|
||||
'email-header': emailHeader.build(),
|
||||
'email-footer': emailFooter.build(),
|
||||
'attachment': attachment.build()
|
||||
},
|
||||
data() {
|
||||
return {attachments};
|
||||
},
|
||||
props: {
|
||||
recipientId: {
|
||||
required: true
|
||||
},
|
||||
from: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
subject: Extracto de tu balance
|
||||
title: Extracto de tu balance
|
||||
description:
|
||||
instructions: Adjuntamos el extracto de tu balance.
|
|
@ -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();
|
|
@ -0,0 +1,3 @@
|
|||
table.column-oriented {
|
||||
margin-top: 50px !important
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
<!DOCTYPE html>
|
||||
<html v-bind:lang="$i18n.locale">
|
||||
<body>
|
||||
<table class="grid">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<!-- Header block -->
|
||||
<report-header v-bind="$props"></report-header>
|
||||
<!-- Block -->
|
||||
<div class="grid-row">
|
||||
<div class="grid-block">
|
||||
<div class="columns">
|
||||
<div class="size50">
|
||||
<div class="size75">
|
||||
<h1 class="title uppercase">{{$t('title')}}</h1>
|
||||
<table class="row-oriented">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font gray uppercase">{{$t('clientId')}}</td>
|
||||
<th>{{client.id}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font gray uppercase">{{$t('date')}}</td>
|
||||
<th>{{dated}}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size50">
|
||||
<div class="panel">
|
||||
<div class="header">{{$t('clientData')}}</div>
|
||||
<div class="body">
|
||||
<h3 class="uppercase">{{client.socialName}}</h3>
|
||||
<div>
|
||||
{{client.street}}
|
||||
</div>
|
||||
<div>
|
||||
{{client.postcode}}, {{client.city}} ({{client.province}})
|
||||
</div>
|
||||
<div>
|
||||
{{client.country}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="column-oriented">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{$t('date')}}</th>
|
||||
<th>{{$t('concept')}}</th>
|
||||
<th class="number">{{$t('invoiced')}}</th>
|
||||
<th class="number">{{$t('payed')}}</th>
|
||||
<th class="number">{{$t('balance')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="sale in sales" :key="sale.id">
|
||||
<tr>
|
||||
<td>{{sale.issued | date('%d-%m-%Y')}}</td>
|
||||
<td>{{sale.ref}}</td>
|
||||
<td class="number">{{sale.debtOut}}</td>
|
||||
<td class="number">{{sale.debtIn}}</td>
|
||||
<td class="number">{{getBalance(sale)}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="number">
|
||||
<strong class="pull-left">Total</strong>
|
||||
{{getTotalDebtOut() | currency('EUR', $i18n.locale)}}
|
||||
</td>
|
||||
<td class="number">{{getTotalDebtIn() | currency('EUR', $i18n.locale)}}</td>
|
||||
<td class="number">{{totalBalance | currency('EUR', $i18n.locale)}}</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Footer block -->
|
||||
<report-footer id="pageFooter"
|
||||
v-bind:left-text="$t('client', [client.id])"
|
||||
v-bind:center-text="client.socialName"
|
||||
v-bind="$props">
|
||||
</report-footer>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,78 @@
|
|||
const Component = require(`${appPath}/core/component`);
|
||||
const reportHeader = new Component('report-header');
|
||||
const reportFooter = new Component('report-footer');
|
||||
|
||||
module.exports = {
|
||||
name: 'client-debt-statement',
|
||||
async serverPrefetch() {
|
||||
this.client = await this.fetchClient(this.recipientId);
|
||||
this.sales = await this.fetchSales(this.recipientId, this.from);
|
||||
|
||||
if (!this.client)
|
||||
throw new Error('Something went wrong');
|
||||
},
|
||||
computed: {
|
||||
dated: function() {
|
||||
const filters = this.$options.filters;
|
||||
|
||||
return filters.date(new Date(), '%d-%m-%Y');
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {totalBalance: 0.00};
|
||||
},
|
||||
methods: {
|
||||
fetchClient(clientId) {
|
||||
return this.findOneFromDef('client', [clientId]);
|
||||
},
|
||||
fetchSales(clientId, from) {
|
||||
return this.rawSqlFromDef('sales', [
|
||||
from,
|
||||
clientId,
|
||||
from,
|
||||
clientId,
|
||||
from,
|
||||
clientId,
|
||||
from,
|
||||
clientId,
|
||||
from,
|
||||
clientId
|
||||
]);
|
||||
},
|
||||
getBalance(sale) {
|
||||
if (sale.debtOut)
|
||||
this.totalBalance += parseFloat(sale.debtOut);
|
||||
|
||||
if (sale.debtIn)
|
||||
this.totalBalance -= parseFloat(sale.debtIn);
|
||||
|
||||
return parseFloat(this.totalBalance.toFixed(2));
|
||||
},
|
||||
getTotalDebtOut() {
|
||||
let debtOut = 0.00;
|
||||
for (let sale of this.sales)
|
||||
debtOut += sale.debtOut ? parseFloat(sale.debtOut) : 0;
|
||||
|
||||
return debtOut.toFixed(2);
|
||||
},
|
||||
getTotalDebtIn() {
|
||||
let debtIn = 0.00;
|
||||
for (let sale of this.sales)
|
||||
debtIn += sale.debtIn ? parseFloat(sale.debtIn) : 0;
|
||||
|
||||
return debtIn.toFixed(2);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'report-header': reportHeader.build(),
|
||||
'report-footer': reportFooter.build()
|
||||
},
|
||||
props: {
|
||||
recipientId: {
|
||||
required: true
|
||||
},
|
||||
from: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
title: Extracto
|
||||
clientId: Cliente
|
||||
clientData: Datos del cliente
|
||||
date: Fecha
|
||||
concept: Concepto
|
||||
invoiced: Facturado
|
||||
payed: Pagado
|
||||
balance: Saldo
|
||||
client: Cliente {0}
|
|
@ -0,0 +1,9 @@
|
|||
title: Relevé de compte
|
||||
clientId: Client
|
||||
clientData: Données client
|
||||
date: Date
|
||||
concept: Objet
|
||||
invoiced: Facturé
|
||||
payed: Payé
|
||||
balance: Solde
|
||||
client: Client {0}
|
|
@ -0,0 +1,13 @@
|
|||
SELECT
|
||||
c.id,
|
||||
c.socialName,
|
||||
c.street,
|
||||
c.postcode,
|
||||
c.city,
|
||||
c.fi,
|
||||
p.name AS province,
|
||||
ct.country
|
||||
FROM client c
|
||||
JOIN country ct ON ct.id = c.countryFk
|
||||
LEFT JOIN province p ON p.id = c.provinceFk
|
||||
WHERE c.id = ?
|
|
@ -0,0 +1,53 @@
|
|||
SELECT
|
||||
issued,
|
||||
CAST(debtOut AS DECIMAL(10,2)) debtOut,
|
||||
CAST(debtIn AS DECIMAL(10,2)) debtIn,
|
||||
ref,
|
||||
companyFk,
|
||||
priority
|
||||
FROM (
|
||||
SELECT
|
||||
? AS issued,
|
||||
SUM(amountUnpaid) AS debtOut,
|
||||
NULL AS debtIn,
|
||||
'Saldo Anterior' AS ref,
|
||||
companyFk,
|
||||
0 as priority
|
||||
FROM (
|
||||
SELECT SUM(amount) AS amountUnpaid, companyFk, 0
|
||||
FROM invoiceOut io
|
||||
WHERE io.clientFk = ?
|
||||
AND io.issued < ?
|
||||
GROUP BY io.companyFk
|
||||
UNION ALL
|
||||
SELECT SUM(-1 * amountPaid), companyFk, 0
|
||||
FROM receipt
|
||||
WHERE clientFk = ?
|
||||
AND payed < ?
|
||||
GROUP BY companyFk) AS transactions
|
||||
GROUP BY companyFk
|
||||
UNION ALL
|
||||
SELECT
|
||||
issued,
|
||||
amount as debtOut,
|
||||
NULL AS debtIn,
|
||||
ref,
|
||||
companyFk,
|
||||
1
|
||||
FROM invoiceOut
|
||||
WHERE clientFk = ?
|
||||
AND issued >= ?
|
||||
UNION ALL
|
||||
SELECT
|
||||
r.payed,
|
||||
NULL as debtOut,
|
||||
r.amountPaid,
|
||||
r.invoiceFk,
|
||||
r.companyFk,
|
||||
0
|
||||
FROM receipt r
|
||||
WHERE r.clientFk = ?
|
||||
AND r.payed >= ?) t
|
||||
INNER JOIN `client` c ON c.id = ?
|
||||
HAVING debtOut <> 0 OR debtIn <> 0
|
||||
ORDER BY issued, priority DESC, debtIn;
|
Loading…
Reference in New Issue