Merge pull request '#7277 - RefundInvoices' (!2904) from 7277-RefundInvoices into dev
gitea/salix/pipeline/head There was a failure building this commit Details

Reviewed-on: #2904
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
This commit is contained in:
Javi Gallego 2024-09-05 09:07:24 +00:00
commit 40a1b24403
18 changed files with 535 additions and 271 deletions

View File

@ -15,9 +15,7 @@ describe('ticket assignCollection()', () => {
args: {} args: {}
}; };
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
active: ctx.req
});
options = {transaction: tx}; options = {transaction: tx};
tx = await models.Sale.beginTransaction({}); tx = await models.Sale.beginTransaction({});
@ -25,7 +23,7 @@ describe('ticket assignCollection()', () => {
}); });
afterEach(async() => { afterEach(async() => {
await tx.rollback(); if (tx) await tx.rollback();
}); });
it('should throw an error when there is not picking tickets', async() => { it('should throw an error when there is not picking tickets', async() => {

View File

@ -1959,7 +1959,7 @@ INSERT INTO `ACL` VALUES (746,'Claim','getSummary','READ','ALLOW','ROLE','claimV
INSERT INTO `ACL` VALUES (747,'CplusRectificationType','*','READ','ALLOW','ROLE','administrative',NULL); INSERT INTO `ACL` VALUES (747,'CplusRectificationType','*','READ','ALLOW','ROLE','administrative',NULL);
INSERT INTO `ACL` VALUES (748,'SiiTypeInvoiceOut','*','READ','ALLOW','ROLE','salesPerson',NULL); INSERT INTO `ACL` VALUES (748,'SiiTypeInvoiceOut','*','READ','ALLOW','ROLE','salesPerson',NULL);
INSERT INTO `ACL` VALUES (749,'InvoiceCorrectionType','*','READ','ALLOW','ROLE','salesPerson',NULL); INSERT INTO `ACL` VALUES (749,'InvoiceCorrectionType','*','READ','ALLOW','ROLE','salesPerson',NULL);
INSERT INTO `ACL` VALUES (750,'InvoiceOut','transferInvoice','WRITE','ALLOW','ROLE','administrative',NULL); INSERT INTO `ACL` VALUES (750,'InvoiceOut','transfer','WRITE','ALLOW','ROLE','administrative',NULL);
INSERT INTO `ACL` VALUES (751,'Application','executeProc','*','ALLOW','ROLE','employee',NULL); INSERT INTO `ACL` VALUES (751,'Application','executeProc','*','ALLOW','ROLE','employee',NULL);
INSERT INTO `ACL` VALUES (752,'Application','executeFunc','*','ALLOW','ROLE','employee',NULL); INSERT INTO `ACL` VALUES (752,'Application','executeFunc','*','ALLOW','ROLE','employee',NULL);
INSERT INTO `ACL` VALUES (753,'NotificationSubscription','getList','READ','ALLOW','ROLE','employee',NULL); INSERT INTO `ACL` VALUES (753,'NotificationSubscription','getList','READ','ALLOW','ROLE','employee',NULL);

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES ('InvoiceOut','refundAndInvoice','WRITE','ALLOW','ROLE','administrative');

View File

@ -230,10 +230,12 @@
"This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency", "This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency",
"You can only have one PDA": "You can only have one PDA", "You can only have one PDA": "You can only have one PDA",
"Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member", "Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member",
"It has been invoiced but the PDF could not be generated": "It has been invoiced but the PDF could not be generated", "The invoices have been created but the PDFs could not be generated": "The invoices have been created but the PDFs could not be generated",
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated", "It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
"Cannot add holidays on this day": "Cannot add holidays on this day", "Cannot add holidays on this day": "Cannot add holidays on this day",
"Cannot send mail": "Cannot send mail", "Cannot send mail": "Cannot send mail",
"CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`", "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`",
"This postcode already exists": "This postcode already exists" "This postcode already exists": "This postcode already exists",
} "Original invoice not found": "Original invoice not found"
}

View File

@ -363,12 +363,13 @@
"You can not use the same password": "No puedes usar la misma contraseña", "You can not use the same password": "No puedes usar la misma contraseña",
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario", "This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario",
"You can only have one PDA": "Solo puedes tener un PDA", "You can only have one PDA": "Solo puedes tener un PDA",
"It has been invoiced but the PDF could not be generated": "Se ha facturado pero no se ha podido generar el PDF", "The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono", "It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
"Payment method is required": "El método de pago es obligatorio", "Payment method is required": "El método de pago es obligatorio",
"Cannot send mail": "Não é possível enviar o email", "Cannot send mail": "Não é possível enviar o email",
"CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos", "CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos",
"The sale not exists in the item shelving": "La venta no existe en la estantería del artículo", "The sale not exists in the item shelving": "La venta no existe en la estantería del artículo",
"The entry not have stickers": "La entrada no tiene etiquetas", "The entry not have stickers": "La entrada no tiene etiquetas",
"Too many records": "Demasiados registros" "Too many records": "Demasiados registros",
} "Original invoice not found": "Factura original no encontrada"
}

View File

@ -358,7 +358,9 @@
"This workCenter is already assigned to this agency": "Ce centre de travail est déjà assigné à cette agence", "This workCenter is already assigned to this agency": "Ce centre de travail est déjà assigné à cette agence",
"Select ticket or client": "Choisissez un ticket ou un client", "Select ticket or client": "Choisissez un ticket ou un client",
"It was not able to create the invoice": "Il n'a pas été possible de créer la facture", "It was not able to create the invoice": "Il n'a pas été possible de créer la facture",
"It has been invoiced but the PDF could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré", "The invoices have been created but the PDFs could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré",
"It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré", "It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré",
"Cannot send mail": "Impossible d'envoyer le mail" "Cannot send mail": "Impossible d'envoyer le mail",
"Original invoice not found": "Facture originale introuvable"
} }

