From 1e3ab2d31a1034183c572ead9ef8a51e60462b50 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 29 Aug 2024 10:07:02 +0200 Subject: [PATCH 01/15] feat: refs #7905 Added param toCsv --- modules/entry/back/methods/entry/getBuys.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/entry/back/methods/entry/getBuys.js b/modules/entry/back/methods/entry/getBuys.js index 245dada09..65fb047c6 100644 --- a/modules/entry/back/methods/entry/getBuys.js +++ b/modules/entry/back/methods/entry/getBuys.js @@ -1,5 +1,6 @@ const UserError = require('vn-loopback/util/user-error'); const mergeFilters = require('vn-loopback/util/filter').mergeFilters; +const {toCSV} = require('vn-loopback/util/csv'); module.exports = Self => { Self.remoteMethodCtx('getBuys', { @@ -16,6 +17,11 @@ module.exports = Self => { arg: 'filter', type: 'object', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + { + arg: 'toCsv', + type: 'boolean', + description: 'If true, return the data in CSV format' } ], returns: { @@ -28,7 +34,7 @@ module.exports = Self => { } }); - Self.getBuys = async(ctx, id, filter, options) => { + Self.getBuys = async(ctx, id, filter, toCsv, options) => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const myOptions = {}; @@ -129,6 +135,15 @@ module.exports = Self => { }; defaultFilter = mergeFilters(defaultFilter, filter); - return models.Buy.find(defaultFilter, myOptions); + const data = models.Buy.find(defaultFilter, myOptions); + + if (toCsv) { + return [ + toCSV(data), + 'text/csv', + `attachment; filename="${id}.csv"` + ]; + } else + return data; }; }; From 72b4607d54344cf49d37d627d0e4a804e0e06927 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 29 Aug 2024 12:19:06 +0200 Subject: [PATCH 02/15] feat: refs #7905 Added new method getBuysCsv --- .../11206-turquoiseCyca/00-firstScript.sql | 2 + modules/entry/back/methods/entry/getBuys.js | 19 +---- .../entry/back/methods/entry/getBuysCsv.js | 75 +++++++++++++++++++ modules/entry/back/models/entry.js | 1 + 4 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 db/versions/11206-turquoiseCyca/00-firstScript.sql create mode 100644 modules/entry/back/methods/entry/getBuysCsv.js diff --git a/db/versions/11206-turquoiseCyca/00-firstScript.sql b/db/versions/11206-turquoiseCyca/00-firstScript.sql new file mode 100644 index 000000000..c1d63fcec --- /dev/null +++ b/db/versions/11206-turquoiseCyca/00-firstScript.sql @@ -0,0 +1,2 @@ +INSERT INTO salix.ACL (model,property,principalId) + VALUES ('Entry','getBuysCsv','supplier'); diff --git a/modules/entry/back/methods/entry/getBuys.js b/modules/entry/back/methods/entry/getBuys.js index 65fb047c6..245dada09 100644 --- a/modules/entry/back/methods/entry/getBuys.js +++ b/modules/entry/back/methods/entry/getBuys.js @@ -1,6 +1,5 @@ const UserError = require('vn-loopback/util/user-error'); const mergeFilters = require('vn-loopback/util/filter').mergeFilters; -const {toCSV} = require('vn-loopback/util/csv'); module.exports = Self => { Self.remoteMethodCtx('getBuys', { @@ -17,11 +16,6 @@ module.exports = Self => { arg: 'filter', type: 'object', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' - }, - { - arg: 'toCsv', - type: 'boolean', - description: 'If true, return the data in CSV format' } ], returns: { @@ -34,7 +28,7 @@ module.exports = Self => { } }); - Self.getBuys = async(ctx, id, filter, toCsv, options) => { + Self.getBuys = async(ctx, id, filter, options) => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const myOptions = {}; @@ -135,15 +129,6 @@ module.exports = Self => { }; defaultFilter = mergeFilters(defaultFilter, filter); - const data = models.Buy.find(defaultFilter, myOptions); - - if (toCsv) { - return [ - toCSV(data), - 'text/csv', - `attachment; filename="${id}.csv"` - ]; - } else - return data; + return models.Buy.find(defaultFilter, myOptions); }; }; diff --git a/modules/entry/back/methods/entry/getBuysCsv.js b/modules/entry/back/methods/entry/getBuysCsv.js new file mode 100644 index 000000000..76b9cdc57 --- /dev/null +++ b/modules/entry/back/methods/entry/getBuysCsv.js @@ -0,0 +1,75 @@ +const UserError = require('vn-loopback/util/user-error'); +const {toCSV} = require('vn-loopback/util/csv'); + +module.exports = Self => { + Self.remoteMethodCtx('getBuys', { + description: 'Returns buys for one entry', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The entry id', + http: {source: 'path'} + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: `/:id/getBuysCsv`, + verb: 'GET' + } + }); + + Self.getBuys = async(ctx, id, options) => { + const userId = ctx.req.accessToken.userId; + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const client = await models.Client.findById(userId, myOptions); + const supplier = await models.Supplier.findOne({where: {nif: client.fi}}, myOptions); + if (supplier) { + const isEntryOwner = (await Self.findById(id)).supplierFk === supplier.id; + if (!isEntryOwner) throw new UserError('Access Denied'); + } + + const data = await Self.rawSql(` + SELECT b.id, + b.itemFk, + i.name, + b.stickers, + b.packing, + b.grouping, + b.packing, + b.groupingMode, + b.quantity, + b.packagingFk, + b.weight, + b.buyingValue, + b.price2, + b.price3, + b.printedStickers + FROM buy b + JOIN item i ON i.id = b.itemFk + WHERE b.entryFk = ? + `, [id]); + + return [toCSV(data), 'text/csv', `inline; filename="buys-${id}.csv"`]; + }; +}; diff --git a/modules/entry/back/models/entry.js b/modules/entry/back/models/entry.js index b11d64415..8ca79f531 100644 --- a/modules/entry/back/models/entry.js +++ b/modules/entry/back/models/entry.js @@ -3,6 +3,7 @@ module.exports = Self => { require('../methods/entry/filter')(Self); require('../methods/entry/getEntry')(Self); require('../methods/entry/getBuys')(Self); + require('../methods/entry/getBuysCsv')(Self); require('../methods/entry/importBuys')(Self); require('../methods/entry/importBuysPreview')(Self); require('../methods/entry/lastItemBuys')(Self); From e50c67c3058bacb56c7964a0c52dc5d9aaff1109 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 29 Aug 2024 12:22:29 +0200 Subject: [PATCH 03/15] feat: refs #7905 Added new method getBuysCsv --- db/versions/11206-turquoiseCyca/00-firstScript.sql | 2 +- modules/entry/back/methods/entry/getBuysCsv.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db/versions/11206-turquoiseCyca/00-firstScript.sql b/db/versions/11206-turquoiseCyca/00-firstScript.sql index c1d63fcec..ab6b35a0f 100644 --- a/db/versions/11206-turquoiseCyca/00-firstScript.sql +++ b/db/versions/11206-turquoiseCyca/00-firstScript.sql @@ -1,2 +1,2 @@ -INSERT INTO salix.ACL (model,property,principalId) +INSERT IGNORE INTO salix.ACL (model,property,principalId) VALUES ('Entry','getBuysCsv','supplier'); diff --git a/modules/entry/back/methods/entry/getBuysCsv.js b/modules/entry/back/methods/entry/getBuysCsv.js index 76b9cdc57..541179a1d 100644 --- a/modules/entry/back/methods/entry/getBuysCsv.js +++ b/modules/entry/back/methods/entry/getBuysCsv.js @@ -2,8 +2,8 @@ const UserError = require('vn-loopback/util/user-error'); const {toCSV} = require('vn-loopback/util/csv'); module.exports = Self => { - Self.remoteMethodCtx('getBuys', { - description: 'Returns buys for one entry', + Self.remoteMethodCtx('getBuysCsv', { + description: 'Returns buys for one entry in CSV file format', accessType: 'READ', accepts: [{ arg: 'id', @@ -34,7 +34,7 @@ module.exports = Self => { } }); - Self.getBuys = async(ctx, id, options) => { + Self.getBuysCsv = async(ctx, id, options) => { const userId = ctx.req.accessToken.userId; const models = Self.app.models; const myOptions = {}; From 0f4bc3f83b33e320bdd7cd748d1e26716902f45c Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Fri, 30 Aug 2024 09:42:50 +0200 Subject: [PATCH 04/15] feat(salix): refs #7905 #7905 use getBuys toCSV flattened --- loopback/util/flatten.js | 28 ++++++++ .../entry/back/methods/entry/getBuysCsv.js | 65 ++++++++----------- 2 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 loopback/util/flatten.js diff --git a/loopback/util/flatten.js b/loopback/util/flatten.js new file mode 100644 index 000000000..90e9184b7 --- /dev/null +++ b/loopback/util/flatten.js @@ -0,0 +1,28 @@ + +function flatten(dataArray) { + return dataArray.map(item => flatten(item.__data)); +} +function flattenObj(data, prefix = '') { + let result = {}; + try { + for (let key in data) { + if (!data[key]) continue; + + const newKey = prefix ? `${prefix}_${key}` : key; + const value = data[key]; + + if (typeof value === 'object' && value !== null && !Array.isArray(value)) + Object.assign(result, flattenObj(value.__data, newKey)); + else + result[newKey] = value; + } + } catch (error) { + console.error(error); + } + + return result; +} +module.exports = { + flatten, + flattenObj, +}; diff --git a/modules/entry/back/methods/entry/getBuysCsv.js b/modules/entry/back/methods/entry/getBuysCsv.js index 541179a1d..647f41d22 100644 --- a/modules/entry/back/methods/entry/getBuysCsv.js +++ b/modules/entry/back/methods/entry/getBuysCsv.js @@ -1,5 +1,5 @@ -const UserError = require('vn-loopback/util/user-error'); const {toCSV} = require('vn-loopback/util/csv'); +const {flatten} = require('vn-loopback/util/flatten'); module.exports = Self => { Self.remoteMethodCtx('getBuysCsv', { @@ -35,41 +35,32 @@ module.exports = Self => { }); Self.getBuysCsv = async(ctx, id, options) => { - const userId = ctx.req.accessToken.userId; - const models = Self.app.models; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - const client = await models.Client.findById(userId, myOptions); - const supplier = await models.Supplier.findOne({where: {nif: client.fi}}, myOptions); - if (supplier) { - const isEntryOwner = (await Self.findById(id)).supplierFk === supplier.id; - if (!isEntryOwner) throw new UserError('Access Denied'); - } - - const data = await Self.rawSql(` - SELECT b.id, - b.itemFk, - i.name, - b.stickers, - b.packing, - b.grouping, - b.packing, - b.groupingMode, - b.quantity, - b.packagingFk, - b.weight, - b.buyingValue, - b.price2, - b.price3, - b.printedStickers - FROM buy b - JOIN item i ON i.id = b.itemFk - WHERE b.entryFk = ? - `, [id]); - - return [toCSV(data), 'text/csv', `inline; filename="buys-${id}.csv"`]; + const data = await Self.getBuys(ctx, id, null, options); + const dataFlatted = flatten(data); + return [toCSV(dataFlatted), 'text/csv', `inline; filename="buys-${id}.csv"`]; }; }; +// function flattenJSON(dataArray) { +// return dataArray.map(item => flatten(item.__data)); +// } +// function flatten(data, prefix = '') { +// let result = {}; +// try { +// for (let key in data) { +// if (!data[key]) continue; + +// const newKey = prefix ? `${prefix}_${key}` : key; +// const value = data[key]; + +// if (typeof value === 'object' && value !== null && !Array.isArray(value)) +// Object.assign(result, flatten(value.__data, newKey)); +// else +// result[newKey] = value; +// } +// } catch (error) { +// console.error(error); +// } + +// return result; +// } + From 51535bfa6b6ee895145ca16f1b4916e18875db70 Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 30 Aug 2024 13:50:51 +0200 Subject: [PATCH 05/15] feat: refs #7277 refundInvoices --- db/dump/.dump/data.sql | 2 +- .../11207-turquoiseMastic/00-firstScript.sql | 2 + .../methods/client/specs/consumption.spec.js | 2 +- .../back/methods/invoiceOut/invoiceClient.js | 14 +- .../methods/invoiceOut/refundAndInvoice.js | 89 +++++++++++ .../invoiceOut/specs/makePdfAndNotify.spec.js | 105 +++++++++++++ .../invoiceOut/specs/refundAndInvoice.spec.js | 46 ++++++ .../methods/invoiceOut/specs/transfer.spec.js | 146 ++++++++++++++++++ .../invoiceOut/specs/transferinvoice.spec.js | 116 -------------- .../back/methods/invoiceOut/transfer.js | 114 ++++++++++++++ .../methods/invoiceOut/transferInvoice.js | 131 ---------------- modules/invoiceOut/back/models/invoice-out.js | 3 +- .../invoiceOut/front/descriptor-menu/index.js | 3 +- .../ticket/back/methods/ticket/cloneAll.js | 5 +- 14 files changed, 520 insertions(+), 258 deletions(-) create mode 100644 db/versions/11207-turquoiseMastic/00-firstScript.sql create mode 100644 modules/invoiceOut/back/methods/invoiceOut/refundAndInvoice.js create mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js create mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js create mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js delete mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js create mode 100644 modules/invoiceOut/back/methods/invoiceOut/transfer.js delete mode 100644 modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js diff --git a/db/dump/.dump/data.sql b/db/dump/.dump/data.sql index 37e7835fc..db8a25b76 100644 --- a/db/dump/.dump/data.sql +++ b/db/dump/.dump/data.sql @@ -1922,7 +1922,7 @@ INSERT INTO `ACL` VALUES (746,'Claim','getSummary','READ','ALLOW','ROLE','claimV INSERT INTO `ACL` VALUES (747,'CplusRectificationType','*','READ','ALLOW','ROLE','administrative',NULL); INSERT INTO `ACL` VALUES (748,'SiiTypeInvoiceOut','*','READ','ALLOW','ROLE','salesPerson',NULL); INSERT INTO `ACL` VALUES (749,'InvoiceCorrectionType','*','READ','ALLOW','ROLE','salesPerson',NULL); -INSERT INTO `ACL` VALUES (750,'InvoiceOut','transferInvoice','WRITE','ALLOW','ROLE','administrative',NULL); +INSERT INTO `ACL` VALUES (750,'InvoiceOut','transfer','WRITE','ALLOW','ROLE','administrative',NULL); INSERT INTO `ACL` VALUES (751,'Application','executeProc','*','ALLOW','ROLE','employee',NULL); INSERT INTO `ACL` VALUES (752,'Application','executeFunc','*','ALLOW','ROLE','employee',NULL); INSERT INTO `ACL` VALUES (753,'NotificationSubscription','getList','READ','ALLOW','ROLE','employee',NULL); diff --git a/db/versions/11207-turquoiseMastic/00-firstScript.sql b/db/versions/11207-turquoiseMastic/00-firstScript.sql new file mode 100644 index 000000000..d1fd184aa --- /dev/null +++ b/db/versions/11207-turquoiseMastic/00-firstScript.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES ('InvoiceOut','refundAndInvoice','WRITE','ALLOW','ROLE','administrative'); diff --git a/modules/client/back/methods/client/specs/consumption.spec.js b/modules/client/back/methods/client/specs/consumption.spec.js index 85dbb7422..dd2037541 100644 --- a/modules/client/back/methods/client/specs/consumption.spec.js +++ b/modules/client/back/methods/client/specs/consumption.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -describe('client consumption() filter', () => { +fdescribe('client consumption() filter', () => { it('should return a list of buyed items by ticket', async() => { const tx = await models.Client.beginTransaction({}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index 2c44cef34..bf7e7d3cb 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -47,12 +47,16 @@ module.exports = Self => { Self.invoiceClient = async(ctx, options) => { const args = ctx.args; const models = Self.app.models; - options = typeof options === 'object' ? {...options} : {}; - options.userId = ctx.req.accessToken.userId; - let tx; - if (!options.transaction) - tx = options.transaction = await Self.beginTransaction({}); + const myOptions = {userId: ctx.req.accessToken.userId}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } const minShipped = Date.vnNew(); minShipped.setFullYear(args.maxShipped.getFullYear() - 1); diff --git a/modules/invoiceOut/back/methods/invoiceOut/refundAndInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/refundAndInvoice.js new file mode 100644 index 000000000..7c7788459 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/refundAndInvoice.js @@ -0,0 +1,89 @@ +module.exports = Self => { + Self.remoteMethodCtx('refundAndInvoice', { + description: 'Refund an invoice and create a new one', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'Issued invoice id' + }, + { + arg: 'cplusRectificationTypeFk', + type: 'number', + required: true + }, + { + arg: 'siiTypeInvoiceOutFk', + type: 'number', + required: true + }, + { + arg: 'invoiceCorrectionTypeFk', + type: 'number', + required: true + }, + ], + returns: { + type: 'object', + root: true + }, + http: { + path: '/refundAndInvoice', + verb: 'post' + } + }); + + Self.refundAndInvoice = async( + ctx, + id, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + options + ) => { + const models = Self.app.models; + const myOptions = {userId: ctx.req.accessToken.userId}; + let refundId; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let tx; + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const originalInvoice = await models.InvoiceOut.findById(id, myOptions); + + const refundedTickets = await Self.refund(ctx, originalInvoice.ref, false, myOptions); + + const invoiceCorrection = { + correctedFk: id, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk + }; + const ticketIds = refundedTickets.map(ticket => ticket.id); + refundId = await models.Ticket.invoiceTickets(ctx, ticketIds, invoiceCorrection, myOptions); + + tx && await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + + if (tx) { + try { + await models.InvoiceOut.makePdfList(ctx, refundId); + } catch (e) { + throw new UserError('The invoices have been created but the PDFs could not be generated'); + } + } + + return {refundId}; + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js new file mode 100644 index 000000000..0d73eba88 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js @@ -0,0 +1,105 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); +const UserError = require('vn-loopback/util/user-error'); + +fdescribe('InvoiceOut makePdfAndNotify()', () => { + const userId = 5; + const ctx = { + req: { + accessToken: {userId}, + __: (key, obj) => `Translated: ${key}, ${JSON.stringify(obj)}`, + getLocale: () => 'es' + }, + args: {} + }; + const activeCtx = {accessToken: {userId}}; + const id = 4; + const printerFk = 1; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx}); + }); + + it('should generate PDF and send email when client is to be mailed', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + await models.InvoiceOut.makePdfAndNotify(ctx, id, printerFk, options); + + const invoice = await models.InvoiceOut.findById(id, { + fields: ['ref', 'clientFk'], + include: { + relation: 'client', + scope: { + fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk'] + } + } + }, options); + + expect(invoice).toBeDefined(); + expect(invoice.client().isToBeMailed).toBe(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should generate PDF and print when client is not to be mailed', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + await models.InvoiceOut.makePdfAndNotify(ctx, id, null, options); + + const invoice = await models.InvoiceOut.findById(id, { + fields: ['ref', 'clientFk'], + include: { + relation: 'client', + scope: { + fields: ['id', 'email', 'isToBeMailed', 'salesPersonFk'] + } + } + }, options); + + expect(invoice).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw UserError when PDF generation fails', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + await models.InvoiceOut.makePdfAndNotify(ctx, null, null, options); + await tx.rollback(); + } catch (e) { + expect(e instanceof UserError).toBe(true); + expect(e.message).toContain('Error while generating PDF'); + await tx.rollback(); + } + }); + + it('should send message to salesperson when email fails', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + spyOn(models.InvoiceOut, 'invoiceEmail').and.rejectWith(new Error('Test Error')); + await models.InvoiceOut.makePdfAndNotify(ctx, id, null, options); + await tx.rollback(); + } catch (e) { + expect(e instanceof UserError).toBe(true); + expect(e.message).toContain('Error when sending mail to client'); + + await tx.rollback(); + } + }); +}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js new file mode 100644 index 000000000..46cc4458b --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js @@ -0,0 +1,46 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('InvoiceOut refundAndInvoice()', () => { + const userId = 5; + const ctx = {req: {accessToken: {userId}}}; + const activeCtx = {accessToken: {userId}}; + const id = 4; + const cplusRectificationTypeFk = 1; + const siiTypeInvoiceOutFk = 1; + const invoiceCorrectionTypeFk = 1; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx}); + }); + + fit('should refund an invoice and create a new invoice', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + const result = await models.InvoiceOut.refundAndInvoice( + ctx, + id, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + options + ); + + expect(result).toBeDefined(); + expect(result.refundId).toBeDefined(); + + const invoicesAfter = await models.InvoiceOut.find({where: {id: result.refundId}}, options); + const ticketsAfter = await models.Ticket.find({where: {refFk: 'R10100001'}}, options); + + expect(invoicesAfter.length).toBeGreaterThan(0); + expect(ticketsAfter.length).toBeGreaterThan(0); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js new file mode 100644 index 000000000..2c9d46930 --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -0,0 +1,146 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); +const UserError = require('vn-loopback/util/user-error'); + +fdescribe('InvoiceOut transfer()', () => { + const userId = 5; + const ctx = { + req: { + accessToken: {userId}, + __: (key, obj) => `Translated: ${key}, ${JSON.stringify(obj)}`, + getLocale: () => 'es' + }, + args: {} + }; + const activeCtx = {accessToken: {userId}}; + const id = 4; + const newClientFk = 1101; + const cplusRectificationTypeFk = 1; + const siiTypeInvoiceOutFk = 1; + const invoiceCorrectionTypeFk = 1; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx}); + }); + + it('should transfer an invoice to a new client and return the new invoice ID', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + const makeInvoice = true; + + try { + const result = await models.InvoiceOut.transfer( + ctx, + id, + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + makeInvoice + ); + + const newInvoice = await models.InvoiceOut.findById(result, options); + + expect(newInvoice.clientFk).toBe(newClientFk); + + const transferredTickets = await models.Ticket.find({ + where: { + invoiceOutFk: result, + clientFk: newClientFk + } + }, options); + + expect(transferredTickets.length).toBeGreaterThan(0); + + if (tx) await tx.rollback(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }); + + it('should throw an error if original invoice is not found', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + const makeInvoice = true; + try { + await models.InvoiceOut.transfer( + ctx, + 'idNotExists', + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + makeInvoice, + options + ); + fail('Expected an error to be thrown'); + } catch (e) { + expect(e).toBeInstanceOf(UserError); + expect(e.message).toBe('Original invoice not found'); + await tx.rollback(); + } + }); + + it('should throw an error if the new client is the same as the original client', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + const makeInvoice = true; + const originalInvoice = await models.InvoiceOut.findById(id); + + try { + await models.InvoiceOut.transfer( + ctx, + id, + originalInvoice.clientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + makeInvoice, + options + ); + fail('Expected an error to be thrown'); + } catch (e) { + expect(e).toBeInstanceOf(UserError); + expect(e.message).toBe('Select a different client'); + await tx.rollback(); + } + }); + + fit('should not create a new invoice if makeInvoice is false', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + + try { + const originalTickets = await models.Ticket.find({ + where: {clientFk: newClientFk, invoiceOutFk: null}, + options + }); + + const result = await models.InvoiceOut.transfer( + ctx, + id, + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + false, + options + ); + + expect(result).toBeUndefined(); + + const transferredTickets = await models.Ticket.find({ + where: {clientFk: newClientFk, invoiceOutFk: null}, + options + }); + + expect(transferredTickets.length).toBeGreaterThan(originalTickets.length); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js deleted file mode 100644 index 22787e730..000000000 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js +++ /dev/null @@ -1,116 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); - -describe('InvoiceOut transferInvoice()', () => { - const activeCtx = { - accessToken: {userId: 5}, - http: { - req: { - headers: {origin: 'http://localhost'} - } - } - }; - const ctx = {req: activeCtx}; - - beforeEach(() => { - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - }); - - it('should return the id of the created issued invoice', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; - const id = 4; - const newClient = 1; - spyOn(models.InvoiceOut, 'makePdfList'); - - try { - const {clientFk: oldClient} = await models.InvoiceOut.findById(id, {fields: ['clientFk']}); - const invoicesBefore = await models.InvoiceOut.find({}, options); - const result = await models.InvoiceOut.transferInvoice( - ctx, - id, - 'T4444444', - newClient, - 1, - 1, - 1, - true, - options); - const invoicesAfter = await models.InvoiceOut.find({}, options); - const rectificativeInvoice = invoicesAfter[invoicesAfter.length - 2]; - const newInvoice = invoicesAfter[invoicesAfter.length - 1]; - - expect(result).toBeDefined(); - expect(invoicesAfter.length - invoicesBefore.length).toEqual(2); - expect(rectificativeInvoice.clientFk).toEqual(oldClient); - expect(newInvoice.clientFk).toEqual(newClient); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should throw an error when it is the same client', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; - spyOn(models.InvoiceOut, 'makePdfList'); - - try { - await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1101, 1, 1, 1, true, options); - await tx.rollback(); - } catch (e) { - expect(e.message).toBe(`Select a different client`); - await tx.rollback(); - } - }); - - it('should throw an error when it is refund', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; - spyOn(models.InvoiceOut, 'makePdfList'); - try { - await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, true, options); - await tx.rollback(); - } catch (e) { - expect(e.message).toContain(`This ticket is already a refund`); - await tx.rollback(); - } - }); - - it('should throw an error when pdf failed', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; - spyOn(models.InvoiceOut, 'makePdfList').and.returnValue(() => { - throw new Error('test'); - }); - - try { - await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, true, options); - await tx.rollback(); - } catch (e) { - expect(e.message).toContain(`It has been invoiced but the PDF could not be generated`); - await tx.rollback(); - } - }); - - it('should not generate an invoice', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; - spyOn(models.InvoiceOut, 'makePdfList'); - - let response; - try { - response = await models.InvoiceOut.transferInvoice(ctx, '1', 'T1111111', 1102, 1, 1, 1, false, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - - expect(response).not.toBeDefined(); - }); -}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/transfer.js b/modules/invoiceOut/back/methods/invoiceOut/transfer.js new file mode 100644 index 000000000..8f6e9dfee --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/transfer.js @@ -0,0 +1,114 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('transfer', { + description: 'Transfer an issued invoice to another client', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'Issued invoice id' + }, + { + arg: 'newClientFk', + type: 'number', + required: true + }, + { + arg: 'cplusRectificationTypeFk', + type: 'number', + required: true + }, + { + arg: 'siiTypeInvoiceOutFk', + type: 'number', + required: true + }, + { + arg: 'invoiceCorrectionTypeFk', + type: 'number', + required: true + }, + { + arg: 'makeInvoice', + type: 'boolean', + required: true + }, + ], + returns: {type: 'object', root: true}, + http: {path: '/transfer', verb: 'post'} + }); + + Self.transfer = async( + ctx, + id, + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + makeInvoice, + options + ) => { + const models = Self.app.models; + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + const originalInvoice = await models.InvoiceOut.findById(id); + if (!originalInvoice) + throw new UserError('Original invoice not found'); + + if (originalInvoice.clientFk === newClientFk) + throw new UserError('Select a different client'); + + let transferredInvoiceId; + try { + await Self.refundAndInvoice( + ctx, + id, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + myOptions + ); + + const tickets = await models.Ticket.find({where: {refFk: originalInvoice.ref}}, myOptions); + const ticketIds = tickets.map(ticket => ticket.id); + const transferredTickets = await models.Ticket.cloneAll(ctx, ticketIds, false, false, myOptions); + + const transferredTicketIds = transferredTickets.map(ticket => ticket.id); + await models.Ticket.updateAll( + {id: {inq: transferredTicketIds}}, + {clientFk: newClientFk}, + myOptions + ); + + if (makeInvoice) + transferredInvoiceId = await models.Ticket.invoiceTickets(ctx, transferredTicketIds, null, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + + if (transferredInvoiceId) { + try { + await models.InvoiceOut.makePdfList(ctx, transferredInvoiceId); + } catch (e) { + throw new UserError('The transferred invoice has been created but the PDF could not be generated'); + } + } + + return transferredInvoiceId; + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js deleted file mode 100644 index c31f381d9..000000000 --- a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js +++ /dev/null @@ -1,131 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethodCtx('transferInvoice', { - description: 'Transfer an issued invoice to another client', - accessType: 'WRITE', - accepts: [ - { - arg: 'id', - type: 'number', - required: true, - description: 'Issued invoice id' - }, - { - arg: 'refFk', - type: 'string', - required: true - }, - { - arg: 'newClientFk', - type: 'number', - required: true - }, - { - arg: 'cplusRectificationTypeFk', - type: 'number', - required: true - }, - { - arg: 'siiTypeInvoiceOutFk', - type: 'number', - required: true - }, - { - arg: 'invoiceCorrectionTypeFk', - type: 'number', - required: true - }, - { - arg: 'makeInvoice', - type: 'boolean', - required: true - }, - ], - returns: { - type: 'object', - root: true - }, - http: { - path: '/transferInvoice', - verb: 'post' - } - }); - - Self.transferInvoice = async( - ctx, - id, - refFk, - newClientFk, - cplusRectificationTypeFk, - siiTypeInvoiceOutFk, - invoiceCorrectionTypeFk, - makeInvoice, - options - ) => { - const models = Self.app.models; - const myOptions = {userId: ctx.req.accessToken.userId}; - let invoiceId; - let refundId; - - let tx; - if (typeof options == 'object') - Object.assign(myOptions, options); - - const {clientFk} = await models.InvoiceOut.findById(id); - - if (clientFk == newClientFk) - throw new UserError(`Select a different client`); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } - try { - const tickets = await models.Ticket.find({where: {refFk}}, myOptions); - const ticketsIds = tickets.map(ticket => ticket.id); - const refundTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, true, myOptions); - - const clonedTickets = await models.Ticket.cloneAll(ctx, ticketsIds, false, false, myOptions); - - const clonedTicketIds = []; - - for (const clonedTicket of clonedTickets) { - await clonedTicket.updateAttribute('clientFk', newClientFk, myOptions); - clonedTicketIds.push(clonedTicket.id); - } - - const invoiceCorrection = { - correctedFk: id, - cplusRectificationTypeFk, - siiTypeInvoiceOutFk, - invoiceCorrectionTypeFk - }; - const refundTicketIds = refundTickets.map(ticket => ticket.id); - - refundId = await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions); - - if (makeInvoice) - invoiceId = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, null, myOptions); - - tx && await tx.commit(); - } catch (e) { - if (tx) await tx.rollback(); - throw e; - } - - if (tx && makeInvoice) { - try { - await models.InvoiceOut.makePdfList(ctx, invoiceId); - } catch (e) { - throw new UserError('It has been invoiced but the PDF could not be generated'); - } - try { - await models.InvoiceOut.makePdfList(ctx, refundId); - } catch (e) { - throw new UserError('It has been invoiced but the PDF of refund not be generated'); - } - } - return invoiceId; - }; -}; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index b0e05b626..47dbcbea4 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -26,7 +26,8 @@ module.exports = Self => { require('../methods/invoiceOut/getInvoiceDate')(Self); require('../methods/invoiceOut/negativeBases')(Self); require('../methods/invoiceOut/negativeBasesCsv')(Self); - require('../methods/invoiceOut/transferInvoice')(Self); + require('../methods/invoiceOut/transfer')(Self); + require('../methods/invoiceOut/refundAndInvoice')(Self); Self.filePath = async function(id, options) { const fields = ['ref', 'issued']; diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index 07a0f1768..136b12517 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -137,7 +137,6 @@ class Controller extends Section { transferInvoice() { const params = { - id: this.invoiceOut.id, refFk: this.invoiceOut.ref, newClientFk: this.clientId, cplusRectificationTypeFk: this.cplusRectificationType, @@ -155,7 +154,7 @@ class Controller extends Section { return; } - this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => { + this.$http.post(`InvoiceOuts/transfer`, params).then(res => { const invoiceId = res.data; this.vnApp.showSuccess(this.$t('Transferred invoice')); this.$state.go('invoiceOut.card.summary', {id: invoiceId}); diff --git a/modules/ticket/back/methods/ticket/cloneAll.js b/modules/ticket/back/methods/ticket/cloneAll.js index cf99a7edc..29d45615f 100644 --- a/modules/ticket/back/methods/ticket/cloneAll.js +++ b/modules/ticket/back/methods/ticket/cloneAll.js @@ -35,8 +35,11 @@ module.exports = Self => { Self.cloneAll = async(ctx, ticketsIds, withWarehouse, negative, options) => { const models = Self.app.models; - const myOptions = typeof options == 'object' ? {...options} : {}; let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); if (!myOptions.transaction) { tx = await Self.beginTransaction({}); From c45a861ef4d2553eae7a6241ced1e23d53d9b3bb Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 30 Aug 2024 13:51:36 +0200 Subject: [PATCH 06/15] feat: refs #7277 fdescribe --- .../invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js index 2c9d46930..6ef6783cc 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); const UserError = require('vn-loopback/util/user-error'); -fdescribe('InvoiceOut transfer()', () => { +describe('InvoiceOut transfer()', () => { const userId = 5; const ctx = { req: { From ac36762e21576f626b104c213651b1554fe5326d Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 30 Aug 2024 13:56:07 +0200 Subject: [PATCH 07/15] feat: refs #7277 fdescribe --- modules/client/back/methods/client/specs/consumption.spec.js | 2 +- .../back/methods/invoiceOut/specs/makePdfAndNotify.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/client/back/methods/client/specs/consumption.spec.js b/modules/client/back/methods/client/specs/consumption.spec.js index dd2037541..85dbb7422 100644 --- a/modules/client/back/methods/client/specs/consumption.spec.js +++ b/modules/client/back/methods/client/specs/consumption.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -fdescribe('client consumption() filter', () => { +describe('client consumption() filter', () => { it('should return a list of buyed items by ticket', async() => { const tx = await models.Client.beginTransaction({}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js index 0d73eba88..002face07 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/makePdfAndNotify.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); const UserError = require('vn-loopback/util/user-error'); -fdescribe('InvoiceOut makePdfAndNotify()', () => { +describe('InvoiceOut makePdfAndNotify()', () => { const userId = 5; const ctx = { req: { From 2e3fb7646f2ba69402c3535fc14781919e28a46e Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 30 Aug 2024 14:03:02 +0200 Subject: [PATCH 08/15] feat: refs #7277 fit --- .../invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js index 6ef6783cc..5aeb92ec3 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -107,7 +107,7 @@ describe('InvoiceOut transfer()', () => { } }); - fit('should not create a new invoice if makeInvoice is false', async() => { + it('should not create a new invoice if makeInvoice is false', async() => { const tx = await models.InvoiceOut.beginTransaction({}); const options = {transaction: tx}; From e2f1fea6bec70b80ff49fe537d1279f3632d6ced Mon Sep 17 00:00:00 2001 From: jgallego Date: Fri, 30 Aug 2024 17:04:45 +0200 Subject: [PATCH 09/15] fix: refs #7277 error test --- .../collection/spec/assignCollection.spec.js | 6 +- .../invoiceOut/specs/refundAndInvoice.spec.js | 2 +- .../methods/invoiceOut/specs/transfer.spec.js | 93 +++++++++---------- 3 files changed, 48 insertions(+), 53 deletions(-) diff --git a/back/methods/collection/spec/assignCollection.spec.js b/back/methods/collection/spec/assignCollection.spec.js index e8f3882a3..7cdcd6cb6 100644 --- a/back/methods/collection/spec/assignCollection.spec.js +++ b/back/methods/collection/spec/assignCollection.spec.js @@ -15,9 +15,7 @@ describe('ticket assignCollection()', () => { args: {} }; - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: ctx.req - }); + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req}); options = {transaction: tx}; tx = await models.Sale.beginTransaction({}); @@ -25,7 +23,7 @@ describe('ticket assignCollection()', () => { }); afterEach(async() => { - await tx.rollback(); + if (tx) await tx.rollback(); }); it('should throw an error when there is not picking tickets', async() => { diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js index 46cc4458b..c54ae5f6c 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/refundAndInvoice.spec.js @@ -14,7 +14,7 @@ describe('InvoiceOut refundAndInvoice()', () => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx}); }); - fit('should refund an invoice and create a new invoice', async() => { + it('should refund an invoice and create a new invoice', async() => { const tx = await models.InvoiceOut.beginTransaction({}); const options = {transaction: tx}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js index 5aeb92ec3..f8a43dc2f 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -2,16 +2,11 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); const UserError = require('vn-loopback/util/user-error'); -describe('InvoiceOut transfer()', () => { +fdescribe('InvoiceOut transfer()', () => { const userId = 5; - const ctx = { - req: { - accessToken: {userId}, - __: (key, obj) => `Translated: ${key}, ${JSON.stringify(obj)}`, - getLocale: () => 'es' - }, - args: {} - }; + let options; + let tx; + let ctx; const activeCtx = {accessToken: {userId}}; const id = 4; const newClientFk = 1101; @@ -19,13 +14,28 @@ describe('InvoiceOut transfer()', () => { const siiTypeInvoiceOutFk = 1; const invoiceCorrectionTypeFk = 1; - beforeEach(() => { + beforeEach(async() => { + ctx = { + req: { + accessToken: {userId: 1106}, + headers: {origin: 'http://localhost'}, + __: value => value, + getLocale: () => 'es' + }, + args: {} + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: activeCtx}); + options = {transaction: tx}; + tx = await models.Sale.beginTransaction({}); + options.transaction = tx; + }); + + afterEach(async() => { + await tx.rollback(); }); it('should transfer an invoice to a new client and return the new invoice ID', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; const makeInvoice = true; try { @@ -45,7 +55,7 @@ describe('InvoiceOut transfer()', () => { const transferredTickets = await models.Ticket.find({ where: { - invoiceOutFk: result, + refFk: result, clientFk: newClientFk } }, options); @@ -60,8 +70,6 @@ describe('InvoiceOut transfer()', () => { }); it('should throw an error if original invoice is not found', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; const makeInvoice = true; try { await models.InvoiceOut.transfer( @@ -83,8 +91,6 @@ describe('InvoiceOut transfer()', () => { }); it('should throw an error if the new client is the same as the original client', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; const makeInvoice = true; const originalInvoice = await models.InvoiceOut.findById(id); @@ -107,40 +113,31 @@ describe('InvoiceOut transfer()', () => { } }); - it('should not create a new invoice if makeInvoice is false', async() => { - const tx = await models.InvoiceOut.beginTransaction({}); - const options = {transaction: tx}; + fit('should not create a new invoice if makeInvoice is false', async() => { + const originalTickets = await models.Ticket.find({ + where: {clientFk: newClientFk, refFk: null}, + options + }); - try { - const originalTickets = await models.Ticket.find({ - where: {clientFk: newClientFk, invoiceOutFk: null}, - options - }); + const result = await models.InvoiceOut.transfer( + ctx, + id, + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + false, + options + ); - const result = await models.InvoiceOut.transfer( - ctx, - id, - newClientFk, - cplusRectificationTypeFk, - siiTypeInvoiceOutFk, - invoiceCorrectionTypeFk, - false, - options - ); + expect(result).toBeUndefined(); - expect(result).toBeUndefined(); + // await tx.commit(); + const transferredTickets = await models.Ticket.find({ + where: {clientFk: newClientFk, refFk: null}, + options + }); - const transferredTickets = await models.Ticket.find({ - where: {clientFk: newClientFk, invoiceOutFk: null}, - options - }); - - expect(transferredTickets.length).toBeGreaterThan(originalTickets.length); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } + expect(transferredTickets.length).toBeGreaterThan(originalTickets.length); }); }); From 355714fec8d290ec284d1c0d20a45e7de7c98370 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Mon, 2 Sep 2024 11:43:35 +0200 Subject: [PATCH 10/15] test: refs #7277 fix test proposal --- .../back/methods/invoiceOut/specs/transfer.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js index f8a43dc2f..c3b5bebcd 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); const UserError = require('vn-loopback/util/user-error'); -fdescribe('InvoiceOut transfer()', () => { +describe('InvoiceOut transfer()', () => { const userId = 5; let options; let tx; @@ -134,9 +134,9 @@ fdescribe('InvoiceOut transfer()', () => { // await tx.commit(); const transferredTickets = await models.Ticket.find({ - where: {clientFk: newClientFk, refFk: null}, - options - }); + where: {clientFk: newClientFk, refFk: null} + }, + options); expect(transferredTickets.length).toBeGreaterThan(originalTickets.length); }); From 652395bee135481a829f7e519ad21150f5bd4188 Mon Sep 17 00:00:00 2001 From: jgallego Date: Tue, 3 Sep 2024 15:14:33 +0200 Subject: [PATCH 11/15] feat: refs #7277 transfer addressFk --- .../methods/invoiceOut/specs/transfer.spec.js | 61 ++++++++----------- .../back/methods/invoiceOut/transfer.js | 10 ++- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js index f8a43dc2f..dd1932d55 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transfer.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); const UserError = require('vn-loopback/util/user-error'); -fdescribe('InvoiceOut transfer()', () => { +describe('InvoiceOut transfer()', () => { const userId = 5; let options; let tx; @@ -37,36 +37,32 @@ fdescribe('InvoiceOut transfer()', () => { it('should transfer an invoice to a new client and return the new invoice ID', async() => { const makeInvoice = true; + const makePdfListMock = spyOn(models.InvoiceOut, 'makePdfList').and.returnValue(); - try { - const result = await models.InvoiceOut.transfer( - ctx, - id, - newClientFk, - cplusRectificationTypeFk, - siiTypeInvoiceOutFk, - invoiceCorrectionTypeFk, - makeInvoice - ); + const [result] = await models.InvoiceOut.transfer( + ctx, + id, + newClientFk, + cplusRectificationTypeFk, + siiTypeInvoiceOutFk, + invoiceCorrectionTypeFk, + makeInvoice, + options + ); - const newInvoice = await models.InvoiceOut.findById(result, options); + const newInvoice = await models.InvoiceOut.findById(result, null, options); - expect(newInvoice.clientFk).toBe(newClientFk); + expect(newInvoice.clientFk).toBe(newClientFk); - const transferredTickets = await models.Ticket.find({ - where: { - refFk: result, - clientFk: newClientFk - } - }, options); + const transferredTickets = await models.Ticket.find({ + where: { + refFk: newInvoice.ref, + clientFk: newClientFk + } + }, options); - expect(transferredTickets.length).toBeGreaterThan(0); - - if (tx) await tx.rollback(); - } catch (e) { - if (tx) await tx.rollback(); - throw e; - } + expect(transferredTickets.length).toBeGreaterThan(0); + expect(makePdfListMock).toHaveBeenCalledWith(ctx, [result]); }); it('should throw an error if original invoice is not found', async() => { @@ -86,13 +82,12 @@ fdescribe('InvoiceOut transfer()', () => { } catch (e) { expect(e).toBeInstanceOf(UserError); expect(e.message).toBe('Original invoice not found'); - await tx.rollback(); } }); it('should throw an error if the new client is the same as the original client', async() => { const makeInvoice = true; - const originalInvoice = await models.InvoiceOut.findById(id); + const originalInvoice = await models.InvoiceOut.findById(id, options); try { await models.InvoiceOut.transfer( @@ -109,11 +104,10 @@ fdescribe('InvoiceOut transfer()', () => { } catch (e) { expect(e).toBeInstanceOf(UserError); expect(e.message).toBe('Select a different client'); - await tx.rollback(); } }); - fit('should not create a new invoice if makeInvoice is false', async() => { + it('should not create a new invoice if makeInvoice is false', async() => { const originalTickets = await models.Ticket.find({ where: {clientFk: newClientFk, refFk: null}, options @@ -132,11 +126,10 @@ fdescribe('InvoiceOut transfer()', () => { expect(result).toBeUndefined(); - // await tx.commit(); const transferredTickets = await models.Ticket.find({ - where: {clientFk: newClientFk, refFk: null}, - options - }); + where: {clientFk: newClientFk, refFk: null} + + }, options); expect(transferredTickets.length).toBeGreaterThan(originalTickets.length); }); diff --git a/modules/invoiceOut/back/methods/invoiceOut/transfer.js b/modules/invoiceOut/back/methods/invoiceOut/transfer.js index 8f6e9dfee..08ed69c8a 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/transfer.js +++ b/modules/invoiceOut/back/methods/invoiceOut/transfer.js @@ -84,11 +84,19 @@ module.exports = Self => { const tickets = await models.Ticket.find({where: {refFk: originalInvoice.ref}}, myOptions); const ticketIds = tickets.map(ticket => ticket.id); const transferredTickets = await models.Ticket.cloneAll(ctx, ticketIds, false, false, myOptions); + const client = await models.Client.findById(newClientFk, + {fields: ['id', 'defaultAddressFk']}, myOptions); + const address = await models.Address.findById(client.defaultAddressFk, + {fields: ['id', 'nickname']}, myOptions); const transferredTicketIds = transferredTickets.map(ticket => ticket.id); await models.Ticket.updateAll( {id: {inq: transferredTicketIds}}, - {clientFk: newClientFk}, + { + clientFk: newClientFk, + addressFk: client.defaultAddressFk, + nickname: address.nickname + }, myOptions ); From 99243fa2487db3e675ff2fd7c7c1d85d8fa9767a Mon Sep 17 00:00:00 2001 From: jgallego Date: Wed, 4 Sep 2024 08:30:01 +0200 Subject: [PATCH 12/15] feat: refs #7277 traducciones --- loopback/locale/en.json | 8 +++++--- loopback/locale/es.json | 7 ++++--- loopback/locale/fr.json | 6 ++++-- loopback/locale/pt.json | 6 ++++-- modules/invoiceOut/back/methods/invoiceOut/transfer.js | 2 +- modules/invoiceOut/front/descriptor-menu/index.js | 2 +- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 06538a524..7505d82f5 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -230,10 +230,12 @@ "This workCenter is already assigned to this agency": "This workCenter is already assigned to this agency", "You can only have one PDA": "You can only have one PDA", "Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member", - "It has been invoiced but the PDF could not be generated": "It has been invoiced but the PDF could not be generated", + "The invoices have been created but the PDFs could not be generated": "The invoices have been created but the PDFs could not be generated", "It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated", "Cannot add holidays on this day": "Cannot add holidays on this day", "Cannot send mail": "Cannot send mail", "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`", - "This postcode already exists": "This postcode already exists" -} \ No newline at end of file + "This postcode already exists": "This postcode already exists", + "Original invoice not found": "Original invoice not found" + +} diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 377691ae6..a3ed32994 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -363,12 +363,13 @@ "You can not use the same password": "No puedes usar la misma contraseña", "This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario", "You can only have one PDA": "Solo puedes tener un PDA", - "It has been invoiced but the PDF could not be generated": "Se ha facturado pero no se ha podido generar el PDF", + "The invoices have been created but the PDFs could not be generated": "Se ha facturado pero no se ha podido generar el PDF", "It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono", "Payment method is required": "El método de pago es obligatorio", "Cannot send mail": "Não é possível enviar o email", "CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos", "The sale not exists in the item shelving": "La venta no existe en la estantería del artículo", "The entry not have stickers": "La entrada no tiene etiquetas", - "Too many records": "Demasiados registros" -} \ No newline at end of file + "Too many records": "Demasiados registros", + "Original invoice not found": "Factura original no encontrada" +} diff --git a/loopback/locale/fr.json b/loopback/locale/fr.json index 49584ef0e..76897737d 100644 --- a/loopback/locale/fr.json +++ b/loopback/locale/fr.json @@ -358,7 +358,9 @@ "This workCenter is already assigned to this agency": "Ce centre de travail est déjà assigné à cette agence", "Select ticket or client": "Choisissez un ticket ou un client", "It was not able to create the invoice": "Il n'a pas été possible de créer la facture", - "It has been invoiced but the PDF could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré", + "The invoices have been created but the PDFs could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré", "It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré", - "Cannot send mail": "Impossible d'envoyer le mail" + "Cannot send mail": "Impossible d'envoyer le mail", + "Original invoice not found": "Facture originale introuvable" + } diff --git a/loopback/locale/pt.json b/loopback/locale/pt.json index 95c1fff0a..6425db9ed 100644 --- a/loopback/locale/pt.json +++ b/loopback/locale/pt.json @@ -358,6 +358,8 @@ "This workCenter is already assigned to this agency": "Este centro de trabalho já está atribuído a esta agência", "Select ticket or client": "Selecione um ticket ou cliente", "It was not able to create the invoice": "Não foi possível criar a fatura", - "It has been invoiced but the PDF could not be generated": "Foi faturado, mas o PDF não pôde ser gerado", - "It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso" + "The invoices have been created but the PDFs could not be generated": "Foi faturado, mas o PDF não pôde ser gerado", + "It has been invoiced but the PDF of refund not be generated": "Foi faturado mas não foi gerado o PDF do reembolso", + "Original invoice not found": "Fatura original não encontrada" + } diff --git a/modules/invoiceOut/back/methods/invoiceOut/transfer.js b/modules/invoiceOut/back/methods/invoiceOut/transfer.js index 08ed69c8a..954adf780 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/transfer.js +++ b/modules/invoiceOut/back/methods/invoiceOut/transfer.js @@ -113,7 +113,7 @@ module.exports = Self => { try { await models.InvoiceOut.makePdfList(ctx, transferredInvoiceId); } catch (e) { - throw new UserError('The transferred invoice has been created but the PDF could not be generated'); + throw new UserError('The invoices have been created but the PDFs could not be generatedd'); } } diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index 136b12517..288de879e 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -137,7 +137,7 @@ class Controller extends Section { transferInvoice() { const params = { - refFk: this.invoiceOut.ref, + id: this.invoiceOut.id, newClientFk: this.clientId, cplusRectificationTypeFk: this.cplusRectificationType, siiTypeInvoiceOutFk: this.siiTypeInvoiceOut, From d49707828b22169ed88be14b1e987f08f1d23d99 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Thu, 5 Sep 2024 09:13:33 +0200 Subject: [PATCH 13/15] fix(salix): refs #7905 #7905 use right fn to flatten data --- loopback/util/flatten.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopback/util/flatten.js b/loopback/util/flatten.js index 90e9184b7..d6030f7d0 100644 --- a/loopback/util/flatten.js +++ b/loopback/util/flatten.js @@ -1,6 +1,6 @@ function flatten(dataArray) { - return dataArray.map(item => flatten(item.__data)); + return dataArray.map(item => flattenObj(item.__data)); } function flattenObj(data, prefix = '') { let result = {}; From 2aeff1957811614c5523f8eb67dde70972528d95 Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 5 Sep 2024 09:21:00 +0200 Subject: [PATCH 14/15] fix: refs #7905 added comments to flatten.js --- loopback/util/flatten.js | 15 ++++++++++++ .../entry/back/methods/entry/getBuysCsv.js | 24 ------------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/loopback/util/flatten.js b/loopback/util/flatten.js index d6030f7d0..35c368d3b 100644 --- a/loopback/util/flatten.js +++ b/loopback/util/flatten.js @@ -1,7 +1,22 @@ +/** + * Flattens an array of objects by converting each object into a flat structure. + * + * @param {Array} dataArray Array of objects to be flattened + * @return {Array} Array of flattened objects + */ function flatten(dataArray) { return dataArray.map(item => flattenObj(item.__data)); } + +/** + * Recursively flattens an object, converting nested properties into a single level object + * with keys representing the original nested structure. + * + * @param {Object} data The object to be flattened + * @param {String} [prefix=''] Optional prefix for nested keys + * @return {Object} Flattened object + */ function flattenObj(data, prefix = '') { let result = {}; try { diff --git a/modules/entry/back/methods/entry/getBuysCsv.js b/modules/entry/back/methods/entry/getBuysCsv.js index 647f41d22..a46f09c66 100644 --- a/modules/entry/back/methods/entry/getBuysCsv.js +++ b/modules/entry/back/methods/entry/getBuysCsv.js @@ -40,27 +40,3 @@ module.exports = Self => { return [toCSV(dataFlatted), 'text/csv', `inline; filename="buys-${id}.csv"`]; }; }; -// function flattenJSON(dataArray) { -// return dataArray.map(item => flatten(item.__data)); -// } -// function flatten(data, prefix = '') { -// let result = {}; -// try { -// for (let key in data) { -// if (!data[key]) continue; - -// const newKey = prefix ? `${prefix}_${key}` : key; -// const value = data[key]; - -// if (typeof value === 'object' && value !== null && !Array.isArray(value)) -// Object.assign(result, flatten(value.__data, newKey)); -// else -// result[newKey] = value; -// } -// } catch (error) { -// console.error(error); -// } - -// return result; -// } - From ac01da5eed7296ce7108894c2e0280ffc2222a4c Mon Sep 17 00:00:00 2001 From: guillermo Date: Thu, 5 Sep 2024 11:19:00 +0200 Subject: [PATCH 15/15] feat: refs #7759 Deleted version 11163-maroonEucalyptus --- db/versions/11163-maroonEucalyptus/00-firstScript.sql | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 db/versions/11163-maroonEucalyptus/00-firstScript.sql diff --git a/db/versions/11163-maroonEucalyptus/00-firstScript.sql b/db/versions/11163-maroonEucalyptus/00-firstScript.sql deleted file mode 100644 index 88e5fc022..000000000 --- a/db/versions/11163-maroonEucalyptus/00-firstScript.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE USER 'vn'@'localhost'; -GRANT ALL PRIVILEGES ON *.* TO 'vn'@'localhost' WITH GRANT OPTION;;