3951-invoiceOut.index_downloadPdfs #1110
|
@ -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) {
|
||||||
vicent marked this conversation as resolved
|
|||||||
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;
|
||||||
vicent marked this conversation as resolved
joan
commented
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
vicent
commented
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.
|
|||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
=== 1