#4466 InvoiceIn Rectification #1902

Merged
jorgep merged 20 commits from 4466-invoiceInRectificated into dev 2024-02-06 07:49:20 +00:00
14 changed files with 100 additions and 190 deletions
Showing only changes of commit 17680b2cc7 - Show all commits

View File

@ -0,0 +1,4 @@
UPDATE salix.ACL
SET property='clone'
WHERE model = 'Sale'
AND property = 'refund'

View File

@ -203,5 +203,7 @@
"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}}",
"Incorrect pin": "Incorrect pin.",
"The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified"
"The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified",
"Fecha fuera de rango": "Fecha fuera de rango",
"There is no zone for these parameters 34": "There is no zone for these parameters 34"
}

View File

@ -1,4 +1,32 @@
module.exports = Self => {
Self.remoteMethodCtx('clone', {
description: 'Clone sales and services provided',
accessType: 'WRITE',
accepts: [
{
arg: 'salesIds',
type: ['number'],
}, {
arg: 'servicesIds',
type: ['number']
}, {
arg: 'withWarehouse',
type: 'boolean',
required: true
}, {
arg: 'negative',
type: 'boolean'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/clone`,
verb: 'POST'
}
});
Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, negative, options) => {
const models = Self.app.models;
const myOptions = {};

View File

@ -1,61 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('refund', {
description: 'Create refund tickets with sales and services if provided',
accessType: 'WRITE',
accepts: [
{
arg: 'salesIds',
type: ['number'],
},
{
arg: 'servicesIds',
type: ['number']
},
{
arg: 'withWarehouse',
type: 'boolean',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/refund`,
verb: 'post'
}
});
Self.refund = async(ctx, salesIds, servicesIds, withWarehouse, options) => {
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const refundsTicket = await models.Sale.clone(
ctx,
salesIds,
servicesIds,
withWarehouse,
true,
myOptions
);
if (tx) await tx.commit();
return refundsTicket;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -67,4 +67,42 @@ describe('Ticket cloning - clone function', () => {
expect(services.length).toBeGreaterThan(0);
}
});
it('should create a ticket without sales', async() => {
const servicesIds = [4];
const tx = await models.Sale.beginTransaction({});
const options = {transaction: tx};
try {
const tickets = await models.Sale.clone(ctx, null, servicesIds, false, options);
const refundedTicket = await getTicketRefund(tickets[0].id, options);
expect(refundedTicket).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
async function getTicketRefund(id, options) {
return models.Ticket.findOne({
where: {
id
},
include: [
{
relation: 'ticketSales',
scope: {
include: {
relation: 'components'
}
}
},
{
relation: 'ticketServices',
}
]
}, options);
}

View File

@ -1,101 +0,0 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Sale refund()', () => {
const userId = 5;
const ctx = {req: {accessToken: userId}, args: {}};
const activeCtx = {
accessToken: {userId},
};
const servicesIds = [3];
const withWarehouse = true;
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create ticket with the selected lines', async() => {
const tx = await models.Sale.beginTransaction({});
const salesIds = [7, 8];
try {
const options = {transaction: tx};
const refundedTickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
expect(refundedTickets).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should create one ticket for each unique ticketFk in the sales', async() => {
const tx = await models.Sale.beginTransaction({});
const salesIds = [6, 7];
try {
const options = {transaction: tx};
const ticketsBefore = await models.Ticket.find({}, options);
const tickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
const refundedTicket = await getTicketRefund(tickets[0].id, options);
const ticketsAfter = await models.Ticket.find({}, options);
const salesLength = refundedTicket.ticketSales().length;
const componentsLength = refundedTicket.ticketSales()[0].components().length;
expect(refundedTicket).toBeDefined();
expect(salesLength).toEqual(1);
expect(ticketsBefore.length).toEqual(ticketsAfter.length - 2);
expect(componentsLength).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should create a ticket without sales', async() => {
const servicesIds = [4];
const tx = await models.Sale.beginTransaction({});
const options = {transaction: tx};
try {
const tickets = await models.Sale.refund(ctx, null, servicesIds, withWarehouse, options);
const refundedTicket = await getTicketRefund(tickets[0].id, options);
expect(refundedTicket).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
async function getTicketRefund(id, options) {
return models.Ticket.findOne({
where: {
id
},
include: [
{
relation: 'ticketSales',
scope: {
include: {
relation: 'components'
}
}
},
{
relation: 'ticketServices',
}
]
}, options);
}

View File

@ -108,6 +108,23 @@ module.exports = function(Self) {
await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions);
const client = await models.Client.findById(clientId,
{fields: ['hasElectronicInvoice', 'name', 'email']}, myOptions);
if (client.hasElectronicInvoice) {
const url = await models.Url.getUrl();
await models.NotificationQueue.create({
notificationFk: 'invoice-electronic',
authorFk: client.id,
params: JSON.stringify(
{
'name': client.name,
'email': client.email,
'ticketId': ticketsIds.join(','),
'url': url + 'ticket/index?q=' + encodeURIComponent(JSON.stringify({clientFk: clientId}))
})
}, myOptions);
}
if (tx) await tx.commit();
return resultInvoice.id;

View File

@ -20,7 +20,7 @@ module.exports = Self => {
},
http: {
path: `/refund`,
verb: 'post'
verb: 'POST'
}
});
@ -45,7 +45,7 @@ module.exports = Self => {
const services = await models.TicketService.find(filter, myOptions);
const servicesIds = services.map(service => service.id);
const refundedTickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, myOptions);
const refundedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, true, myOptions);
if (tx) await tx.commit();

View File

@ -9,7 +9,6 @@ module.exports = Self => {
require('../methods/sale/updateQuantity')(Self);
require('../methods/sale/updateConcept')(Self);
require('../methods/sale/recalculatePrice')(Self);
require('../methods/sale/refund')(Self);
require('../methods/sale/canEdit')(Self);
require('../methods/sale/usesMana')(Self);
require('../methods/sale/clone')(Self);

View File

@ -248,23 +248,6 @@ class Controller extends Section {
if (this.ticket.address.incotermsFk && !this.ticket.weight && !force)
return this.$.withoutWeightConfirmation.show();
const client = this.ticket.client;
if (client.hasElectronicInvoice) {
this.$http.post(`NotificationQueues`, {
notificationFk: 'invoice-electronic',
authorFk: client.id,
params: JSON.stringify(
{
'name': client.name,
'email': client.email,
'ticketId': this.id,
'url': window.location.href
})
}).then(() => {
this.vnApp.showSuccess(this.$t('Invoice sent'));
});
}
return this.$http.post(`Tickets/invoiceTicketsAndPdf`, {ticketsIds: [this.id]})
.then(() => this.reload())
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));

View File

@ -523,8 +523,8 @@ class Controller extends Section {
if (!sales) return;
const salesIds = sales.map(sale => sale.id);
const params = {salesIds: salesIds, withWarehouse: withWarehouse};
const query = 'Sales/refund';
const params = {salesIds: salesIds, withWarehouse: withWarehouse, negative: true};
const query = 'Sales/clone';
this.$http.post(query, params).then(res => {
const [refundTicket] = res.data;
this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {

View File

@ -727,9 +727,10 @@ describe('Ticket', () => {
jest.spyOn(controller.$state, 'go');
const params = {
salesIds: [1, 4],
negative: true
};
const refundTicket = {id: 99};
$httpBackend.expect('POST', 'Sales/refund', params).respond(200, [refundTicket]);
$httpBackend.expect('POST', 'Sales/clone', params).respond(200, [refundTicket]);
controller.createRefund();
$httpBackend.flush();

View File

@ -55,10 +55,10 @@ class Controller extends Section {
createRefund() {
if (!this.checkeds.length) return;
const params = {servicesIds: this.checkeds, withWarehouse: false};
const query = 'Sales/refund';
const params = {servicesIds: this.checkeds, withWarehouse: false, negative: true};
const query = 'Sales/clone';
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', {
ticketId: refundTicket.id
}));

View File

@ -10,7 +10,7 @@ module.exports = {
required: true
},
ticketId: {
type: [Number],
type: [String],
required: true
},
url: {