From a1db140d67c2ae6e816843ab269a084765643159 Mon Sep 17 00:00:00 2001 From: pablone Date: Tue, 18 Feb 2025 14:30:12 +0100 Subject: [PATCH 01/13] fix: update option-filter-value to use thermographFk in TravelThermographsForm --- src/pages/Travel/Card/TravelThermographsForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Travel/Card/TravelThermographsForm.vue b/src/pages/Travel/Card/TravelThermographsForm.vue index 7aec32972..446e5d506 100644 --- a/src/pages/Travel/Card/TravelThermographsForm.vue +++ b/src/pages/Travel/Card/TravelThermographsForm.vue @@ -209,7 +209,7 @@ const onThermographCreated = async (data) => { }" sort-by="thermographFk ASC" option-label="thermographFk" - option-filter-value="id" + option-filter-value="thermographFk" :disable="viewAction === 'edit'" :tooltip="t('New thermograph')" :roles-allowed-to-create="['logistic']" From 98239e010564b6da8533e740c8a5ac55c96283e5 Mon Sep 17 00:00:00 2001 From: pablone Date: Tue, 18 Feb 2025 22:04:47 +0100 Subject: [PATCH 02/13] feat: refs #6897 add time formatting and improve column alignment handling in VnTable --- src/components/VnTable/VnColumn.vue | 2 +- src/components/VnTable/VnTable.vue | 34 +++++++++++++++++++++++++---- src/composables/getColAlign.js | 1 + 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index 44364cca1..d0e245388 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -1,6 +1,6 @@ {{ toPercentage(row.discount / 100) }} From 7ade3f4f84c4102b5941ed6cf218c57dbc1c1fe6 Mon Sep 17 00:00:00 2001 From: jorgep Date: Wed, 19 Feb 2025 18:36:54 +0100 Subject: [PATCH 05/13] fix: refs #6943 reset formData to originalData on reset function --- src/components/FormModel.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index 5681ce11c..04ef13d45 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -247,6 +247,7 @@ async function saveAndGo() { } function reset() { + formData.value = JSON.parse(JSON.stringify(originalData.value)); updateAndEmit('onFetch', { val: originalData.value }); if ($props.observeFormChanges) { hasChanges.value = false; From 380965fbea461e4f99799e147c14f6cacd2b3ae9 Mon Sep 17 00:00:00 2001 From: pablone Date: Wed, 19 Feb 2025 22:03:48 +0100 Subject: [PATCH 06/13] feat: refs #6897 enhance VnTable input handling and improve WorkerMedical component filters --- src/components/VnTable/VnTable.vue | 43 ++++++++++++----------- src/pages/Worker/Card/WorkerFormation.vue | 2 +- src/pages/Worker/Card/WorkerMedical.vue | 14 +++++++- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index b896fa769..b053b94a4 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -18,7 +18,6 @@ import { useQuasar, date } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { useFilterParams } from 'src/composables/useFilterParams'; import { dashIfEmpty, toDate } from 'src/filters'; -import { toTimeFormat } from 'src/filters/date'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; @@ -346,7 +345,7 @@ const clickHandler = async (event) => { if (isDateElement || isTimeElement || isQselectDropDown) return; if (clickedElement === null) { - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); return; } const rowIndex = clickedElement.getAttribute('data-row-index'); @@ -356,7 +355,7 @@ const clickHandler = async (event) => { if (editingRow.value !== null && editingField.value !== null) { if (editingRow.value == rowIndex && editingField.value == colField) return; - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); } if (isEditableColumn(column)) { @@ -366,7 +365,7 @@ const clickHandler = async (event) => { async function handleTabKey(event, rowIndex, colField) { if (editingRow.value == rowIndex && editingField.value == colField) - destroyInput(editingRow.value, editingField.value); + await destroyInput(editingRow.value, editingField.value); const direction = event.shiftKey ? -1 : 1; const { nextRowIndex, nextColumnName } = await handleTabNavigation( @@ -426,7 +425,8 @@ async function renderInput(rowId, field, clickedElement) { await column?.cellEvent?.['update:modelValue']?.(value, oldValue, row); }, keyup: async (event) => { - if (event.key === 'Enter') handleBlur(rowId, field, clickedElement); + if (event.key === 'Enter') + await destroyInput(rowIndex, field, clickedElement); }, keydown: async (event) => { switch (event.key) { @@ -435,7 +435,7 @@ async function renderInput(rowId, field, clickedElement) { event.stopPropagation(); break; case 'Escape': - destroyInput(rowId, field, clickedElement); + await destroyInput(rowId, field, clickedElement); break; default: break; @@ -457,12 +457,13 @@ async function renderInput(rowId, field, clickedElement) { node.el?.querySelector('span > div > div').focus(); } -function destroyInput(rowIndex, field, clickedElement) { +async function destroyInput(rowIndex, field, clickedElement) { if (!clickedElement) clickedElement = document.querySelector( `[data-row-index="${rowIndex}"][data-col-field="${field}"]`, ); if (clickedElement) { + await nextTick(); render(null, clickedElement); Array.from(clickedElement.childNodes).forEach((child) => { child.style.visibility = 'visible'; @@ -474,10 +475,6 @@ function destroyInput(rowIndex, field, clickedElement) { editingField.value = null; } -function handleBlur(rowIndex, field, clickedElement) { - destroyInput(rowIndex, field, clickedElement); -} - async function handleTabNavigation(rowIndex, colName, direction) { const columns = $props.columns; const totalColumns = columns.length; @@ -538,18 +535,22 @@ function formatColumnValue(col, row, dashIfEmpty) { : row[col?.name]; if (selectRegex.test(col?.component) && $props.isEditable) { - const findBy = find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); + const { find, url } = col.attrs; + const urlRelation = url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1); - if (col?.attrs.options) - return dashIfEmpty( - col?.attrs.options.find((option) => option.id === row[col.name])?.name, - ); - - if (typeof row[findBy] == 'object') { - return dashIfEmpty(row[findBy][col?.attrs.optionLabel ?? 'name']); + if (col?.attrs.options) { + const find = col?.attrs.options.find((option) => option.id === row[col.name]); + if (!col.attrs?.optionLabel || !find) return dashIfEmpty(row[col?.name]); + return dashIfEmpty(find[col.attrs?.optionLabel ?? 'name']); } - if (row[findBy]) return dashIfEmpty(row[findBy]); - if (!findBy || !row) return; + + if (typeof row[urlRelation] == 'object') { + if (typeof find == 'object') + return dashIfEmpty(row[urlRelation][find?.label ?? 'name']); + + return dashIfEmpty(row[urlRelation][col?.attrs.optionLabel ?? 'name']); + } + if (typeof row[urlRelation] == 'string') return dashIfEmpty(row[urlRelation]); } else { return dashIfEmpty(row[col?.name]); } diff --git a/src/pages/Worker/Card/WorkerFormation.vue b/src/pages/Worker/Card/WorkerFormation.vue index e05eca7f8..e8680f7dd 100644 --- a/src/pages/Worker/Card/WorkerFormation.vue +++ b/src/pages/Worker/Card/WorkerFormation.vue @@ -119,7 +119,7 @@ const columns = computed(() => [ :url="`Workers/${entityId}/trainingCourse`" :url-create="`Workers/${entityId}/trainingCourse`" save-url="TrainingCourses/crud" - :filter="courseFilter" + :user-filter="courseFilter" :create="{ urlCreate: 'trainingCourses', title: t('Create training course'), diff --git a/src/pages/Worker/Card/WorkerMedical.vue b/src/pages/Worker/Card/WorkerMedical.vue index c220df76a..b3a599af7 100644 --- a/src/pages/Worker/Card/WorkerMedical.vue +++ b/src/pages/Worker/Card/WorkerMedical.vue @@ -8,6 +8,17 @@ const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => route.params.id); +const centerFilter = { + include: [ + { + relation: 'center', + scope: { + fields: ['id', 'name'], + }, + }, + ], +}; + const columns = [ { align: 'left', @@ -33,7 +44,7 @@ const columns = [ create: true, component: 'select', attrs: { - url: 'medicalCenters', + url: 'centers', fields: ['id', 'name'], }, }, @@ -84,6 +95,7 @@ const columns = [ ref="tableRef" data-key="WorkerMedical" :url="`Workers/${entityId}/medicalReview`" + :user-filter="centerFilter" save-url="MedicalReviews/crud" :create="{ urlCreate: 'medicalReviews', From 874fbb48f5cb94a97e9b509ebd95713788691a9a Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 19 Feb 2025 22:16:02 +0100 Subject: [PATCH 07/13] feat: refactor canProceed --- src/i18n/locale/en.yml | 1 + src/i18n/locale/es.yml | 1 + src/pages/Ticket/Card/TicketSale.vue | 105 +++++++++++++++++---------- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index d1fbdc312..7d0f3e0b2 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -48,6 +48,7 @@ globals: rowRemoved: Row removed pleaseWait: Please wait... noPinnedModules: You don't have any pinned modules + enterToConfirm: Press Enter to confirm summary: basicData: Basic data daysOnward: Days onward diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index bfab41a75..7ca9e4b4c 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -48,6 +48,7 @@ globals: rowRemoved: Fila eliminada pleaseWait: Por favor espera... noPinnedModules: No has fijado ningún módulo + enterToConfirm: Pulsa Enter para confirmar summary: basicData: Datos básicos daysOnward: Días adelante diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 94d393900..082e14014 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -52,7 +52,6 @@ const transfer = ref({ sales: [], }); const tableRef = ref([]); -const canProceed = ref(); watch( () => route.params.id, @@ -132,7 +131,6 @@ const columns = computed(() => [ align: 'left', label: t('globals.amount'), name: 'amount', - format: (row) => toCurrency(getSaleTotal(row)), }, { align: 'left', @@ -182,8 +180,6 @@ const resetChanges = async () => { }; const rowToUpdate = ref(null); const changeQuantity = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; if ( !sale.itemFk || sale.quantity == null || @@ -192,11 +188,21 @@ const changeQuantity = async (sale) => { return; if (!sale.id) return addSale(sale); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateQuantity(sale)); + } else await updateQuantity(sale); +}; + +const updateQuantity = async (sale) => { try { + let { quantity, id } = sale; if (!rowToUpdate.value) return; rowToUpdate.value = null; sale.isNew = false; - await updateQuantity(sale); + const params = { quantity: quantity }; + await axios.post(`Sales/${id}/updateQuantity`, params); + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); } catch (e) { const { quantity } = tableRef.value.CrudModelRef.originalData.find( (s) => s.id === sale.id, @@ -206,12 +212,6 @@ const changeQuantity = async (sale) => { } }; -const updateQuantity = async ({ quantity, id }) => { - const params = { quantity: quantity }; - await axios.post(`Sales/${id}/updateQuantity`, params); - notify('globals.dataSaved', 'positive'); -}; - const addSale = async (sale) => { const params = { barcode: sale.itemFk, @@ -236,13 +236,17 @@ const addSale = async (sale) => { sale.isNew = false; arrayData.fetch({}); }; +const changeConcept = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateConcept(sale)); + } else await updateConcept(sale); +}; const updateConcept = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; const data = { newConcept: sale.concept }; await axios.post(`Sales/${sale.id}/updateConcept`, data); notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); }; const DEFAULT_EDIT = { @@ -294,33 +298,36 @@ const onOpenEditDiscountPopover = async (sale) => { }; } }; - -const updatePrice = async (sale) => { - canProceed.value = await isSalePrepared(sale); - if (!canProceed.value) return; +const changePrice = async (sale) => { const newPrice = edit.value.price; if (newPrice != null && newPrice != sale.price) { - await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); - sale.price = newPrice; - edit.value = { ...DEFAULT_EDIT }; - notify('globals.dataSaved', 'positive'); + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updatePrice(sale, newPrice)); + } else updatePrice(sale, newPrice); } - await getMana(); }; +const updatePrice = async (sale, newPrice) => { + await axios.post(`Sales/${sale.id}/updatePrice`, { newPrice }); + sale.price = newPrice; + edit.value = { ...DEFAULT_EDIT }; + notify('globals.dataSaved', 'positive'); + tableRef.value.reload(); +}; const changeDiscount = async (sale) => { const newDiscount = edit.value.discount; if (newDiscount != null && newDiscount != sale.discount) { - if (isSalePrepared(sale)) + if (await isSalePrepared(sale)) await confirmUpdate(() => updateDiscount([sale], newDiscount)); + else await updateDiscount([sale], newDiscount); } }; const updateDiscounts = async (sales, newDiscount = null) => { - const someSaleIsPrepared = sales.some(isSalePrepared); - if (someSaleIsPrepared); - await confirmUpdate(() => updateDiscount(sales, newDiscount)); + const someSaleIsPrepared = await sales.some(isSalePrepared); + if (someSaleIsPrepared) await confirmUpdate(() => updateDiscount(sales, newDiscount)); + else updateDiscount(sales, newDiscount); }; const updateDiscount = async (sales, newDiscount = null) => { @@ -426,9 +433,13 @@ onMounted(async () => { const items = ref([]); const newRow = ref({}); +const changeItem = async (sale) => { + if (await isSalePrepared(sale)) { + await confirmUpdate(() => updateItem(sale)); + } else await updateItem(sale); +}; + const updateItem = async (row) => { - canProceed.value = await isSalePrepared(row); - if (!canProceed.value) return; const selectedItem = items.value.find((item) => item.id === row.itemFk); if (selectedItem) { row.item = selectedItem; @@ -499,22 +510,27 @@ async function isSalePrepared(sale) { const matchingSale = data.find(({ itemFk }) => itemFk === sale.itemFk); if (!matchingSale) { + return false; + } + + const flagsToCheck = [ + 'hasSaleGroupDetail', + 'isControled', + 'isPrepared', + 'isPrevious', + 'isPreviousSelected', + ]; + if (flagsToCheck.some((flag) => matchingSale[flag] === 1)) { return true; } - return ( - matchingSale.hasSaleGroupDetail || - matchingSale.isControled || - matchingSale.isPrepared || - matchingSale.isPrevious || - matchingSale.isPreviousSelected - ); + return false; } watch( () => newRow.value.itemFk, (newItemFk) => { if (newItemFk) { - updateItem(newRow.value); + changeItem(newRow.value); } }, ); @@ -751,7 +767,7 @@ watch( option-value="id" v-model="row.itemFk" :use-like="false" - @update:model-value="updateItem(row)" + @update:model-value="changeItem(row)" > + From b2184635d3d0d7ca8bde9c690430c5e6d45e69eb Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Wed, 19 Feb 2025 23:42:10 +0100 Subject: [PATCH 08/13] test: improve test --- src/pages/Ticket/Card/TicketEditMana.vue | 2 +- src/pages/Ticket/Card/TicketSale.vue | 29 +- .../integration/ticket/ticketSale.spec.js | 298 +++++++++++------- 3 files changed, 213 insertions(+), 116 deletions(-) diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index de9a982b9..14eec9db9 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -46,7 +46,7 @@ defineExpose({ save });