View File

@ -358,6 +358,8 @@
"This workCenter is already assigned to this agency": "Este centro de trabalho já está atribuído a esta agência", "This workCenter is already assigned to this agency": "Este centro de trabalho já está atribuído a esta agência",
"Select ticket or client": "Selecione um ticket ou cliente", "Select ticket or client": "Selecione um ticket ou cliente",
"It was not able to create the invoice": "Não foi possível criar a fatura", "It was not able to create the invoice": "Não foi possível criar a fatura",
"It has been invoiced but the PDF could not be generated": "Foi faturado, mas o PDF não pôde ser gerado", "The invoices have been created but the PDFs could not be generated": "Foi faturado, mas o PDF não pôde ser gerado",
"It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso" "It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso",
"Original invoice not found": "Fatura original não encontrada"
} }

View File

@ -47,12 +47,16 @@ module.exports = Self => {
Self.invoiceClient = async(ctx, options) => { Self.invoiceClient = async(ctx, options) => {
const args = ctx.args; const args = ctx.args;
const models = Self.app.models; const models = Self.app.models;
options = typeof options === 'object' ? {...options} : {};
options.userId = ctx.req.accessToken.userId;
let tx; let tx;
if (!options.transaction) const myOptions = {userId: ctx.req.accessToken.userId};
tx = options.transaction = await Self.beginTransaction({});
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const minShipped = Date.vnNew(); const minShipped = Date.vnNew();
minShipped.setFullYear(args.maxShipped.getFullYear() - 1); minShipped.setFullYear(args.maxShipped.getFullYear() - 1);

View File

@ -0,0 +1,89 @@
module.exports = Self => {
Self.remoteMethodCtx('refundAndInvoice', {
description: 'Refund an invoice and create a new one',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'Issued invoice id'
},
{
arg: 'cplusRectificationTypeFk',
type: 'number',
required: true
},
{
arg: 'siiTypeInvoiceOutFk',
type: 'number',
required: true
},
{
arg: 'invoiceCorrectionTypeFk',
type: 'number',
required: true
},
],
returns: {
type: 'object',
root: true
},
http: {
path: '/refundAndInvoice',
verb: 'post'
}
});
Self.refundAndInvoice = async(
ctx,
id,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
options
) => {
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
let refundId;
if (typeof options == 'object')
Object.assign(myOptions, options);
let tx;
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const originalInvoice = await models.InvoiceOut.findById(id, myOptions);
const refundedTickets = await Self.refund(ctx, originalInvoice.ref, false, myOptions);
const invoiceCorrection = {
correctedFk: id,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk
};
const ticketIds = refundedTickets.map(ticket => ticket.id);
refundId = await models.Ticket.invoiceTickets(ctx, ticketIds, invoiceCorrection, myOptions);
tx && await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
if (tx) {
try {
await models.InvoiceOut.makePdfList(ctx, refundId);
} catch (e) {
throw new UserError('The invoices have been created but the PDFs could not be generated');
}
}
return {refundId};
};
};

View File

@ -0,0 +1,105 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
const UserError = require('vn-loopback/util/user-error');
describe('InvoiceOut makePdfAndNotify()', () => {
const userId = 5;
const ctx = {
req: {
accessToken: {userId},
__: (key, obj) => `Translated: ${key}, ${JSON.stringify(obj)}`,
getLocale: () => 'es'
},
args: {}
};
const activeCtx = {accessToken: {userId}};
const id = 4;
const printerFk = 1;
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx});
});
it('should generate PDF and send email when client is to be mailed', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceOut.makePdfAndNotify(ctx, id, printerFk, options);
const invoice = await models.InvoiceOut.findById(id, {
fields: ['ref', 'clientFk'],
include: {
relation: 'client',
scope: {
fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk']
}
}
}, options);
expect(invoice).toBeDefined();
expect(invoice.client().isToBeMailed).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should generate PDF and print when client is not to be mailed', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceOut.makePdfAndNotify(ctx, id, null, options);
const invoice = await models.InvoiceOut.findById(id, {
fields: ['ref', 'clientFk'],
include: {
relation: 'client',
scope: {
fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk']
}
}
}, options);
expect(invoice).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw UserError when PDF generation fails', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceOut.makePdfAndNotify(ctx, null, null, options);
await tx.rollback();
} catch (e) {
expect(e instanceof UserError).toBe(true);
expect(e.message).toContain('Error while generating PDF');
await tx.rollback();
}
});
it('should send message to salesperson when email fails', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
spyOn(models.InvoiceOut, 'invoiceEmail').and.rejectWith(new Error('Test Error'));
await models.InvoiceOut.makePdfAndNotify(ctx, id, null, options);
await tx.rollback();
} catch (e) {
expect(e instanceof UserError).toBe(true);
expect(e.message).toContain('Error when sending mail to client');
await tx.rollback();
}
});
});

