4077-login_recover-password & account_verifyEmail #1063

Merged
alexm merged 52 commits from 4077-login_recover-password into dev 2022-11-28 11:34:03 +00:00
24 changed files with 205 additions and 44204 deletions
Showing only changes of commit 2165ca0f34 - Show all commits

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) {
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;
});
}
}
}

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

View File

@ -377,6 +377,7 @@ class Controller extends Section {
const params = {
ticketFk: this.ticket.id,
created: this.ticket.updated,
landed: this.ticket.landed,
notAvailables
};
this.newSMS = {

View File

@ -1,3 +1,3 @@
Product not available: >-
Verdnatura communicates: Your order {{ticketFk}} created on {{created | date: "dd/MM/yyyy"}}.
Verdnatura communicates: Your order {{ticketFk}} with reception date on {{landed | date: "dd/MM/yyyy"}}.
{{notAvailables}} not available. Sorry for the inconvenience.

View File

@ -26,7 +26,7 @@ Destination ticket: Ticket destinatario
Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok'
Reserved: Reservado
Send shortage SMS: Enviar SMS faltas
Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{created | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias."
Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} con fecha recepción {{landed | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias."
Continue anyway?: ¿Continuar de todas formas?
This ticket is now empty: El ticket ha quedado vacio
Do you want to delete it?: ¿Quieres eliminarlo?

View File

@ -58,8 +58,7 @@ module.exports = Self => {
emailBody = bufferCopy.toUpperCase().trim();
const bodyPositionOK = emailBody.match(/\bOK\b/i);
const bodyPositionIndex = (bodyPositionOK.index == 0 || bodyPositionOK.index == 122);
if (bodyPositionOK != null && bodyPositionIndex)
if (bodyPositionOK != null && (bodyPositionOK.index == 0 || bodyPositionOK.index == 122))
isEmailOk = true;
else
isEmailOk = false;
@ -157,7 +156,7 @@ module.exports = Self => {
let [user] = await Self.rawSql(`SELECT u.id,u.name FROM account.user u
LEFT JOIN account.mailForward m on m.account = u.id
WHERE forwardTo =? OR
WHERE forwardTo =? OR
CONCAT(u.name,'@verdnatura.es') = ?`,
[userEmail[0], userEmail[0]]);

44185
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "8.8.0",
"version": "9.0.0",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",
@ -39,7 +39,7 @@
"node-ssh": "^11.0.0",
"object-diff": "0.0.4",
"object.pick": "^1.3.0",
"puppeteer": "^19.0.0",
"puppeteer": "^18.0.5",
"read-chunk": "^3.2.0",
"require-yaml": "0.0.1",
"sharp": "^0.31.0",

View File

@ -9,6 +9,7 @@ module.exports = {
concurrency: Cluster.CONCURRENCY_CONTEXT,
maxConcurrency: cpus().length,
puppeteerOptions: {
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',