From 1007a884b9634d1d1bee939a48a5fd59f955d02b Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Thu, 8 May 2025 12:25:59 +0200 Subject: [PATCH 01/14] feat: remove reset selected variable --- src/components/VnTable/VnTable.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 29a9200f0..f26f96170 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -312,7 +312,6 @@ function stopEventPropagation(event) { } function reload(params) { - selected.value = []; CrudModelRef.value.reload(params); } From 7dcfeb0fc76dc0576bf778e95f178de821424c97 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Fri, 9 May 2025 11:56:18 +0200 Subject: [PATCH 02/14] test: try to fix entryBuys test --- test/cypress/integration/entry/commands.js | 4 ++-- test/cypress/integration/entry/entryCard/entryBuys.spec.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/cypress/integration/entry/commands.js b/test/cypress/integration/entry/commands.js index 4d4a8f980..8bc502f60 100644 --- a/test/cypress/integration/entry/commands.js +++ b/test/cypress/integration/entry/commands.js @@ -7,8 +7,8 @@ Cypress.Commands.add('selectTravel', (warehouse = '1') => { }); Cypress.Commands.add('deleteEntry', () => { - cy.get('[data-cy="descriptor-more-opts"]').should('be.visible').click(); - cy.waitForElement('div[data-cy="delete-entry"]').click(); + cy.openActionsDescriptor(); + cy.get('[data-cy="delete-entry"]').click(); }); Cypress.Commands.add('createEntry', () => { diff --git a/test/cypress/integration/entry/entryCard/entryBuys.spec.js b/test/cypress/integration/entry/entryCard/entryBuys.spec.js index b5e185a8e..a61ac4221 100644 --- a/test/cypress/integration/entry/entryCard/entryBuys.spec.js +++ b/test/cypress/integration/entry/entryCard/entryBuys.spec.js @@ -94,8 +94,10 @@ describe('EntryBuys', () => { cy.get('button[data-cy="vnTableCreateBtn"]').click(); cy.get('input[data-cy="itemFk-create-popup"]').type('1'); + cy.intercept('GET', /\/api\/Items\/1\/getVisibleAvailable/).as('item'); cy.get('div[role="listbox"] > div > div[role="option"]').eq(0).click(); + cy.wait('@item'); cy.get('input[data-cy="Grouping mode_select"]').should('have.value', 'packing'); - cy.get('button[data-cy="FormModelPopup_save"]').click(); + cy.saveFormModel(); } }); From 359f1cc4db8faf22a63a83e36e980485ebe31287 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Tue, 13 May 2025 13:51:05 +0200 Subject: [PATCH 03/14] fix: reset selected values on reload in VnTable and comment out selectedRows reset in TicketSale --- src/components/VnTable/VnTable.vue | 1 + src/pages/Ticket/Card/TicketSale.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index f26f96170..29a9200f0 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -312,6 +312,7 @@ function stopEventPropagation(event) { } function reload(params) { + selected.value = []; CrudModelRef.value.reload(params); } diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 90ea4c11f..dbba77ab6 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -191,7 +191,7 @@ const resetChanges = async () => { tableRef.value.CrudModelRef.hasChanges = false; await tableRef.value.reload(); - selectedRows.value = []; + // selectedRows.value = []; }; const changeQuantity = async (sale) => { if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) From 5be52872678cbfc428a67688a32c2eade62647a6 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Tue, 13 May 2025 13:51:33 +0200 Subject: [PATCH 04/14] fix: improve error handling in TicketList --- src/pages/Ticket/Card/TicketSale.vue | 2 -- src/pages/Ticket/TicketList.vue | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index dbba77ab6..774f7fc57 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -190,8 +190,6 @@ const resetChanges = async () => { await arrayData.fetch({ append: false }); tableRef.value.CrudModelRef.hasChanges = false; await tableRef.value.reload(); - - // selectedRows.value = []; }; const changeQuantity = async (sale) => { if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 456747cf8..879700b9e 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -369,7 +369,11 @@ function openBalanceDialog(ticket) { ); if (!isSameClient) { - throw new Error('You cannot make a payment on account from multiple clients'); + notify( + t('You cannot make a payment on account from multiple clients'), + 'negative', + ); + return; } for (let ticketData of checkedTickets) { @@ -837,4 +841,5 @@ es: Zone: Zona New ticket: Nuevo ticket Component lack: Faltan componentes + You cannot make a payment on account from multiple clients: No puedes hacer un pago a cuenta de varios clientes From 5a92f1a6ccae6ebfde76d31296e74f257e19e9fe Mon Sep 17 00:00:00 2001 From: jon Date: Thu, 15 May 2025 13:16:26 +0200 Subject: [PATCH 05/14] fix: no downloading file when clicking in the file field --- src/components/common/VnDmsList.vue | 1 - src/pages/Customer/Card/CustomerFileManagement.vue | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/common/VnDmsList.vue b/src/components/common/VnDmsList.vue index 943a2a733..cbff944e0 100644 --- a/src/components/common/VnDmsList.vue +++ b/src/components/common/VnDmsList.vue @@ -7,7 +7,6 @@ import axios from 'axios'; import { usePrintService } from 'composables/usePrintService'; import VnUserLink from '../ui/VnUserLink.vue'; -import { downloadFile } from 'src/composables/downloadFile'; import VnImg from 'components/ui/VnImg.vue'; import VnPaginate from 'components/ui/VnPaginate.vue'; import VnDms from 'src/components/common/VnDms.vue'; diff --git a/src/pages/Customer/Card/CustomerFileManagement.vue b/src/pages/Customer/Card/CustomerFileManagement.vue index 419719251..f914993b7 100644 --- a/src/pages/Customer/Card/CustomerFileManagement.vue +++ b/src/pages/Customer/Card/CustomerFileManagement.vue @@ -5,7 +5,7 @@ import { useRoute, useRouter } from 'vue-router'; import { QBadge, QBtn, QCheckbox } from 'quasar'; -import { downloadFile } from 'src/composables/downloadFile'; +import { usePrintService } from 'composables/usePrintService'; import { toDateTimeFormat } from 'src/filters/date'; import FetchData from 'components/FetchData.vue'; @@ -15,7 +15,7 @@ import CustomerFileManagementActions from 'src/pages/Customer/components/Custome const { t } = useI18n(); const route = useRoute(); const router = useRouter(); - +const { openReport } = usePrintService(); const ClientDmsRef = ref(null); const rows = ref([]); @@ -87,7 +87,7 @@ const tableColumnComponents = { file: { component: QBtn, props: () => ({ flat: true }), - event: ({ row }) => downloadFile(row.dmsFk), + event: ({ row }) => openReport(`dms/${row.dmsFk}/downloadFile`, {}, '_blank'), }, employee: { component: QBtn, From 8e5b5f15f16998215f322c89c50097943334dc5e Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 19 May 2025 13:12:14 +0200 Subject: [PATCH 06/14] fix: update descriptor when changing client's credit --- src/pages/Customer/Card/CustomerCredits.vue | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/Customer/Card/CustomerCredits.vue b/src/pages/Customer/Card/CustomerCredits.vue index d6e4be89e..1469fbe3d 100644 --- a/src/pages/Customer/Card/CustomerCredits.vue +++ b/src/pages/Customer/Card/CustomerCredits.vue @@ -3,12 +3,13 @@ import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import { toCurrency, toDateHourMin } from 'src/filters'; +import { useArrayData } from 'composables/useArrayData'; import VnTable from 'components/VnTable/VnTable.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; const { t } = useI18n(); const route = useRoute(); - +const arrayData = useArrayData('Customer'); const filter = computed(() => { return { include: [ @@ -77,7 +78,10 @@ const columns = computed(() => [ :create="{ urlUpdate: `Clients/${route.params.id}`, title: t('New credit'), - onDataSaved: () => tableRef.reload(), + onDataSaved: () => { + arrayData.fetch({ append: false }); + tableRef.reload(); + }, formInitialData: { credit: tableData.at(0)?.amount }, }" > From 547d0a289549260c74d65a77cc7e89fba1b03f40 Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 19 May 2025 13:22:24 +0200 Subject: [PATCH 07/14] feat: improved test --- .../integration/customer/clientCredits.spec.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/cypress/integration/customer/clientCredits.spec.js b/test/cypress/integration/customer/clientCredits.spec.js index f39f3c06c..2d7c660f2 100644 --- a/test/cypress/integration/customer/clientCredits.spec.js +++ b/test/cypress/integration/customer/clientCredits.spec.js @@ -1,5 +1,8 @@ /// describe('Client credits', () => { + const credit = 100; + const descriptorCreditValue = '[data-cy="vnLvCredit"] > .value > span'; + beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -11,9 +14,19 @@ describe('Client credits', () => { it('Should put a new credit', () => { cy.get('.q-page').should('be.visible'); cy.dataCy('vnTableCreateBtn').click(); - cy.dataCy('Credit_input').type('100'); + cy.dataCy('Credit_input').type(credit); cy.dataCy('FormModelPopup_save').click(); cy.checkNotification('Data saved'); + cy.get(descriptorCreditValue) + .invoke('text') + .then((text) => { + const creditValue = parseInt( + text.trim().replace('€', '').split('.')[0], + 10, + ); + cy.log(creditValue); + expect(creditValue).to.equal(credit); + }); }); it('Should put a new credit with value 0 to close the client card', () => { From 0ec8283ba595dd638b734f24cc84b38c9aeb5713 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Mon, 19 May 2025 14:08:46 +0200 Subject: [PATCH 08/14] test: fix test ticketSale --- src/pages/Ticket/Card/TicketSale.vue | 2 ++ test/cypress/integration/ticket/ticketSale.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 627bfb86b..97ae0477c 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -187,9 +187,11 @@ const getRowUpdateInputEvents = (sale) => { }; const resetChanges = async () => { + const _selectedRows = selectedRows.value; await arrayData.fetch({ append: false }); tableRef.value.CrudModelRef.hasChanges = false; await tableRef.value.reload(); + tableRef.value.selected = _selectedRows; }; const changeQuantity = async (sale) => { if (!sale.itemFk || sale.quantity == null || sale?.originalQuantity === sale.quantity) diff --git a/test/cypress/integration/ticket/ticketSale.spec.js b/test/cypress/integration/ticket/ticketSale.spec.js index 7fc843cf1..8bb276f81 100644 --- a/test/cypress/integration/ticket/ticketSale.spec.js +++ b/test/cypress/integration/ticket/ticketSale.spec.js @@ -137,7 +137,7 @@ describe('TicketSale', { testIsolation: true }, () => { cy.dataCy('recalculatePriceItem').click(); cy.wait('@recalculatePrice').its('response.statusCode').should('eq', 200); cy.checkNotification('Data saved'); - cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); + cy.dataCy('ticketSaleMoreActionsDropdown').should('not.be.disabled'); }); it('should update discount when "Update discount" is clicked', () => { @@ -159,7 +159,7 @@ describe('TicketSale', { testIsolation: true }, () => { cy.dataCy('saveManaBtn').click(); cy.wait('@updateDiscount').its('response.statusCode').should('eq', 204); cy.checkNotification('Data saved'); - cy.dataCy('ticketSaleMoreActionsDropdown').should('be.disabled'); + cy.dataCy('ticketSaleMoreActionsDropdown').should('not.be.disabled'); }); it('adds claim', () => { From 9a7f4cdd8c70e21299cbfb740bee314622ffea2b Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Mon, 19 May 2025 15:05:45 +0200 Subject: [PATCH 09/14] fix: minor issue and add test --- .../Ticket/components/TicketNewPayment.vue | 42 ++++++++++++++----- .../integration/ticket/ticketList.spec.js | 19 +++++++++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/pages/Ticket/components/TicketNewPayment.vue b/src/pages/Ticket/components/TicketNewPayment.vue index 4951e5249..15a018b82 100644 --- a/src/pages/Ticket/components/TicketNewPayment.vue +++ b/src/pages/Ticket/components/TicketNewPayment.vue @@ -52,30 +52,50 @@ const filterBanks = { const state = useState(); const user = state.getUser(); +const originalDescription = ref(''); const initialData = ref({ ...$props.formData, companyFk: user.value.companyFk, payed: Date.vnNew(), + originalDescription: '', }); function setPaymentType(data, accounting) { - data.bankFk = accounting.id; if (!accounting) return; + + data.bankFk = accounting.id; accountingType.value = accounting.accountingType; + data.description = []; - data.payed = Date.vnNew(); isCash.value = accountingType.value.code == 'cash'; viewReceipt.value = isCash.value; - if (accountingType.value.daysInFuture) - data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture); - maxAmount.value = accountingType.value && accountingType.value.maxAmount; - if (accountingType.value.code == 'compensation') return (data.description = ''); - let descriptions = []; - if (accountingType.value.receiptDescription) - descriptions.push(accountingType.value.receiptDescription); - if (data.description > 0) descriptions.push(data.description); - data.description = descriptions.join(', '); + switch (accountingType.value.code) { + case 'compensation': + data.description.push($props.formData.description); + break; + + default: + if ( + accountingType.value.receiptDescription != null && + accountingType.value.receiptDescription != '' + ) { + data.description.push(accountingType.value.receiptDescription); + } + const originalDescription = + data.originalDescription || $props.formData.description; + if (originalDescription) { + data.description.push(originalDescription); + } + } + + data.description = data.description.join(', '); + data.payed = Date.vnNew(); + if (accountingType.value.daysInFuture) { + data.payed.setDate(data.payed.getDate() + accountingType.value.daysInFuture); + } + + maxAmount.value = accountingType.value && accountingType.value.maxAmount; } const calculateFromAmount = (event) => { diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index ae0e0ff10..67a1c29e2 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -26,6 +26,25 @@ describe('TicketList', () => { cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); + it.only('should create payment ticket', () => { + cy.get('[data-cy="vnFilterPanel_search"] > .q-btn__content > .q-icon').click(); + + const rowSelected = + 'tbody > :nth-child(2) > :nth-child(1) > .q-checkbox > .q-checkbox__inner '; + cy.get(rowSelected).click(); + cy.get( + '[style="transform: translate(-256px, 0px); margin: 140px 20px; z-index: 2;"] > div > .q-btn', + ).click(); + const description = 'Albaran: 31'; + cy.dataCy('Reference_input').should('have.value', description); + cy.selectOption('[data-cy="paymentBank"]', 'Cash').click(); + cy.get('[data-cy="Delivered amount_input"]').clear(); + cy.get('[data-cy="Delivered amount_input"]').type('41.62'); + cy.dataCy('Reference_input').should('have.value', `Cash, ${description}`); + cy.get('[aria-label="View recipt"]').click(); + cy.get('.q-btn--standard > .q-btn__content > .block').click(); + }); + it('should open ticket summary', () => { searchResults(); cy.getRow().find('.q-btn:last').click(); From a6d6ad92f6958c044781c4622ebdfc66c94c00b3 Mon Sep 17 00:00:00 2001 From: Javier Segarra Date: Tue, 20 May 2025 08:34:20 +0200 Subject: [PATCH 10/14] test: skip entryBuys --- src/pages/Item/components/ItemProposal.vue | 110 ++++++++++-------- src/pages/Ticket/Card/TicketSplit.vue | 7 +- .../entry/entryCard/entryBuys.spec.js | 2 +- 3 files changed, 69 insertions(+), 50 deletions(-) diff --git a/src/pages/Item/components/ItemProposal.vue b/src/pages/Item/components/ItemProposal.vue index 30f050097..5c735d170 100644 --- a/src/pages/Item/components/ItemProposal.vue +++ b/src/pages/Item/components/ItemProposal.vue @@ -56,26 +56,6 @@ const defaultColumnAttrs = { sortable: false, }; const emit = defineEmits(['onDialogClosed', 'itemReplaced']); - -const priceStatusClass = (proposalPrice) => { - const originalPrice = sale.value?.price; - - if ( - !originalPrice || - !ticketConfig.value || - typeof ticketConfig.value.lackAlertPrice !== 'number' - ) { - return 'price-ok'; - } - - const priceIncreasePercentage = - ((proposalPrice - originalPrice) / originalPrice) * 100; - - return priceIncreasePercentage > ticketConfig.value.lackAlertPrice - ? 'price-alert' - : 'price-ok'; -}; - const columns = computed(() => [ { ...defaultColumnAttrs, @@ -196,7 +176,6 @@ const columns = computed(() => [ { title: t('Replace'), icon: 'change_circle', - show: (row) => isSelectionAvailable(row), action: change, isPrimary: true, }, @@ -204,11 +183,18 @@ const columns = computed(() => [ }, ]); -function extractMatchValues(obj) { - return Object.keys(obj) - .filter((key) => key.startsWith(MATCH)) - .map((key) => parseInt(key.replace(MATCH, ''), 10)); -} +const priceStatusClass = (proposalPrice) => { + const originalPrice = sale.value?.price; + const { lackAlertPrice: lackAlert } = ticketConfig.value; + if (!originalPrice || !ticketConfig.value || typeof lackAlert !== 'number') { + return 'price-ok'; + } + + const percentage = ((proposalPrice - originalPrice) / originalPrice) * 100; + + return percentage > lackAlert ? 'price-alert' : 'price-ok'; +}; + const gradientStyleClass = (row) => { let color = 'white'; const value = parseFloat(row); @@ -226,28 +212,49 @@ const gradientStyleClass = (row) => { } return color; }; + +const extractMatchValues = (obj) => { + return Object.keys(obj) + .filter((key) => key.startsWith(MATCH)) + .map((key) => parseInt(key.replace(MATCH, ''), 10)); +}; + const statusConditionalValue = (row) => { const matches = extractMatchValues(row); const value = matches.reduce((acc, i) => acc + row[`${MATCH}${i}`], 0); return 100 * (value / matches.length); }; -const isSelectionAvailable = (itemProposal) => { - const { price2, available } = itemProposal; - const salePrice = sale.value.price; - const { lackAlertPrice } = ticketConfig.value; - const isPriceTooHigh = (100 * price2) / salePrice > lackAlertPrice; - if (isPriceTooHigh) { - return isPriceTooHigh; +const canReplace = (itemProposal) => { + if (!canReplaceByPrice(itemProposal)) { + return false; } - const hasEnoughQuantity = - (100 * available) / Math.abs($props.itemLack.lack) < lackAlertPrice; - return hasEnoughQuantity; + return canReplaceByQuantity(itemProposal); +}; +const differenceByPrice = ({ price2: proposalPrice }) => { + const { price: salePrice } = sale.value; + const percentage = ((proposalPrice - salePrice) / salePrice) * 100; + return percentage; +}; +const canReplaceByPrice = (itemProposal) => + differenceByPrice(itemProposal) < ticketConfig.value.lackAlertPrice; + +const differenceByQuantity = ({ available }) => { + const { quantity: saleQuantity } = sale.value; + const percentage = ((saleQuantity - available) / available) * 100; + return percentage; }; +const canReplaceByQuantity = (itemProposal) => + differenceByQuantity(itemProposal) < ticketConfig.value.lackAlertPrice; + async function change(itemSubstitution) { - if (!isSelectionAvailable(itemSubstitution)) { - notify(t('notAvailable'), 'warning'); + if (!canReplaceByPrice(itemSubstitution)) { + notify(t('notAvailableByPrice'), 'warning'); + return; + } + if (!canReplaceByQuantity(itemSubstitution)) { + notify(t('notAvailableByQuantity'), 'warning'); return; } const { itemFk: substitutionFk } = itemSubstitution; @@ -277,9 +284,7 @@ async function handleTicketConfig(data) { } function filterRows(data) { - const filteredRows = data.sort( - (a, b) => isSelectionAvailable(b) - isSelectionAvailable(a), - ); + const filteredRows = data.sort((a, b) => canReplace(b) - canReplace(a)); proposalTableRef.value.CrudModelRef.formData = filteredRows; } @@ -315,6 +320,7 @@ function filterRows(data) { >