View File

@ -0,0 +1,46 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut refundAndInvoice()', () => {
const userId = 5;
const ctx = {req: {accessToken: {userId}}};
const activeCtx = {accessToken: {userId}};
const id = 4;
const cplusRectificationTypeFk = 1;
const siiTypeInvoiceOutFk = 1;
const invoiceCorrectionTypeFk = 1;
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx});
});
it('should refund an invoice and create a new invoice', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
try {
const result = await models.InvoiceOut.refundAndInvoice(
ctx,
id,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
options
);
expect(result).toBeDefined();
expect(result.refundId).toBeDefined();
const invoicesAfter = await models.InvoiceOut.find({where: {id: result.refundId}}, options);
const ticketsAfter = await models.Ticket.find({where: {refFk: 'R10100001'}}, options);
expect(invoicesAfter.length).toBeGreaterThan(0);
expect(ticketsAfter.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,135 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
const UserError = require('vn-loopback/util/user-error');
describe('InvoiceOut transfer()', () => {
const userId = 5;
let options;
let tx;
let ctx;
const activeCtx = {accessToken: {userId}};
const id = 4;
const newClientFk = 1101;
const cplusRectificationTypeFk = 1;
const siiTypeInvoiceOutFk = 1;
const invoiceCorrectionTypeFk = 1;
beforeEach(async() => {
ctx = {
req: {
accessToken: {userId: 1106},
headers: {origin: 'http://localhost'},
__: value => value,
getLocale: () => 'es'
},
args: {}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx});
options = {transaction: tx};
tx = await models.Sale.beginTransaction({});
options.transaction = tx;
});
afterEach(async() => {
await tx.rollback();
});
it('should transfer an invoice to a new client and return the new invoice ID', async() => {
const makeInvoice = true;
const makePdfListMock = spyOn(models.InvoiceOut, 'makePdfList').and.returnValue();
const [result] = await models.InvoiceOut.transfer(
ctx,
id,
newClientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
makeInvoice,
options
);
const newInvoice = await models.InvoiceOut.findById(result, null, options);
expect(newInvoice.clientFk).toBe(newClientFk);
const transferredTickets = await models.Ticket.find({
where: {
refFk: newInvoice.ref,
clientFk: newClientFk
}
}, options);
expect(transferredTickets.length).toBeGreaterThan(0);
expect(makePdfListMock).toHaveBeenCalledWith(ctx, [result]);
});
it('should throw an error if original invoice is not found', async() => {
const makeInvoice = true;
try {
await models.InvoiceOut.transfer(
ctx,
'idNotExists',
newClientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
makeInvoice,
options
);
fail('Expected an error to be thrown');
} catch (e) {
expect(e).toBeInstanceOf(UserError);
expect(e.message).toBe('Original invoice not found');
}
});
it('should throw an error if the new client is the same as the original client', async() => {
const makeInvoice = true;
const originalInvoice = await models.InvoiceOut.findById(id, options);
try {
await models.InvoiceOut.transfer(
ctx,
id,
originalInvoice.clientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
makeInvoice,
options
);
fail('Expected an error to be thrown');
} catch (e) {
expect(e).toBeInstanceOf(UserError);
expect(e.message).toBe('Select a different client');
}
});
it('should not create a new invoice if makeInvoice is false', async() => {
const originalTickets = await models.Ticket.find({
where: {clientFk: newClientFk, refFk: null},
options
});
const result = await models.InvoiceOut.transfer(
ctx,
id,
newClientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
false,
options
);
expect(result).toBeUndefined();
const transferredTickets = await models.Ticket.find({
where: {clientFk: newClientFk, refFk: null}
}, options);
expect(transferredTickets.length).toBeGreaterThan(originalTickets.length);
});
});

View File

@ -1,116 +0,0 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut transferInvoice()', () => {
const activeCtx = {
accessToken: {userId: 5},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return the id of the created issued invoice', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
const id = 4;
const newClient = 1;
spyOn(models.InvoiceOut, 'makePdfList');
try {
const {clientFk: oldClient} = await models.InvoiceOut.findById(id, {fields: ['clientFk']});
const invoicesBefore = await models.InvoiceOut.find({}, options);
const result = await models.InvoiceOut.transferInvoice(
ctx,
id,
'T4444444',
newClient,
1,
1,
1,
true,
options);
const invoicesAfter = await models.InvoiceOut.find({}, options);
const rectificativeInvoice = invoicesAfter[invoicesAfter.length - 2];
const newInvoice = invoicesAfter[invoicesAfter.length - 1];
expect(result).toBeDefined();
expect(invoicesAfter.length - invoicesBefore.length).toEqual(2);
expect(rectificativeInvoice.clientFk).toEqual(oldClient);
expect(newInvoice.clientFk).toEqual(newClient);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error when it is the same client', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
spyOn(models.InvoiceOut, 'makePdfList');
try {
await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1101, 1, 1, 1, true, options);
await tx.rollback();
} catch (e) {
expect(e.message).toBe(`Select a different client`);
await tx.rollback();
}
});
it('should throw an error when it is refund', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
spyOn(models.InvoiceOut, 'makePdfList');
try {
await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, true, options);
await tx.rollback();
} catch (e) {
expect(e.message).toContain(`This ticket is already a refund`);
await tx.rollback();
}
});
it('should throw an error when pdf failed', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
spyOn(models.InvoiceOut, 'makePdfList').and.returnValue(() => {
throw new Error('test');
});
try {
await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, true, options);
await tx.rollback();
} catch (e) {
expect(e.message).toContain(`It has been invoiced but the PDF could not be generated`);
await tx.rollback();
}
});
it('should not generate an invoice', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
spyOn(models.InvoiceOut, 'makePdfList');
let response;
try {
response = await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, false, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(response).not.toBeDefined();
});
});

