#7710 - clone with ticket packaging #2878
|
@ -0,0 +1,24 @@
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
|||||||
|
WHERE `model` = 'Ticket'
|
||||||
|
AND `property` = 'refund'
|
||||||
|
AND `accessType` = 'WRITE'
|
||||||
|
AND `permission` = 'ALLOW'
|
||||||
|
AND `principalType` = 'ROLE'
|
||||||
|
AND `principalId` = 'salesAssistant';
|
||||||
|
|
||||||
|
UPDATE `salix`.`ACL`
|
||||||
|
SET `property` = 'cloneAll'
|
||||||
|
WHERE `model` = 'Ticket'
|
||||||
|
AND `property` = 'refund'
|
||||||
|
AND `accessType` = 'WRITE'
|
||||||
|
AND `permission` = 'ALLOW'
|
||||||
|
AND `principalType` = 'ROLE'
|
||||||
|
AND `principalId` IN ('invoicing', 'claimManager', 'logistic');
|
||||||
|
|
||||||
|
DELETE FROM `salix`.`ACL`
|
||||||
|
WHERE `model` = 'Ticket'
|
||||||
|
AND `property` = 'clone'
|
||||||
|
AND `accessType` = 'WRITE'
|
||||||
|
AND `permission` = 'ALLOW'
|
||||||
|
AND `principalType` = 'ROLE'
|
||||||
|
AND `principalId` = 'administrative';
|
|
@ -43,7 +43,7 @@ module.exports = Self => {
|
||||||
const tickets = await models.Ticket.find(filter, myOptions);
|
const tickets = await models.Ticket.find(filter, myOptions);
|
||||||
|
|
||||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||||
const refundedTickets = await models.Ticket.refund(ctx, ticketsIds, withWarehouse, myOptions);
|
const refundedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, withWarehouse, true, myOptions);
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
|
|
@ -82,20 +82,12 @@ module.exports = Self => {
|
||||||
myOptions.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const filterRef = {where: {refFk: refFk}};
|
const tickets = await models.Ticket.find({where: {refFk}}, myOptions);
|
||||||
jsegarra
commented
Si la clave tiene el mismo nombre que la variable del valor, basta con hacer Si la clave tiene el mismo nombre que la variable del valor, basta con hacer `{where:{refFk}`
|
|||||||
const tickets = await models.Ticket.find(filterRef, myOptions);
|
|
||||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||||
const refundTickets = await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
|
const refundTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, true, myOptions);
|
||||||
|
|
||||||
const filterTicket = {where: {ticketFk: {inq: ticketsIds}}};
|
const clonedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, false, myOptions);
|
||||||
|
|
||||||
const services = await models.TicketService.find(filterTicket, myOptions);
|
|
||||||
const servicesIds = services.map(service => service.id);
|
|
||||||
|
|
||||||
const sales = await models.Sale.find(filterTicket, myOptions);
|
|
||||||
const salesIds = sales.map(sale => sale.id);
|
|
||||||
|
|
||||||
const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, myOptions);
|
|
||||||
const clonedTicketIds = [];
|
const clonedTicketIds = [];
|
||||||
|
|
||||||
for (const clonedTicket of clonedTickets) {
|
for (const clonedTicket of clonedTickets) {
|
||||||
|
|
|
@ -88,28 +88,7 @@
|
||||||
translate>
|
translate>
|
||||||
Show CITES letter
|
Show CITES letter
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item class="dropdown"
|
|
||||||
vn-click-stop="refundMenu.show($event, 'left')"
|
|
||||||
vn-tooltip="Create a refund ticket for each ticket on the current invoice"
|
|
||||||
vn-acl="invoicing, claimManager, salesAssistant"
|
|
||||||
vn-acl-action="remove"
|
|
||||||
translate>
|
|
||||||
Refund...
|
|
||||||
<vn-menu vn-id="refundMenu">
|
|
||||||
<vn-list>
|
|
||||||
<vn-item
|
|
||||||
ng-click="$ctrl.refundInvoiceOut(true)"
|
|
||||||
translate>
|
|
||||||
with warehouse
|
|
||||||
</vn-item>
|
|
||||||
<vn-item
|
|
||||||
ng-click="$ctrl.refundInvoiceOut(false)"
|
|
||||||
translate>
|
|
||||||
without warehouse
|
|
||||||
</vn-item>
|
|
||||||
</vn-list>
|
|
||||||
</vn-menu>
|
|
||||||
</vn-item>
|
|
||||||
</vn-list>
|
</vn-list>
|
||||||
</vn-menu>
|
</vn-menu>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
|
|
|
@ -135,21 +135,6 @@ class Controller extends Section {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
refundInvoiceOut(withWarehouse) {
|
|
||||||
const query = 'InvoiceOuts/refund';
|
|
||||||
const params = {ref: this.invoiceOut.ref, withWarehouse: withWarehouse};
|
|
||||||
this.$http.post(query, params).then(res => {
|
|
||||||
const tickets = res.data;
|
|
||||||
const refundTickets = tickets.map(ticket => ticket.id);
|
|
||||||
|
|
||||||
this.vnApp.showSuccess(this.$t('The following refund tickets have been created', {
|
|
||||||
ticketId: refundTickets.join(',')
|
|
||||||
}));
|
|
||||||
if (refundTickets.length == 1)
|
|
||||||
this.$state.go('ticket.card.sale', {id: refundTickets[0]});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
transferInvoice() {
|
transferInvoice() {
|
||||||
const params = {
|
const params = {
|
||||||
id: this.invoiceOut.id,
|
id: this.invoiceOut.id,
|
||||||
|
|
|
@ -105,17 +105,4 @@ describe('vnInvoiceOutDescriptorMenu', () => {
|
||||||
expect(controller.vnApp.showMessage).toHaveBeenCalled();
|
expect(controller.vnApp.showMessage).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refundInvoiceOut()', () => {
|
|
||||||
it('should make a query and show a success message', () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
const params = {ref: controller.invoiceOut.ref};
|
|
||||||
|
|
||||||
$httpBackend.expectPOST(`InvoiceOuts/refund`, params).respond([{id: 1}, {id: 2}]);
|
|
||||||
controller.refundInvoiceOut();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,40 +1,25 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clone', {
|
Self.remoteMethodCtx('clone', {
|
||||||
description: 'Clone sales and services provided',
|
description: 'Clone sales, services, and ticket packaging provided',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{arg: 'salesIds', type: ['number']},
|
||||||
arg: 'salesIds',
|
{arg: 'servicesIds', type: ['number']},
|
||||||
type: ['number'],
|
{arg: 'ticketPackagingIds', type: ['number']},
|
||||||
}, {
|
{arg: 'withWarehouse', type: 'boolean', required: true},
|
||||||
arg: 'servicesIds',
|
{arg: 'negative', type: 'boolean'}
|
||||||
type: ['number']
|
|
||||||
}, {
|
|
||||||
arg: 'withWarehouse',
|
|
||||||
type: 'boolean',
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
arg: 'negative',
|
|
||||||
type: 'boolean'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {type: ['object'], root: true},
|
||||||
type: ['object'],
|
http: {path: `/clone`, verb: 'POST'}
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/clone`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, negative, options) => {
|
|
||||||
|
Self.clone = async(ctx, salesIds, servicesIds, ticketPackagingIds, withWarehouse, negative, options) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
let tx;
|
let tx;
|
||||||
const newTickets = [];
|
const newTickets = [];
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options === 'object') Object.assign(myOptions, options);
|
||||||
Object.assign(myOptions, options);
|
|
||||||
|
|
||||||
if (!myOptions.transaction) {
|
if (!myOptions.transaction) {
|
||||||
tx = await Self.beginTransaction({});
|
tx = await Self.beginTransaction({});
|
||||||
|
@ -44,8 +29,9 @@ module.exports = Self => {
|
||||||
try {
|
try {
|
||||||
let sales;
|
let sales;
|
||||||
let services;
|
let services;
|
||||||
|
let ticketPackaging;
|
||||||
|
|
||||||
if (salesIds && salesIds.length) {
|
if (salesIds?.length) {
|
||||||
sales = await models.Sale.find({
|
sales = await models.Sale.find({
|
||||||
where: {id: {inq: salesIds}},
|
where: {id: {inq: salesIds}},
|
||||||
include: {
|
include: {
|
||||||
|
@ -57,12 +43,18 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (servicesIds && servicesIds.length) {
|
if (servicesIds?.length) {
|
||||||
services = await models.TicketService.find({
|
services = await models.TicketService.find({
|
||||||
where: {id: {inq: servicesIds}}
|
where: {id: {inq: servicesIds}}
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ticketPackagingIds?.length) {
|
||||||
|
ticketPackaging = await models.TicketPackaging.find({
|
||||||
|
where: {id: {inq: ticketPackagingIds}}
|
||||||
|
}, myOptions);
|
||||||
|
}
|
||||||
|
|
||||||
let ticketsIds = sales ?
|
let ticketsIds = sales ?
|
||||||
[...new Set(sales.map(sale => sale.ticketFk))] :
|
[...new Set(sales.map(sale => sale.ticketFk))] :
|
||||||
[...new Set(services.map(service => service.ticketFk))];
|
[...new Set(services.map(service => service.ticketFk))];
|
||||||
|
@ -74,12 +66,12 @@ module.exports = Self => {
|
||||||
ctx,
|
ctx,
|
||||||
ticketId,
|
ticketId,
|
||||||
withWarehouse,
|
withWarehouse,
|
||||||
negative,
|
|
||||||
myOptions
|
myOptions
|
||||||
);
|
);
|
||||||
newTickets.push(newTicket);
|
newTickets.push(newTicket);
|
||||||
mappedTickets.set(ticketId, newTicket.id);
|
mappedTickets.set(ticketId, newTicket.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sales) {
|
if (sales) {
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
const newTicketId = mappedTickets.get(sale.ticketFk);
|
const newTicketId = mappedTickets.get(sale.ticketFk);
|
||||||
|
@ -107,7 +99,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
await models.TicketService.create({
|
await models.TicketService.create({
|
||||||
description: service.description,
|
description: service.description,
|
||||||
quantity: negative ? - service.quantity : service.quantity,
|
quantity: negative ? -service.quantity : service.quantity,
|
||||||
price: service.price,
|
price: service.price,
|
||||||
taxClassFk: service.taxClassFk,
|
taxClassFk: service.taxClassFk,
|
||||||
ticketFk: newTicketId,
|
ticketFk: newTicketId,
|
||||||
|
@ -116,6 +108,18 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ticketPackaging) {
|
||||||
|
for (const packaging of ticketPackaging) {
|
||||||
|
const newTicketId = mappedTickets.get(packaging.ticketFk);
|
||||||
|
|
||||||
|
await models.TicketPackaging.create({
|
||||||
|
ticketFk: newTicketId,
|
||||||
|
packagingFk: packaging.packagingFk,
|
||||||
|
quantity: negative ? -packaging.quantity : packaging.quantity
|
||||||
|
}, myOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return newTickets;
|
return newTickets;
|
||||||
|
@ -124,13 +128,7 @@ module.exports = Self => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTicket(
|
async function createTicket(ctx, ticketId, withWarehouse, myOptions) {
|
||||||
ctx,
|
|
||||||
ticketId,
|
|
||||||
withWarehouse,
|
|
||||||
negative,
|
|
||||||
myOptions
|
|
||||||
) {
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const now = Date.vnNew();
|
const now = Date.vnNew();
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ describe('Ticket cloning - clone function', () => {
|
||||||
const servicesIds = [];
|
const servicesIds = [];
|
||||||
const withWarehouse = true;
|
const withWarehouse = true;
|
||||||
const negative = false;
|
const negative = false;
|
||||||
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, negative, options);
|
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, withWarehouse, negative, options);
|
||||||
|
|
||||||
expect(newTickets).toBeDefined();
|
expect(newTickets).toBeDefined();
|
||||||
expect(newTickets.length).toBeGreaterThan(0);
|
expect(newTickets.length).toBeGreaterThan(0);
|
||||||
|
@ -30,7 +30,7 @@ describe('Ticket cloning - clone function', () => {
|
||||||
const negative = true;
|
const negative = true;
|
||||||
const salesIds = [7, 8];
|
const salesIds = [7, 8];
|
||||||
const servicesIds = [];
|
const servicesIds = [];
|
||||||
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, negative, options);
|
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, negative, options);
|
||||||
|
|
||||||
for (const ticket of newTickets) {
|
for (const ticket of newTickets) {
|
||||||
const sales = await models.Sale.find({where: {ticketFk: ticket.id}}, options);
|
const sales = await models.Sale.find({where: {ticketFk: ticket.id}}, options);
|
||||||
|
@ -43,7 +43,7 @@ describe('Ticket cloning - clone function', () => {
|
||||||
it('should create new components and services for cloned tickets', async() => {
|
it('should create new components and services for cloned tickets', async() => {
|
||||||
const servicesIds = [2];
|
const servicesIds = [2];
|
||||||
const salesIds = [5];
|
const salesIds = [5];
|
||||||
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, false, options);
|
const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, options);
|
||||||
|
|
||||||
for (const ticket of newTickets) {
|
for (const ticket of newTickets) {
|
||||||
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
|
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
|
||||||
|
@ -58,7 +58,7 @@ describe('Ticket cloning - clone function', () => {
|
||||||
it('should create a ticket without sales', async() => {
|
it('should create a ticket without sales', async() => {
|
||||||
const servicesIds = [4];
|
const servicesIds = [4];
|
||||||
|
|
||||||
const tickets = await models.Sale.clone(ctx, null, servicesIds, false, false, options);
|
const tickets = await models.Sale.clone(ctx, null, servicesIds, null, false, false, options);
|
||||||
const refundedTicket = await getTicketRefund(tickets[0].id, options);
|
const refundedTicket = await getTicketRefund(tickets[0].id, options);
|
||||||
|
|
||||||
expect(refundedTicket).toBeDefined();
|
expect(refundedTicket).toBeDefined();
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethodCtx('clone', {
|
|
||||||
description: 'clone a ticket and return the new ticket id',
|
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [{
|
|
||||||
arg: 'id',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
description: 'The ticket id',
|
|
||||||
http: {source: 'path'}
|
|
||||||
}, {
|
|
||||||
arg: 'shipped',
|
|
||||||
type: 'date',
|
|
||||||
}, {
|
|
||||||
arg: 'withWarehouse',
|
|
||||||
type: 'boolean',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: 'number',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/:id/clone`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.clone = async(ctx, id, shipped, withWarehouse, options) => {
|
|
||||||
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 [, [{clonedTicketId}]] = await Self.rawSql(`
|
|
||||||
CALL vn.ticket_cloneAll(?, ?, ?, @clonedTicketId);
|
|
||||||
SELECT @clonedTicketId clonedTicketId;`,
|
|
||||||
[id, shipped, withWarehouse], myOptions);
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
|
||||||
return clonedTicketId;
|
|
||||||
} catch (e) {
|
|
||||||
if (tx) await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('cloneAll', {
|
||||||
|
description: 'Clone tickets, sales, services and packages',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'ticketsIds',
|
||||||
|
type: ['number'],
|
||||||
|
required: true,
|
||||||
|
description: 'IDs of the tickets to clone'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'withWarehouse',
|
||||||
|
type: 'boolean',
|
||||||
|
required: true,
|
||||||
|
description: 'true: keep original warehouse; false: set to null'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'negative',
|
||||||
|
type: 'boolean',
|
||||||
|
required: true,
|
||||||
|
description: 'true: invert quantities; false: keep as is.'
|
||||||
jsegarra
commented
Siguiendo con el patrón del campo anterior. Siguiendo con el patrón del campo anterior.
Se asume que si es true: Whether...
|
|||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true,
|
||||||
|
description: 'The cloned tickets with associated data'
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/cloneAll`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const myOptions = typeof options == 'object' ? {...options} : {};
|
||||||
|
let tx;
|
||||||
|
|
||||||
|
if (!myOptions.transaction) {
|
||||||
|
tx = await Self.beginTransaction({});
|
||||||
|
myOptions.transaction = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filter = {where: {ticketFk: {inq: ticketsIds}}};
|
||||||
|
|
||||||
|
const [sales, services, ticketPackaging] = await Promise.all([
|
||||||
|
models.Sale.find(filter, myOptions),
|
||||||
|
models.TicketService.find(filter, myOptions),
|
||||||
|
models.TicketPackaging.find(filter, myOptions)
|
||||||
jsegarra
commented
Si una promesa falla...la siguientes fallaran. Si una promesa falla...la siguientes fallaran.
Propuesta, usar allSettled
jgallego
commented
No lo sabia, he preguntado a chatgpt, las diferencias, no tengo claro que en este caso nos aporte algo, PERO decidelo tu leyendo esto que tendras mas criterio
No lo sabia, he preguntado a chatgpt, las diferencias, no tengo claro que en este caso nos aporte algo, PERO decidelo tu leyendo esto que tendras mas criterio
> Promise.all puede ser adecuado si deseas abortar todo el proceso tan pronto como una promesa falle. Esto puede ser útil para asegurarte de que, si hay un error temprano, no continúas con acciones que dependen de ese estado.
> Promise.allSettled sería útil si prefieres recopilar los resultados o errores de todas las promesas y luego decidir cómo proceder. Esto es útil cuando fallas en una parte del proceso no deben impedirte completar otras partes.
jsegarra
commented
en realidad queremos que si una falle el resto también. en realidad queremos que si una falle el resto también.
Lo había planteado al revés. dejamos Promises.all
|
|||||||
|
]);
|
||||||
|
|
||||||
|
const salesIds = sales.map(({id}) => id);
|
||||||
|
const servicesIds = services.map(({id}) => id);
|
||||||
|
const ticketPackagingIds = ticketPackaging.map(({id}) => id);
|
||||||
|
|
||||||
jsegarra
commented
Propuesta: Y podria servir para las otras 2 más O incluso en la linea 52 en vez de hacer Por ejemplo: ` const results = await Promise.allSettled([
Propuesta:
`const salesIds = sales.map(({id})=> id);`
Y podria servir para las otras 2 más
O incluso en la linea 52 en vez de hacer `[sales, services, ticketPackaging]` podrías usar ese map para cada elemento del array
Por ejemplo:
` const results = await Promise.allSettled([
models.Sale.find(filter, myOptions),
models.TicketService.find(filter, myOptions),
models.TicketPackaging.find(filter, myOptions)
]);
const [salesIds, servicesIds, ticketPackagingIds] = results.map(result =>
result.value.map(({ id }) => id)
);`
|
|||||||
|
const clonedTickets = await models.Sale.clone(
|
||||||
|
ctx,
|
||||||
|
salesIds,
|
||||||
|
servicesIds,
|
||||||
|
ticketPackagingIds,
|
||||||
|
withWarehouse,
|
||||||
|
negative,
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
return clonedTickets;
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,58 +0,0 @@
|
||||||
module.exports = Self => {
|
|
||||||
Self.remoteMethodCtx('refund', {
|
|
||||||
description: 'Create refund tickets with all their sales and services',
|
|
||||||
accessType: 'WRITE',
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
arg: 'ticketsIds',
|
|
||||||
type: ['number'],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'withWarehouse',
|
|
||||||
type: 'boolean',
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: ['object'],
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/refund`,
|
|
||||||
verb: 'POST'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.refund = async(ctx, ticketsIds, withWarehouse, options) => {
|
|
||||||
const models = Self.app.models;
|
|
||||||
const myOptions = {};
|
|
||||||
let tx;
|
|
||||||
|
|
||||||
if (typeof options == 'object')
|
|
||||||
Object.assign(myOptions, options);
|
|
||||||
|
|
||||||
if (!myOptions.transaction) {
|
|
||||||
tx = await Self.beginTransaction({});
|
|
||||||
myOptions.transaction = tx;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const filter = {where: {ticketFk: {inq: ticketsIds}}};
|
|
||||||
const sales = await models.Sale.find(filter, myOptions);
|
|
||||||
const salesIds = sales.map(sale => sale.id);
|
|
||||||
|
|
||||||
const services = await models.TicketService.find(filter, myOptions);
|
|
||||||
const servicesIds = services.map(service => service.id);
|
|
||||||
|
|
||||||
const refundedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, true, myOptions);
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
|
||||||
|
|
||||||
return refundedTickets;
|
|
||||||
} catch (e) {
|
|
||||||
if (tx) await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,43 +0,0 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
|
||||||
|
|
||||||
describe('Ticket cloning - clone function', () => {
|
|
||||||
const ctx = beforeAll.getCtx();
|
|
||||||
let options;
|
|
||||||
let tx;
|
|
||||||
const ticketId = 1;
|
|
||||||
const shipped = Date.vnNew();
|
|
||||||
|
|
||||||
beforeEach(async() => {
|
|
||||||
options = {transaction: tx};
|
|
||||||
tx = await models.Ticket.beginTransaction({});
|
|
||||||
options.transaction = tx;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async() => {
|
|
||||||
await tx.rollback();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clone a new ticket without warehouse', async() => {
|
|
||||||
const originalTicket = await models.Ticket.findById(ticketId, null, options);
|
|
||||||
|
|
||||||
const newTicketId = await models.Ticket.clone(ctx, ticketId, shipped, false, options);
|
|
||||||
const newTicket = await models.Ticket.findById(newTicketId, null, options);
|
|
||||||
|
|
||||||
expect(newTicket.clientFk).toEqual(originalTicket.clientFk);
|
|
||||||
expect(newTicket.companyFk).toEqual(originalTicket.companyFk);
|
|
||||||
expect(newTicket.addressFk).toEqual(originalTicket.addressFk);
|
|
||||||
expect(newTicket.warehouseFk).toBeFalsy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clone a new ticket with warehouse', async() => {
|
|
||||||
const originalTicket = await models.Ticket.findById(ticketId, null, options);
|
|
||||||
|
|
||||||
const newTicketId = await models.Ticket.clone(ctx, ticketId, shipped, true, options);
|
|
||||||
const newTicket = await models.Ticket.findById(newTicketId, null, options);
|
|
||||||
|
|
||||||
expect(newTicket.clientFk).toEqual(originalTicket.clientFk);
|
|
||||||
expect(newTicket.companyFk).toEqual(originalTicket.companyFk);
|
|
||||||
expect(newTicket.addressFk).toEqual(originalTicket.addressFk);
|
|
||||||
expect(newTicket.warehouseFk).toEqual(originalTicket.warehouseFk);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
|
describe('Ticket cloning - cloneAll function', () => {
|
||||||
|
const activeCtx = {
|
||||||
|
accessToken: {userId: 1},
|
||||||
|
http: {
|
||||||
|
req: {
|
||||||
|
headers: {origin: 'http://localhost'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const ctx = {req: activeCtx};
|
||||||
|
let options;
|
||||||
|
let tx;
|
||||||
|
const ticketIds = [1, 2];
|
||||||
|
const withWarehouse = true;
|
||||||
|
const negative = false;
|
||||||
|
|
||||||
|
beforeEach(async() => {
|
||||||
|
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
|
||||||
|
tx = await models.Ticket.beginTransaction({});
|
||||||
|
options = {transaction: tx};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async() => {
|
||||||
|
if (tx)
|
||||||
|
await tx.rollback();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clone all provided tickets with their associated sales, services, and packages', async() => {
|
||||||
|
const originalTickets = await models.Ticket.find({where: {id: {inq: ticketIds}}}, options);
|
||||||
|
const originalSales = await models.Sale.find({where: {ticketFk: {inq: ticketIds}}}, options);
|
||||||
|
const originalServices = await models.TicketService.find({where: {ticketFk: {inq: ticketIds}}}, options);
|
||||||
|
const originalTicketPackaging =
|
||||||
|
await models.TicketPackaging.find({where: {ticketFk: {inq: ticketIds}}}, options);
|
||||||
|
|
||||||
|
// Pass the ctx correctly to the cloneAll function
|
||||||
|
const clonedTickets = await models.Ticket.cloneAll(ctx, ticketIds, withWarehouse, negative, options);
|
||||||
|
|
||||||
|
expect(clonedTickets.length).toEqual(originalTickets.length);
|
||||||
|
|
||||||
|
const clonedSales = await models.Sale.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
|
||||||
|
const clonedServices =
|
||||||
|
await models.TicketService.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
|
||||||
|
const clonedTicketPackaging =
|
||||||
|
await models.TicketPackaging.find({where: {ticketFk: {inq: clonedTickets.map(t => t.id)}}}, options);
|
||||||
|
|
||||||
|
expect(clonedSales.length).toEqual(originalSales.length);
|
||||||
|
expect(clonedServices.length).toEqual(originalServices.length);
|
||||||
|
expect(clonedTicketPackaging.length).toEqual(originalTicketPackaging.length);
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,7 +26,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/ticket/isLocked')(Self);
|
require('../methods/ticket/isLocked')(Self);
|
||||||
require('../methods/ticket/freightCost')(Self);
|
require('../methods/ticket/freightCost')(Self);
|
||||||
require('../methods/ticket/getComponentsSum')(Self);
|
require('../methods/ticket/getComponentsSum')(Self);
|
||||||
require('../methods/ticket/refund')(Self);
|
require('../methods/ticket/cloneAll')(Self);
|
||||||
require('../methods/ticket/deliveryNotePdf')(Self);
|
require('../methods/ticket/deliveryNotePdf')(Self);
|
||||||
require('../methods/ticket/deliveryNoteEmail')(Self);
|
require('../methods/ticket/deliveryNoteEmail')(Self);
|
||||||
require('../methods/ticket/deliveryNoteCsv')(Self);
|
require('../methods/ticket/deliveryNoteCsv')(Self);
|
||||||
|
@ -46,5 +46,4 @@ module.exports = function(Self) {
|
||||||
require('../methods/ticket/invoiceTicketsAndPdf')(Self);
|
require('../methods/ticket/invoiceTicketsAndPdf')(Self);
|
||||||
require('../methods/ticket/docuwareDownload')(Self);
|
require('../methods/ticket/docuwareDownload')(Self);
|
||||||
require('../methods/ticket/myLastModified')(Self);
|
require('../methods/ticket/myLastModified')(Self);
|
||||||
require('../methods/ticket/clone')(Self);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -287,15 +287,24 @@ class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
refund(withWarehouse) {
|
refund(withWarehouse) {
|
||||||
const params = {ticketsIds: [this.id], withWarehouse: withWarehouse};
|
const params = {
|
||||||
const query = 'Tickets/refund';
|
ticketsIds: [this.id],
|
||||||
|
withWarehouse: withWarehouse,
|
||||||
|
negative: true // Asumimos que queremos cantidades negativas para reembolsos
|
||||||
|
};
|
||||||
|
const query = 'Tickets/cloneAll';
|
||||||
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 has been created', {
|
||||||
ticketId: refundTicket.id
|
ticketId: refundTicket.id
|
||||||
}));
|
}));
|
||||||
this.$state.go('ticket.card.sale', {id: refundTicket.id});
|
this.$state.go('ticket.card.sale', {id: refundTicket.id});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.vnApp.showError(this.$t('Error creating refund ticket', {
|
||||||
|
error: error.data?.error?.message || 'Unknown error'
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,24 +217,6 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refund()', () => {
|
|
||||||
it('should make a query and go to ticket.card.sale', () => {
|
|
||||||
controller.$state.go = jest.fn();
|
|
||||||
|
|
||||||
controller._id = ticket.id;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
ticketsIds: [16]
|
|
||||||
};
|
|
||||||
const response = {id: 99};
|
|
||||||
$httpBackend.expectPOST('Tickets/refund', params).respond([response]);
|
|
||||||
controller.refund();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sendChangesSms()', () => {
|
describe('sendChangesSms()', () => {
|
||||||
it('should make a query and open the sms dialog', () => {
|
it('should make a query and open the sms dialog', () => {
|
||||||
controller.$.sms = {open: () => {}};
|
controller.$.sms = {open: () => {}};
|
||||||
|
|
Entiendo que con estos DELETES quitamos permisos a 2 roles