refs #5914 fix: ticket canBeInvoiced and optimized invoiceTickets
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2023-12-14 15:56:49 +01:00
parent 1ce1af82fe
commit 21a1dbeae7
13 changed files with 64 additions and 51 deletions

View File

@ -26,3 +26,4 @@ BEGIN
END$$
DELIMITER ;

View File

@ -720,7 +720,7 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen
INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`, `weight`)
VALUES
(1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1),
(2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2),
(2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 1, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2),
(3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL),
(4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL),
(5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL),

View File

@ -35,7 +35,7 @@ describe('Ticket index payout path', () => {
await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
await page.write(selectors.ticketsIndex.advancedSearchClient, '1101');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 9);
await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 10);
await page.waitToClick(selectors.ticketsIndex.firstTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);

View File

@ -28,7 +28,6 @@ describe('InvoiceOut summary path', () => {
it('should contain the tax breakdown', async() => {
const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText');
const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText');
expect(firstTax).toContain('10%');
@ -37,10 +36,9 @@ describe('InvoiceOut summary path', () => {
it('should contain the tickets info', async() => {
const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText');
const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText');
expect(firstTicket).toContain('Bat cave');
expect(secondTicket).toContain('Stark tower');
expect(secondTicket).toContain('Bat cave');
});
});

View File

@ -45,6 +45,7 @@
"Extension format is invalid": "Extension format is invalid",
"NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS",
"This client can't be invoiced": "This client can't be invoiced",
"You must provide the correction information to generate a corrective invoice": "You must provide the correction information to generate a corrective invoice",
"The introduced hour already exists": "The introduced hour already exists",
"Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket",
"Concept cannot be blank": "Concept cannot be blank",
@ -178,7 +179,8 @@
"The renew period has not been exceeded": "The renew period has not been exceeded",
"You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d",
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}",
"hasAnyNegativeBase": "Negative basis of tickets: {{ticketsIds}}",
"hasAnyPositiveBase": "Positive basis of tickets: {{ticketsIds}}",
"This ticket cannot be left empty.": "This ticket cannot be left empty. %s",
"Social name should be uppercase": "Social name should be uppercase",
"Street should be uppercase": "Street should be uppercase",
@ -201,4 +203,4 @@
"keepPrice": "keepPrice",
"Cannot past travels with entries": "Cannot past travels with entries",
"It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}"
}
}

View File

@ -72,6 +72,7 @@
"The secret can't be blank": "La contraseña no puede estar en blanco",
"We weren't able to send this SMS": "No hemos podido enviar el SMS",
"This client can't be invoiced": "Este cliente no puede ser facturado",
"You must provide the correction information to generate a corrective invoice": "Debes informar la información de corrección para generar una factura rectificativa",
"This ticket can't be invoiced": "Este ticket no puede ser facturado",
"You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado",
"This ticket can not be modified": "Este ticket no puede ser modificado",
@ -305,7 +306,8 @@
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"hasAnyNegativeBase": "Base negativa para los tickets: {{ticketsIds}}",
"hasAnyPositiveBase": "Base positivas para los tickets: {{ticketsIds}}",
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
"This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
@ -330,5 +332,8 @@
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
"No tickets to invoice": "No hay tickets para facturar",
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}"
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}",
"Base negativa para los tickets: 39": "Base negativa para los tickets: 39",
"Base negativa para los tickets: 41": "Base negativa para los tickets: 41",
"keepPrice": "keepPrice"
}

View File

@ -16,7 +16,7 @@ describe('client consumption() filter', () => {
};
const result = await models.Client.consumption(ctx, filter, options);
expect(result.length).toEqual(10);
expect(result.length).toEqual(11);
await tx.rollback();
} catch (e) {
@ -49,7 +49,7 @@ describe('client consumption() filter', () => {
const thirdRow = result[2];
expect(result.length).toEqual(3);
expect(firstRow.quantity).toEqual(10);
expect(firstRow.quantity).toEqual(11);
expect(secondRow.quantity).toEqual(15);
expect(thirdRow.quantity).toEqual(20);

View File

@ -2,7 +2,7 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut tranferInvoice()', () => {
describe('InvoiceOut transferInvoice()', () => {
const activeCtx = {
accessToken: {userId: 5},
http: {
@ -69,9 +69,33 @@ describe('InvoiceOut tranferInvoice()', () => {
await models.InvoiceOut.transferInvoice(
ctx,
options);
await tx.rollback();
} catch (e) {
expect(e.message).toBe(`Select a different client`);
await tx.rollback();
}
});
it('should throw an UserError when it is refund', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
const args = {
id: '1',
refFk: 'T1111111',
newClientFk: 1102,
cplusRectificationTypeFk: 1,
siiTypeInvoiceOutFk: 1,
invoiceCorrectionTypeFk: 1
};
ctx.args = args;
try {
await models.InvoiceOut.transferInvoice(
ctx,
options);
await tx.rollback();
} catch (e) {
expect(e.message).toContain(`This ticket is already a refund`);
await tx.rollback();
}
});
});

View File

@ -227,7 +227,7 @@
vn-one
vn-id="siiTypeInvoiceOut"
data="siiTypeInvoiceOuts"
show-field="code"
show-field="description"
value-field="id"
fields="['id','code','description']"
required="true"

View File

@ -6,6 +6,7 @@
auto-load="true"
url="InvoiceOutSerials"
data="invoiceOutSerials"
where="{code: {neq: 'R'}}"
order="code">
</vn-crud-model>
<vn-crud-model

View File

@ -39,27 +39,14 @@ module.exports = function(Self) {
where: {
id: {inq: ticketsIds}
},
fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk']
fields: ['id', 'refFk', 'shipped', 'totalWithVat']
}, myOptions);
const [firstTicket] = tickets;
const companyFk = firstTicket.companyFk;
const query =
`SELECT COUNT(*) isSpanishCompany
FROM supplier s
JOIN country c ON c.id = s.countryFk
AND c.code = 'ES'
WHERE s.id = ?`;
const [supplierCompany] = await Self.rawSql(query, [companyFk], options);
const isSpanishCompany = supplierCompany?.isSpanishCompany;
const taxBaseFunction = isRectificative ? 'hasAnyPositiveBase' : 'hasAnyNegativeBase';
const [result] =
await Self.rawSql(`SELECT ?() AS hasBasesProblem`, [taxBaseFunction], options);
const hasAnyIncorrectBase = result?.hasBasesProblem && isSpanishCompany;
if (hasAnyIncorrectBase)
throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds}));
const [hasAnyIncorrectBase] =
await Self.rawSql(`SELECT ${taxBaseFunction}() AS hasBasesProblem`, null, options);
if (hasAnyIncorrectBase?.hasBasesProblem)
throw new UserError($t(taxBaseFunction, {ticketsIds: ticketsIds}));
const today = Date.vnNew();
tickets.some(ticket => {

View File

@ -47,10 +47,10 @@ module.exports = function(Self) {
let invoicesIds = [];
try {
const tickets = await models.Ticket.find({
fields: ['id', 'clientFk', 'companyFk', 'addressFk'],
where: {
id: {inq: ticketsIds}
},
fields: ['id', 'clientFk', 'companyFk']
}
}, myOptions);
const [firstTicket] = tickets;
@ -65,18 +65,17 @@ module.exports = function(Self) {
fields: ['id', 'hasToInvoiceByAddress']
}, myOptions);
let ticketsByAddress = {[firstTicket.addressFk]: ticketsIds};
if (client.hasToInvoiceByAddress) {
const query = `
SELECT DISTINCT addressFk
FROM ticket t
WHERE id IN (?)`;
const result = await Self.rawSql(query, [ticketsIds], myOptions);
const addressIds = result.map(address => address.addressFk);
for (const address of addressIds)
await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, invoiceCorrection, myOptions);
} else
await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, invoiceCorrection, myOptions);
ticketsByAddress = tickets.reduce((group, ticket) => {
const {addressFk} = ticket;
group[addressFk] = group[addressFk] ?? [];
group[addressFk].push(ticket.id);
return group;
}, {});
}
for (const ticketIds of Object.values(ticketsByAddress))
invoicesIds.push(await createInvoice(ctx, companyId, ticketIds, invoiceCorrection, myOptions));
if (tx) await tx.commit();
} catch (e) {
@ -91,7 +90,7 @@ module.exports = function(Self) {
return invoicesIds;
};
async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, invoiceCorrection, myOptions) {
async function createInvoice(ctx, companyId, ticketsIds, invoiceCorrection, myOptions) {
const models = Self.app.models;
await models.Ticket.rawSql(`
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
@ -100,12 +99,8 @@ module.exports = function(Self) {
SELECT id
FROM vn.ticket
WHERE id IN (?)
${address ? `AND addressFk = ${address}` : ''}
`, [ticketsIds], myOptions);
const invoiceId =
await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
invoicesIds.push(invoiceId);
return models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
}
};

View File

@ -106,7 +106,7 @@ module.exports = function(Self) {
);
}
if (resultInvoice.id) // serial != 'R' &&
if (resultInvoice.id)
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
if (tx) await tx.commit();