Merge branch 'dev' into 7524-addFixture
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
This commit is contained in:
commit
4869322476
|
@ -15,9 +15,7 @@ describe('ticket assignCollection()', () => {
|
|||
args: {}
|
||||
};
|
||||
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: ctx.req
|
||||
});
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
|
||||
|
||||
options = {transaction: tx};
|
||||
tx = await models.Sale.beginTransaction({});
|
||||
|
@ -25,7 +23,7 @@ describe('ticket assignCollection()', () => {
|
|||
});
|
||||
|
||||
afterEach(async() => {
|
||||
await tx.rollback();
|
||||
if (tx) await tx.rollback();
|
||||
});
|
||||
|
||||
it('should throw an error when there is not picking tickets', async() => {
|
||||
|
|
|
@ -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 (748,'SiiTypeInvoiceOut','*','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 (752,'Application','executeFunc','*','ALLOW','ROLE','employee',NULL);
|
||||
INSERT INTO `ACL` VALUES (753,'NotificationSubscription','getList','READ','ALLOW','ROLE','employee',NULL);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
CREATE USER 'vn'@'localhost';
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'vn'@'localhost' WITH GRANT OPTION;;
|
|
@ -0,0 +1,2 @@
|
|||
INSERT IGNORE INTO salix.ACL (model,property,principalId)
|
||||
VALUES ('Entry','getBuysCsv','supplier');
|
|
@ -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",
|
||||
"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",
|
||||
"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",
|
||||
"Cannot add holidays on this day": "Cannot add holidays on this day",
|
||||
"Cannot send mail": "Cannot send mail",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"Payment method is required": "El método de pago es obligatorio",
|
||||
"Cannot send mail": "Não é possível enviar o email",
|
||||
"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 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",
|
||||
"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 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é",
|
||||
"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",
|
||||
"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 has been invoiced but the PDF 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"
|
||||
"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",
|
||||
"Original invoice not found": "Fatura original não encontrada"
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
/**
|
||||
* Flattens an array of objects by converting each object into a flat structure.
|
||||
*
|
||||
* @param {Array} dataArray Array of objects to be flattened
|
||||
* @return {Array} Array of flattened objects
|
||||
*/
|
||||
function flatten(dataArray) {
|
||||
return dataArray.map(item => flattenObj(item.__data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively flattens an object, converting nested properties into a single level object
|
||||
* with keys representing the original nested structure.
|
||||
*
|
||||
* @param {Object} data The object to be flattened
|
||||
* @param {String} [prefix=''] Optional prefix for nested keys
|
||||
* @return {Object} Flattened object
|
||||
*/
|
||||
function flattenObj(data, prefix = '') {
|
||||
let result = {};
|
||||
try {
|
||||
for (let key in data) {
|
||||
if (!data[key]) continue;
|
||||
|
||||
const newKey = prefix ? `${prefix}_${key}` : key;
|
||||
const value = data[key];
|
||||
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value))
|
||||
Object.assign(result, flattenObj(value.__data, newKey));
|
||||
else
|
||||
result[newKey] = value;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
module.exports = {
|
||||
flatten,
|
||||
flattenObj,
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
const {toCSV} = require('vn-loopback/util/csv');
|
||||
const {flatten} = require('vn-loopback/util/flatten');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('getBuysCsv', {
|
||||
description: 'Returns buys for one entry in CSV file format',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The entry id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/getBuysCsv`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getBuysCsv = async(ctx, id, options) => {
|
||||
const data = await Self.getBuys(ctx, id, null, options);
|
||||
const dataFlatted = flatten(data);
|
||||
return [toCSV(dataFlatted), 'text/csv', `inline; filename="buys-${id}.csv"`];
|
||||
};
|
||||
};
|
|
@ -3,6 +3,7 @@ module.exports = Self => {
|
|||
require('../methods/entry/filter')(Self);
|
||||
require('../methods/entry/getEntry')(Self);
|
||||
require('../methods/entry/getBuys')(Self);
|
||||
require('../methods/entry/getBuysCsv')(Self);
|
||||
require('../methods/entry/importBuys')(Self);
|
||||
require('../methods/entry/importBuysPreview')(Self);
|
||||
require('../methods/entry/lastItemBuys')(Self);
|
||||
|
|
|
@ -47,12 +47,16 @@ module.exports = Self => {
|
|||
Self.invoiceClient = async(ctx, options) => {
|
||||
const args = ctx.args;
|
||||
const models = Self.app.models;
|
||||
options = typeof options === 'object' ? {...options} : {};
|
||||
options.userId = ctx.req.accessToken.userId;
|
||||
|
||||
let tx;
|
||||
if (!options.transaction)
|
||||
tx = options.transaction = await Self.beginTransaction({});
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
const minShipped = Date.vnNew();
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
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/negativeBases')(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) {
|
||||
const fields = ['ref', 'issued'];
|
||||
|
|
|
@ -138,7 +138,6 @@ class Controller extends Section {
|
|||
transferInvoice() {
|
||||
const params = {
|
||||
id: this.invoiceOut.id,
|
||||
refFk: this.invoiceOut.ref,
|
||||
newClientFk: this.clientId,
|
||||
cplusRectificationTypeFk: this.cplusRectificationType,
|
||||
siiTypeInvoiceOutFk: this.siiTypeInvoiceOut,
|
||||
|
@ -155,7 +154,7 @@ class Controller extends Section {
|
|||
return;
|
||||
}
|
||||
|
||||
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => {
|
||||
this.$http.post(`InvoiceOuts/transfer`, params).then(res => {
|
||||
const invoiceId = res.data;
|
||||
this.vnApp.showSuccess(this.$t('Transferred invoice'));
|
||||
this.$state.go('invoiceOut.card.summary', {id: invoiceId});
|
||||
|
|
|
@ -35,8 +35,11 @@ module.exports = Self => {
|
|||
|
||||
Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = typeof options == 'object' ? {...options} : {};
|
||||
let tx;
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
|
|
Loading…
Reference in New Issue