diff --git a/src/pages/Customer/composables/__tests__/getAddresses.spec.js b/src/pages/Customer/composables/__tests__/getAddresses.spec.js index 9e04a83cc..8c90bf281 100644 --- a/src/pages/Customer/composables/__tests__/getAddresses.spec.js +++ b/src/pages/Customer/composables/__tests__/getAddresses.spec.js @@ -17,9 +17,9 @@ describe('getAddresses', () => { expect(axios.get).toHaveBeenCalledWith(`Clients/${clientId}/addresses`, { params: { filter: JSON.stringify({ - fields: ['nickname', 'street', 'city', 'id'], + fields: ['nickname', 'street', 'city', 'id', 'isActive'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }), }, }); @@ -30,4 +30,4 @@ describe('getAddresses', () => { expect(axios.get).not.toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/src/pages/Customer/composables/getAddresses.js b/src/pages/Customer/composables/getAddresses.js index e65e64455..5f18530e7 100644 --- a/src/pages/Customer/composables/getAddresses.js +++ b/src/pages/Customer/composables/getAddresses.js @@ -1,15 +1,15 @@ import axios from 'axios'; -export async function getAddresses(clientId, _filter = {}) { +export async function getAddresses(clientId, _filter = {}) { if (!clientId) return; const filter = { ..._filter, - fields: ['nickname', 'street', 'city', 'id'], + fields: ['nickname', 'street', 'city', 'id', 'isActive'], where: { isActive: true }, - order: 'nickname ASC', + order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], }; const params = { filter: JSON.stringify(filter) }; return await axios.get(`Clients/${clientId}/addresses`, { params, }); -}; \ No newline at end of file +} diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 40990f329..59ec37f98 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -155,11 +155,23 @@ onMounted(() => { }); async function fetchClientAddress(id, formData = {}) { - const { data } = await axios.get( - `Clients/${id}/addresses?filter[order]=isActive DESC` - ); + const { data } = await axios.get(`Clients/${id}/addresses`, { + params: { + filter: JSON.stringify({ + include: [ + { + relation: 'client', + scope: { + fields: ['defaultAddressFk'], + }, + }, + ], + order: ['isActive DESC'], + }), + }, + }); addressOptions.value = data; - formData.addressId = data.defaultAddressFk; + formData.addressId = data[0].client.defaultAddressFk; fetchAgencies(formData); } @@ -167,7 +179,13 @@ async function fetchAgencies({ landed, addressId }) { if (!landed || !addressId) return (agencyList.value = []); const { data } = await axios.get('Agencies/landsThatDay', { - params: { addressFk: addressId, landed }, + params: { + filter: JSON.stringify({ + order: ['agencyMode DESC', 'agencyModeFk ASC'], + }), + addressFk: addressId, + landed, + }, }); agencyList.value = data; } @@ -258,6 +276,7 @@ const getDateColor = (date) => { </template> </VnSelect> <VnSelect + :disable="!data.clientFk" v-model="data.addressId" :options="addressOptions" :label="t('module.address')" @@ -284,6 +303,9 @@ const getDateColor = (date) => { {{ scope.opt?.street }}, {{ scope.opt?.city }} </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> diff --git a/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js index ccf7872cb..99966569c 100644 --- a/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js +++ b/src/pages/Route/Agency/composables/__tests__/getAgencies.spec.js @@ -27,14 +27,17 @@ describe('getAgencies', () => { landed: 'true', }; const filter = { - fields: ['nickname', 'street', 'city', 'id'], + fields: ['name', 'street', 'city', 'id'], where: { isActive: true }, - order: 'nickname ASC', + order: ['name ASC'], }; await getAgencies(formData, null, filter); - expect(axios.get).toHaveBeenCalledWith('Agencies/getAgenciesWithWarehouse', generateParams(formData, filter)); + expect(axios.get).toHaveBeenCalledWith( + 'Agencies/getAgenciesWithWarehouse', + generateParams(formData, filter), + ); }); it('should not call API when formData is missing required landed field', async () => { @@ -64,19 +67,19 @@ describe('getAgencies', () => { it('should return options and agency when default agency is found', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; const client = { defaultAddress: { agencyModeFk: 'Agency1' } }; - + const { options, agency } = await getAgencies(formData, client); - + expect(options).toEqual(response.data); expect(agency).toEqual(response.data[0]); - }); + }); - it('should return options and agency when client is not provided', async () => { + it('should return options and agency when client is not provided', async () => { const formData = { warehouseId: '123', addressId: '456', landed: 'true' }; - + const { options, agency } = await getAgencies(formData); - + expect(options).toEqual(response.data); expect(agency).toBeNull(); - }); + }); }); diff --git a/src/pages/Route/Agency/composables/getAgencies.js b/src/pages/Route/Agency/composables/getAgencies.js index 850f87456..180ac943e 100644 --- a/src/pages/Route/Agency/composables/getAgencies.js +++ b/src/pages/Route/Agency/composables/getAgencies.js @@ -1,14 +1,14 @@ import axios from 'axios'; -import agency from 'src/router/modules/agency'; export async function getAgencies(formData, client, _filter = {}) { if (!formData.warehouseId || !formData.addressId || !formData.landed) return; - + const filter = { - ..._filter + ..._filter, + order: ['name ASC'], }; - let defaultAgency = null; + let agency = null; let params = { filter: JSON.stringify(filter), warehouseFk: formData.warehouseId, @@ -16,11 +16,15 @@ export async function getAgencies(formData, client, _filter = {}) { landed: formData.landed, }; - const { data } = await axios.get('Agencies/getAgenciesWithWarehouse', { params }); + const { data: options } = await axios.get('Agencies/getAgenciesWithWarehouse', { + params, + }); - if(data && client) { - defaultAgency = data.find((agency) => agency.agencyModeFk === client.defaultAddress.agencyModeFk ); - }; - - return {options: data, agency: defaultAgency} + if (options && client) { + agency = options.find( + ({ agencyModeFk }) => agencyModeFk === client.defaultAddress.agencyModeFk, + ); + } + + return { options, agency }; } diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index f8084ff2f..a41d492ed 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -105,6 +105,9 @@ const columns = computed(() => [ name: 'created', align: 'left', cardVisible: true, + columnFilter: { + component: 'date', + }, format: (row) => toDateTimeFormat(row.created), }, { @@ -201,7 +204,7 @@ const getExpeditionState = async (expedition) => { const openGrafana = (expeditionFk) => { useOpenURL( - `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}` + `https://grafana.verdnatura.es/d/de1njb6p5answd/control-de-expediciones?orgId=1&var-expeditionFk=${expeditionFk}`, ); }; @@ -287,7 +290,7 @@ onMounted(async () => { openConfirmationModal( '', t('expedition.removeExpeditionSubtitle'), - deleteExpedition + deleteExpedition, ) " > @@ -302,7 +305,6 @@ onMounted(async () => { url="Expeditions/filter" search-url="expeditions" :columns="columns" - :filter="expeditionsFilter" v-model:selected="selectedRows" :table="{ 'row-key': 'id', @@ -316,6 +318,8 @@ onMounted(async () => { return { id: value }; case 'packageItemName': return { packagingItemFk: value }; + case 'created': + return { 'e.created': { gte: value } }; } } " diff --git a/src/pages/Ticket/Card/TicketTransferProxy.vue b/src/pages/Ticket/Card/TicketTransferProxy.vue index 3f3f018df..7d5d82f85 100644 --- a/src/pages/Ticket/Card/TicketTransferProxy.vue +++ b/src/pages/Ticket/Card/TicketTransferProxy.vue @@ -42,7 +42,7 @@ const transferRef = ref(null); /> </div> - <div v-else> + <div style="display: flex; flex-direction: row" v-else> <TicketTransfer ref="transferRef" :ticket="$props.ticket" diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue index 71b16f878..db78094cf 100644 --- a/src/pages/Ticket/Card/TicketVolume.vue +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -142,7 +142,7 @@ onMounted(() => (stateStore.rightDrawer = true)); <template #column-concept="{ row }"> <span>{{ row.item.name }}</span> <span class="color-vn-label q-pl-md">{{ row.item.subName }}</span> - <FetchedTags :item="row.item" /> + <FetchedTags :item="row.item" :columns="6" /> </template> <template #column-volume="{ rowIndex }"> <span>{{ packingTypeVolume?.[rowIndex]?.volume }}</span> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index c82c0067f..5da2a858c 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -46,7 +46,12 @@ const getGroupedStates = (data) => { " auto-load /> - <FetchData url="AgencyModes" @on-fetch="(data) => (agencies = data)" auto-load /> + <FetchData + url="AgencyModes" + :sort-by="['name ASC']" + @on-fetch="(data) => (agencies = data)" + auto-load + /> <FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load /> <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> @@ -74,10 +79,20 @@ const getGroupedStates = (data) => { </QItem> <QItem> <QItemSection> - <VnInputDate v-model="params.from" :label="t('From')" is-outlined /> + <VnInputDate + v-model="params.from" + :label="t('From')" + is-outlined + data-cy="From_date" + /> </QItemSection> <QItemSection> - <VnInputDate v-model="params.to" :label="t('To')" is-outlined /> + <VnInputDate + v-model="params.to" + :label="t('To')" + is-outlined + data-cy="To_date" + /> </QItemSection> </QItem> <QItem> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index 33b841d2d..c2d8f39f3 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { computed, ref, onBeforeMount } from 'vue'; +import { computed, ref, onBeforeMount, watch } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useStateStore } from 'stores/useStateStore'; import { useI18n } from 'vue-i18n'; @@ -69,6 +69,8 @@ const companiesOptions = ref([]); const accountingOptions = ref([]); const amountToReturn = ref(); const dataKey = 'TicketList'; +const filterPanelRef = ref(null); +const formInitialData = ref({}); const columns = computed(() => [ { @@ -119,12 +121,16 @@ const columns = computed(() => [ { align: 'left', name: 'shipped', + component: 'time', + columnFilter: false, label: t('ticketList.hour'), format: (row) => toTimeFormat(row.shipped), }, { align: 'left', name: 'zoneLanding', + component: 'time', + columnFilter: false, label: t('ticketList.closure'), format: (row, dashIfEmpty) => dashIfEmpty(toTimeFormat(row.zoneLanding)), }, @@ -144,9 +150,16 @@ const columns = computed(() => [ }, { align: 'left', - name: 'province', + name: 'provinceFk', label: t('ticketList.province'), - columnClass: 'expand', + component: 'select', + attrs: { + url: 'Provinces', + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.province), }, { align: 'left', @@ -180,9 +193,19 @@ const columns = computed(() => [ }, { align: 'left', - name: 'warehouse', - label: t('ticketList.warehouse'), - columnClass: 'expand', + name: 'warehouseFk', + label: t('globals.warehouse'), + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + format: (row) => row.warehouse, + columnField: { + component: null, + }, + cardVisible: false, + create: false, }, { align: 'left', @@ -422,6 +445,22 @@ function setReference(data) { dialogData.value.value.description = newDescription; } + +watch( + () => route.query.table, + (newValue) => { + if (newValue) { + const clientId = +JSON.parse(newValue)?.clientFk; + if (!clientId) return; + formInitialData.value = { + clientId, + }; + if (tableRef.value) tableRef.value.create.formInitialData = { clientId }; + onClientSelected({ clientId }); + } + }, + { immediate: true }, +); </script> <template> @@ -456,7 +495,7 @@ function setReference(data) { urlCreate: 'Tickets/new', title: t('ticketList.createTicket'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { clientId: null }, + formInitialData, }" default-mode="table" :columns="columns" @@ -538,11 +577,9 @@ function setReference(data) { :label="t('ticketList.client')" v-model="data.clientId" :options="clientsOptions" - option-value="id" - option-label="name" hide-selected required - @update:model-value="(client) => onClientSelected(data)" + @update:model-value="() => onClientSelected(data)" :sort-by="'id ASC'" > <template #option="scope"> @@ -564,7 +601,6 @@ function setReference(data) { :label="t('basicData.address')" v-model="data.addressId" :options="addressesOptions" - option-value="id" option-label="nickname" hide-selected map-options @@ -610,6 +646,9 @@ function setReference(data) { {{ scope.opt?.city }} </span> </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt?.id}` }} + </QItemLabel> </QItemSection> </QItem> </template> @@ -633,8 +672,6 @@ function setReference(data) { :label="t('globals.warehouse')" v-model="data.warehouseId" :options="warehousesOptions" - option-value="id" - option-label="name" hide-selected required @update:model-value="() => fetchAvailableAgencies(data)" @@ -694,7 +731,6 @@ function setReference(data) { :label="t('ticketList.company')" v-model="dialogData.companyFk" :options="companiesOptions" - option-value="id" option-label="code" hide-selected > @@ -705,7 +741,6 @@ function setReference(data) { :label="t('ticketList.bank')" v-model="dialogData.bankFk" :options="accountingOptions" - option-value="id" option-label="bank" hide-selected @update:model-value="setReference" diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js index 8673c9083..5d82aa4bc 100644 --- a/test/cypress/integration/client/clientAddress.spec.js +++ b/test/cypress/integration/client/clientAddress.spec.js @@ -17,7 +17,7 @@ describe('Client consignee', () => { const addressName = 'test'; cy.dataCy('Consignee_input').type(addressName); cy.dataCy('Location_select').click(); - cy.get('[role="listbox"] .q-item:nth-child(1)').click(); + cy.getOption(); cy.dataCy('Street address_input').type('TEST ADDRESS'); cy.get('.q-btn-group > .q-btn--standard').click(); cy.location('href').should('contain', '#/customer/1107/address'); diff --git a/test/cypress/integration/ticket/ticketFilter.spec.js b/test/cypress/integration/ticket/ticketFilter.spec.js new file mode 100644 index 000000000..659a9f83c --- /dev/null +++ b/test/cypress/integration/ticket/ticketFilter.spec.js @@ -0,0 +1,50 @@ +/// <reference types="cypress" /> +describe('TicketFilter', () => { + beforeEach(() => { + cy.login('developer'); + cy.viewport(1920, 1080); + cy.visit('/#/ticket/list'); + }); + + it('use search button', function () { + cy.waitForElement('.q-page'); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.searchBtnFilterPanel(); + cy.waitRequest('@ticketFilter', ({ request }) => { + const { query } = request; + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + }); + cy.on('uncaught:exception', () => { + return false; + }); + cy.get('.q-field__control-container > [data-cy="From_date"]') + .type(`${today()} `) + .type('{enter}'); + cy.get('.q-notification').should( + 'contain', + `The date range must have both 'from' and 'to'`, + ); + + cy.get('.q-field__control-container > [data-cy="To_date"]').type( + `${today()}{enter}`, + ); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.searchBtnFilterPanel(); + cy.wait('@ticketFilter').then(({ request }) => { + const { query } = request; + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + }); + cy.location('href').should('contain', '#/ticket/999999'); + }); +}); +function today(date) { + // return new Date().toISOString().split('T')[0]; + + return new Intl.DateTimeFormat('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }).format(date ?? new Date()); +} diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index 593021e6e..6a6dc24af 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -1,6 +1,6 @@ /// <reference types="cypress" /> describe('TicketList', () => { - const firstRow = 'tbody > :nth-child(1)'; + const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; beforeEach(() => { cy.login('developer'); @@ -9,8 +9,7 @@ describe('TicketList', () => { }); const searchResults = (search) => { - cy.dataCy('vn-searchbar').find('input').focus(); - if (search) cy.dataCy('vn-searchbar').find('input').type(search); + if (search) cy.typeSearchbar().type(search); cy.dataCy('vn-searchbar').find('input').type('{enter}'); cy.dataCy('ticketListTable').should('exist'); cy.get(firstRow).should('exist'); @@ -27,7 +26,7 @@ describe('TicketList', () => { cy.window().then((win) => { cy.stub(win, 'open').as('windowOpen'); }); - cy.get(firstRow).find('.q-btn:first').click(); + cy.get(firstRow).should('be.visible').find('.q-btn:first').click(); cy.get('@windowOpen').should('be.calledWithMatch', /\/ticket\/\d+\/sale/); }); @@ -38,6 +37,31 @@ describe('TicketList', () => { cy.get('.summaryBody').should('exist'); }); + it('filter client and create ticket', () => { + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketSearchbar'); + searchResults(); + cy.wait('@ticketSearchbar').then(({ request }) => { + const { query } = request; + expect(query).to.have.property('from'); + expect(query).to.have.property('to'); + expect(query).to.not.have.property('clientFk'); + }); + cy.intercept('GET', /\/api\/Tickets\/filter/).as('ticketFilter'); + cy.dataCy('Customer ID_input').clear('1'); + cy.dataCy('Customer ID_input').type('1101{enter}'); + cy.wait('@ticketFilter').then(({ request }) => { + const { query } = request; + expect(query).to.not.have.property('from'); + expect(query).to.not.have.property('to'); + expect(query).to.have.property('clientFk'); + }); + cy.get('[data-cy="vnTableCreateBtn"] > .q-btn__content > .q-icon').click(); + cy.dataCy('Customer_select').should('have.value', 'Bruce Wayne'); + cy.dataCy('Address_select').click(); + + cy.getOption().click(); + cy.dataCy('Address_select').should('have.value', 'Bruce Wayne'); + }); it('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); cy.dataCy('vnTableCreateBtn').click(); diff --git a/test/cypress/integration/vnComponent/UserPanel.spec.js b/test/cypress/integration/vnComponent/UserPanel.spec.js index e83d07954..25724e873 100644 --- a/test/cypress/integration/vnComponent/UserPanel.spec.js +++ b/test/cypress/integration/vnComponent/UserPanel.spec.js @@ -18,7 +18,7 @@ describe('UserPanel', () => { cy.get(userWarehouse).should('have.value', 'VNL').click(); // Actualizo la opción - getOption(3); + cy.getOption(3); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -26,7 +26,7 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userWarehouse).click(); - getOption(2); + cy.getOption(2); }); it('should notify when update user company', () => { const userCompany = @@ -39,7 +39,7 @@ describe('UserPanel', () => { cy.get(userCompany).should('have.value', 'Warehouse One').click(); //Actualizo la opción - getOption(2); + cy.getOption(2); //Compruebo la notificación cy.get('.q-notification').should('be.visible'); @@ -47,12 +47,6 @@ describe('UserPanel', () => { //Restauro el valor cy.get(userCompany).click(); - getOption(1); + cy.getOption(1); }); }); - -function getOption(index) { - cy.waitForElement('[role="listbox"]'); - const option = `[role="listbox"] .q-item:nth-child(${index})`; - cy.get(option).click(); -} diff --git a/test/cypress/integration/vnComponent/VnLocation.spec.js b/test/cypress/integration/vnComponent/VnLocation.spec.js index 986cbcaaf..ee49d6065 100644 --- a/test/cypress/integration/vnComponent/VnLocation.spec.js +++ b/test/cypress/integration/vnComponent/VnLocation.spec.js @@ -88,7 +88,7 @@ describe('VnLocation', () => { cy.get( firstOption.concat(' > .q-item__section > .q-item__label--caption'), ).should('have.text', postCodeLabel); - cy.get(firstOption).click(); + cy.getOption(); cy.get('.q-btn-group > .q-btn--standard > .q-btn__content > .q-icon').click(); cy.reload(); cy.waitForElement('.q-form'); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index a36a4f8cd..f5f1dc2d9 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -437,6 +437,21 @@ Cypress.Commands.add('clickButtonWithText', (buttonText) => { cy.get('.q-btn').contains(buttonText).click(); }); +Cypress.Commands.add('getOption', (index = 1) => { + cy.waitForElement('[role="listbox"]'); + cy.get(`[role="listbox"] .q-item:nth-child(${index})`).click(); +}); + +Cypress.Commands.add('searchBtnFilterPanel', () => { + cy.get( + '.q-scrollarea__content > .q-btn--standard > .q-btn__content > .q-icon', + ).click(); +}); + +Cypress.Commands.add('waitRequest', (alias, cb) => { + cy.wait(alias).then(cb); +}); + Cypress.Commands.add('validateDescriptor', (toCheck = {}) => { const { title, listbox = {} } = toCheck;