3092-module_transactions #740

Merged
joan merged 41 commits from 3092-module_transactions into dev 2021-10-18 07:42:24 +00:00
25 changed files with 142 additions and 78 deletions
Showing only changes of commit 16b27c2074 - Show all commits

View File

@ -23,5 +23,13 @@
"model": "Account",
"foreignKey": "userFk"
}
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1 @@
UPDATE salix.ACL t SET t.principalId = 'employee' WHERE t.id = 269;

View File

@ -489,11 +489,11 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES
(1, 'T', 1014.24, CURDATE(), 1101, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(2, 'T', 121.36, CURDATE(), 1102, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(3, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(4, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(5, 'A', 8.88, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1);
(1, 'T', 1014.24, CURDATE(), 1101, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(2, 'T', 121.36, CURDATE(), 1102, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(3, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(4, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(5, 'A', 8.88, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 0);
UPDATE `vn`.`invoiceOut` SET ref = 'T1111111' WHERE id = 1;
UPDATE `vn`.`invoiceOut` SET ref = 'T2222222' WHERE id = 2;

View File

@ -115,5 +115,6 @@
"A ticket with a negative base can't be invoiced": "A ticket with a negative base can't be invoiced",
"This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated"
"reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option"
}

View File

@ -209,5 +209,6 @@
"Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes",
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente"
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'"
}

View File

@ -19,7 +19,7 @@ module.exports = Self => {
}
});
Self.hasCustomerRole = (id, options) => {
Self.hasCustomerRole = async(id, options) => {
const myOptions = {};
if (typeof options == 'object')
@ -31,7 +31,9 @@ module.exports = Self => {
JOIN salix.Role r ON r.id = A.roleFK
WHERE r.name = 'customer'
AND A.id IN (?)`;
const [result] = await Self.rawSql(query, [id], myOptions);
const {isCustomer} = result;
return Self.rawSql(query, [id], myOptions);
return isCustomer;
};
};

View File

@ -9,9 +9,9 @@ describe('Client hasCustomerRole', () => {
const id = 1101;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 1}));
expect(result).toBeTruthy();
await tx.rollback();
} catch (e) {
@ -27,9 +27,9 @@ describe('Client hasCustomerRole', () => {
const options = {transaction: tx};
const id = 8;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {
@ -46,9 +46,9 @@ describe('Client hasCustomerRole', () => {
const id = 999;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {
@ -65,9 +65,9 @@ describe('Client hasCustomerRole', () => {
const id = 'WRONG!';
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {

View File

@ -19,7 +19,7 @@ export default class Controller extends Section {
isCustomer() {
if (this.client.id) {
this.$http.get(`Clients/${this.client.id}/hasCustomerRole`).then(res => {
this.canChangePassword = res.data && res.data.isCustomer;
this.canChangePassword = res.data && res.data;
});
}
}

View File

@ -33,7 +33,7 @@ describe('Component VnClientWebAccess', () => {
it('should return true if the password can be modified', () => {
controller.client = {id: '1234'};
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond({isCustomer: true});
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(true);
controller.isCustomer();
$httpBackend.flush();
@ -43,7 +43,7 @@ describe('Component VnClientWebAccess', () => {
it(`should return a false if the password can't be modified`, () => {
controller.client = {id: '1234'};
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond({isCustomer: false});
$httpBackend.expectGET(`Clients/${controller.client.id}/hasCustomerRole`).respond(false);
controller.isCustomer();
$httpBackend.flush();

View File

@ -29,7 +29,7 @@ module.exports = Self => {
const models = Self.app.models;
const headers = ctx.req.headers;
const origin = headers.origin;
const authorization = headers.authorization;
const auth = ctx.req.accessToken;
if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`);
@ -48,25 +48,30 @@ module.exports = Self => {
let fileSrc;
try {
const invoiceOut = await Self.findById(id, null, myOptions);
const hasInvoicing = await models.Account.hasRole(auth.userId, 'invoicing', myOptions);
if (invoiceOut.hasPdf && !hasInvoicing)
throw new UserError(`You don't have enough privileges`);
await invoiceOut.updateAttributes({
hasPdf: true
}, myOptions);
const response = got.stream(`${origin}/api/report/invoice`, {
query: {
authorization: authorization,
authorization: auth.id,
invoiceId: id
}
});
const created = invoiceOut.created;
const year = created.getFullYear().toString();
const month = created.getMonth().toString();
const day = created.getDate().toString();
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const container = await models.InvoiceContainer.container(year);
const rootPath = container.client.root;
const fileName = `${invoiceOut.ref}.pdf`;
const fileName = `${year}${invoiceOut.ref}.pdf`;
const src = path.join(rootPath, year, month, day);
fileSrc = path.join(src, fileName);
@ -75,16 +80,11 @@ module.exports = Self => {
if (tx) await tx.commit();
const writeStream = fs.createWriteStream(fileSrc);
writeStream.on('open', () => {
response.pipe(writeStream);
});
writeStream.on('open', () => response.pipe(writeStream));
writeStream.on('finish', () => writeStream.end());
return new Promise(resolve => {
writeStream.on('finish', () => {
writeStream.end();
resolve(invoiceOut);
});
writeStream.on('close', () => resolve(invoiceOut));
});
} catch (e) {
if (tx) await tx.rollback();

View File

@ -1,8 +1,9 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('download', {
Self.remoteMethodCtx('download', {
description: 'Download an invoice PDF',
accessType: 'READ',
accepts: [
@ -34,34 +35,41 @@ module.exports = Self => {
}
});
Self.download = async function(id, options) {
Self.download = async function(ctx, id, options) {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const invoiceOut = await models.InvoiceOut.findById(id, null, myOptions);
try {
const invoiceOut = await models.InvoiceOut.findById(id, null, myOptions);
const created = invoiceOut.created;
const year = created.getFullYear().toString();
const month = created.getMonth().toString();
const day = created.getDate().toString();
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const container = await models.InvoiceContainer.container(year);
const rootPath = container.client.root;
const src = path.join(rootPath, year, month, day);
const fileName = `${invoiceOut.ref}.pdf`;
const fileSrc = path.join(src, fileName);
const container = await models.InvoiceContainer.container(year);
const rootPath = container.client.root;
const src = path.join(rootPath, year, month, day);
const fileName = `${year}${invoiceOut.ref}.pdf`;
const fileSrc = path.join(src, fileName);
const file = {
path: fileSrc,
contentType: 'application/pdf',
name: `${id}.pdf`
};
const file = {
path: fileSrc,
contentType: 'application/pdf',
name: fileName
};
await fs.access(file.path);
let stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`];
await fs.access(file.path);
let stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${file.name}"`];
} catch (error) {
if (error.code === 'ENOENT')
throw new UserError('The PDF document does not exists');
throw error;
}
};
};

View File

@ -2,16 +2,27 @@ const models = require('vn-loopback/server/server').models;
const fs = require('fs-extra');
describe('InvoiceOut download()', () => {
it('should return the downloaded fine name', async() => {
const userId = 9;
const invoiceId = 1;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
it('should return the downloaded file name', async() => {
spyOn(models.InvoiceContainer, 'container').and.returnValue({
client: {root: '/path'}
});
spyOn(fs, 'createReadStream').and.returnValue(new Promise(resolve => resolve('streamObject')));
spyOn(fs, 'access').and.returnValue(true);
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
const result = await models.InvoiceOut.download(1);
const result = await models.InvoiceOut.download(ctx, invoiceId);
expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual('filename="1.pdf"');
expect(result[2]).toEqual('filename="2021T1111111.pdf"');
});
});

View File

@ -61,9 +61,12 @@ describe('InvoiceOut filter()', () => {
}
};
const invoiceOut = await models.InvoiceOut.findById(1, null, options);
await invoiceOut.updateAttribute('hasPdf', true, options);
const result = await models.InvoiceOut.filter(ctx, {}, options);
expect(result.length).toEqual(5);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {

View File

@ -1,5 +1,8 @@
<vn-portal slot="menu">
<vn-invoice-out-descriptor invoice-out="$ctrl.invoiceOut"></vn-invoice-out-descriptor>
<vn-invoice-out-descriptor
invoice-out="$ctrl.invoiceOut"
card-reload="$ctrl.reload()">
</vn-invoice-out-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -10,7 +10,8 @@ class Controller extends ModuleCard {
'issued',
'amount',
'clientFk',
'companyFk'
'companyFk',
'hasPdf'
],
include: [
{

View File

@ -33,11 +33,10 @@
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
ng-show="$ctrl.hasInvoicing || !$ctrl.invoiceOut.hasPdf"
name="regenerateInvoice"
translate>
Regenerate invoice PDF
{{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
</slot-menu>
<slot-body>
@ -101,8 +100,8 @@
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createInvoicePdf()"
question="Are you sure you want to regenerate the invoice PDF document?"
message="You are going to regenerate the invoice PDF document">
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send invoice confirmation popup -->

View File

@ -10,6 +10,10 @@ class Controller extends Descriptor {
this.entity = value;
}
get hasInvoicing() {
return this.aclService.hasAny(['invoicing']);
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
@ -25,6 +29,7 @@ class Controller extends Descriptor {
createInvoicePdf() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
@ -60,6 +65,17 @@ class Controller extends Descriptor {
.then(res => this.entity = res.data);
}
reload() {
return this.loadData().then(() => {
if (this.cardReload)
this.cardReload();
});
}
cardReload() {
// Prevents error when not defined
}
sendInvoice($data) {
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
@ -73,6 +89,7 @@ ngModule.vnComponent('vnInvoiceOutDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
invoiceOut: '<'
invoiceOut: '<',
cardReload: '&'
}
});

View File

@ -18,6 +18,7 @@ describe('vnInvoiceOutDescriptor', () => {
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createInvoicePdf();
$httpBackend.flush();

View File

@ -12,5 +12,6 @@ Are you sure you want to clone this invoice?: Estas seguro de clonar esta factur
Book invoice: Asentar factura
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate invoice PDF: Regenerar PDF factura
Generate PDF invoice: Generar PDF factura
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado

View File

@ -1,5 +1,8 @@
<vn-portal slot="menu">
<vn-ticket-descriptor ticket="$ctrl.ticket" card-reload="$ctrl.reload()"></vn-ticket-descriptor>
<vn-ticket-descriptor
ticket="$ctrl.ticket"
card-reload="$ctrl.reload()">
</vn-ticket-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -80,12 +80,10 @@
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.isInvoiced"
vn-acl="invoicing"
vn-acl-action="remove"
ng-show="$ctrl.isInvoiced && ($ctrl.hasInvoicing || !$ctrl.ticket.invoiceOut.hasPdf)"
name="regenerateInvoice"
translate>
Regenerate invoice PDF
{{!$ctrl.ticket.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
<vn-item
ng-click="recalculateComponentsConfirmation.show()"
@ -210,8 +208,8 @@
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createInvoicePdf()"
question="Are you sure you want to regenerate the invoice PDF document?"
message="You are going to regenerate the invoice PDF document">
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Recalculate components confirmation dialog -->

View File

@ -226,6 +226,7 @@ class Controller extends Section {
createInvoicePdf() {
const invoiceId = this.ticket.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);

View File

@ -153,6 +153,7 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
it('should make a query and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.whenGET(`Tickets/16`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${ticket.invoiceOut.id}/createPdf`).respond();
controller.createInvoicePdf();
$httpBackend.flush();

View File

@ -18,6 +18,10 @@ class Controller extends Descriptor {
super.entity = value;
}
get hasInvoicing() {
return this.aclService.hasAny(['invoicing']);
}
loadData() {
const filter = {
include: [

View File

@ -21,8 +21,8 @@ Regenerate invoice PDF: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
You are going to invoice this ticket: Vas a facturar este ticket
Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket?
You are going to regenerate the invoice PDF document: Vas a regenerar el documento PDF de la factura
Are you sure you want to regenerate the invoice PDF document?: ¿Seguro que quieres regenerar el documento PDF de la factura?
Generate PDF invoice document: Generar PDF de la factura
Are you sure you want to generate/regenerate the PDF invoice?: ¿Seguro que quieres generar/regenerar el PDF de la factura?
Shipped hour updated: Hora de envio modificada
Deleted ticket: Ticket eliminado
Recalculate components: Recalcular componentes