Merge pull request '3951-invoiceOut.index_downloadPdfs' (!1110) from 3951-invoiceOut.index_downloadPdfs into dev
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
Reviewed-on: #1110 Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
commit
1bcde47ab1
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE TABLE `vn`.`zipConfig` (
|
||||||
|
`id` double(10,2) NOT NULL,
|
||||||
|
`maxSize` int(11) DEFAULT NULL COMMENT 'in MegaBytes',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES
|
||||||
|
('ZipConfig', '*', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,55 @@
|
||||||
|
const JSZip = require('jszip');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('downloadZip', {
|
||||||
|
description: 'Download a zip file with multiple invoices pdfs',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'ids',
|
||||||
|
type: ['number'],
|
||||||
|
description: 'The invoice ids'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
arg: 'base64',
|
||||||
|
type: 'string',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: '/downloadZip',
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.downloadZip = async function(ctx, ids, options) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
let totalSize = 0;
|
||||||
|
const zipConfig = await models.ZipConfig.findOne(null, myOptions);
|
||||||
|
for (let id of ids) {
|
||||||
|
if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large');
|
||||||
|
const invoiceOutPdf = await models.InvoiceOut.download(ctx, id, myOptions);
|
||||||
|
const fileName = extractFileName(invoiceOutPdf[2]);
|
||||||
|
const body = invoiceOutPdf[0];
|
||||||
|
const sizeInBytes = (await fs.promises.stat(body.path)).size;
|
||||||
|
const sizeInMegabytes = sizeInBytes / (1024 * 1024);
|
||||||
|
totalSize += sizeInMegabytes;
|
||||||
|
zip.file(fileName, body);
|
||||||
|
}
|
||||||
|
const base64 = await zip.generateAsync({type: 'base64'});
|
||||||
|
return base64;
|
||||||
|
};
|
||||||
|
|
||||||
|
function extractFileName(str) {
|
||||||
|
const matches = str.match(/"(.*?)"/);
|
||||||
|
return matches ? matches[1] : str;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,53 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
describe('InvoiceOut downloadZip()', () => {
|
||||||
|
const userId = 9;
|
||||||
|
const invoiceIds = [1, 2];
|
||||||
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
|
||||||
|
accessToken: {userId: userId},
|
||||||
|
headers: {origin: 'http://localhost:5000'},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should return part of link to dowloand the zip', async() => {
|
||||||
|
const tx = await models.Order.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const result = await models.InvoiceOut.downloadZip(ctx, invoiceIds, options);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an error if the size of the files is too large', async() => {
|
||||||
|
const tx = await models.Order.beginTransaction({});
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const zipConfig = {
|
||||||
|
maxSize: 0
|
||||||
|
};
|
||||||
|
await models.ZipConfig.create(zipConfig, options);
|
||||||
|
|
||||||
|
await models.InvoiceOut.downloadZip(ctx, invoiceIds, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toEqual(new UserError(`Files are too large`));
|
||||||
|
});
|
||||||
|
});
|
|
@ -22,5 +22,8 @@
|
||||||
},
|
},
|
||||||
"TaxType": {
|
"TaxType": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ZipConfig": {
|
||||||
|
"dataSource": "vn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ module.exports = Self => {
|
||||||
require('../methods/invoiceOut/summary')(Self);
|
require('../methods/invoiceOut/summary')(Self);
|
||||||
require('../methods/invoiceOut/getTickets')(Self);
|
require('../methods/invoiceOut/getTickets')(Self);
|
||||||
require('../methods/invoiceOut/download')(Self);
|
require('../methods/invoiceOut/download')(Self);
|
||||||
|
require('../methods/invoiceOut/downloadZip')(Self);
|
||||||
require('../methods/invoiceOut/delete')(Self);
|
require('../methods/invoiceOut/delete')(Self);
|
||||||
require('../methods/invoiceOut/book')(Self);
|
require('../methods/invoiceOut/book')(Self);
|
||||||
require('../methods/invoiceOut/createPdf')(Self);
|
require('../methods/invoiceOut/createPdf')(Self);
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "ZipConfig",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "zipConfig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"maxSize": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -4,10 +4,24 @@
|
||||||
<vn-data-viewer
|
<vn-data-viewer
|
||||||
model="model"
|
model="model"
|
||||||
class="vn-w-lg">
|
class="vn-w-lg">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-button
|
||||||
|
disabled="$ctrl.totalChecked == 0"
|
||||||
|
vn-click-stop="$ctrl.openPdf()"
|
||||||
|
icon="cloud_download"
|
||||||
|
title="Download PDF"
|
||||||
|
vn-tooltip="Download PDF">
|
||||||
|
</vn-button>
|
||||||
|
</vn-card>
|
||||||
<vn-card>
|
<vn-card>
|
||||||
<vn-table model="model">
|
<vn-table model="model">
|
||||||
<vn-thead>
|
<vn-thead>
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
|
<vn-th shrink>
|
||||||
|
<vn-multi-check
|
||||||
|
model="model">
|
||||||
|
</vn-multi-check>
|
||||||
|
</vn-th>
|
||||||
<vn-th field="ref">Reference</vn-th>
|
<vn-th field="ref">Reference</vn-th>
|
||||||
<vn-th field="issued" expand>Issued</vn-th>
|
<vn-th field="issued" expand>Issued</vn-th>
|
||||||
<vn-th field="amount" number>Amount</vn-th>
|
<vn-th field="amount" number>Amount</vn-th>
|
||||||
|
@ -23,6 +37,12 @@
|
||||||
<a ng-repeat="invoiceOut in model.data"
|
<a ng-repeat="invoiceOut in model.data"
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
||||||
|
<vn-td>
|
||||||
|
<vn-check
|
||||||
|
ng-model="invoiceOut.checked"
|
||||||
|
vn-click-stop>
|
||||||
|
</vn-check>
|
||||||
|
</vn-td>
|
||||||
<vn-td>{{::invoiceOut.ref | dashIfEmpty}}</vn-td>
|
<vn-td>{{::invoiceOut.ref | dashIfEmpty}}</vn-td>
|
||||||
<vn-td shrink>{{::invoiceOut.issued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td shrink>{{::invoiceOut.issued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td number>{{::invoiceOut.amount | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
|
<vn-td number>{{::invoiceOut.amount | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
|
||||||
|
@ -36,15 +56,6 @@
|
||||||
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
|
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
|
||||||
<vn-td shrink>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
<vn-td shrink>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
|
||||||
<vn-td shrink>
|
|
||||||
<vn-icon-button
|
|
||||||
ng-show="invoiceOut.hasPdf"
|
|
||||||
vn-click-stop="$ctrl.openPdf(invoiceOut.id)"
|
|
||||||
icon="cloud_download"
|
|
||||||
title="Download PDF"
|
|
||||||
vn-tooltip="Download PDF">
|
|
||||||
</vn-icon-button>
|
|
||||||
</vn-td>
|
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
vn-click-stop="$ctrl.preview(invoiceOut)"
|
vn-click-stop="$ctrl.preview(invoiceOut)"
|
||||||
|
|
|
@ -2,14 +2,41 @@ import ngModule from '../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
|
get checked() {
|
||||||
|
const rows = this.$.model.data || [];
|
||||||
|
const checkedRows = [];
|
||||||
|
for (let row of rows) {
|
||||||
|
if (row.checked)
|
||||||
|
checkedRows.push(row.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalChecked() {
|
||||||
|
return this.checked.length;
|
||||||
|
}
|
||||||
|
|
||||||
preview(invoiceOut) {
|
preview(invoiceOut) {
|
||||||
this.selectedInvoiceOut = invoiceOut;
|
this.selectedInvoiceOut = invoiceOut;
|
||||||
this.$.summary.show();
|
this.$.summary.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
openPdf(id) {
|
openPdf() {
|
||||||
let url = `api/InvoiceOuts/${id}/download?access_token=${this.vnToken.token}`;
|
if (this.checked.length <= 1) {
|
||||||
window.open(url, '_blank');
|
const [invoiceOutId] = this.checked;
|
||||||
|
const url = `api/InvoiceOuts/${invoiceOutId}/download?access_token=${this.vnToken.token}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
} else {
|
||||||
|
const invoiceOutIds = this.checked;
|
||||||
|
const params = {
|
||||||
|
ids: invoiceOutIds
|
||||||
|
};
|
||||||
|
this.$http.post(`InvoiceOuts/downloadZip`, params)
|
||||||
|
.then(res => {
|
||||||
|
location.href = 'data:application/zip;base64,' + res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,3 +6,4 @@ Minimum: Minimo
|
||||||
Maximum: Máximo
|
Maximum: Máximo
|
||||||
Global invoicing: Facturación global
|
Global invoicing: Facturación global
|
||||||
Manual invoicing: Facturación manual
|
Manual invoicing: Facturación manual
|
||||||
|
Files are too large: Los archivos son demasiado grandes
|
Loading…
Reference in New Issue