refs #5914 feat: make invoice support rectificative
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Alex Moreno 2023-11-30 15:16:41 +01:00
parent e634a5e213
commit b2e7ea3089
17 changed files with 72 additions and 51 deletions

View File

@ -328,5 +328,6 @@
"User disabled": "Usuario desactivado", "User disabled": "Usuario desactivado",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima", "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima",
"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"
} }

View File

@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', { Self.remoteMethod('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses', description: 'Change property isEqualizated in all client addresses',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [

View File

@ -80,6 +80,7 @@ module.exports = Self => {
invoiceType, invoiceType,
args.companyFk, args.companyFk,
args.invoiceDate, args.invoiceDate,
null,
options options
); );

View File

@ -23,20 +23,29 @@ describe('InvoiceOut tranferInvoice()', () => {
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};
const args = { const args = {
id: '1', id: '4',
refFk: 'T4444444', refFk: 'T4444444',
newClientFk: 1, newClientFk: 1,
cplusRectificationId: 1, cplusRectificationTypeFk: 1,
siiTypeInvoiceOutId: 1, siiTypeInvoiceOutFk: 1,
invoiceCorrectionTypeId: 1 invoiceCorrectionTypeFk: 1
}; };
ctx.args = args; ctx.args = args;
try { try {
const {clientFk: oldClient} = await models.InvoiceOut.findById(args.id, {fields: ['clientFk']});
const invoicesBefore = await models.InvoiceOut.find({}, options);
const result = await models.InvoiceOut.transferInvoice( const result = await models.InvoiceOut.transferInvoice(
ctx, ctx,
options); options);
const invoicesAfter = await models.InvoiceOut.find({}, options);
const rectificativeInvoice = invoicesAfter[invoicesAfter.length - 2];
const newInvoice = invoicesAfter[invoicesAfter.length - 1];
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(invoicesAfter.length - invoicesBefore.length).toEqual(2);
expect(rectificativeInvoice.clientFk).toEqual(oldClient);
expect(newInvoice.clientFk).toEqual(args.newClientFk);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -51,9 +60,9 @@ describe('InvoiceOut tranferInvoice()', () => {
id: '1', id: '1',
refFk: 'T1111111', refFk: 'T1111111',
newClientFk: 1101, newClientFk: 1101,
cplusRectificationId: 1, cplusRectificationTypeFk: 1,
siiTypeInvoiceOutId: 1, siiTypeInvoiceOutFk: 1,
invoiceCorrectionTypeId: 1 invoiceCorrectionTypeFk: 1
}; };
ctx.args = args; ctx.args = args;
try { try {

View File

@ -22,7 +22,7 @@ module.exports = Self => {
required: true required: true
}, },
{ {
arg: 'cplusRectificationFk', arg: 'cplusRectificationTypeFk',
type: 'number', type: 'number',
required: true required: true
}, },
@ -50,7 +50,7 @@ module.exports = Self => {
Self.transferInvoice = async(ctx, options) => { Self.transferInvoice = async(ctx, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId}; const myOptions = {userId: ctx.req.accessToken.userId};
const {id, refFk, newClientFk, cplusRectificationFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk} = ctx.args; const {id, refFk, newClientFk, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk} = ctx.args;
let tx; let tx;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -87,11 +87,11 @@ module.exports = Self => {
} }
const invoiceCorrection = const invoiceCorrection =
{correctedFk: id, cplusRectificationFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk}; {correctedFk: id, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk};
const refundTicketIds = refundTickets.map(ticket => ticket.id); const refundTicketIds = refundTickets.map(ticket => ticket.id);
await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions); await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions);
const [[invoiceId]] = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, myOptions); const [invoiceId] = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, null, myOptions);
if (tx) { if (tx) {
await tx.commit(); await tx.commit();

View File

@ -131,7 +131,7 @@ class Controller extends Section {
id: this.invoiceOut.id, id: this.invoiceOut.id,
refFk: this.invoiceOut.ref, refFk: this.invoiceOut.ref,
newClientFk: this.clientId, newClientFk: this.clientId,
cplusRectificationFk: this.cplusRectificationType, cplusRectificationTypeFk: this.cplusRectificationType,
siiTypeInvoiceOutFk: this.siiTypeInvoiceOut, siiTypeInvoiceOutFk: this.siiTypeInvoiceOut,
invoiceCorrectionTypeFk: this.invoiceCorrectionType invoiceCorrectionTypeFk: this.invoiceCorrectionType
}; };

View File

@ -23,9 +23,9 @@ describe('Sale refund()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const refundedTicket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); const refundedTickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
expect(refundedTicket).toBeDefined(); expect(refundedTickets).toBeDefined();
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -42,11 +42,11 @@ describe('Sale refund()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketsBefore = await models.Ticket.find({}, options); const ticketsBefore = await models.Ticket.find({}, options);
const ticket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); const tickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
const refundedTicket = await models.Ticket.findOne({ const refundedTicket = await models.Ticket.findOne({
where: { where: {
id: ticket.id id: tickets[0].id
}, },
include: [ include: [
{ {

View File

@ -10,6 +10,11 @@ module.exports = function(Self) {
description: 'The tickets id', description: 'The tickets id',
type: ['number'], type: ['number'],
required: true required: true
},
{
arg: 'isRectificative',
description: 'If it is rectificative',
type: 'boolean'
} }
], ],
returns: { returns: {
@ -23,7 +28,7 @@ module.exports = function(Self) {
} }
}); });
Self.canBeInvoiced = async(ctx, ticketsIds, options) => { Self.canBeInvoiced = async(ctx, ticketsIds, isRectificative, options) => {
const myOptions = {}; const myOptions = {};
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
@ -48,10 +53,11 @@ module.exports = function(Self) {
const [supplierCompany] = await Self.rawSql(query, [companyFk], options); const [supplierCompany] = await Self.rawSql(query, [companyFk], options);
const isSpanishCompany = supplierCompany?.isSpanishCompany; const isSpanishCompany = supplierCompany?.isSpanishCompany;
const taxBaseFunction = isRectificative ? 'hasAnyPositiveBase' : 'hasAnyNegativeBase';
const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options); const [result] =
const hasAnyNegativeBase = result?.base && isSpanishCompany; await Self.rawSql(`SELECT ?() AS hasBasesProblem`, [taxBaseFunction], options);
if (hasAnyNegativeBase) const hasAnyIncorrectBase = result?.hasBasesProblem && isSpanishCompany;
if (hasAnyIncorrectBase)
throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds})); throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds}));
const today = Date.vnNew(); const today = Date.vnNew();

View File

@ -93,7 +93,6 @@ module.exports = function(Self) {
async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, invoiceCorrection, myOptions) { async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, invoiceCorrection, myOptions) {
const models = Self.app.models; const models = Self.app.models;
console.log(ticketsIds, address);
await models.Ticket.rawSql(` await models.Ticket.rawSql(`
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice
(PRIMARY KEY (id)) (PRIMARY KEY (id))

View File

@ -67,20 +67,24 @@ module.exports = function(Self) {
fields: ['id', 'clientFk', 'addressFk'] fields: ['id', 'clientFk', 'addressFk']
}, myOptions); }, myOptions);
await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); await models.Ticket.canBeInvoiced(ctx, ticketsIds, !!invoiceCorrection, myOptions);
const [firstTicket] = tickets; const [firstTicket] = tickets;
const clientId = firstTicket.clientFk; const clientId = firstTicket.clientFk;
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, invoiceCorrection, myOptions); const clientCanBeInvoiced =
if (!clientCanBeInvoiced && !invoiceCorrection) await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
if (!clientCanBeInvoiced)
throw new UserError(`This client can't be invoiced`); throw new UserError(`This client can't be invoiced`);
const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`; const [{serial}] = invoiceCorrection ? [{serial: 'R'}] : await Self.rawSql(
const [{serial}] = await Self.rawSql(query, [ `SELECT vn.invoiceSerial(?, ?, ?) AS serial`,
clientId, [
companyFk, clientId,
invoiceType, companyFk,
], myOptions); invoiceType
],
myOptions);
const invoiceOutSerial = await models.InvoiceOutSerial.findById(serial); const invoiceOutSerial = await models.InvoiceOutSerial.findById(serial);
if (invoiceOutSerial?.taxAreaFk == 'WORLD') { if (invoiceOutSerial?.taxAreaFk == 'WORLD') {
@ -92,7 +96,7 @@ module.exports = function(Self) {
await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions); await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions);
const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions);
if (!resultInvoice) if (!resultInvoice?.id)
throw new UserError('No tickets to invoice', 'notInvoiced'); throw new UserError('No tickets to invoice', 'notInvoiced');
if (invoiceCorrection) { if (invoiceCorrection) {
@ -102,7 +106,7 @@ module.exports = function(Self) {
); );
} }
if (serial != 'R' && resultInvoice.id) if (resultInvoice.id) // serial != 'R' &&
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();

View File

@ -27,7 +27,7 @@ describe('ticket canBeInvoiced()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketId], options); `, [ticketId], options);
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
expect(canBeInvoiced).toEqual(false); expect(canBeInvoiced).toEqual(false);
@ -59,7 +59,7 @@ describe('ticket canBeInvoiced()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketId], options); `, [ticketId], options);
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
expect(canBeInvoiced).toEqual(false); expect(canBeInvoiced).toEqual(false);
@ -95,7 +95,7 @@ describe('ticket canBeInvoiced()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketId], options); `, [ticketId], options);
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
expect(canBeInvoiced).toEqual(false); expect(canBeInvoiced).toEqual(false);
@ -123,7 +123,7 @@ describe('ticket canBeInvoiced()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketId], options); `, [ticketId], options);
const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options);
expect(canBeInvoiced).toEqual(true); expect(canBeInvoiced).toEqual(true);

View File

@ -31,7 +31,7 @@ describe('ticket invoiceTickets()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketsIds = [11, 16]; const ticketsIds = [11, 16];
await models.Ticket.invoiceTickets(ctx, ticketsIds, options); await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -57,7 +57,7 @@ describe('ticket invoiceTickets()', () => {
await client.updateAttribute('isTaxDataChecked', false, options); await client.updateAttribute('isTaxDataChecked', false, options);
const ticketsIds = [11]; const ticketsIds = [11];
await models.Ticket.invoiceTickets(ctx, ticketsIds, options); await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -80,8 +80,8 @@ describe('ticket invoiceTickets()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketsIds = [11]; const ticketsIds = [11];
await models.Ticket.invoiceTickets(ctx, ticketsIds, options); await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
await models.Ticket.invoiceTickets(ctx, ticketsIds, options); await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -102,7 +102,7 @@ describe('ticket invoiceTickets()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketsIds = [11]; const ticketsIds = [11];
const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options); const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options);
expect(invoicesIds.length).toBeGreaterThan(0); expect(invoicesIds.length).toBeGreaterThan(0);

View File

@ -42,7 +42,7 @@ describe('ticket makeInvoice()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketsIds], options); `, [ticketsIds], options);
const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options);
expect(invoiceId).toBeDefined(); expect(invoiceId).toBeDefined();
@ -70,7 +70,7 @@ describe('ticket makeInvoice()', () => {
WHERE id IN (?) WHERE id IN (?)
`, [ticketsId], options); `, [ticketsId], options);
await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
error = e; error = e;

View File

@ -292,7 +292,7 @@ class Controller extends Section {
const query = 'Tickets/refund'; const query = 'Tickets/refund';
return this.$http.post(query, params) return this.$http.post(query, params)
.then(res => { .then(res => {
const refundTicket = res.data; const [refundTicket] = res.data;
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
ticketId: refundTicket.id ticketId: refundTicket.id
})); }));

View File

@ -262,11 +262,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
const params = { const params = {
ticketsIds: [16] ticketsIds: [16]
}; };
$httpBackend.expectPOST('Tickets/refund', params).respond({id: 99}); const response = {id: 99};
$httpBackend.expectPOST('Tickets/refund', params).respond([response]);
controller.refund(); controller.refund();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99}); expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response);
}); });
}); });

View File

@ -526,7 +526,7 @@ class Controller extends Section {
const params = {salesIds: salesIds, withWarehouse: withWarehouse}; const params = {salesIds: salesIds, withWarehouse: withWarehouse};
const query = 'Sales/refund'; const query = 'Sales/refund';
this.$http.post(query, params).then(res => { this.$http.post(query, params).then(res => {
const refundTicket = res.data; const [refundTicket] = res.data;
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
ticketId: refundTicket.id ticketId: refundTicket.id
})); }));

View File

@ -729,7 +729,7 @@ describe('Ticket', () => {
salesIds: [1, 4], salesIds: [1, 4],
}; };
const refundTicket = {id: 99}; const refundTicket = {id: 99};
$httpBackend.expect('POST', 'Sales/refund', params).respond(200, refundTicket); $httpBackend.expect('POST', 'Sales/refund', params).respond(200, [refundTicket]);
controller.createRefund(); controller.createRefund();
$httpBackend.flush(); $httpBackend.flush();