#7277 - RefundInvoices #2904
|
@ -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() => {
|
||||||
|
|
|
@ -1922,7 +1922,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);
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES ('InvoiceOut','refundAndInvoice','WRITE','ALLOW','ROLE','administrative');
|
|
@ -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"
|
||||||
|
|
||||||
}
|
}
|
|
@ -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"
|
||||||
}
|
}
|
|
@ -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"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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');
|
||||||
jsegarra marked this conversation as resolved
|
|||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {refundId};
|
||||||
|
};
|
||||||
|
};
|
|
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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');
|
||||||
jsegarra marked this conversation as resolved
Outdated
|
|||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transferredInvoiceId;
|
||||||
|
};
|
||||||
|
};
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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'];
|
||||||
|
|
|
@ -138,7 +138,6 @@ class Controller extends Section {
|
||||||
transferInvoice() {
|
transferInvoice() {
|
||||||
const params = {
|
const params = {
|
||||||
id: this.invoiceOut.id,
|
id: this.invoiceOut.id,
|
||||||
jsegarra marked this conversation as resolved
Outdated
jsegarra
commented
duda, en Lilium, has quitado este param. porque aquí no? duda, en Lilium, has quitado este param. porque aquí no?
En Lilium has dejado 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});
|
||||||
|
|
|
@ -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({});
|
||||||
|
|
Loading…
Reference in New Issue
Este texto no deberia tener traducción?