View File

@ -0,0 +1,122 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transfer', {
description: 'Transfer an issued invoice to another client',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'Issued invoice id'
},
{
arg: 'newClientFk',
type: 'number',
required: true
},
{
arg: 'cplusRectificationTypeFk',
type: 'number',
required: true
},
{
arg: 'siiTypeInvoiceOutFk',
type: 'number',
required: true
},
{
arg: 'invoiceCorrectionTypeFk',
type: 'number',
required: true
},
{
arg: 'makeInvoice',
type: 'boolean',
required: true
},
],
returns: {type: 'object', root: true},
http: {path: '/transfer', verb: 'post'}
});
Self.transfer = async(
ctx,
id,
newClientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
makeInvoice,
options
) => {
const models = Self.app.models;
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const originalInvoice = await models.InvoiceOut.findById(id);
if (!originalInvoice)
throw new UserError('Original invoice not found');
if (originalInvoice.clientFk === newClientFk)
throw new UserError('Select a different client');
let transferredInvoiceId;
try {
await Self.refundAndInvoice(
ctx,
id,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
myOptions
);
const tickets = await models.Ticket.find({where: {refFk: originalInvoice.ref}}, myOptions);
const ticketIds = tickets.map(ticket => ticket.id);
const transferredTickets = await models.Ticket.cloneAll(ctx, ticketIds, false, false, myOptions);
const client = await models.Client.findById(newClientFk,
{fields: ['id', 'defaultAddressFk']}, myOptions);
const address = await models.Address.findById(client.defaultAddressFk,
{fields: ['id', 'nickname']}, myOptions);
const transferredTicketIds = transferredTickets.map(ticket => ticket.id);
await models.Ticket.updateAll(
{id: {inq: transferredTicketIds}},
{
clientFk: newClientFk,
addressFk: client.defaultAddressFk,
nickname: address.nickname
},
myOptions
);
if (makeInvoice)
transferredInvoiceId = await models.Ticket.invoiceTickets(ctx, transferredTicketIds, null, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
if (transferredInvoiceId) {
try {
await models.InvoiceOut.makePdfList(ctx, transferredInvoiceId);
} catch (e) {
throw new UserError('The invoices have been created but the PDFs could not be generatedd');
}
}
return transferredInvoiceId;
};
};

View File

@ -1,131 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transferInvoice', {
description: 'Transfer an issued invoice to another client',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'Issued invoice id'
},
{
arg: 'refFk',
type: 'string',
required: true
},
{
arg: 'newClientFk',
type: 'number',
required: true
},
{
arg: 'cplusRectificationTypeFk',
type: 'number',
required: true
},
{
arg: 'siiTypeInvoiceOutFk',
type: 'number',
required: true
},
{
arg: 'invoiceCorrectionTypeFk',
type: 'number',
required: true
},
{
arg: 'makeInvoice',
type: 'boolean',
required: true
},
],
returns: {
type: 'object',
root: true
},
http: {
path: '/transferInvoice',
verb: 'post'
}
});
Self.transferInvoice = async(
ctx,
id,
refFk,
newClientFk,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk,
makeInvoice,
options
) => {
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
let invoiceId;
let refundId;
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
const {clientFk} = await models.InvoiceOut.findById(id);
if (clientFk == newClientFk)
throw new UserError(`Select a different client`);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const tickets = await models.Ticket.find({where: {refFk}}, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id);
const refundTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, true, myOptions);
const clonedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, false, myOptions);
const clonedTicketIds = [];
for (const clonedTicket of clonedTickets) {
await clonedTicket.updateAttribute('clientFk', newClientFk, myOptions);
clonedTicketIds.push(clonedTicket.id);
}
const invoiceCorrection = {
correctedFk: id,
cplusRectificationTypeFk,
siiTypeInvoiceOutFk,
invoiceCorrectionTypeFk
};
const refundTicketIds = refundTickets.map(ticket => ticket.id);
refundId = await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions);
if (makeInvoice)
invoiceId = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, null, myOptions);
tx && await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
if (tx && makeInvoice) {
try {
await models.InvoiceOut.makePdfList(ctx, invoiceId);
} catch (e) {
throw new UserError('It has been invoiced but the PDF could not be generated');
}
try {
await models.InvoiceOut.makePdfList(ctx, refundId);
} catch (e) {
throw new UserError('It has been invoiced but the PDF of refund not be generated');
}
}
return invoiceId;
};
};

