3951-invoiceOut.index_downloadPdfs #1110

Merged
joan merged 7 commits from 3951-invoiceOut.index_downloadPdfs into dev 2022-11-04 10:42:35 +00:00
9 changed files with 197 additions and 12 deletions

View File

@ -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');

View File

@ -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;
}
};

View File

@ -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`));
});
});

View File

@ -22,5 +22,8 @@
},
"TaxType": {
"dataSource": "vn"
},
"ZipConfig": {
"dataSource": "vn"
}
}

View File

@ -3,6 +3,7 @@ module.exports = Self => {
require('../methods/invoiceOut/summary')(Self);
require('../methods/invoiceOut/getTickets')(Self);
require('../methods/invoiceOut/download')(Self);
require('../methods/invoiceOut/downloadZip')(Self);
require('../methods/invoiceOut/delete')(Self);
require('../methods/invoiceOut/book')(Self);
require('../methods/invoiceOut/createPdf')(Self);

View File

@ -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"
}]
}

View File

@ -4,10 +4,24 @@
<vn-data-viewer
model="model"
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-table model="model">
<vn-thead>
<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="issued" expand>Issued</vn-th>
<vn-th field="amount" number>Amount</vn-th>
@ -23,6 +37,12 @@
<a ng-repeat="invoiceOut in model.data"
class="clickable vn-tr search-result"
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 shrink>{{::invoiceOut.issued | date:'dd/MM/yyyy' | 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>{{::invoiceOut.companyCode | 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-icon-button
vn-click-stop="$ctrl.preview(invoiceOut)"

View File

@ -2,14 +2,41 @@ import ngModule from '../module';
import Section from 'salix/components/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) {
this.selectedInvoiceOut = invoiceOut;
this.$.summary.show();
}
openPdf(id) {
let url = `api/InvoiceOuts/${id}/download?access_token=${this.vnToken.token}`;
window.open(url, '_blank');
openPdf() {
if (this.checked.length <= 1) {
vicent marked this conversation as resolved
Review

=== 1

=== 1
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;
vicent marked this conversation as resolved
Review

Se podría hacer para que en vez de que la ruta devuelva un base64, que hiciese algo similar a InvoiceOut/download? Podrías abrirlo directamente con window.open

Se podría hacer para que en vez de que la ruta devuelva un base64, que hiciese algo similar a InvoiceOut/download? Podrías abrirlo directamente con window.open
Review

No se podia crear el stream directamente. Al final se ha dejado como estaba planteado.

No se podia crear el stream directamente. Al final se ha dejado como estaba planteado.
});
}
}
}

View File

@ -6,3 +6,4 @@ Minimum: Minimo
Maximum: Máximo
Global invoicing: Facturación global
Manual invoicing: Facturación manual
Files are too large: Los archivos son demasiado grandes