#5914 - hotFix-transferInvoice #1893

Merged
alexm merged 17 commits from 5914-hotFix-transferInvoice into test 2024-01-04 07:13:01 +00:00
13 changed files with 64 additions and 51 deletions
Showing only changes of commit 21a1dbeae7 - Show all commits

View File

@ -26,3 +26,4 @@ BEGIN
END$$ END$$
DELIMITER ; 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`) INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`, `weight`)
VALUES 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), (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),
Review

Li he donat coherencia a alguna fixture

Li he donat coherencia a alguna fixture
(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), (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), (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), (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.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton);
await page.write(selectors.ticketsIndex.advancedSearchClient, '1101'); await page.write(selectors.ticketsIndex.advancedSearchClient, '1101');
await page.keyboard.press('Enter'); 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.firstTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox); await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);

View File

@ -28,7 +28,6 @@ describe('InvoiceOut summary path', () => {
it('should contain the tax breakdown', async() => { it('should contain the tax breakdown', async() => {
const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText'); const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText');
const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText'); const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText');
expect(firstTax).toContain('10%'); expect(firstTax).toContain('10%');
@ -37,10 +36,9 @@ describe('InvoiceOut summary path', () => {
it('should contain the tickets info', async() => { it('should contain the tickets info', async() => {
const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText'); const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText');
const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText'); const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText');
expect(firstTicket).toContain('Bat cave'); 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", "Extension format is invalid": "Extension format is invalid",
"NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS", "NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS",
"This client can't be invoiced": "This client can't be invoiced", "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", "The introduced hour already exists": "The introduced hour already exists",
"Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket", "Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket",
"Concept cannot be blank": "Concept cannot be blank", "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", "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", "You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d", "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", "This ticket cannot be left empty.": "This ticket cannot be left empty. %s",
"Social name should be uppercase": "Social name should be uppercase", "Social name should be uppercase": "Social name should be uppercase",
"Street should be uppercase": "Street should be uppercase", "Street should be uppercase": "Street should be uppercase",
@ -201,4 +203,4 @@
"keepPrice": "keepPrice", "keepPrice": "keepPrice",
"Cannot past travels with entries": "Cannot past travels with entries", "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}}" "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", "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", "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", "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", "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", "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", "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", "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", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d", "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", "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", "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", "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", "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", "Cannot past travels with entries": "No se pueden pasar envíos con entradas",
"No tickets to invoice": "No hay tickets para facturar", "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); const result = await models.Client.consumption(ctx, filter, options);
expect(result.length).toEqual(10); expect(result.length).toEqual(11);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -49,7 +49,7 @@ describe('client consumption() filter', () => {
const thirdRow = result[2]; const thirdRow = result[2];
expect(result.length).toEqual(3); expect(result.length).toEqual(3);
expect(firstRow.quantity).toEqual(10); expect(firstRow.quantity).toEqual(11);
expect(secondRow.quantity).toEqual(15); expect(secondRow.quantity).toEqual(15);
expect(thirdRow.quantity).toEqual(20); expect(thirdRow.quantity).toEqual(20);

View File

@ -2,7 +2,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('InvoiceOut tranferInvoice()', () => { describe('InvoiceOut transferInvoice()', () => {
const activeCtx = { const activeCtx = {
accessToken: {userId: 5}, accessToken: {userId: 5},
http: { http: {
@ -69,9 +69,33 @@ describe('InvoiceOut tranferInvoice()', () => {
await models.InvoiceOut.transferInvoice( await models.InvoiceOut.transferInvoice(
ctx, ctx,
options); options);
await tx.rollback();
} catch (e) { } catch (e) {
expect(e.message).toBe(`Select a different client`); expect(e.message).toBe(`Select a different client`);
await tx.rollback(); 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-one
vn-id="siiTypeInvoiceOut" vn-id="siiTypeInvoiceOut"
data="siiTypeInvoiceOuts" data="siiTypeInvoiceOuts"
show-field="code" show-field="description"
value-field="id" value-field="id"
fields="['id','code','description']" fields="['id','code','description']"
required="true" required="true"

View File

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

View File

@ -39,27 +39,14 @@ module.exports = function(Self) {
where: { where: {
id: {inq: ticketsIds} id: {inq: ticketsIds}
}, },
fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk'] fields: ['id', 'refFk', 'shipped', 'totalWithVat']
}, myOptions); }, 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 taxBaseFunction = isRectificative ? 'hasAnyPositiveBase' : 'hasAnyNegativeBase';
const [result] = const [hasAnyIncorrectBase] =
await Self.rawSql(`SELECT ?() AS hasBasesProblem`, [taxBaseFunction], options); await Self.rawSql(`SELECT ${taxBaseFunction}() AS hasBasesProblem`, null, options);
const hasAnyIncorrectBase = result?.hasBasesProblem && isSpanishCompany; if (hasAnyIncorrectBase?.hasBasesProblem)
if (hasAnyIncorrectBase) throw new UserError($t(taxBaseFunction, {ticketsIds: ticketsIds}));
throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds}));
const today = Date.vnNew(); const today = Date.vnNew();
tickets.some(ticket => { tickets.some(ticket => {
alexm marked this conversation as resolved
Review

el método some ya devuelve un booleano, por lo que el return sería innecesario, o la lógica dice que siempre queremos devolver true?

el método some ya devuelve un booleano, por lo que el return sería innecesario, o la lógica dice que siempre queremos devolver true?
Review

Si la logica estaba ya asi, pero ahora lo cambio

Si la logica estaba ya asi, pero ahora lo cambio

View File

@ -47,10 +47,10 @@ module.exports = function(Self) {
let invoicesIds = []; let invoicesIds = [];
try { try {
const tickets = await models.Ticket.find({ const tickets = await models.Ticket.find({
fields: ['id', 'clientFk', 'companyFk', 'addressFk'],
where: { where: {
id: {inq: ticketsIds} id: {inq: ticketsIds}
}, }
fields: ['id', 'clientFk', 'companyFk']
}, myOptions); }, myOptions);
alexm marked this conversation as resolved
Review

Defines clientId y companyId que vienen de firstTicket, pero porque no haces lo mismo con addressFk?

Defines clientId y companyId que vienen de firstTicket, pero porque no haces lo mismo con addressFk?
Review

Como solo lo uso una vez no lo he hecho variable.
En el caso de si que se usaban mas de una vez clientId y companyId.

Pero con el refactor companyId solo se usa una vez, ahora quito la variable

Como solo lo uso una vez no lo he hecho variable. En el caso de si que se usaban mas de una vez clientId y companyId. Pero con el refactor companyId solo se usa una vez, ahora quito la variable
const [firstTicket] = tickets; const [firstTicket] = tickets;
jsegarra marked this conversation as resolved
Review

Traes un Array, pero luego seleccionas el primero, porque no haces findOne y cambias el nombre de la variable de la línea 49?

Traes un Array<Tickets>, pero luego seleccionas el primero, porque no haces findOne y cambias el nombre de la variable de la línea 49?
@ -65,18 +65,17 @@ module.exports = function(Self) {
fields: ['id', 'hasToInvoiceByAddress'] fields: ['id', 'hasToInvoiceByAddress']
alexm marked this conversation as resolved Outdated

La propiedad id no la usas, al igual que en la línea 50, aunque es poco significativo.

La propiedad id no la usas, al igual que en la línea 50, aunque es poco significativo.
}, myOptions); }, myOptions);
let ticketsByAddress = {[firstTicket.addressFk]: ticketsIds};
alexm marked this conversation as resolved Outdated

Asignas unos valores que luego puede que se machaquen
Quizás declararia la variable vacía y pondria un else, o también mover a un método aparte

Asignas unos valores que luego puede que se machaquen Quizás declararia la variable vacía y pondria un else, o también mover a un método aparte
if (client.hasToInvoiceByAddress) { if (client.hasToInvoiceByAddress) {
alexm marked this conversation as resolved Outdated

Si es la única llamada a client, porque no haces const { hasToInvoiceByAddress} en la línea 64

Si es la única llamada a client, porque no haces const { hasToInvoiceByAddress} en la línea 64
const query = ` ticketsByAddress = tickets.reduce((group, ticket) => {
alexm marked this conversation as resolved
Review

Todo esto es para hacer un Set de los Ids de tickets?
Porque más abajo haces object.values entonces las keys(addressFk) te dan igual, correcto?

Todo esto es para hacer un Set de los Ids de tickets? Porque más abajo haces object.values entonces las keys(addressFk) te dan igual, correcto?
SELECT DISTINCT addressFk const {addressFk} = ticket;
FROM ticket t group[addressFk] = group[addressFk] ?? [];
WHERE id IN (?)`; group[addressFk].push(ticket.id);
const result = await Self.rawSql(query, [ticketsIds], myOptions); return group;
}, {});
const addressIds = result.map(address => address.addressFk); }
for (const address of addressIds) for (const ticketIds of Object.values(ticketsByAddress))
await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, invoiceCorrection, myOptions); invoicesIds.push(await createInvoice(ctx, companyId, ticketIds, invoiceCorrection, myOptions));
} else
await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, invoiceCorrection, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();
} catch (e) { } catch (e) {
@ -91,7 +90,7 @@ module.exports = function(Self) {
return invoicesIds; 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; const models = Self.app.models;
await models.Ticket.rawSql(` await models.Ticket.rawSql(`
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
@ -100,12 +99,8 @@ module.exports = function(Self) {
SELECT id SELECT id
FROM vn.ticket FROM vn.ticket
WHERE id IN (?) WHERE id IN (?)
${address ? `AND addressFk = ${address}` : ''}
`, [ticketsIds], myOptions); `, [ticketsIds], myOptions);
return models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
const invoiceId =
await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions);
invoicesIds.push(invoiceId);
} }
}; };

View File

@ -106,7 +106,7 @@ module.exports = function(Self) {
); );
} }
if (resultInvoice.id) // serial != 'R' && if (resultInvoice.id)
alexm marked this conversation as resolved Outdated

Puede ser resultInvoice.id nulo? Porque en la línea 99 ya se emitiría un error

Puede ser resultInvoice.id nulo? Porque en la línea 99 ya se emitiría un error
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();