View File

@ -26,7 +26,8 @@ module.exports = Self => {
require('../methods/invoiceOut/getInvoiceDate')(Self); require('../methods/invoiceOut/getInvoiceDate')(Self);
require('../methods/invoiceOut/negativeBases')(Self); require('../methods/invoiceOut/negativeBases')(Self);
require('../methods/invoiceOut/negativeBasesCsv')(Self); require('../methods/invoiceOut/negativeBasesCsv')(Self);
require('../methods/invoiceOut/transferInvoice')(Self); require('../methods/invoiceOut/transfer')(Self);
require('../methods/invoiceOut/refundAndInvoice')(Self);
Self.filePath = async function(id, options) { Self.filePath = async function(id, options) {
const fields = ['ref', 'issued']; const fields = ['ref', 'issued'];

View File

@ -138,7 +138,6 @@ class Controller extends Section {
transferInvoice() { transferInvoice() {
const params = { const params = {
id: this.invoiceOut.id, id: this.invoiceOut.id,
refFk: this.invoiceOut.ref,
newClientFk: this.clientId, newClientFk: this.clientId,
cplusRectificationTypeFk: this.cplusRectificationType, cplusRectificationTypeFk: this.cplusRectificationType,
siiTypeInvoiceOutFk: this.siiTypeInvoiceOut, siiTypeInvoiceOutFk: this.siiTypeInvoiceOut,
@ -155,7 +154,7 @@ class Controller extends Section {
return; return;
} }
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => { this.$http.post(`InvoiceOuts/transfer`, params).then(res => {
const invoiceId = res.data; const invoiceId = res.data;
this.vnApp.showSuccess(this.$t('Transferred invoice')); this.vnApp.showSuccess(this.$t('Transferred invoice'));
this.$state.go('invoiceOut.card.summary', {id: invoiceId}); this.$state.go('invoiceOut.card.summary', {id: invoiceId});

View File

@ -35,8 +35,11 @@ module.exports = Self => {
Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => { Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = typeof options == 'object' ? {...options} : {};
let tx; let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) { if (!myOptions.transaction) {
tx = await Self.beginTransaction({}); tx = await Self.beginTransaction({});