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": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ZipConfig": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
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)"
|
||||
|
|
|
@ -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) {
|
||||
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
|
||||
Global invoicing: Facturación global
|
||||
Manual invoicing: Facturación manual
|
||||
Files are too large: Los archivos son demasiado grandes
|
Loading…
Reference in New Issue