From ebb4d36fdaaef90d2b9e74c9624a2315d907ad67 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 7 Jan 2025 15:04:45 +0100 Subject: [PATCH 01/22] fix: refs #8264 parallelism --- src/stores/invoiceOutGlobal.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js index cc8d86ea8..42f0c9db2 100644 --- a/src/stores/invoiceOutGlobal.js +++ b/src/stores/invoiceOutGlobal.js @@ -10,7 +10,6 @@ const { notify } = useNotify(); export const useInvoiceOutGlobalStore = defineStore({ id: 'invoiceOutGlobal', - state: () => ({ initialDataLoading: true, formInitialData: { @@ -33,6 +32,7 @@ export const useInvoiceOutGlobalStore = defineStore({ nRequests: 0, nPdfs: 0, totalPdfs: 0, + formData: null, }), actions: { async init() { @@ -94,7 +94,6 @@ export const useInvoiceOutGlobalStore = defineStore({ async makeInvoice(formData, clientsToInvoice) { this.invoicing = true; - const promises = []; try { this.printer = formData.printer; const params = { @@ -120,10 +119,9 @@ export const useInvoiceOutGlobalStore = defineStore({ throw new Error("There aren't addresses to invoice"); } this.status = 'invoicing'; - for (let index = 0; index < this.parallelism; index++) { - promises.push(this.invoiceClient(formData, index)); - } - await Promise.all(promises); + this.formData = formData; + this.addressIndex = 0; + await this.invoiceClient(this.addressIndex); } catch (err) { this.handleError(err); } @@ -182,8 +180,11 @@ export const useInvoiceOutGlobalStore = defineStore({ } }, - async invoiceClient(formData, index) { + async invoiceClient(index = this.addressIndex++) { + if (this.nRequests >= this.parallelism) return; + const address = this.addresses[index]; + if (!address || !this.status || this.status == 'stopping') { this.status = 'stopping'; this.invoicing = false; @@ -193,17 +194,17 @@ export const useInvoiceOutGlobalStore = defineStore({ const params = { clientId: address.clientId, addressId: address.id, - invoiceDate: new Date(formData.invoiceDate), - maxShipped: new Date(formData.maxShipped), - companyFk: formData.companyFk, - serialType: formData.serialType, + invoiceDate: new Date(this.formData.invoiceDate), + maxShipped: new Date(this.formData.maxShipped), + companyFk: this.formData.companyFk, + serialType: this.formData.serialType, }; this.invoicing = true; const { data } = await axios.post('InvoiceOuts/invoiceClient', params); - if (data) await this.makePdfAndNotify(data, address); + if (data) this.makePdfAndNotify(data, address); this.isInvoicing = false; } catch (err) { if (err?.response?.status >= 400 && err?.response?.status < 500) { @@ -218,9 +219,7 @@ export const useInvoiceOutGlobalStore = defineStore({ throw new Error('Critical invoicing error, process stopped'); } } finally { - this.addressIndex++; - if (this.status != 'stopping') - await this.invoiceClient(formData, this.addressIndex); + await this.invoiceClient(); } }, @@ -231,9 +230,11 @@ export const useInvoiceOutGlobalStore = defineStore({ const params = { printerFk: this.printer }; await axios.post(`InvoiceOuts/${invoiceId}/makePdfAndNotify`, params); this.nPdfs++; - this.nRequests--; } catch (err) { this.invoiceClientError(client, err.response?.data?.error?.message, true); + } finally { + this.nRequests--; + await this.invoiceClient(); // Comprobar que no haya ninguna factura pendiente } }, From 5dc14b8dc1e715f8442fe077342528b3da71cc7b Mon Sep 17 00:00:00 2001 From: provira Date: Wed, 8 Jan 2025 13:02:08 +0100 Subject: [PATCH 02/22] feat: refs #7055 created FilterItemForm test --- .../__tests__/FilterItemForm.spec.js | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/components/__tests__/FilterItemForm.spec.js diff --git a/src/components/__tests__/FilterItemForm.spec.js b/src/components/__tests__/FilterItemForm.spec.js new file mode 100644 index 000000000..f36a479d9 --- /dev/null +++ b/src/components/__tests__/FilterItemForm.spec.js @@ -0,0 +1,71 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import FilterItemForm from 'components/FilterItemForm.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('FilterItemForm', () => { + let vm; + let wrapper; + + beforeAll(() => { + wrapper = createWrapper(FilterItemForm, { + props: { + url: 'Items/withName', + }, + }); + vm = wrapper.vm; + wrapper = wrapper.wrapper; + + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + }); + + it('should set up itemFilter and itemFilterParams correctly', async () => { + wrapper.setProps({ + itemFilter: { + include: [ + { relation: 'producer', scope: { fields: ['name'] } }, + { relation: 'ink', scope: { fields: ['name'] } }, + ], + where: { name: { like: '%bolas de madera%' } }, + }, + itemFilterParams: { name: 'bolas de madera' }, + }); + + await vm.onSubmit(); + + const expectedFilter = { + include: [ + { relation: 'producer', scope: { fields: ['name'] } }, + { relation: 'ink', scope: { fields: ['name'] } }, + ], + where: { + name: { like: '%bolas de madera%' }, + size: 'large', + producerFk: 1, + typeFk: 2, + inkFk: 3, + }, + }; + + expect(axios.get).toHaveBeenCalledWith('Items/withName', { + params: { filter: JSON.stringify(expectedFilter) }, + }); + }); + + it('should handle an empty itemFilterParams correctly', async () => { + vm.itemFilterParams = {}; + + await vm.onSubmit(); + + const expectedFilter = { + include: [ + { relation: 'producer', scope: { fields: ['name'] } }, + { relation: 'ink', scope: { fields: ['name'] } }, + ], + where: {}, + }; + + expect(axios.get).toHaveBeenCalledWith('Items/withName', { + params: { filter: JSON.stringify(expectedFilter) }, + }); + }); +}); From 0c30a8244001d4970a7fb9ee13d10f19e08533e0 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 9 Jan 2025 07:55:57 +0100 Subject: [PATCH 03/22] fix(InvoiceOutGlobal): refs #8264 fix invoicing --- src/stores/invoiceOutGlobal.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js index 42f0c9db2..d8649753f 100644 --- a/src/stores/invoiceOutGlobal.js +++ b/src/stores/invoiceOutGlobal.js @@ -93,7 +93,6 @@ export const useInvoiceOutGlobalStore = defineStore({ }, async makeInvoice(formData, clientsToInvoice) { - this.invoicing = true; try { this.printer = formData.printer; const params = { @@ -118,10 +117,12 @@ export const useInvoiceOutGlobalStore = defineStore({ ); throw new Error("There aren't addresses to invoice"); } + this.invoicing = false; this.status = 'invoicing'; this.formData = formData; this.addressIndex = 0; - await this.invoiceClient(this.addressIndex); + this.errors = []; + await this.invoiceClient(); } catch (err) { this.handleError(err); } @@ -180,10 +181,9 @@ export const useInvoiceOutGlobalStore = defineStore({ } }, - async invoiceClient(index = this.addressIndex++) { - if (this.nRequests >= this.parallelism) return; - - const address = this.addresses[index]; + async invoiceClient() { + if (this.invoicing || this.nRequests >= this.parallelism) return; + const address = this.addresses[this.addressIndex]; if (!address || !this.status || this.status == 'stopping') { this.status = 'stopping'; @@ -191,6 +191,7 @@ export const useInvoiceOutGlobalStore = defineStore({ return; } try { + this.invoicing = true; const params = { clientId: address.clientId, addressId: address.id, @@ -200,26 +201,22 @@ export const useInvoiceOutGlobalStore = defineStore({ serialType: this.formData.serialType, }; - this.invoicing = true; - const { data } = await axios.post('InvoiceOuts/invoiceClient', params); - if (data) this.makePdfAndNotify(data, address); - this.isInvoicing = false; } catch (err) { if (err?.response?.status >= 400 && err?.response?.status < 500) { this.invoiceClientError(address, err.response?.data?.error?.message); return; } else { - this.invoicing = false; notify( 'invoiceOut.globalInvoices.errors.criticalInvoiceError', 'negative' ); - throw new Error('Critical invoicing error, process stopped'); } } finally { - await this.invoiceClient(); + this.invoicing = false; + this.addressIndex++; + this.invoiceClient(); } }, @@ -234,7 +231,7 @@ export const useInvoiceOutGlobalStore = defineStore({ this.invoiceClientError(client, err.response?.data?.error?.message, true); } finally { this.nRequests--; - await this.invoiceClient(); // Comprobar que no haya ninguna factura pendiente + this.invoiceClient(); } }, From b466dfe034e3d13632e65359b43d5b04d491c02f Mon Sep 17 00:00:00 2001 From: provira Date: Thu, 9 Jan 2025 08:25:23 +0100 Subject: [PATCH 04/22] feat: refs #7055 added new test case --- .../__tests__/FilterItemForm.spec.js | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/components/__tests__/FilterItemForm.spec.js b/src/components/__tests__/FilterItemForm.spec.js index f36a479d9..0c88bf421 100644 --- a/src/components/__tests__/FilterItemForm.spec.js +++ b/src/components/__tests__/FilterItemForm.spec.js @@ -1,6 +1,6 @@ import { createWrapper, axios } from 'app/test/vitest/helper'; import FilterItemForm from 'components/FilterItemForm.vue'; -import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { vi, beforeAll, describe, expect, it } from 'vitest'; describe('FilterItemForm', () => { let vm; @@ -15,40 +15,42 @@ describe('FilterItemForm', () => { vm = wrapper.vm; wrapper = wrapper.wrapper; - vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { + id: 999996, + name: 'Bolas de madera', + size: 2, + inkFk: null, + producerFk: null, + }, + ], + }); }); - it('should set up itemFilter and itemFilterParams correctly', async () => { + it('should filter data and populate tableRows for table display', async () => { wrapper.setProps({ itemFilter: { - include: [ - { relation: 'producer', scope: { fields: ['name'] } }, - { relation: 'ink', scope: { fields: ['name'] } }, - ], - where: { name: { like: '%bolas de madera%' } }, + include: [ + { relation: 'producer', scope: { fields: ['name'] } }, + { relation: 'ink', scope: { fields: ['name'] } }, + ], + where: { name: { like: '%bolas de madera%' } }, }, itemFilterParams: { name: 'bolas de madera' }, - }); + }); await vm.onSubmit(); - const expectedFilter = { - include: [ - { relation: 'producer', scope: { fields: ['name'] } }, - { relation: 'ink', scope: { fields: ['name'] } }, - ], - where: { - name: { like: '%bolas de madera%' }, - size: 'large', - producerFk: 1, - typeFk: 2, - inkFk: 3, + expect(vm.tableRows).toEqual([ + { + id: 999996, + name: 'Bolas de madera', + size: 2, + inkFk: null, + producerFk: null, }, - }; - - expect(axios.get).toHaveBeenCalledWith('Items/withName', { - params: { filter: JSON.stringify(expectedFilter) }, - }); + ]); }); it('should handle an empty itemFilterParams correctly', async () => { @@ -68,4 +70,9 @@ describe('FilterItemForm', () => { params: { filter: JSON.stringify(expectedFilter) }, }); }); -}); + + it('should emit "itemSelected" with the correct id and close the form', () => { + vm.selectItem({ id: 12345 }); + expect(wrapper.emitted('itemSelected')[0]).toEqual([12345]); + }); +}); \ No newline at end of file From 5882ee84638e4993a4c49a4fa343ae4d59bd3149 Mon Sep 17 00:00:00 2001 From: carlossa Date: Thu, 9 Jan 2025 09:44:37 +0100 Subject: [PATCH 05/22] fix: fix confirmRequest --- src/pages/Item/ItemRequest.vue | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue index 4447d1bcf..d96fbca2f 100644 --- a/src/pages/Item/ItemRequest.vue +++ b/src/pages/Item/ItemRequest.vue @@ -172,24 +172,22 @@ const changeQuantity = async (request) => { }; await axios.patch(`Sales/${request.saleFk}`, params); - notify(t('globals.dataSaved'), 'positive'); - confirmRequest(request); - } else confirmRequest(request); + } + await confirmRequest(request); + notify(t('globals.dataSaved'), 'positive'); }; const confirmRequest = async (request) => { - if (request.itemFk && request.saleQuantity) { - const params = { - itemFk: request.itemFk, - quantity: request.saleQuantity, - attenderFk: request.attenderFk, - }; + if (!request.itemFk || !request.saleQuantity) return; + const params = { + itemFk: request.itemFk, + quantity: request.saleQuantity, + attenderFk: request.attenderFk, + }; - const { data } = await axios.post(`TicketRequests/${request.id}/confirm`, params); - request.itemDescription = data.concept; - request.isOk = true; - notify(t('globals.dataSaved'), 'positive'); - } + const { data } = await axios.post(`TicketRequests/${request.id}/confirm`, params); + request.itemDescription = data.concept; + request.isOk = true; }; const getState = (isOk) => { From 95712728d6082f9c5180af558f508aa70bfe46d3 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 10 Jan 2025 10:00:42 +0100 Subject: [PATCH 06/22] feat: refs #8225 added moreOptions and use it in customer and ticket summary --- src/components/ui/CardDescriptor.vue | 31 +++++++-------------- src/components/ui/CardSummary.vue | 24 +++++++++------- src/components/ui/VnMoreOptions.vue | 20 +++++++++++++ src/pages/Customer/Card/CustomerSummary.vue | 9 ++++-- src/pages/Ticket/Card/TicketSummary.vue | 18 ++++-------- 5 files changed, 55 insertions(+), 47 deletions(-) create mode 100644 src/components/ui/VnMoreOptions.vue diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f4c0091d2..4a79e562a 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -6,6 +6,7 @@ import { useArrayData } from 'composables/useArrayData'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useState } from 'src/composables/useState'; import { useRoute } from 'vue-router'; +import VnMoreOptions from './VnMoreOptions.vue'; const $props = defineProps({ url: { @@ -159,25 +160,11 @@ const toModule = computed(() => - - - {{ t('components.cardDescriptor.moreOptions') }} - - - - - - - + + +
@@ -222,8 +209,8 @@ const toModule = computed(() => /> - + + +en: + addTurn: Add turn + invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" + +es: + Show Delivery Note...: Ver albarán... + Send Delivery Note...: Enviar albarán... + as PDF: como PDF + as PDF without prices: como PDF sin precios + as CSV: Como CSV + Send PDF: Enviar PDF + Send PDF to tablet: Enviar PDF a tablet + Send CSV: Enviar CSV + Show Proforma: Ver proforma + Delete ticket: Eliminar ticket + Send SMS...: Enviar SMS... + Pending payment: Pago pendiente + Minimum import: Importe mínimo + Notify changes: Notificar cambios + Ticket deleted: Ticket eliminado + You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora + To clone ticket: Clonar ticket + Ticket cloned: Ticked clonado + It was not able to clone the ticket: No se pudo clonar el ticket + Generate PDF invoice: Generar PDF factura + Regenerate PDF invoice: Regenerar PDF factura + The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado + Transfer client: Transferir cliente + Client: Cliente + addTurn: Añadir a turno + What is the day of receipt of the ticket?: ¿Cuál es el día de preparación del pedido? + Current ticket deleted and added to shift: Ticket actual eliminado y añadido al turno + Refund all...: Abonar todo... + with warehouse: con almacén + without warehouse: sin almacén + Make invoice: Crear factura + Change shipped hour: Cambiar hora de envío + Shipped hour: Hora de envío + Recalculate components: Recalcular componentes + Are you sure you want to recalculate components?: ¿Seguro que quieres recalcular los componentes? + Data saved: Datos guardados + Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket? + You are going to invoice this ticket: Vas a facturar este ticket + Ticket invoiced: Ticket facturado + Set weight: Establecer peso + Weight set: Peso establecido + This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar? + invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}" + This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas? + You are going to delete this ticket: Vas a eliminar este ticket + as PDF signed: como PDF firmado + Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán? + diff --git a/src/pages/Item/Card/ItemSummary.vue b/src/pages/Item/Card/ItemSummary.vue index e1b97d7c9..bc828bbf6 100644 --- a/src/pages/Item/Card/ItemSummary.vue +++ b/src/pages/Item/Card/ItemSummary.vue @@ -8,6 +8,7 @@ import VnLv from 'src/components/ui/VnLv.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import ItemDescriptorMenu from './ItemDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -43,10 +44,13 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`; +