From 0b15c284494f6bf8e24fc6207b162a701c385495 Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Fri, 27 Dec 2024 14:09:27 +0100 Subject: [PATCH 01/21] refactor: refs #8316 used VnSection and VnCardBeta --- src/pages/Order/Card/OrderCard.vue | 32 +---- src/pages/Order/OrderList.vue | 224 +++++++++++++++-------------- src/pages/Order/locale/en.yml | 3 + src/pages/Order/locale/es.yml | 3 + src/router/modules/order.js | 148 ++++++++++--------- 5 files changed, 207 insertions(+), 203 deletions(-) diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue index 67c0f1de510..823815f59b9 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -1,38 +1,12 @@ <script setup> -import { computed } from 'vue'; -import { useRoute } from 'vue-router'; -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue'; -import OrderFilter from './OrderFilter.vue'; -import OrderSearchbar from './OrderSearchbar.vue'; -import OrderCatalogFilter from './OrderCatalogFilter.vue'; -const config = { - OrderCatalog: OrderCatalogFilter, -}; -const route = useRoute(); - -const routeName = computed(() => route.name); -const customRouteRedirectName = computed(() => { - const route = config[routeName.value]; - if (route) return null; - return 'OrderList'; -}); -const customFilterPanel = computed(() => { - const filterPanel = config[routeName.value] ?? OrderFilter; - return filterPanel; -}); </script> <template> - <VnCard + <VnCardBeta data-key="Order" base-url="Orders" :descriptor="OrderDescriptor" - :filter-panel="customFilterPanel" - :search-data-key="customRouteRedirectName" - > - <template #searchbar> - <OrderSearchbar /> - </template> - </VnCard> + /> </template> diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index baa20354152..6ce3c91a443 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -8,15 +8,14 @@ import { useRoute } from 'vue-router'; import axios from 'axios'; import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; -import OrderSearchbar from './Card/OrderSearchbar.vue'; import OrderFilter from './Card/OrderFilter.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -24,6 +23,8 @@ const tableRef = ref(); const agencyList = ref([]); const route = useRoute(); const addressOptions = ref([]); +const dataKey='OrderList'; + const columns = computed(() => [ { align: 'left', @@ -178,117 +179,126 @@ const getDateColor = (date) => { if (difference < 0) return 'bg-success'; }; </script> + <template> - <OrderSearchbar /> - <RightMenu> + <VnSection + :data-key="dataKey" + :columns="columns" + prefix="order" + :array-data-props="{ + url: 'Orders/filter', + order: ['landed DESC', 'clientFk ASC', 'id DESC'], + exprBuilder, + }" + > <template #right-panel> <OrderFilter data-key="OrderList" /> </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="OrderList" - url="Orders/filter" - :order="['landed DESC', 'clientFk ASC', 'id DESC']" - :create="{ - urlCreate: 'Orders/new', - title: t('module.cerateOrder'), - onDataSaved: (url) => { - tableRef.redirect(`${url}/catalog`); - }, - formInitialData: { - active: true, - addressId: null, - clientFk: null, - }, - }" - :user-params="{ showEmpty: false }" - :columns="columns" - :right-search="false" - redirect="order" - > - <template #column-clientFk="{ row }"> - <span class="link" @click.stop> - {{ row?.clientName }} - <CustomerDescriptorProxy :id="row?.clientFk" /> - </span> - </template> - <template #column-salesPersonFk="{ row }"> - <span class="link" @click.stop> - {{ row?.name }} - <WorkerDescriptorProxy :id="row?.salesPersonFk" /> - </span> - </template> - <template #column-landed="{ row }"> - <span v-if="getDateColor(row.landed)"> - <QChip :class="getDateColor(row.landed)" dense square> - {{ toDate(row?.landed) }} - </QChip> - </span> - </template> - <template #more-create-dialog="{ data }"> - <VnSelect - url="Clients" - :include="{ relation: 'addresses' }" - v-model="data.clientFk" - :label="t('module.customer')" - @update:model-value="(id) => fetchClientAddress(id, data)" + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :create="{ + urlCreate: 'Orders/new', + title: t('module.cerateOrder'), + onDataSaved: (url) => { + tableRef.redirect(`${url}/catalog`); + }, + formInitialData: { + active: true, + addressId: null, + clientFk: null, + }, + }" + :user-params="{ showEmpty: false }" + :columns="columns" + :right-search="false" + redirect="order" > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel> - {{ scope.opt.name }} - </QItemLabel> - <QItemLabel caption> - {{ `#${scope.opt.id}` }} - </QItemLabel> - </QItemSection> - </QItem> + <template #column-clientFk="{ row }"> + <span class="link" @click.stop> + {{ row?.clientName }} + <CustomerDescriptorProxy :id="row?.clientFk" /> + </span> </template> - </VnSelect> - <VnSelect - v-model="data.addressId" - :options="addressOptions" - :label="t('module.address')" - option-value="id" - option-label="nickname" - @update:model-value="() => fetchAgencies(data)" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel - :class="{ - 'color-vn-label': !scope.opt?.isActive, - }" - > - {{ - `${ - !scope.opt?.isActive - ? t('basicData.inactive') - : '' - } ` - }} - {{ scope.opt?.nickname }}: {{ scope.opt?.street }}, - {{ scope.opt?.city }} - </QItemLabel> - </QItemSection> - </QItem> + <template #column-salesPersonFk="{ row }"> + <span class="link" @click.stop> + {{ row?.name }} + <WorkerDescriptorProxy :id="row?.salesPersonFk" /> + </span> </template> - </VnSelect> - <VnInputDate - v-model="data.landed" - :label="t('module.landed')" - @update:model-value="() => fetchAgencies(data)" - /> - <VnSelect - v-model="data.agencyModeId" - :label="t('module.agency')" - :options="agencyList" - option-value="agencyModeFk" - option-label="agencyMode" - /> + <template #column-landed="{ row }"> + <span v-if="getDateColor(row.landed)"> + <QChip :class="getDateColor(row.landed)" dense square> + {{ toDate(row?.landed) }} + </QChip> + </span> + </template> + <template #more-create-dialog="{ data }"> + <VnSelect + url="Clients" + :include="{ relation: 'addresses' }" + v-model="data.clientFk" + :label="t('module.customer')" + @update:model-value="(id) => fetchClientAddress(id, data)" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnSelect + v-model="data.addressId" + :options="addressOptions" + :label="t('module.address')" + option-value="id" + option-label="nickname" + @update:model-value="() => fetchAgencies(data)" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel + :class="{ + 'color-vn-label': !scope.opt?.isActive, + }" + > + {{ + `${ + !scope.opt?.isActive + ? t('basicData.inactive') + : '' + } ` + }} + {{ scope.opt?.nickname }}: {{ scope.opt?.street }}, + {{ scope.opt?.city }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + <VnInputDate + v-model="data.landed" + :label="t('module.landed')" + @update:model-value="() => fetchAgencies(data)" + /> + <VnSelect + v-model="data.agencyModeId" + :label="t('module.agency')" + :options="agencyList" + option-value="agencyModeFk" + option-label="agencyMode" + /> + </template> + </VnTable> </template> - </VnTable> + </VnSection> </template> diff --git a/src/pages/Order/locale/en.yml b/src/pages/Order/locale/en.yml index 4349bc76f58..04b0f144481 100644 --- a/src/pages/Order/locale/en.yml +++ b/src/pages/Order/locale/en.yml @@ -21,3 +21,6 @@ lines: image: Image params: tagGroups: Tags +order: + search: Search orders + searchInfo: You can search orders by ticket id diff --git a/src/pages/Order/locale/es.yml b/src/pages/Order/locale/es.yml index cef06cb6d97..8366f0ec4fa 100644 --- a/src/pages/Order/locale/es.yml +++ b/src/pages/Order/locale/es.yml @@ -21,3 +21,6 @@ lines: image: Imagen params: tagGroups: Tags +order: + search: Buscar pedido + searchInfo: Buscar pedidos por el número de ticket diff --git a/src/router/modules/order.js b/src/router/modules/order.js index 77af812cf79..bdd080e7fee 100644 --- a/src/router/modules/order.js +++ b/src/router/modules/order.js @@ -1,35 +1,102 @@ import { RouterView } from 'vue-router'; +const orderCard = { + name: 'OrderCard', + path: ':id', + component: () => import('src/pages/Order/Card/OrderCard.vue'), + redirect: { name: 'OrderSummary' }, + meta: { + menu: [ + 'OrderBasicData', + 'OrderCatalog', + 'OrderVolume', + 'OrderLines', + ], + }, + children: [ + { + path: 'summary', + name: 'OrderSummary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Order/Card/OrderSummary.vue'), + }, + { + path: 'basic-data', + name: 'OrderBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Order/Card/OrderBasicData.vue'), + }, + { + path: 'catalog', + name: 'OrderCatalog', + meta: { + title: 'catalog', + icon: 'vn:basket', + }, + component: () => import('src/pages/Order/Card/OrderCatalog.vue'), + }, + { + path: 'volume', + name: 'OrderVolume', + meta: { + title: 'volume', + icon: 'vn:volume', + }, + component: () => import('src/pages/Order/Card/OrderVolume.vue'), + }, + { + path: 'line', + name: 'OrderLines', + meta: { + title: 'lines', + icon: 'vn:lines', + }, + component: () => import('src/pages/Order/Card/OrderLines.vue'), + }, + ], +}; + export default { - path: '/order', name: 'Order', + path: '/order', meta: { title: 'order', icon: 'vn:basket', moduleName: 'Order', keyBinding: 'o', + menu: ['OrderList'], }, component: RouterView, redirect: { name: 'OrderMain' }, - menus: { - main: ['OrderList'], - card: ['OrderBasicData', 'OrderCatalog', 'OrderVolume', 'OrderLines'], - }, children: [ { - path: '', name: 'OrderMain', + path: '', component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'OrderList' }, + redirect: { name: 'OrderIndexMain' }, children: [ { - path: 'list', - name: 'OrderList', - meta: { - title: 'orderList', - icon: 'view_list', - }, + path: '', + name: 'OrderIndexMain', + redirect: { name: 'OrderList' }, component: () => import('src/pages/Order/OrderList.vue'), + children: [ + { + name: 'OrderList', + path: 'list', + meta: { + title: 'orderList', + icon: 'view_list', + }, + }, + orderCard, + ], }, { path: 'create', @@ -42,58 +109,5 @@ export default { }, ], }, - { - name: 'OrderCard', - path: ':id', - component: () => import('src/pages/Order/Card/OrderCard.vue'), - redirect: { name: 'OrderSummary' }, - children: [ - { - name: 'OrderSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Order/Card/OrderSummary.vue'), - }, - { - name: 'OrderBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => import('src/pages/Order/Card/OrderBasicData.vue'), - }, - { - name: 'OrderCatalog', - path: 'catalog', - meta: { - title: 'catalog', - icon: 'vn:basket', - }, - component: () => import('src/pages/Order/Card/OrderCatalog.vue'), - }, - { - name: 'OrderVolume', - path: 'volume', - meta: { - title: 'volume', - icon: 'vn:volume', - }, - component: () => import('src/pages/Order/Card/OrderVolume.vue'), - }, - { - name: 'OrderLines', - path: 'line', - meta: { - title: 'lines', - icon: 'vn:lines', - }, - component: () => import('src/pages/Order/Card/OrderLines.vue'), - }, - ], - }, ], -}; +}; \ No newline at end of file From 5259b8200bce900b89cde248cab96c690fc7974c Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Thu, 2 Jan 2025 09:57:56 +0100 Subject: [PATCH 02/21] fix: refs #8247 fixed acls and added lost options --- src/i18n/locale/en.yml | 1 + src/i18n/locale/es.yml | 1 + .../Account/Card/AccountDescriptorMenu.vue | 99 ++++++++++++++++--- src/pages/Item/locale/en.yml | 1 + src/pages/Item/locale/es.yml | 1 + 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 4a78811e69b..a0a678e6ecf 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -346,6 +346,7 @@ globals: countryFk: Country companyFk: Company changePass: Change password + setPass: Set password deleteConfirmTitle: Delete selected elements changeState: Change state raid: 'Raid {daysInForward} days' diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2bfe7ec4beb..e448ead83dc 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -348,6 +348,7 @@ globals: countryFk: País companyFk: Empresa changePass: Cambiar contraseña + setPass: Establecer contraseña deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado raid: 'Redada {daysInForward} días' diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index 1780b424710..9bb6a7c520e 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -6,9 +6,11 @@ import { useVnConfirm } from 'composables/useVnConfirm'; import { useRoute } from 'vue-router'; import { useAcl } from 'src/composables/useAcl'; import { useArrayData } from 'src/composables/useArrayData'; +import { useState } from 'src/composables/useState'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import { useQuasar } from 'quasar'; +import { useRouter } from 'vue-router'; const $props = defineProps({ hasAccount: { @@ -21,11 +23,13 @@ const { t } = useI18n(); const { hasAccount } = toRefs($props); const { openConfirmationModal } = useVnConfirm(); const route = useRoute(); +const router = useRouter(); +const state = useState(); +const user = state.getUser(); const { notify } = useQuasar(); const account = computed(() => useArrayData('AccountId').store.data[0]); account.value.hasAccount = hasAccount.value; const entityId = computed(() => +route.params.id); - async function updateStatusAccount(active) { if (active) { await axios.post(`Accounts`, { id: entityId.value }); @@ -36,7 +40,7 @@ async function updateStatusAccount(active) { account.value.hasAccount = active; const status = active ? 'enable' : 'disable'; notify({ - message: t(`account.card.${status}Account.success`), + message: t(`account.card.actions.${status}Account.success`), type: 'positive', }); } @@ -49,6 +53,17 @@ async function updateStatusUser(active) { type: 'positive', }); } + +async function deleteAccount() { + const { data } = await axios.delete(`VnUsers/${entityId.value}`); + if (data) { + notify({ + message: t('account.card.actions.delete.success'), + type: 'positive', + }); + router.push({ name: 'AccountList' }); + } +} const showSyncDialog = ref(false); const syncPassword = ref(null); const shouldSyncPassword = ref(false); @@ -63,11 +78,18 @@ async function sync() { type: 'positive', }); } +const askOldPass = ref(false); +const changePassRef = ref(); + +const onChangePass = (oldPass) => { + askOldPass.value = oldPass; + changePassRef.value.show(); +}; </script> <template> <VnChangePassword ref="changePassRef" - :ask-old-pass="true" + :ask-old-pass="askOldPass" :submit-fn=" async (newPassword, oldPassword) => { await axios.patch(`Accounts/change-password`, { @@ -110,17 +132,57 @@ async function sync() { </VnConfirm> <QItem v-if=" - entityId == account.id && - useAcl().hasAny([{ model: 'Account', props: '*', accessType: 'WRITE' }]) + useAcl().hasAny([ + { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, + ]) " v-ripple clickable - @click="$refs.changePassRef.show()" + @click=" + openConfirmationModal( + t('account.card.actions.disableAccount.title'), + t('account.card.actions.disableAccount.subtitle'), + () => deleteAccount() + ) + " > - <QItemSection>{{ t('globals.changePass') }}</QItemSection> + <QItemSection>{{ t('globals.delete') }}</QItemSection> </QItem> <QItem - v-if="account.hasAccount" + v-if=" + useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) + " + v-ripple + clickable + @click="user.id === account.id ? onChangePass(true) : onChangePass(false)" + > + <QItemSection v-if="user.id === account.id"> + {{ t('globals.changePass') }} + </QItemSection> + <QItemSection v-else>{{ t('globals.setPass') }}</QItemSection> + </QItem> + <QItem + v-if=" + !account.hasAccount && + useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) + " + v-ripple + clickable + @click=" + openConfirmationModal( + t('account.card.actions.enableAccount.title'), + t('account.card.actions.enableAccount.subtitle'), + () => updateStatusAccount(true) + ) + " + > + <QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection> + </QItem> + <QItem + v-if=" + account.hasAccount && + useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) + " v-ripple clickable @click=" @@ -135,7 +197,12 @@ async function sync() { </QItem> <QItem - v-if="!account.active" + v-if=" + !account.active && + useAcl().hasAny([ + { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, + ]) + " v-ripple clickable @click=" @@ -149,7 +216,12 @@ async function sync() { <QItemSection>{{ t('account.card.actions.activateUser.name') }}</QItemSection> </QItem> <QItem - v-if="account.active" + v-if=" + account.active && + useAcl().hasAny([ + { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, + ]) + " v-ripple clickable @click=" @@ -162,7 +234,12 @@ async function sync() { > <QItemSection>{{ t('account.card.actions.deactivateUser.name') }}</QItemSection> </QItem> - <QItem v-ripple clickable @click="showSyncDialog = true"> + <QItem + v-if="useAcl().hasAny([{ model: 'VnRole', props: '*', accessType: 'WRITE' }])" + v-ripple + clickable + @click="showSyncDialog = true" + > <QItemSection>{{ t('account.card.actions.sync.name') }}</QItemSection> </QItem> <QSeparator /> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index ac5010a1237..aa2e5484225 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -58,6 +58,7 @@ lastEntries: pvp: PVP label: Label grouping: Grouping + packing: Packing quantity: Quantity cost: Cost kg: Kg. diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 96c8cbc9a60..33bee1ae162 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -58,6 +58,7 @@ lastEntries: pvp: PVP label: Eti. grouping: Grouping + packing: Packing quantity: Cantidad cost: Coste kg: Kg. From df1b1205cf4c369c8c38153f8e442f02b71df190 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Thu, 2 Jan 2025 12:16:05 +0100 Subject: [PATCH 03/21] feat: refs #7077 created test for VnInputTime --- .../common/__tests__/VnInputTime.spec.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/components/common/__tests__/VnInputTime.spec.js diff --git a/src/components/common/__tests__/VnInputTime.spec.js b/src/components/common/__tests__/VnInputTime.spec.js new file mode 100644 index 00000000000..1ecc01cc398 --- /dev/null +++ b/src/components/common/__tests__/VnInputTime.spec.js @@ -0,0 +1,63 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnInputTime from 'components/common/VnInputTime.vue'; + +describe('VnInputTime', () => { + let wrapper; + let vm; + + beforeAll(() => { + wrapper = createWrapper(VnInputTime, { + props: { + isOutlined: true, + timeOnly: false, // Initial props values + }, + }); + vm = wrapper.vm; + wrapper = wrapper.wrapper; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should return the correct data if isOutlined is true', () => { + expect(vm.isOutlined).toBe(true); + expect(vm.styleAttrs).toEqual({ dense: true, outlined: true, rounded: true }); + }); + + it('should return the formatted data', () => { + expect(vm.dateToTime('2022-01-01T03:23:43')).toBe('03:23'); + }); + + describe('formattedTime', () => { + it('should return the formatted time for a valid ISO date', () => { + vm.model = '2025-01-02T15:45:00'; + expect(vm.formattedTime).toBe('15:45'); + }); + + it('should handle null model value gracefully', () => { + vm.model = null; + expect(vm.formattedTime).toBe(null); + }); + + it('should handle time-only input correctly', async () => { + await wrapper.setProps({ timeOnly: true }); + vm.formattedTime = '14:30'; + expect(vm.model).toBe('14:30'); + }); + + it('should pad short time values correctly', async () => { + await wrapper.setProps({ timeOnly: true }); + vm.formattedTime = '9'; + expect(vm.model).toBe('09:00'); + }); + + it('should not update the model if the value is unchanged', () => { + vm.model = '14:30'; + const previousModel = vm.model; + vm.formattedTime = '14:30'; + expect(vm.model).toBe(previousModel); + }); + }); +}); \ No newline at end of file From 2e3e7c6a5d945ce9f8d064b1aed61cf0b3553d7b Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Fri, 3 Jan 2025 09:44:00 +0100 Subject: [PATCH 04/21] fix: refs #8316 use rightMenu --- src/pages/Order/OrderList.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 6ce3c91a443..ae1fe68bd47 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -23,7 +23,7 @@ const tableRef = ref(); const agencyList = ref([]); const route = useRoute(); const addressOptions = ref([]); -const dataKey='OrderList'; +const dataKey = 'OrderList'; const columns = computed(() => [ { @@ -188,10 +188,9 @@ const getDateColor = (date) => { :array-data-props="{ url: 'Orders/filter', order: ['landed DESC', 'clientFk ASC', 'id DESC'], - exprBuilder, }" > - <template #right-panel> + <template #rightMenu> <OrderFilter data-key="OrderList" /> </template> <template #body> @@ -278,7 +277,8 @@ const getDateColor = (date) => { : '' } ` }} - {{ scope.opt?.nickname }}: {{ scope.opt?.street }}, + {{ scope.opt?.nickname }}: + {{ scope.opt?.street }}, {{ scope.opt?.city }} </QItemLabel> </QItemSection> From bb6fae5d087575a84418549cc75ed22ca8e01a23 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Fri, 10 Jan 2025 14:08:25 +0100 Subject: [PATCH 05/21] feat: refs #7087 created CardSummary test --- .../ui/__tests__/CardSummary.spec.js | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/components/ui/__tests__/CardSummary.spec.js diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js new file mode 100644 index 00000000000..4a32e92e59a --- /dev/null +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -0,0 +1,48 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import CardSummary from 'src/components/ui/CardSummary.vue'; + +describe('CardSummary', () => { + let vm; + let wrapper; + + beforeAll(() => { + const mockApiResponse = { + data: { + data: [], + total: 0, + } + }; + + vi.spyOn(axios, 'get').mockResolvedValue(mockApiResponse); + + wrapper = createWrapper(CardSummary, { + global: { + stubs: [ + 'arrayData', + 'useArrayData', + ], + mocks: { + validate: vi.fn(), + }, + }, + propsData: { + dataKey: 'cardSummaryKey', + url: 'cardSummaryUrl', + }, + }); + + vm = wrapper.vm; + wrapper = wrapper.wrapper; + + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch data', async () => { + expect(wrapper.emitted('fetch')); + }); + +}); \ No newline at end of file From 1295f394f393a6a842f39f94fd0adc034f66b603 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Mon, 13 Jan 2025 08:11:41 +0100 Subject: [PATCH 06/21] feat: refs #7087 added more test cases --- .../ui/__tests__/CardSummary.spec.js | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js index 4a32e92e59a..7a979c2ea6d 100644 --- a/src/components/ui/__tests__/CardSummary.spec.js +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -1,48 +1,63 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import CardSummary from 'src/components/ui/CardSummary.vue'; describe('CardSummary', () => { let vm; let wrapper; - + beforeAll(() => { - const mockApiResponse = { - data: { - data: [], - total: 0, - } - }; - - vi.spyOn(axios, 'get').mockResolvedValue(mockApiResponse); + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + }); + beforeEach(() => { wrapper = createWrapper(CardSummary, { - global: { - stubs: [ - 'arrayData', - 'useArrayData', - ], - mocks: { - validate: vi.fn(), - }, - }, propsData: { dataKey: 'cardSummaryKey', url: 'cardSummaryUrl', + filter: 'cardFilter', }, }); - vm = wrapper.vm; wrapper = wrapper.wrapper; - }); - + afterEach(() => { vi.clearAllMocks(); }); - it('should fetch data', async () => { - expect(wrapper.emitted('fetch')); + it('should fetch data correctly', async () => { + const fetchSpy = vi + .spyOn(vm.arrayData, 'fetch') + .mockResolvedValue({ data: [{ id: 1, name: 'Test Entity' }] }); + await vm.fetch(); + + expect(fetchSpy).toHaveBeenCalledWith({ append: false, updateRouter: false }); + expect(wrapper.emitted('onFetch')).toBeTruthy(); + expect(vm.isLoading).toBe(false); + }); + + it('should set correct props to the store', () => { + expect(vm.store.url).toEqual('cardSummaryUrl'); + expect(vm.store.filter).toEqual('cardFilter'); + }); + + it('should compute entity correctly from store data', () => { + vm.store.data = [{ id: 1, name: 'Entity 1' }]; + expect(vm.entity).toEqual({ id: 1, name: 'Entity 1' }); + }); + + it('should handle empty data gracefully', () => { + vm.store.data = []; + expect(vm.entity).toBeUndefined(); + }); + + it('should respond to prop changes and refetch data', async () => { + const fetchSpy = vi.spyOn(vm.arrayData, 'fetch'); + await wrapper.setProps({ url: 'newUrl', filter: { key: 'newValue' } }); + + expect(fetchSpy).toHaveBeenCalled(); + expect(vm.store.url).toBe('newUrl'); + expect(vm.store.filter).toEqual({ key: 'newValue' }); }); - }); \ No newline at end of file From 1af67db6443836ba228aed19f100c20f45617c63 Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Mon, 13 Jan 2025 08:37:01 +0100 Subject: [PATCH 07/21] refactor: refs #8316 move order localization --- src/i18n/locale/en.yml | 21 --------------------- src/i18n/locale/es.yml | 24 ------------------------ src/pages/Order/locale/en.yml | 20 ++++++++++++++++++++ src/pages/Order/locale/es.yml | 23 +++++++++++++++++++++++ 4 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 4a78811e69b..0c90144ad7e 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -558,27 +558,6 @@ parking: searchBar: info: You can search by parking code label: Search parking... -order: - field: - salesPersonFk: Sales Person - form: - clientFk: Client - addressFk: Address - agencyModeFk: Agency - list: - newOrder: New Order - summary: - basket: Basket - notConfirmed: Not confirmed - created: Created - createdFrom: Created From - address: Address - total: Total - items: Items - orderTicketList: Order Ticket List - amount: Amount - confirm: Confirm - confirmLines: Confirm lines department: chat: Chat bossDepartment: Boss Department diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2bfe7ec4beb..3f3eebc4e45 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -544,30 +544,6 @@ invoiceOut: comercial: Comercial errors: downloadCsvFailed: Error al descargar CSV -order: - field: - salesPersonFk: Comercial - form: - clientFk: Cliente - addressFk: Dirección - agencyModeFk: Agencia - list: - newOrder: Nuevo Pedido - summary: - basket: Cesta - notConfirmed: No confirmada - created: Creado - createdFrom: Creado desde - address: Dirección - total: Total - vat: IVA - state: Estado - alias: Alias - items: Artículos - orderTicketList: Tickets del pedido - amount: Monto - confirm: Confirmar - confirmLines: Confirmar lineas shelving: list: parking: Parking diff --git a/src/pages/Order/locale/en.yml b/src/pages/Order/locale/en.yml index 04b0f144481..14e41c55983 100644 --- a/src/pages/Order/locale/en.yml +++ b/src/pages/Order/locale/en.yml @@ -22,5 +22,25 @@ lines: params: tagGroups: Tags order: + field: + salesPersonFk: Sales Person + form: + clientFk: Client + addressFk: Address + agencyModeFk: Agency + list: + newOrder: New Order + summary: + basket: Basket + notConfirmed: Not confirmed + created: Created + createdFrom: Created From + address: Address + total: Total + items: Items + orderTicketList: Order Ticket List + amount: Amount + confirm: Confirm + confirmLines: Confirm lines search: Search orders searchInfo: You can search orders by ticket id diff --git a/src/pages/Order/locale/es.yml b/src/pages/Order/locale/es.yml index 8366f0ec4fa..44e243ad117 100644 --- a/src/pages/Order/locale/es.yml +++ b/src/pages/Order/locale/es.yml @@ -22,5 +22,28 @@ lines: params: tagGroups: Tags order: + field: + salesPersonFk: Comercial + form: + clientFk: Cliente + addressFk: Dirección + agencyModeFk: Agencia + list: + newOrder: Nuevo Pedido + summary: + basket: Cesta + notConfirmed: No confirmada + created: Creado + createdFrom: Creado desde + address: Dirección + total: Total + vat: IVA + state: Estado + alias: Alias + items: Artículos + orderTicketList: Tickets del pedido + amount: Monto + confirm: Confirmar + confirmLines: Confirmar lineas search: Buscar pedido searchInfo: Buscar pedidos por el número de ticket From ed0730df22a40abccb820313b12c7c403927cd14 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 13 Jan 2025 12:17:55 +0100 Subject: [PATCH 08/21] feat: refs #8197 useHasContent and use in VnSection and RightMenu --- src/components/common/RightMenu.vue | 18 +++--------------- src/components/common/VnSection.vue | 8 ++++++-- src/composables/useHasContent.js | 24 ++++++++++++++++++++++++ src/pages/Order/Card/OrderCatalog.vue | 22 ++++++++++++---------- 4 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 src/composables/useHasContent.js diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 32dc2874dce..9512d32d419 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -1,29 +1,17 @@ <script setup> -import { ref, onMounted, useSlots } from 'vue'; +import { onMounted, useSlots } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; import { useQuasar } from 'quasar'; +import { useHasContent } from 'src/composables/useHasContent'; const { t } = useI18n(); const quasar = useQuasar(); const stateStore = useStateStore(); const slots = useSlots(); -const hasContent = ref(false); -const rightPanel = ref(null); +const hasContent = useHasContent('#right-panel'); onMounted(() => { - rightPanel.value = document.querySelector('#right-panel'); - if (!rightPanel.value) return; - - const observer = new MutationObserver(() => { - hasContent.value = rightPanel.value.childNodes.length; - }); - - observer.observe(rightPanel.value, { - subtree: true, - childList: true, - attributes: true, - }); if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) stateStore.rightDrawer = false; }); diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index edd8d3dfa98..11fd6013f57 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -2,9 +2,10 @@ import RightMenu from './RightMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount, computed } from 'vue'; +import { onBeforeMount, computed, ref } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useRoute } from 'vue-router'; +import { useHasContent } from 'src/composables/useHasContent'; const $props = defineProps({ section: { @@ -55,6 +56,8 @@ const isMainSection = computed(() => { } return isSame; }); +const searchbarId = 'section-searchbar'; +const hasContent = useHasContent(`#${searchbarId}`); onBeforeMount(() => { if ($props.dataKey) @@ -69,12 +72,13 @@ onBeforeMount(() => { <template> <slot name="searchbar"> <VnSearchbar - v-if="searchBar" + v-if="searchBar && !hasContent" v-bind="arrayDataProps" :data-key="dataKey" :label="$t(`${prefix}.search`)" :info="$t(`${prefix}.searchInfo`)" /> + <div :id="searchbarId"></div> </slot> <RightMenu> diff --git a/src/composables/useHasContent.js b/src/composables/useHasContent.js new file mode 100644 index 00000000000..8ab01837605 --- /dev/null +++ b/src/composables/useHasContent.js @@ -0,0 +1,24 @@ +import { onMounted, ref } from 'vue'; + +export function useHasContent(selector) { + const container = ref({}); + const hasContent = ref(); + + onMounted(() => { + container.value = document.querySelector(selector); + if (!container.value) return; + + const observer = new MutationObserver(() => { + if (document.querySelector(selector)) + hasContent.value = !!container.value.childNodes.length; + }); + + observer.observe(container.value, { + subtree: true, + childList: true, + attributes: true, + }); + }); + + return hasContent; +} diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index da2e88aa990..7c454ed0f4f 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -78,16 +78,18 @@ watch( </script> <template> - <VnSearchbar - :data-key="dataKey" - :user-params="catalogParams" - :static-params="['orderFk', 'orderBy']" - :redirect="false" - url="Orders/CatalogFilter" - :label="t('Search items')" - :info="t('You can search items by name or id')" - :search-remove-params="false" - /> + <Teleport to="#section-searchbar" v-if="stateStore.isHeaderMounted()"> + <VnSearchbar + :data-key="dataKey" + :user-params="catalogParams" + :static-params="['orderFk', 'orderBy']" + :redirect="false" + url="Orders/CatalogFilter" + :label="t('Search items')" + :info="t('You can search items by name or id')" + :search-remove-params="false" + /> + </Teleport> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <OrderCatalogFilter :data-key="dataKey" From dd335ef409f31546854cd6169157238c0ebce034 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 13 Jan 2025 12:18:52 +0100 Subject: [PATCH 09/21] fix: refs #8197 not use yet --- src/pages/Order/Card/OrderCatalog.vue | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index 7c454ed0f4f..da2e88aa990 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -78,18 +78,16 @@ watch( </script> <template> - <Teleport to="#section-searchbar" v-if="stateStore.isHeaderMounted()"> - <VnSearchbar - :data-key="dataKey" - :user-params="catalogParams" - :static-params="['orderFk', 'orderBy']" - :redirect="false" - url="Orders/CatalogFilter" - :label="t('Search items')" - :info="t('You can search items by name or id')" - :search-remove-params="false" - /> - </Teleport> + <VnSearchbar + :data-key="dataKey" + :user-params="catalogParams" + :static-params="['orderFk', 'orderBy']" + :redirect="false" + url="Orders/CatalogFilter" + :label="t('Search items')" + :info="t('You can search items by name or id')" + :search-remove-params="false" + /> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <OrderCatalogFilter :data-key="dataKey" From e5d8bfcf3adf3e292fa163226412720195815022 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Tue, 14 Jan 2025 07:37:17 +0100 Subject: [PATCH 10/21] refactor: refs #8247 use new acl for sysadmin --- .../Account/Card/AccountDescriptorMenu.vue | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index 9bb6a7c520e..c1df2115652 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -1,6 +1,6 @@ <script setup> import axios from 'axios'; -import { computed, ref, toRefs } from 'vue'; +import { computed, onMounted, ref, toRefs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; import { useRoute } from 'vue-router'; @@ -30,6 +30,8 @@ const { notify } = useQuasar(); const account = computed(() => useArrayData('AccountId').store.data[0]); account.value.hasAccount = hasAccount.value; const entityId = computed(() => +route.params.id); +const hasitManagementAccess = ref(); +const hasSysadminAccess = ref(); async function updateStatusAccount(active) { if (active) { await axios.post(`Accounts`, { id: entityId.value }); @@ -85,6 +87,15 @@ const onChangePass = (oldPass) => { askOldPass.value = oldPass; changePassRef.value.show(); }; + +onMounted(() => { + hasitManagementAccess.value = useAcl().hasAny([ + { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, + ]); + hasSysadminAccess.value = useAcl().hasAny([ + { model: 'VnUser', props: 'adminUser', accessType: 'WRITE' }, + ]); +}); </script> <template> <VnChangePassword @@ -131,11 +142,7 @@ const onChangePass = (oldPass) => { </template> </VnConfirm> <QItem - v-if=" - useAcl().hasAny([ - { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, - ]) - " + v-if="hasitManagementAccess" v-ripple clickable @click=" @@ -149,9 +156,7 @@ const onChangePass = (oldPass) => { <QItemSection>{{ t('globals.delete') }}</QItemSection> </QItem> <QItem - v-if=" - useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) - " + v-if="hasSysadminAccess" v-ripple clickable @click="user.id === account.id ? onChangePass(true) : onChangePass(false)" @@ -162,10 +167,7 @@ const onChangePass = (oldPass) => { <QItemSection v-else>{{ t('globals.setPass') }}</QItemSection> </QItem> <QItem - v-if=" - !account.hasAccount && - useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) - " + v-if="!account.hasAccount && hasSysadminAccess" v-ripple clickable @click=" @@ -179,10 +181,7 @@ const onChangePass = (oldPass) => { <QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection> </QItem> <QItem - v-if=" - account.hasAccount && - useAcl().hasAny([{ model: 'AccountConfig', props: '*', accessType: 'WRITE' }]) - " + v-if="account.hasAccount && hasSysadminAccess" v-ripple clickable @click=" @@ -197,12 +196,7 @@ const onChangePass = (oldPass) => { </QItem> <QItem - v-if=" - !account.active && - useAcl().hasAny([ - { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, - ]) - " + v-if="!account.active && hasitManagementAccess" v-ripple clickable @click=" @@ -216,12 +210,7 @@ const onChangePass = (oldPass) => { <QItemSection>{{ t('account.card.actions.activateUser.name') }}</QItemSection> </QItem> <QItem - v-if=" - account.active && - useAcl().hasAny([ - { model: 'VnUser', props: 'higherPrivileges', accessType: 'WRITE' }, - ]) - " + v-if="account.active && hasitManagementAccess" v-ripple clickable @click=" From 6e5b70301867d3cd78ea697574e44daabfdb3650 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Tue, 14 Jan 2025 07:47:33 +0100 Subject: [PATCH 11/21] fix: refs #8247 conflicts --- .../Account/Card/AccountDescriptorMenu.vue | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index 151c1bf24a7..ccf029e4493 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -1,43 +1,45 @@ <script setup> import axios from 'axios'; -import { computed, onMounted, onMounted, ref } from 'vue'; +import { computed, onMounted, ref, toRefs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; +import { useRoute } from 'vue-router'; import { useAcl } from 'src/composables/useAcl'; import { useArrayData } from 'src/composables/useArrayData'; import { useState } from 'src/composables/useState'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; -import VnChangePassword from 'src/components/common/VnChangePassword.vue'; -import useNotify from 'src/composables/useNotify.js'; -import useHasAccount from 'src/composables/useHasAccount.js'; import VnInputPassword from 'src/components/common/VnInputPassword.vue'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; +import { useQuasar } from 'quasar'; import { useRouter } from 'vue-router'; const $props = defineProps({ - entityId: { - type: Number, + hasAccount: { + type: Boolean, + default: false, required: true, }, }); const { t } = useI18n(); +const { hasAccount } = toRefs($props); const { openConfirmationModal } = useVnConfirm(); +const route = useRoute(); const router = useRouter(); const state = useState(); const user = state.getUser(); -const { notify } = useNotify(); +const { notify } = useQuasar(); const account = computed(() => useArrayData('AccountId').store.data[0]); - -onMounted(async () => { - account.value.hasAccount = await useHasAccount($props.entityId); -}); +account.value.hasAccount = hasAccount.value; +const entityId = computed(() => +route.params.id); const hasitManagementAccess = ref(); const hasSysadminAccess = ref(); + async function updateStatusAccount(active) { if (active) { - await axios.post(`Accounts`, { id: $props.entityId }); + await axios.post(`Accounts`, { id: entityId.value }); } else { - await axios.delete(`Accounts/${$props.entityId}`); + await axios.delete(`Accounts/${entityId.value}`); } account.value.hasAccount = active; @@ -48,7 +50,7 @@ async function updateStatusAccount(active) { }); } async function updateStatusUser(active) { - await axios.patch(`VnUsers/${$props.entityId}`, { active }); + await axios.patch(`VnUsers/${entityId.value}`, { active }); account.value.active = active; const status = active ? 'activate' : 'deactivate'; notify({ From bd9c2ad4dc679d97c8e2744d7e269d939e2207da Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Tue, 14 Jan 2025 07:57:54 +0100 Subject: [PATCH 12/21] refactor: refs #8316 remove unused OrderSearchbar component --- src/pages/Order/Card/OrderSearchbar.vue | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/pages/Order/Card/OrderSearchbar.vue diff --git a/src/pages/Order/Card/OrderSearchbar.vue b/src/pages/Order/Card/OrderSearchbar.vue deleted file mode 100644 index fa30a097fd2..00000000000 --- a/src/pages/Order/Card/OrderSearchbar.vue +++ /dev/null @@ -1,22 +0,0 @@ -<script setup> -import { useI18n } from 'vue-i18n'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; - -const { t } = useI18n(); -</script> - -<template> - <VnSearchbar - data-key="OrderList" - url="Orders/filter" - :label="t('Search order')" - :info="t('Search orders by ticket id')" - /> -</template> - -<style scoped lang="scss"></style> -<i18n> -es: - Search order: Buscar orden - Search orders by ticket id: Buscar pedido por id ticket -</i18n> From 8b7337f9eec26821305bf4a749340240b72f0607 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 14 Jan 2025 08:37:10 +0100 Subject: [PATCH 13/21] fix: vnlocation --- src/components/common/VnLocation.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index f5822218727..3ede24274d9 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -2,7 +2,7 @@ import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue'; import VnSelectDialog from 'components/common/VnSelectDialog.vue'; import { useI18n } from 'vue-i18n'; -import { computed } from 'vue'; +import { ref, watch } from 'vue'; import { useAttrs } from 'vue'; import { useRequired } from 'src/composables/useRequired'; const { t } = useI18n(); @@ -16,6 +16,14 @@ const props = defineProps({ }, }); +watch( + () => props.location, + (newValue) => { + if (!modelValue.value) return; + modelValue.value = formatLocation(newValue) ?? null; + } +); + const mixinRules = [requiredFieldRule]; const locationProperties = [ 'postcode', @@ -43,9 +51,7 @@ const formatLocation = (obj, properties = locationProperties) => { return filteredParts.join(', '); }; -const modelValue = computed(() => - props.location ? formatLocation(props.location, locationProperties) : null -); +const modelValue = ref(props.location ? formatLocation(props.location) : null); function showLabel(data) { const dataProperties = [ From 54867597aaec1f9a7ae3a9e7937eb6dd3f7a32d2 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 15 Jan 2025 08:52:43 +0100 Subject: [PATCH 14/21] feat: refs #7087 added new test --- src/components/ui/__tests__/CardSummary.spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js index 7a979c2ea6d..b114797ef92 100644 --- a/src/components/ui/__tests__/CardSummary.spec.js +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -1,6 +1,7 @@ import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; import { createWrapper, axios } from 'app/test/vitest/helper'; import CardSummary from 'src/components/ui/CardSummary.vue'; +import * as vueRouter from 'vue-router'; describe('CardSummary', () => { let vm; @@ -10,6 +11,14 @@ describe('CardSummary', () => { vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); }); + vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ + query: {}, + params: {}, + meta: { moduleName: 'mockName' }, + path: 'mockName/1/summary', + name: 'CardSummary', + }); + beforeEach(() => { wrapper = createWrapper(CardSummary, { propsData: { @@ -60,4 +69,8 @@ describe('CardSummary', () => { expect(vm.store.url).toBe('newUrl'); expect(vm.store.filter).toEqual({ key: 'newValue' }); }); + + it('should return true if route path ends with /summary' , () => { + expect(vm.isSummary).toBe(true); + }); }); \ No newline at end of file From 6ecbaf1a0412538a3eaa06ef269385231adecbe1 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 15 Jan 2025 08:55:16 +0100 Subject: [PATCH 15/21] Merge branch '7087-testCardSummary' of https: refs #7087//gitea.verdnatura.es/verdnatura/salix-front into 7087-testCardSummary --- src/components/RegularizeStockForm.vue | 2 +- src/components/ui/CardDescriptor.vue | 32 +- src/components/ui/CardSummary.vue | 24 +- src/components/ui/VnMoreOptions.vue | 20 ++ src/composables/useHasAccount.js | 6 + src/pages/Account/Card/AccountDescriptor.vue | 25 +- .../Account/Card/AccountDescriptorMenu.vue | 29 +- src/pages/Account/Card/AccountMailAlias.vue | 13 +- .../Account/Card/AccountMailForwarding.vue | 21 +- src/pages/Account/Card/AccountSummary.vue | 5 +- src/pages/Claim/Card/ClaimCard.vue | 18 +- src/pages/Claim/Card/ClaimSummary.vue | 4 + src/pages/Claim/ClaimList.vue | 76 ++--- src/pages/Claim/locale/en.yml | 2 + src/pages/Claim/locale/es.yml | 4 +- src/pages/Customer/Card/CustomerSummary.vue | 9 +- .../components/CustomerAddressEdit.vue | 8 +- src/pages/Entry/Card/EntryDescriptor.vue | 16 +- src/pages/Entry/Card/EntryDescriptorMenu.vue | 22 ++ src/pages/Entry/Card/EntrySummary.vue | 9 +- src/pages/Entry/EntryList.vue | 1 - src/pages/Entry/locale/en.yml | 1 + src/pages/Entry/locale/es.yml | 1 + .../InvoiceIn/Card/InvoiceInBasicData.vue | 2 +- .../InvoiceIn/Card/InvoiceInDescriptor.vue | 274 ++---------------- .../Card/InvoiceInDescriptorMenu.vue | 206 +++++++++++++ src/pages/InvoiceIn/Card/InvoiceInSummary.vue | 72 ++--- src/pages/InvoiceIn/InvoiceInCreate.vue | 6 +- src/pages/InvoiceIn/InvoiceInFilter.vue | 2 +- src/pages/InvoiceIn/InvoiceInList.vue | 20 +- src/pages/InvoiceIn/locale/en.yml | 24 +- src/pages/InvoiceIn/locale/es.yml | 26 +- .../InvoiceOut/Card/InvoiceOutSummary.vue | 4 + src/pages/InvoiceOut/InvoiceOutList.vue | 2 +- src/pages/Item/Card/ItemDescriptor.vue | 42 +-- src/pages/Item/Card/ItemDescriptorImage.vue | 1 - src/pages/Item/Card/ItemDescriptorMenu.vue | 44 +++ src/pages/Item/Card/ItemSummary.vue | 8 +- src/pages/Item/ItemList.vue | 3 +- src/pages/Item/ItemRequest.vue | 26 +- src/pages/Item/locale/en.yml | 1 + src/pages/Item/locale/es.yml | 1 + src/pages/Order/Card/OrderDescriptor.vue | 4 - src/pages/Order/Card/OrderSummary.vue | 4 + src/pages/Route/Card/RouteDescriptorMenu.vue | 2 +- src/pages/Route/Card/RouteSummary.vue | 4 + src/pages/Route/Roadmap/RoadmapSummary.vue | 4 + .../Shelving/Card/ShelvingDescriptorMenu.vue | 7 +- src/pages/Shelving/Card/ShelvingSummary.vue | 13 +- src/pages/Ticket/Card/TicketSummary.vue | 18 +- src/pages/Travel/Card/TravelSummary.vue | 5 +- src/pages/Worker/Card/WorkerDescriptor.vue | 55 +--- .../Worker/Card/WorkerDescriptorMenu.vue | 65 +++++ src/pages/Worker/Card/WorkerSummary.vue | 4 + src/pages/Zone/Card/ZoneBasicData.vue | 13 +- .../Zone/Card/ZoneEventInclusionForm.vue | 10 +- src/pages/Zone/Card/ZoneSummary.vue | 4 + src/pages/Zone/locale/es.yml | 1 + src/router/modules/claim.js | 223 +++++++------- src/stores/invoiceOutGlobal.js | 44 ++- .../invoiceIn/invoiceInCorrective.spec.js | 4 +- 61 files changed, 873 insertions(+), 723 deletions(-) create mode 100644 src/components/ui/VnMoreOptions.vue create mode 100644 src/composables/useHasAccount.js create mode 100644 src/pages/Entry/Card/EntryDescriptorMenu.vue create mode 100644 src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue create mode 100644 src/pages/Item/Card/ItemDescriptorMenu.vue create mode 100644 src/pages/Worker/Card/WorkerDescriptorMenu.vue diff --git a/src/components/RegularizeStockForm.vue b/src/components/RegularizeStockForm.vue index 3cd18d1c87e..91a2e5d39ca 100644 --- a/src/components/RegularizeStockForm.vue +++ b/src/components/RegularizeStockForm.vue @@ -44,7 +44,7 @@ const onDataSaved = (data) => { <FormModelPopup url-create="Items/regularize" model="Items" - :title="t('Regularize stock')" + :title="t('item.regularizeStock')" :form-initial-data="regularizeFormData" @on-data-saved="onDataSaved($event)" > diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f4c0091d2c1..cf217555a71 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: { @@ -47,7 +48,6 @@ let store; let entity; const isLoading = ref(false); const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); -const menuRef = ref(); defineExpose({ getData }); onBeforeMount(async () => { @@ -159,25 +159,11 @@ const toModule = computed(() => </QTooltip> </QBtn> </RouterLink> - <QBtn - v-if="$slots.menu" - color="white" - dense - flat - icon="more_vert" - round - size="md" - data-cy="descriptor-more-opts" - > - <QTooltip> - {{ t('components.cardDescriptor.moreOptions') }} - </QTooltip> - <QMenu :ref="menuRef"> - <QList> - <slot name="menu" :entity="entity" :menu-ref="menuRef" /> - </QList> - </QMenu> - </QBtn> + <VnMoreOptions v-if="$slots.menu"> + <template #menu="{ menuRef }"> + <slot name="menu" :entity="entity" :menu-ref="menuRef" /> + </template> + </VnMoreOptions> </div> <slot name="before" /> <div class="body q-py-sm"> @@ -222,8 +208,8 @@ const toModule = computed(() => /> </template> -<style lang="scss" scoped> -:deep(.body) { +<style lang="scss"> +.body { background-color: var(--vn-section-color); .text-h5 { font-size: 20px; @@ -262,7 +248,9 @@ const toModule = computed(() => } } } +</style> +<style lang="scss" scoped> .title { overflow: hidden; text-overflow: ellipsis; diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 8395dfd73dd..52427f3fec9 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -1,10 +1,11 @@ <script setup> -import { ref, computed, watch, onBeforeMount, onMounted } from 'vue'; +import { ref, computed, watch, onBeforeMount } from 'vue'; import { useRoute } from 'vue-router'; import SkeletonSummary from 'components/ui/SkeletonSummary.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; import { useArrayData } from 'src/composables/useArrayData'; import { isDialogOpened } from 'src/filters'; -import { useStateStore } from 'src/stores/useStateStore'; +import VnMoreOptions from './VnMoreOptions.vue'; const props = defineProps({ url: { @@ -40,7 +41,6 @@ const { store } = arrayData; const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data)); const isLoading = ref(false); -const stateStore = useStateStore(); defineExpose({ entity, fetch, @@ -52,9 +52,6 @@ onBeforeMount(async () => { watch(props, async () => await fetch()); }); -onMounted(() => { - stateStore.rightDrawerChangeValue(false); -}); async function fetch() { store.url = props.url; store.filter = props.filter ?? {}; @@ -64,6 +61,7 @@ async function fetch() { isLoading.value = false; } </script> + <template> <div class="summary container"> <QCard class="cardSummary"> @@ -84,11 +82,16 @@ async function fetch() { <span v-else></span> </slot> <slot name="header" :entity="entity" dense> - {{ entity.id + ' - ' + entity.name }} - </slot> - <slot name="header-right" :entity="entity"> - <span></span> + <VnLv :label="`${entity.id} -`" :value="entity.name" /> </slot> + <span class="row no-wrap"> + <slot name="header-right" :entity="entity" /> + <VnMoreOptions v-if="$slots.menu && isDialogOpened()"> + <template #menu="{ menuRef }"> + <slot name="menu" :entity="entity" :menu-ref="menuRef" /> + </template> + </VnMoreOptions> + </span> </div> <div class="summaryBody row q-mb-md"> <slot name="body" :entity="entity" /> @@ -97,6 +100,7 @@ async function fetch() { </QCard> </div> </template> + <style lang="scss"> .summary.container { display: flex; diff --git a/src/components/ui/VnMoreOptions.vue b/src/components/ui/VnMoreOptions.vue new file mode 100644 index 00000000000..39e84be2bab --- /dev/null +++ b/src/components/ui/VnMoreOptions.vue @@ -0,0 +1,20 @@ +<template> + <QBtn + color="white" + dense + flat + icon="more_vert" + round + size="md" + data-cy="descriptor-more-opts" + > + <QTooltip> + {{ $t('components.cardDescriptor.moreOptions') }} + </QTooltip> + <QMenu ref="menuRef"> + <QList> + <slot name="menu" :menu-ref="$refs.menuRef" /> + </QList> + </QMenu> + </QBtn> +</template> diff --git a/src/composables/useHasAccount.js b/src/composables/useHasAccount.js new file mode 100644 index 00000000000..1014bedf3b1 --- /dev/null +++ b/src/composables/useHasAccount.js @@ -0,0 +1,6 @@ +import axios from 'axios'; + +export default async (id) => { + const { data } = await axios.get(`Accounts/${id}/exists`); + return data.exists; +}; diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 8af817fcc00..4e5328de66c 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -1,13 +1,13 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; -import FetchData from 'src/components/FetchData.vue'; import VnImg from 'src/components/ui/VnImg.vue'; +import useHasAccount from 'src/composables/useHasAccount.js'; const $props = defineProps({ id: { @@ -23,6 +23,7 @@ const entityId = computed(() => { return $props.id || route.params.id; }); const data = ref(useCardDescription()); +const hasAccount = ref(); const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id)); const filter = { @@ -30,18 +31,16 @@ const filter = { fields: ['id', 'nickname', 'name', 'role'], include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; -const hasAccount = ref(false); + +onMounted(async () => { + hasAccount.value = await useHasAccount(entityId.value); +}); </script> <template> - <FetchData - :url="`Accounts/${entityId}/exists`" - auto-load - @on-fetch="(data) => (hasAccount = data.exists)" - /> <CardDescriptor ref="descriptor" - url="VnUsers/preview" + :url="`VnUsers/preview`" :filter="filter" module="Account" @on-fetch="setData" @@ -50,7 +49,7 @@ const hasAccount = ref(false); :subtitle="data.subtitle" > <template #menu> - <AccountDescriptorMenu :has-account="hasAccount" /> + <AccountDescriptorMenu :entity-id="entityId" /> </template> <template #before> <VnImg :id="entityId" collection="user" resolution="520x520" class="photo"> @@ -74,7 +73,7 @@ const hasAccount = ref(false); <VnLv :label="t('account.card.nickname')" :value="entity.name" /> <VnLv :label="t('account.card.role')" :value="entity.role.name" /> </template> - <template #icons="{ entity }"> + <template #actions="{ entity }"> <QCardActions class="q-gutter-x-md"> <QIcon v-if="!entity.active" @@ -82,7 +81,7 @@ const hasAccount = ref(false); name="vn:disabled" flat round - size="xs" + size="sm" class="fill-icon" > <QTooltip>{{ t('account.card.deactivated') }}</QTooltip> @@ -93,7 +92,7 @@ const hasAccount = ref(false); v-if="hasAccount" flat round - size="xs" + size="sm" class="fill-icon" > <QTooltip>{{ t('account.card.enabled') }}</QTooltip> diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index c091962fcac..aa49dabe887 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -1,37 +1,37 @@ <script setup> import axios from 'axios'; -import { computed, ref, toRefs } from 'vue'; +import { computed, onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; -import { useRoute } from 'vue-router'; import { useAcl } from 'src/composables/useAcl'; import { useArrayData } from 'src/composables/useArrayData'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; -import { useQuasar } from 'quasar'; +import useNotify from 'src/composables/useNotify.js'; +import useHasAccount from 'src/composables/useHasAccount.js'; import VnInputPassword from 'src/components/common/VnInputPassword.vue'; const $props = defineProps({ - hasAccount: { - type: Boolean, - default: false, + entityId: { + type: Number, required: true, }, }); + const { t } = useI18n(); -const { hasAccount } = toRefs($props); const { openConfirmationModal } = useVnConfirm(); -const route = useRoute(); -const { notify } = useQuasar(); +const { notify } = useNotify(); const account = computed(() => useArrayData('AccountId').store.data[0]); -account.value.hasAccount = hasAccount.value; -const entityId = computed(() => +route.params.id); + +onMounted(async () => { + account.value.hasAccount = await useHasAccount($props.entityId); +}); async function updateStatusAccount(active) { if (active) { - await axios.post(`Accounts`, { id: entityId.value }); + await axios.post(`Accounts`, { id: $props.entityId }); } else { - await axios.delete(`Accounts/${entityId.value}`); + await axios.delete(`Accounts/${$props.entityId}`); } account.value.hasAccount = active; @@ -42,7 +42,7 @@ async function updateStatusAccount(active) { }); } async function updateStatusUser(active) { - await axios.patch(`VnUsers/${entityId.value}`, { active }); + await axios.patch(`VnUsers/${$props.entityId}`, { active }); account.value.active = active; const status = active ? 'activate' : 'deactivate'; notify({ @@ -105,6 +105,7 @@ async function sync() { class="full-width" clearable clear-icon="close" + type="password" /> </template> </VnConfirm> diff --git a/src/pages/Account/Card/AccountMailAlias.vue b/src/pages/Account/Card/AccountMailAlias.vue index 8d3bd3b677a..efd2b481b2a 100644 --- a/src/pages/Account/Card/AccountMailAlias.vue +++ b/src/pages/Account/Card/AccountMailAlias.vue @@ -9,6 +9,7 @@ import AccountMailAliasCreateForm from './AccountMailAliasCreateForm.vue'; import { useVnConfirm } from 'composables/useVnConfirm'; import { useArrayData } from 'composables/useArrayData'; import useNotify from 'src/composables/useNotify.js'; +import useHasAccount from 'src/composables/useHasAccount.js'; import axios from 'axios'; const { t } = useI18n(); @@ -50,16 +51,6 @@ const columns = computed(() => [ }, ]); -const fetchAccountExistence = async () => { - try { - const { data } = await axios.get(`Accounts/${route.params.id}/exists`); - return data.exists; - } catch (error) { - console.error('Error fetching account existence', error); - return false; - } -}; - const deleteMailAlias = async (row) => { await axios.delete(`${urlPath}/${row.id}`); fetchMailAliases(); @@ -79,7 +70,7 @@ const fetchMailAliases = async () => { const getAccountData = async (reload = true) => { loading.value = true; - hasAccount.value = await fetchAccountExistence(); + hasAccount.value = await useHasAccount(route.params.id); if (!hasAccount.value) { loading.value = false; store.data = []; diff --git a/src/pages/Account/Card/AccountMailForwarding.vue b/src/pages/Account/Card/AccountMailForwarding.vue index 30849d44a76..0e5ae3122f3 100644 --- a/src/pages/Account/Card/AccountMailForwarding.vue +++ b/src/pages/Account/Card/AccountMailForwarding.vue @@ -9,6 +9,7 @@ import VnRow from 'components/ui/VnRow.vue'; import axios from 'axios'; import { useStateStore } from 'stores/useStateStore'; import useNotify from 'src/composables/useNotify.js'; +import useHasAccount from 'src/composables/useHasAccount'; const { t } = useI18n(); const route = useRoute(); @@ -30,23 +31,9 @@ const hasDataChanged = computed( initialData.value.hasData !== hasData.value ); -const fetchAccountExistence = async () => { - try { - const { data } = await axios.get(`Accounts/${route.params.id}/exists`); - return data.exists; - } catch (error) { - console.error('Error fetching account existence', error); - return false; - } -}; - const fetchMailForwards = async () => { - try { - const response = await axios.get(`MailForwards/${route.params.id}`); - return response.data; - } catch { - return null; - } + const response = await axios.get(`MailForwards/${route.params.id}`); + return response.data; }; const deleteMailForward = async () => { @@ -72,7 +59,7 @@ const setInitialData = async () => { loading.value = true; initialData.value.account = route.params.id; formData.value.account = route.params.id; - hasAccount.value = await fetchAccountExistence(route.params.id); + hasAccount.value = await useHasAccount(route.params.id); if (!hasAccount.value) { loading.value = false; return; diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index e6c21ed34af..ca17c7975bb 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -7,6 +7,7 @@ import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { useArrayData } from 'src/composables/useArrayData'; +import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; const route = useRoute(); const { t } = useI18n(); @@ -31,12 +32,14 @@ const filter = { <template> <CardSummary data-key="AccountId" - ref="AccountSummary" url="VnUsers/preview" :filter="filter" @on-fetch="(data) => (account = data)" > <template #header>{{ account.id }} - {{ account.nickname }}</template> + <template #menu=""> + <AccountDescriptorMenu :entity-id="entityId" /> + </template> <template #body> <QCard class="vn-one"> <QCardSection class="q-pa-none"> diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 3642dc0d0fb..e1e00081571 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -1,21 +1,13 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import ClaimDescriptor from './ClaimDescriptor.vue'; -import ClaimFilter from '../ClaimFilter.vue'; import filter from './ClaimFilter.js'; </script> <template> - <VnCard - data-key="Claim" - base-url="Claims" - :descriptor="ClaimDescriptor" - :filter-panel="ClaimFilter" - search-data-key="ClaimList" + <VnCardBeta + data-key="Claim" + base-url="Claims" + :descriptor="ClaimDescriptor" :filter="filter" - :searchbar-props="{ - url: 'Claims/filter', - label: 'Search claim', - info: 'You can search by claim id or customer name', - }" /> </template> diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 8939a0785b3..66fb151e523 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -19,6 +19,7 @@ import ClaimNotes from 'src/pages/Claim/Card/ClaimNotes.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import ClaimDescriptorMenu from './ClaimDescriptorMenu.vue'; const route = useRoute(); const router = useRouter(); @@ -228,6 +229,9 @@ function claimUrl(section) { </QList> </QBtnDropdown> </template> + <template #menu="{ entity }"> + <ClaimDescriptorMenu :claim="entity.claim" /> + </template> <template #body="{ entity: { claim, salesClaimed, developments } }"> <QCard class="vn-one" v-if="$route.name != 'ClaimSummary'"> <VnTitle diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index bb97162d87c..35b051cdc1e 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -2,18 +2,18 @@ import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { toDate } from 'filters/index'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import ClaimFilter from './ClaimFilter.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import ClaimSummary from './Card/ClaimSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import RightMenu from 'src/components/common/RightMenu.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import ZoneDescriptorProxy from '../Zone/Card/ZoneDescriptorProxy.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); +const dataKey = 'ClaimList'; const claimFilterRef = ref(); const columns = computed(() => [ @@ -125,48 +125,50 @@ const STATE_COLOR = { </script> <template> - <VnSearchbar - data-key="ClaimList" - :label="t('Search claim')" - :info="t('You can search by claim id or customer name')" - /> - <RightMenu> - <template #right-panel> + <VnSection + :data-key="dataKey" + :columns="columns" + prefix="claim" + :array-data-props="{ + url: 'Claims/filter', + order: ['cs.priority ASC', 'created ASC'], + }" + > + <template #rightMenu> <ClaimFilter data-key="ClaimList" ref="claimFilterRef" /> </template> - </RightMenu> - <VnTable - data-key="ClaimList" - url="Claims/filter" - :order="['cs.priority ASC', 'created ASC']" - :columns="columns" - redirect="claim" - :right-search="false" - > - <template #column-clientFk="{ row }"> - <span class="link" @click.stop> - {{ row.clientName }} - <CustomerDescriptorProxy :id="row.clientFk" /> - </span> + <template #body> + <VnTable + :data-key="dataKey" + :columns="columns" + redirect="claim" + :right-search="false" + auto-load + > + <template #column-clientFk="{ row }"> + <span class="link" @click.stop> + {{ row.clientName }} + <CustomerDescriptorProxy :id="row.clientFk" /> + </span> + </template> + <template #column-attendedBy="{ row }"> + <span @click.stop> + <VnUserLink :name="row.workerName" :worker-id="row.workerFk" /> + </span> + </template> + <template #column-zoneFk="{ row }"> + <span class="link" @click.stop> + {{ row.zoneName }} + <ZoneDescriptorProxy :id="row.zoneId" /> + </span> + </template> + </VnTable> </template> - <template #column-attendedBy="{ row }"> - <span @click.stop> - <VnUserLink :name="row.workerName" :worker-id="row.workerFk" /> - </span> - </template> - <template #column-zoneFk="{ row }"> - <span class="link" @click.stop> - {{ row.zoneName }} - <ZoneDescriptorProxy :id="row.zoneId" /> - </span> - </template> - </VnTable> + </VnSection> </template> <i18n> es: - Search claim: Buscar reclamación - You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente params: stateCode: Estado en: diff --git a/src/pages/Claim/locale/en.yml b/src/pages/Claim/locale/en.yml index ffcb44df61c..11b4a2ca473 100644 --- a/src/pages/Claim/locale/en.yml +++ b/src/pages/Claim/locale/en.yml @@ -44,3 +44,5 @@ claim: fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}' noData: 'There are no images/videos, click here or drag and drop the file' dragDrop: Drag and drop it here + search: Search claims + searchInfo: You can search by claim id or customer name diff --git a/src/pages/Claim/locale/es.yml b/src/pages/Claim/locale/es.yml index 052416aa7f7..d35d2c8e765 100644 --- a/src/pages/Claim/locale/es.yml +++ b/src/pages/Claim/locale/es.yml @@ -1,5 +1,3 @@ -Search claim: Buscar reclamación -You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente claim: customer: Cliente code: Código @@ -46,3 +44,5 @@ claim: fileDescription: 'ID de reclamación {claimId} del cliente {clientName} con ID {clientId}' noData: 'No hay imágenes/videos, haz clic aquí o arrastra y suelta el archivo' dragDrop: Arrastra y suelta aquí + search: Buscar reclamación + searchInfo: Puedes buscar por ID de la reclamación o nombre del cliente diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index 6650ea39515..d2eb125d724 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -12,6 +12,7 @@ import VnLinkMail from 'src/components/ui/VnLinkMail.vue'; import CustomerSummaryTable from 'src/pages/Customer/components/CustomerSummaryTable.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import VnRow from 'src/components/ui/VnRow.vue'; +import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; const route = useRoute(); const { t } = useI18n(); const grafanaUrl = 'https://grafana.verdnatura.es'; @@ -70,6 +71,9 @@ const sumRisk = ({ clientRisks }) => { data-key="CustomerSummary" module-name="Customer" > + <template #menu="{ entity }"> + <CustomerDescriptorMenu :customer="entity" /> + </template> <template #body="{ entity }"> <QCard class="vn-one"> <VnTitle @@ -94,14 +98,13 @@ const sumRisk = ({ clientRisks }) => { :phone-number="entity.mobile" :channel="entity.country?.saySimpleCountry?.channel" class="q-ml-xs" - :country="entity.country?.code" /> </template> </VnLv> <VnLv :value="entity.email" copy ><template #label> {{ t('globals.params.email') }} - <VnLinkMail :email="entity.email"></VnLinkMail> </template + <VnLinkMail email="entity.email"></VnLinkMail> </template ></VnLv> <VnLv :label="t('customer.summary.salesPerson')" @@ -173,7 +176,7 @@ const sumRisk = ({ clientRisks }) => { :label="t('customer.summary.notifyByEmail')" :value="entity.isToBeMailed" /> - <VnLv :label="t('globals.isVies')" :value="entity.isVies" /> + <VnLv :label="t('customer.summary.vies')" :value="entity.isVies" /> </VnRow> </QCard> <QCard class="vn-one"> diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index 150ef3b8414..6789ac56b42 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -247,8 +247,14 @@ function handleLocation(data, location) { :label="t('Longitude')" clearable v-model="data.longitude" + :decimal-places="7" + /> + <VnInputNumber + :label="t('Latitude')" + clearable + v-model="data.latitude" + :decimal-places="7" /> - <VnInputNumber :label="t('Latitude')" clearable v-model="data.latitude" /> </VnRow> <h4 class="q-mb-xs">{{ t('Notes') }}</h4> <VnRow diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 9814615a69b..c54ecc3f0ac 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -7,9 +7,9 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import { toDate } from 'src/filters'; -import { usePrintService } from 'composables/usePrintService'; import { getUrl } from 'src/composables/getUrl'; import filter from './EntryFilter.js'; +import EntryDescriptorMenu from './EntryDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -21,7 +21,6 @@ const $props = defineProps({ const route = useRoute(); const { t } = useI18n(); -const { openReport } = usePrintService(); const entryDescriptorRef = ref(null); const url = ref(); @@ -52,10 +51,6 @@ const getEntryRedirectionFilter = (entry) => { to, }); }; - -const showEntryReport = () => { - openReport(`Entries/${route.params.id}/entry-order-pdf`); -}; </script> <template> @@ -68,14 +63,12 @@ const showEntryReport = () => { data-key="Entry" > <template #menu="{ entity }"> - <QItem v-ripple clickable @click="showEntryReport(entity)"> - <QItemSection>{{ t('Show entry report') }}</QItemSection> - </QItem> + <EntryDescriptorMenu :id="entity.id" /> </template> <template #body="{ entity }"> <VnLv :label="t('globals.agency')" :value="entity.travel?.agency?.name" /> - <VnLv :label="t('globals.shipped')" :value="toDate(entity.travel?.shipped)" /> - <VnLv :label="t('globals.landed')" :value="toDate(entity.travel?.landed)" /> + <VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" /> + <VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" /> <VnLv :label="t('globals.warehouseOut')" :value="entity.travel?.warehouseOut?.name" @@ -154,7 +147,6 @@ es: Supplier card: Ficha del proveedor All travels with current agency: Todos los envíos con la agencia actual All entries with current supplier: Todas las entradas con el proveedor actual - Show entry report: Ver informe del pedido Go to module index: Ir al índice del modulo Inventory entry: Es inventario Virtual entry: Es una redada diff --git a/src/pages/Entry/Card/EntryDescriptorMenu.vue b/src/pages/Entry/Card/EntryDescriptorMenu.vue new file mode 100644 index 00000000000..a357b46fe95 --- /dev/null +++ b/src/pages/Entry/Card/EntryDescriptorMenu.vue @@ -0,0 +1,22 @@ +<script setup> +import { usePrintService } from 'composables/usePrintService'; + +const { openReport } = usePrintService(); + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, +}); + +function showEntryReport() { + openReport(`Entries/${$props.id}/entry-order-pdf`); +} +</script> + +<template> + <QItem v-ripple clickable @click="showEntryReport"> + <QItemSection>{{ $t('entryList.list.showEntryReport') }}</QItemSection> + </QItem> +</template> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index 39dad672cef..755e39454b0 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -12,6 +12,7 @@ import { getUrl } from 'src/composables/getUrl'; import axios from 'axios'; import FetchedTags from 'src/components/ui/FetchedTags.vue'; import VnToSummary from 'src/components/ui/VnToSummary.vue'; +import EntryDescriptorMenu from './EntryDescriptorMenu.vue'; import VnRow from 'src/components/ui/VnRow.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; @@ -172,6 +173,9 @@ const fetchEntryBuys = async () => { <template #header> <span>{{ entry.id }} - {{ entry.supplier.nickname }}</span> </template> + <template #menu="{ entity }"> + <EntryDescriptorMenu :id="entity.id" /> + </template> <template #body> <QCard class="vn-one"> <VnTitle @@ -207,7 +211,10 @@ const fetchEntryBuys = async () => { :label="t('entry.summary.travelAgency')" :value="entry.travel.agency?.name" /> - <VnLv :label="t('globals.shipped')" :value="toDate(entry.travel.shipped)" /> + <VnLv + :label="t('globals.shipped')" + :value="toDate(entry.travel.shipped)" + /> <VnLv :label="t('globals.warehouseOut')" :value="entry.travel.warehouseOut?.name" diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 7e92fe051ae..879a5091434 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -243,7 +243,6 @@ const columns = computed(() => [ <i18n> es: - Inventory entry: Es inventario Virtual entry: Es una redada Search entries: Buscar entradas You can search by entry reference: Puedes buscar por referencia de la entrada diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 6e41566d06c..59c2666a7a9 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -1,6 +1,7 @@ entryList: list: inventoryEntry: Inventory entry + showEntryReport: Show entry report entryFilter: filter: search: General search diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index 7e627b09fb8..4fb7bbf08b4 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -4,6 +4,7 @@ You can search by entry reference: Puedes buscar por referencia de la entrada entryList: list: inventoryEntry: Es inventario + showEntryReport: Ver informe del pedido entryFilter: filter: search: Búsqueda general diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 83b1aa25e1d..90aa50af798 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -268,7 +268,7 @@ function deleteFile(dmsFk) { </VnRow> <VnRow> <VnSelect - :label="t('InvoiceIn.summary.sage')" + :label="t('invoicein.summary.sage')" v-model="data.withholdingSageFk" :options="sageWithholdings" option-value="id" diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index cb8a45833aa..4d9e180eb4a 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -1,60 +1,30 @@ <script setup> -import { ref, reactive, computed, onBeforeMount, capitalize } from 'vue'; +import { ref, reactive, computed, onBeforeMount } from 'vue'; import { useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { useQuasar } from 'quasar'; import axios from 'axios'; import { toCurrency, toDate } from 'src/filters'; -import { useAcl } from 'src/composables/useAcl'; -import { downloadFile } from 'src/composables/downloadFile'; -import { useArrayData } from 'src/composables/useArrayData'; -import { usePrintService } from 'composables/usePrintService'; import VnLv from 'src/components/ui/VnLv.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import FetchData from 'src/components/FetchData.vue'; -import SendEmailDialog from 'components/common/SendEmailDialog.vue'; -import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import { useCapitalize } from 'src/composables/useCapitalize'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; -import InvoiceInToBook from '../InvoiceInToBook.vue'; +import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; const $props = defineProps({ id: { type: Number, default: null } }); - const { push, currentRoute } = useRouter(); - -const quasar = useQuasar(); -const { hasAny } = useAcl(); const { t } = useI18n(); -const { openReport, sendEmail } = usePrintService(); -const arrayData = useArrayData(); -const invoiceIn = computed(() => arrayData.store.data); const cardDescriptorRef = ref(); const correctionDialogRef = ref(); const entityId = computed(() => $props.id || +currentRoute.value.params.id); const totalAmount = ref(); -const currentAction = ref(); const config = ref(); const cplusRectificationTypes = ref([]); const siiTypeInvoiceIns = ref([]); const invoiceCorrectionTypes = ref([]); -const actions = { - unbook: { - title: t('assertAction', { action: t('unbook') }), - action: toUnbook, - }, - delete: { - title: t('assertAction', { action: t('delete') }), - action: deleteInvoice, - }, - clone: { - title: t('assertAction', { action: t('clone') }), - action: cloneInvoice, - }, - showPdf: { cb: showPdfInvoice }, - sendPdf: { cb: sendPdfInvoiceConfirmation }, - correct: { cb: () => correctionDialogRef.value.show() }, -}; + const filter = { include: [ { @@ -90,7 +60,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - table: JSON.stringify({ supplierFk: id }), + params: JSON.stringify({ supplierFk: id }), }, }; }, @@ -99,7 +69,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - table: JSON.stringify({ correctedFk: entityId.value }), + params: JSON.stringify({ correctedFk: entityId.value }), }, }; } @@ -118,21 +88,21 @@ const routes = reactive({ const correctionFormData = reactive({ invoiceReason: 2, invoiceType: 2, - invoiceClass: 8, + invoiceClass: 6, }); const isNotFilled = computed(() => Object.values(correctionFormData).includes(null)); onBeforeMount(async () => { await setInvoiceCorrection(entityId.value); const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`); - totalAmount.value = data.totalTaxableBase; + totalAmount.value = data.totalDueDay; }); onBeforeRouteUpdate(async (to, from) => { if (to.params.id !== from.params.id) { await setInvoiceCorrection(to.params.id); const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`); - totalAmount.value = data.totalTaxableBase; + totalAmount.value = data.totalDueDay; } }); @@ -153,94 +123,6 @@ async function setInvoiceCorrection(id) { ); } -function openDialog() { - quasar.dialog({ - component: VnConfirm, - componentProps: { - title: t(currentAction.value.title), - promise: currentAction.value.action, - }, - }); -} - -async function toUnbook() { - const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`); - const { isLinked, bookEntry, accountingEntries } = data; - - const type = isLinked ? 'warning' : 'positive'; - const message = isLinked - ? t('isLinked', { bookEntry, accountingEntries }) - : t('isNotLinked', { bookEntry }); - - quasar.notify({ type, message }); - if (!isLinked) arrayData.store.data.isBooked = false; -} - -async function deleteInvoice() { - await axios.delete(`InvoiceIns/${entityId.value}`); - quasar.notify({ - type: 'positive', - message: t('Invoice deleted'), - }); - push({ path: '/invoice-in' }); -} - -async function cloneInvoice() { - const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`); - quasar.notify({ - type: 'positive', - message: t('Invoice cloned'), - }); - push({ path: `/invoice-in/${data.id}/summary` }); -} - -const canEditProp = (props) => - hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]); - -const isAgricultural = () => { - if (!config.value) return false; - return ( - invoiceIn.value?.supplier?.sageFarmerWithholdingFk === - config?.value[0]?.sageWithholdingFk - ); -}; - -function showPdfInvoice() { - if (isAgricultural()) - openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`, null, '_blank'); -} - -function sendPdfInvoiceConfirmation() { - quasar.dialog({ - component: SendEmailDialog, - componentProps: { - data: { - address: invoiceIn.value.supplier.contacts[0].email, - }, - promise: sendPdfInvoice, - }, - }); -} - -function sendPdfInvoice({ address }) { - if (!address) - quasar.notify({ - type: 'negative', - message: t(`The email can't be empty`), - }); - else - return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, { - recipientId: invoiceIn.value.supplier.id, - recipient: address, - }); -} - -function triggerMenu(type) { - currentAction.value = actions[type]; - if (currentAction.value.cb) currentAction.value.cb(); - else openDialog(type); -} - const createInvoiceInCorrection = async () => { const { data: correctingId } = await axios.post( 'InvoiceIns/corrective', @@ -262,7 +144,7 @@ const createInvoiceInCorrection = async () => { auto-load /> <FetchData - url="siiTypeInvoiceIns" + url="SiiTypeInvoiceIns" :where="{ code: { like: 'R%' } }" @on-fetch="(data) => (siiTypeInvoiceIns = data)" auto-load @@ -281,87 +163,13 @@ const createInvoiceInCorrection = async () => { title="supplierRef" > <template #menu="{ entity }"> - <InvoiceInToBook> - <template #content="{ book }"> - <QItem - v-if="!entity?.isBooked && canEditProp('toBook')" - v-ripple - clickable - @click="book(entityId)" - > - <QItemSection>{{ t('To book') }}</QItemSection> - </QItem> - </template> - </InvoiceInToBook> - <QItem - v-if="entity?.isBooked && canEditProp('toUnbook')" - v-ripple - clickable - @click="triggerMenu('unbook')" - > - <QItemSection> - {{ t('To unbook') }} - </QItemSection> - </QItem> - <QItem - v-if="canEditProp('deleteById')" - v-ripple - clickable - @click="triggerMenu('delete')" - > - <QItemSection>{{ t('Delete invoice') }}</QItemSection> - </QItem> - <QItem - v-if="canEditProp('clone')" - v-ripple - clickable - @click="triggerMenu('clone')" - > - <QItemSection>{{ t('Clone invoice') }}</QItemSection> - </QItem> - <QItem - v-if="isAgricultural()" - v-ripple - clickable - @click="triggerMenu('showPdf')" - > - <QItemSection>{{ t('Show agricultural receipt as PDF') }}</QItemSection> - </QItem> - <QItem - v-if="isAgricultural()" - v-ripple - clickable - @click="triggerMenu('sendPdf')" - > - <QItemSection - >{{ t('Send agricultural receipt as PDF') }}...</QItemSection - > - </QItem> - <QItem - v-if="!invoiceInCorrection.corrected" - v-ripple - clickable - @click="triggerMenu('correct')" - > - <QItemSection>{{ t('Create rectificative invoice') }}...</QItemSection> - </QItem> - <QItem - v-if="entity.dmsFk" - v-ripple - clickable - @click="downloadFile(entity.dmsFk)" - > - <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> - </QItem> + <InvoiceInDescriptorMenu :invoice="entity" /> </template> <template #body="{ entity }"> - <VnLv :label="t('InvoiceIn.list.issued')" :value="toDate(entity.issued)" /> - <VnLv - :label="t('InvoiceIn.summary.bookedDate')" - :value="toDate(entity.booked)" - /> - <VnLv :label="t('InvoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> - <VnLv :label="t('InvoiceIn.list.supplier')"> + <VnLv :label="t('invoicein.list.issued')" :value="toDate(entity.issued)" /> + <VnLv :label="t('invoicein.summary.bookedDate')" :value="toDate(entity.booked)" /> + <VnLv :label="t('invoicein.list.amount')" :value="toCurrency(totalAmount)" /> + <VnLv :label="t('invoicein.list.supplier')"> <template #value> <span class="link"> {{ entity?.supplier?.nickname }} @@ -378,7 +186,7 @@ const createInvoiceInCorrection = async () => { color="primary" :to="routes.getSupplier(entity.supplierFk)" > - <QTooltip>{{ t('InvoiceIn.list.supplier') }}</QTooltip> + <QTooltip>{{ t('invoicein.list.supplier') }}</QTooltip> </QBtn> <QBtn size="md" @@ -394,7 +202,7 @@ const createInvoiceInCorrection = async () => { color="primary" :to="routes.getTickets(entity.supplierFk)" > - <QTooltip>{{ t('InvoiceIn.descriptor.ticketList') }}</QTooltip> + <QTooltip>{{ t('InvoiceOut.card.ticketList') }}</QTooltip> </QBtn> <QBtn v-if=" @@ -438,7 +246,7 @@ const createInvoiceInCorrection = async () => { readonly /> <VnSelect - :label="`${capitalize(t('globals.class'))}`" + :label="`${useCapitalize(t('globals.class'))}`" v-model="correctionFormData.invoiceClass" :options="siiTypeInvoiceIns" option-value="id" @@ -448,27 +256,15 @@ const createInvoiceInCorrection = async () => { </QItemSection> <QItemSection> <VnSelect - :label="`${capitalize(t('globals.type'))}`" + :label="`${useCapitalize(t('globals.type'))}`" v-model="correctionFormData.invoiceType" :options="cplusRectificationTypes" option-value="id" option-label="description" :required="true" - > - <template #option="{ opt }"> - <QItem> - <QItemSection> - <QItemLabel - >{{ opt.code }} - - {{ opt.description }}</QItemLabel - > - </QItemSection> - </QItem> - <div></div> - </template> - </VnSelect> + /> <VnSelect - :label="`${capitalize(t('globals.reason'))}`" + :label="`${useCapitalize(t('globals.reason'))}`" v-model="correctionFormData.invoiceReason" :options="invoiceCorrectionTypes" option-value="id" @@ -512,31 +308,3 @@ const createInvoiceInCorrection = async () => { } } </style> -<i18n> -en: - isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries - isLinked: The entry {bookEntry} has been linked to Sage. Please contact administration for further information - assertAction: Are you sure you want to {action} this invoice? -es: - book: asentar - unbook: desasentar - delete: eliminar - clone: clonar - To book: Contabilizar - To unbook: Descontabilizar - Delete invoice: Eliminar factura - Invoice deleted: Factura eliminada - Clone invoice: Clonar factura - Invoice cloned: Factura clonada - Show agricultural receipt as PDF: Ver recibo agrícola como PDF - Send agricultural receipt as PDF: Enviar recibo agrícola como PDF - Are you sure you want to send it?: Estás seguro que quieres enviarlo? - Send PDF invoice: Enviar factura a PDF - Create rectificative invoice: Crear factura rectificativa - Rectificative invoice: Factura rectificativa - Original invoice: Factura origen - Entry: entrada - isNotLinked: Se ha eliminado el asiento nº {bookEntry} con {accountingEntries} apuntes - isLinked: El asiento {bookEntry} fue enlazado a Sage, por favor contacta con administración - assertAction: Estas seguro de querer {action} esta factura? -</i18n> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue new file mode 100644 index 00000000000..647b68f88ed --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorMenu.vue @@ -0,0 +1,206 @@ +<script setup> +import { ref, computed, toRefs, reactive } from 'vue'; +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { useQuasar } from 'quasar'; +import axios from 'axios'; +import { useAcl } from 'src/composables/useAcl'; +import { downloadFile } from 'src/composables/downloadFile'; +import { useArrayData } from 'src/composables/useArrayData'; +import { usePrintService } from 'composables/usePrintService'; +import VnConfirm from 'src/components/ui/VnConfirm.vue'; +import SendEmailDialog from 'components/common/SendEmailDialog.vue'; +import InvoiceInToBook from '../InvoiceInToBook.vue'; + +const { hasAny } = useAcl(); +const { t } = useI18n(); +const { openReport, sendEmail } = usePrintService(); +const { push, currentRoute } = useRouter(); +const $props = defineProps({ + invoice: { + type: Object, + required: true, + }, +}); +const { invoice } = toRefs($props); +const quasar = useQuasar(); +const arrayData = useArrayData(); +const currentAction = ref(); +const config = ref(); +const correctionDialogRef = ref(); +const invoiceInCorrection = reactive({ correcting: [], corrected: null }); +const entityId = computed(() => $props.invoice.id || +currentRoute.value.params.id); +const invoiceIn = computed(() => arrayData.store.data); +const actions = { + unbook: { + title: t('assertAction', { action: t('invoicein.descriptorMenu.unbook') }), + action: toUnbook, + }, + delete: { + title: t('assertAction', { action: t('invoicein.descriptorMenu.delete') }), + action: deleteInvoice, + }, + clone: { + title: t('assertAction', { action: t('invoicein.descriptorMenu.clone') }), + action: cloneInvoice, + }, + showPdf: { cb: showPdfInvoice }, + sendPdf: { cb: sendPdfInvoiceConfirmation }, + correct: { cb: () => correctionDialogRef.value.show() }, +}; +const canEditProp = (props) => + hasAny([{ model: 'InvoiceIn', props, accessType: 'WRITE' }]); + +function triggerMenu(type) { + currentAction.value = actions[type]; + if (currentAction.value.cb) currentAction.value.cb(); + else openDialog(type); +} + +function openDialog() { + quasar.dialog({ + component: VnConfirm, + componentProps: { + title: t(currentAction.value.title), + promise: currentAction.value.action, + }, + }); +} + +async function toUnbook() { + const { data } = await axios.post(`InvoiceIns/${entityId.value}/toUnbook`); + const { isLinked, bookEntry, accountingEntries } = data; + + const type = isLinked ? 'warning' : 'positive'; + const message = isLinked + ? t('isLinked', { bookEntry }) + : t('isNotLinked', { bookEntry, accountingEntries }); + + quasar.notify({ type, message }); + if (!isLinked) arrayData.store.data.isBooked = false; +} + +async function deleteInvoice() { + await axios.delete(`InvoiceIns/${entityId.value}`); + quasar.notify({ + type: 'positive', + message: t('invoicein.descriptorMenu.invoiceDeleted'), + }); + push({ path: '/invoice-in' }); +} + +async function cloneInvoice() { + const { data } = await axios.post(`InvoiceIns/${entityId.value}/clone`); + quasar.notify({ + type: 'positive', + message: t('invoicein.descriptorMenu.invoiceCloned'), + }); + push({ path: `/invoice-in/${data.id}/summary` }); +} + +const isAgricultural = () => { + if (!config.value) return false; + return ( + invoiceIn.value?.supplier?.sageFarmerWithholdingFk === + config?.value[0]?.sageWithholdingFk + ); +}; +function showPdfInvoice() { + if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`); +} + +function sendPdfInvoiceConfirmation() { + quasar.dialog({ + component: SendEmailDialog, + componentProps: { + data: { + address: invoiceIn.value.supplier.contacts[0].email, + }, + promise: sendPdfInvoice, + }, + }); +} + +function sendPdfInvoice({ address }) { + if (!address) + quasar.notify({ + type: 'negative', + message: t(`The email can't be empty`), + }); + else + return sendEmail(`InvoiceIns/${entityId.value}/invoice-in-email`, { + recipientId: invoiceIn.value.supplier.id, + recipient: address, + }); +} +</script> + +<template> + <InvoiceInToBook> + <template #content="{ book }"> + <QItem + v-if="!invoice?.isBooked && canEditProp('toBook')" + v-ripple + clickable + @click="book(entityId)" + > + <QItemSection>{{ t('invoicein.descriptorMenu.toBook') }}</QItemSection> + </QItem> + </template> + </InvoiceInToBook> + <QItem + v-if="invoice?.isBooked && canEditProp('toUnbook')" + v-ripple + clickable + @click="triggerMenu('unbook')" + > + <QItemSection> + {{ t('invoicein.descriptorMenu.toUnbook') }} + </QItemSection> + </QItem> + <QItem + v-if="canEditProp('deleteById')" + v-ripple + clickable + @click="triggerMenu('invoicein.descriptorMenu.delete')" + > + <QItemSection>{{ t('invoicein.descriptorMenu.deleteInvoice') }}</QItemSection> + </QItem> + <QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')"> + <QItemSection>{{ t('invoicein.descriptorMenu.cloneInvoice') }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')"> + <QItemSection>{{ + t('invoicein.descriptorMenu.showAgriculturalPdf') + }}</QItemSection> + </QItem> + <QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')"> + <QItemSection + >{{ t('invoicein.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection + > + </QItem> + <QItem + v-if="!invoiceInCorrection.corrected" + v-ripple + clickable + @click="triggerMenu('correct')" + > + <QItemSection + >{{ t('invoicein.descriptorMenu.createCorrective') }}...</QItemSection + > + </QItem> + <QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)"> + <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> + </QItem> +</template> + +<i18n> +en: + isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries + isLinked: The entry has been linked to Sage. Please contact administration for further information + assertAction: Are you sure you want to {action} this invoice? +es: + isNotLinked: Se ha eliminado el asiento nº {bookEntry} con {accountingEntries} apuntes + isLinked: El asiento fue enlazado a Sage, por favor contacta con administración + assertAction: Estas seguro de querer {action} esta factura? +</i18n> diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 115a3320868..eca0c7af1a0 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -10,6 +10,7 @@ import VnLv from 'src/components/ui/VnLv.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import InvoiceIntoBook from '../InvoiceInToBook.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; const props = defineProps({ id: { type: [Number, String], default: 0 } }); const { t } = useI18n(); @@ -26,14 +27,14 @@ const intrastatTotals = ref({ amount: 0, net: 0, stems: 0 }); const vatColumns = ref([ { name: 'expense', - label: 'InvoiceIn.summary.expense', + label: 'invoicein.summary.expense', field: (row) => row.expenseFk, sortable: true, align: 'left', }, { name: 'landed', - label: 'InvoiceIn.summary.taxableBase', + label: 'invoicein.summary.taxableBase', field: (row) => row.taxableBase, format: (value) => toCurrency(value), sortable: true, @@ -41,7 +42,7 @@ const vatColumns = ref([ }, { name: 'vat', - label: 'InvoiceIn.summary.sageVat', + label: 'invoicein.summary.sageVat', field: (row) => { if (row.taxTypeSage) return `#${row.taxTypeSage.id} : ${row.taxTypeSage.vat}`; }, @@ -51,7 +52,7 @@ const vatColumns = ref([ }, { name: 'transaction', - label: 'InvoiceIn.summary.sageTransaction', + label: 'invoicein.summary.sageTransaction', field: (row) => { if (row.transactionTypeSage) return `#${row.transactionTypeSage.id} : ${row.transactionTypeSage?.transaction}`; @@ -62,7 +63,7 @@ const vatColumns = ref([ }, { name: 'rate', - label: 'InvoiceIn.summary.rate', + label: 'invoicein.summary.rate', field: (row) => taxRate(row.taxableBase, row.taxTypeSage?.rate), format: (value) => toCurrency(value), sortable: true, @@ -70,7 +71,7 @@ const vatColumns = ref([ }, { name: 'currency', - label: 'InvoiceIn.summary.currency', + label: 'invoicein.summary.currency', field: (row) => row.foreignValue, format: (val) => val && toCurrency(val, currency.value), sortable: true, @@ -81,21 +82,21 @@ const vatColumns = ref([ const dueDayColumns = ref([ { name: 'date', - label: 'InvoiceIn.summary.dueDay', + label: 'invoicein.summary.dueDay', field: (row) => toDate(row.dueDated), sortable: true, align: 'left', }, { name: 'bank', - label: 'InvoiceIn.summary.bank', + label: 'invoicein.summary.bank', field: (row) => row.bank.bank, sortable: true, align: 'left', }, { name: 'amount', - label: 'InvoiceIn.list.amount', + label: 'invoicein.list.amount', field: (row) => row.amount, format: (value) => toCurrency(value), sortable: true, @@ -103,7 +104,7 @@ const dueDayColumns = ref([ }, { name: 'landed', - label: 'InvoiceIn.summary.foreignValue', + label: 'invoicein.summary.foreignValue', field: (row) => row.foreignValue, format: (val) => val && toCurrency(val, currency.value), sortable: true, @@ -114,7 +115,7 @@ const dueDayColumns = ref([ const intrastatColumns = ref([ { name: 'code', - label: 'InvoiceIn.summary.code', + label: 'invoicein.summary.code', field: (row) => { return `${row.intrastat.id}: ${row.intrastat?.description}`; }, @@ -123,21 +124,21 @@ const intrastatColumns = ref([ }, { name: 'amount', - label: 'InvoiceIn.list.amount', + label: 'invoicein.list.amount', field: (row) => toCurrency(row.amount), sortable: true, align: 'left', }, { name: 'net', - label: 'InvoiceIn.summary.net', + label: 'invoicein.summary.net', field: (row) => row.net, sortable: true, align: 'left', }, { name: 'stems', - label: 'InvoiceIn.summary.stems', + label: 'invoicein.summary.stems', field: (row) => row.stems, format: (value) => value, sortable: true, @@ -145,7 +146,7 @@ const intrastatColumns = ref([ }, { name: 'landed', - label: 'InvoiceIn.summary.country', + label: 'invoicein.summary.country', field: (row) => row.country?.code, format: (value) => value, sortable: true, @@ -200,6 +201,9 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </template> </InvoiceIntoBook> </template> + <template #menu="{ entity }"> + <InvoiceInDescriptorMenu :invoice="entity" /> + </template> <template #body="{ entity }"> <!--Basic Data--> <QCard class="vn-one"> @@ -210,7 +214,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; /> </QCardSection> <VnLv - :label="t('InvoiceIn.list.supplier')" + :label="t('invoicein.list.supplier')" :value="entity.supplier?.name" > <template #value> @@ -221,14 +225,14 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </template> </VnLv> <VnLv - :label="t('InvoiceIn.list.supplierRef')" + :label="t('invoicein.list.supplierRef')" :value="entity.supplierRef" /> <VnLv - :label="t('InvoiceIn.summary.currency')" + :label="t('invoicein.summary.currency')" :value="entity.currency?.code" /> - <VnLv :label="t('InvoiceIn.serial')" :value="`${entity.serial}`" /> + <VnLv :label="t('invoicein.serial')" :value="`${entity.serial}`" /> <VnLv :label="t('globals.country')" :value="entity.supplier?.country?.code" @@ -243,19 +247,19 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QCardSection> <VnLv :ellipsis-value="false" - :label="t('InvoiceIn.summary.issued')" + :label="t('invoicein.summary.issued')" :value="toDate(entity.issued)" /> <VnLv - :label="t('InvoiceIn.summary.operated')" + :label="t('invoicein.summary.operated')" :value="toDate(entity.operated)" /> <VnLv - :label="t('InvoiceIn.summary.bookEntried')" + :label="t('invoicein.summary.bookEntried')" :value="toDate(entity.bookEntried)" /> <VnLv - :label="t('InvoiceIn.summary.bookedDate')" + :label="t('invoicein.summary.bookedDate')" :value="toDate(entity.booked)" /> <VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" /> @@ -268,18 +272,18 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; /> </QCardSection> <VnLv - :label="t('InvoiceIn.summary.sage')" + :label="t('invoicein.summary.sage')" :value="entity.sageWithholding?.withholding" /> <VnLv - :label="t('InvoiceIn.summary.vat')" + :label="t('invoicein.summary.vat')" :value="entity.expenseDeductible?.name" /> <VnLv - :label="t('InvoiceIn.card.company')" + :label="t('invoicein.card.company')" :value="entity.company?.code" /> - <VnLv :label="t('InvoiceIn.isBooked')" :value="invoiceIn?.isBooked" /> + <VnLv :label="t('invoicein.isBooked')" :value="invoiceIn?.isBooked" /> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> @@ -290,11 +294,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QCardSection> <QCardSection class="q-pa-none"> <VnLv - :label="t('InvoiceIn.summary.taxableBase')" + :label="t('invoicein.summary.taxableBase')" :value="toCurrency(entity.totals.totalTaxableBase)" /> <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" /> - <VnLv :label="t('InvoiceIn.summary.dueTotal')"> + <VnLv :label="t('invoicein.summary.dueTotal')"> <template #value> <QChip dense @@ -302,8 +306,8 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :color="amountsNotMatch ? 'negative' : 'transparent'" :title=" amountsNotMatch - ? t('InvoiceIn.summary.noMatch') - : t('InvoiceIn.summary.dueTotal') + ? t('invoicein.summary.noMatch') + : t('invoicein.summary.dueTotal') " > {{ toCurrency(entity.totals.totalDueDay) }} @@ -314,7 +318,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QCard> <!--Vat--> <QCard v-if="entity.invoiceInTax.length" class="vat"> - <VnTitle :url="getLink('vat')" :text="t('InvoiceIn.card.vat')" /> + <VnTitle :url="getLink('vat')" :text="t('invoicein.card.vat')" /> <QTable :columns="vatColumns" :rows="entity.invoiceInTax" @@ -362,7 +366,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; </QCard> <!--Due Day--> <QCard v-if="entity.invoiceInDueDay.length" class="due-day"> - <VnTitle :url="getLink('due-day')" :text="t('InvoiceIn.card.dueDay')" /> + <VnTitle :url="getLink('due-day')" :text="t('invoicein.card.dueDay')" /> <QTable :columns="dueDayColumns" :rows="entity.invoiceInDueDay" flat> <template #header="dueDayProps"> <QTr :props="dueDayProps" class="bg"> @@ -400,7 +404,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <QCard v-if="entity.invoiceInIntrastat.length"> <VnTitle :url="getLink('intrastat')" - :text="t('InvoiceIn.card.intrastat')" + :text="t('invoicein.card.intrastat')" /> <QTable :columns="intrastatColumns" diff --git a/src/pages/InvoiceIn/InvoiceInCreate.vue b/src/pages/InvoiceIn/InvoiceInCreate.vue index 200997f6517..f180410aaa2 100644 --- a/src/pages/InvoiceIn/InvoiceInCreate.vue +++ b/src/pages/InvoiceIn/InvoiceInCreate.vue @@ -83,7 +83,7 @@ const redirectToInvoiceInBasicData = (__, { id }) => { </template> </VnSelect> <VnInput - :label="t('InvoiceIn.list.supplierRef')" + :label="t('invoicein.list.supplierRef')" v-model="data.supplierRef" /> </VnRow> @@ -97,10 +97,10 @@ const redirectToInvoiceInBasicData = (__, { id }) => { map-options hide-selected :required="true" - :rules="validate('InvoiceIn.companyFk')" + :rules="validate('invoicein.companyFk')" /> <VnInputDate - :label="t('InvoiceIn.summary.issued')" + :label="t('invoicein.summary.issued')" v-model="data.issued" /> </VnRow> diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index 6259030e0db..31a611936ab 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -164,7 +164,7 @@ function handleDaysAgo(params, daysAgo) { <QItem> <QItemSection> <QCheckbox - :label="$t('InvoiceIn.isBooked')" + :label="$t('invoicein.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 2d8fb2989a4..5e80ae652ca 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -26,7 +26,7 @@ const cols = computed(() => [ { align: 'left', name: 'isBooked', - label: t('InvoiceIn.isBooked'), + label: t('invoicein.isBooked'), columnFilter: false, }, { @@ -41,7 +41,7 @@ const cols = computed(() => [ { align: 'left', name: 'supplierFk', - label: t('InvoiceIn.list.supplier'), + label: t('invoicein.list.supplier'), columnFilter: { component: 'select', attrs: { @@ -55,16 +55,16 @@ const cols = computed(() => [ { align: 'left', name: 'supplierRef', - label: t('InvoiceIn.list.supplierRef'), + label: t('invoicein.list.supplierRef'), }, { align: 'left', name: 'serial', - label: t('InvoiceIn.serial'), + label: t('invoicein.serial'), }, { align: 'left', - label: t('InvoiceIn.list.issued'), + label: t('invoicein.list.issued'), name: 'issued', component: null, columnFilter: { @@ -74,7 +74,7 @@ const cols = computed(() => [ }, { align: 'left', - label: t('InvoiceIn.list.dueDated'), + label: t('invoicein.list.dueDated'), name: 'dueDated', component: null, columnFilter: { @@ -86,12 +86,12 @@ const cols = computed(() => [ { align: 'left', name: 'awbCode', - label: t('InvoiceIn.list.awb'), + label: t('invoicein.list.awb'), }, { align: 'left', name: 'amount', - label: t('InvoiceIn.list.amount'), + label: t('invoicein.list.amount'), format: ({ amount }) => toCurrency(amount), cardVisible: true, }, @@ -182,7 +182,7 @@ const cols = computed(() => [ </template> </VnSelect> <VnInput - :label="t('InvoiceIn.list.supplierRef')" + :label="t('invoicein.list.supplierRef')" v-model="data.supplierRef" /> <VnSelect @@ -194,7 +194,7 @@ const cols = computed(() => [ option-label="code" :required="true" /> - <VnInputDate :label="t('InvoiceIn.summary.issued')" v-model="data.issued" /> + <VnInputDate :label="t('invoicein.summary.issued')" v-model="data.issued" /> </template> </VnTable> </template> diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 3723a0f05e7..94db50066aa 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -1,4 +1,4 @@ -InvoiceIn: +invoicein: serial: Serial isBooked: Is booked list: @@ -12,6 +12,26 @@ InvoiceIn: amount: Amount descriptor: ticketList: Ticket list + descriptorMenu: + book: Book + unbook: Unbook + delete: Delete + clone: Clone + toBook: To book + toUnbook: To unbook + deleteInvoice: Delete invoice + invoiceDeleted: invoice deleted + cloneInvoice: Clone invoice + invoiceCloned: Invoice cloned + showAgriculturalPdf: Show agricultural receipt as PDF + sendAgriculturalPdf: Send agricultural receipt as PDF + checkSendInvoice: Are you sure you want to send it? + sendPdfInvoice: Send PDF invoice + createCorrective: Create rectificative invoice + correctiveInvoice: Rectificative invoice + originalInvoice: Original invoice + entry: Entry + emailEmpty: The email can't be empty card: client: Client company: Company @@ -46,8 +66,6 @@ InvoiceIn: search: Id or supplier name correctedFk: Corrected isBooked: Is booked -invoicein: - params: account: Ledger account correctingFk: Rectificative \ No newline at end of file diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 5637605f6bc..bcb9c05516f 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -1,4 +1,4 @@ -InvoiceIn: +invoicein: serial: Serie isBooked: Contabilizada list: @@ -12,6 +12,26 @@ InvoiceIn: amount: Importe descriptor: ticketList: Listado de tickets + descriptorMenu: + book: Asentar + unbook: Desasentar + delete: Eliminar + clone: Clonar + toBook: Contabilizar + toUnbook: Descontabilizar + deleteInvoice: Eliminar factura + invoiceDeleted: Factura eliminada + cloneInvoice: Clonar factura + invoiceCloned: Factura clonada + showAgriculturalPdf: Ver recibo agrícola como PDF + sendAgriculturalPdf: Enviar recibo agrícola como PDF + checkSendInvoice: ¿Estás seguro que quieres enviarlo? + sendPdfInvoice: Enviar factura a PDF + createCorrective: Crear factura rectificativa + correctiveInvoice: Factura rectificativa + originalInvoice: Factura origen + entry: Entrada + emailEmpty: El email no puede estar vacío card: client: Cliente company: Empresa @@ -43,8 +63,6 @@ InvoiceIn: params: search: Id o nombre proveedor correctedFk: Rectificada -invoicein: - params: account: Cuenta contable correctingFk: Rectificativa - + diff --git a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue index 81b3e7c41aa..3ceb447dd23 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutSummary.vue @@ -10,6 +10,7 @@ import { getUrl } from 'src/composables/getUrl'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; onMounted(async () => { fetch(); @@ -113,6 +114,9 @@ const ticketsColumns = ref([ <template #header="{ entity: { invoiceOut } }"> <div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div> </template> + <template #menu="{ entity }"> + <InvoiceOutDescriptorMenu :invoice-out-data="entity.invoiceOut" /> + </template> <template #body="{ entity: { invoiceOut } }"> <QCard class="vn-one"> <VnTitle :text="t('globals.pageTitles.basicData')" /> diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 48d61201c5c..09873642d3c 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -355,7 +355,7 @@ watchEffect(selectedRows); <VnSelect url="InvoiceOutSerials" v-model="data.serial" - :label="t('InvoiceIn.serial')" + :label="t('invoicein.serial')" :options="invoiceOutSerialsOptions" option-label="description" option-value="code" diff --git a/src/pages/Item/Card/ItemDescriptor.vue b/src/pages/Item/Card/ItemDescriptor.vue index 4705525fb69..c6fee8540ef 100644 --- a/src/pages/Item/Card/ItemDescriptor.vue +++ b/src/pages/Item/Card/ItemDescriptor.vue @@ -6,17 +6,16 @@ import { useI18n } from 'vue-i18n'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; -import RegularizeStockForm from 'components/RegularizeStockForm.vue'; import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue'; import useCardDescription from 'src/composables/useCardDescription'; import axios from 'axios'; import { dashIfEmpty } from 'src/filters'; import { useArrayData } from 'src/composables/useArrayData'; -import { cloneItem } from 'src/pages/Item/composables/cloneItem'; +import ItemDescriptorMenu from './ItemDescriptorMenu.vue'; const $props = defineProps({ id: { - type: [Number, String], + type: Number, required: false, default: null, }, @@ -29,7 +28,7 @@ const $props = defineProps({ default: null, }, saleFk: { - type: [Number, String], + type: Number, default: null, }, warehouseFk: { @@ -38,7 +37,6 @@ const $props = defineProps({ }, }); -const { openCloneDialog } = cloneItem(); const route = useRoute(); const { t } = useI18n(); const warehouseConfig = ref(null); @@ -46,7 +44,6 @@ const entityId = computed(() => { return $props.id || route.params.id; }); -const regularizeStockFormDialog = ref(null); const mounted = ref(); const arrayDataStock = useArrayData('descriptorStock', { @@ -61,14 +58,14 @@ onMounted(async () => { const data = ref(useCardDescription()); const setData = async (entity) => { if (!entity) return; - data.value = useCardDescription(entity?.name, entity?.id); + data.value = useCardDescription(entity.name, entity.id); await updateStock(); }; const getItemConfigs = async () => { const { data } = await axios.get('ItemConfigs/findOne'); if (!data) return; - return (warehouseConfig.value = data.warehouseFk); + warehouseConfig.value = data.warehouseFk; }; const updateStock = async () => { if (!mounted.value) return; @@ -89,10 +86,6 @@ const updateStock = async () => { if (storeData?.itemFk == entityId.value) return; await stock.fetch({}); }; - -const openRegularizeStockForm = () => { - regularizeStockFormDialog.value.show(); -}; </script> <template> @@ -105,24 +98,12 @@ const openRegularizeStockForm = () => { :url="`Items/${entityId}/getCard`" @on-fetch="setData" > - <template #menu="{}"> - <QItem v-ripple clickable @click="openRegularizeStockForm()"> - <QItemSection> - {{ t('Regularize stock') }} - <QDialog ref="regularizeStockFormDialog"> - <RegularizeStockForm - :item-fk="entityId" - :warehouse-fk="warehouseFk" - @on-data-saved="updateStock()" - /> - </QDialog> - </QItemSection> - </QItem> - <QItem v-ripple clickable @click="openCloneDialog(entityId)"> - <QItemSection> - {{ t('globals.clone') }} - </QItemSection> - </QItem> + <template #menu> + <ItemDescriptorMenu + :entity-id="entityId" + :warehouse-fk="warehouseFk" + @regularized="updateStock" + /> </template> <template #before> <ItemDescriptorImage @@ -191,7 +172,6 @@ const openRegularizeStockForm = () => { <i18n> es: - Regularize stock: Regularizar stock Inactive article: Artículo inactivo </i18n> diff --git a/src/pages/Item/Card/ItemDescriptorImage.vue b/src/pages/Item/Card/ItemDescriptorImage.vue index 05185c5891b..a887964e978 100644 --- a/src/pages/Item/Card/ItemDescriptorImage.vue +++ b/src/pages/Item/Card/ItemDescriptorImage.vue @@ -131,7 +131,6 @@ const handlePhotoUpdated = (evt = false) => { <i18n> es: - Regularize stock: Regularizar stock All it's properties will be copied: Todas sus propiedades serán copiadas Do you want to clone this item?: ¿Desea clonar este artículo? warehouseText: Calculado sobre el almacén de { warehouseName } diff --git a/src/pages/Item/Card/ItemDescriptorMenu.vue b/src/pages/Item/Card/ItemDescriptorMenu.vue new file mode 100644 index 00000000000..3e9c6f2d4ce --- /dev/null +++ b/src/pages/Item/Card/ItemDescriptorMenu.vue @@ -0,0 +1,44 @@ +<script setup> +import RegularizeStockForm from 'components/RegularizeStockForm.vue'; +import { cloneItem } from 'src/pages/Item/composables/cloneItem'; + +const { openCloneDialog } = cloneItem(); + +defineProps({ + entityId: { + type: Number, + default: null, + }, + warehouseFk: { + type: Number, + default: null, + }, +}); + +defineEmits(['regularized']); +</script> +<template> + <QItem v-ripple clickable @click="$refs.regularizeStockFormDialog.show()"> + <QItemSection> + {{ $t('item.regularizeStock') }} + <QDialog ref="regularizeStockFormDialog"> + <RegularizeStockForm + :item-fk="entityId" + :warehouse-fk="warehouseFk" + @on-data-saved="$emit('regularized')" + /> + </QDialog> + </QItemSection> + </QItem> + <QItem v-ripple clickable @click="openCloneDialog(entityId)"> + <QItemSection> + {{ $t('globals.clone') }} + </QItemSection> + </QItem> +</template> + +<style lang="scss" scoped> +.weekdaysBtn { + margin: 1%; +} +</style> diff --git a/src/pages/Item/Card/ItemSummary.vue b/src/pages/Item/Card/ItemSummary.vue index e1b97d7c994..bc828bbf64c 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}`; <template #header="{ entity: { item } }"> {{ item.id }} - {{ item.name }} </template> + <template #menu> + <ItemDescriptorMenu :entity-id="entityId" :warehouse-fk="warehouseFk" /> + </template> <template #body="{ entity: { item, tags, visible, available, botanical } }"> <QCard class="vn-one photo"> <ItemDescriptorImage - :entity-id="Number(entityId)" + :entity-id="entityId" :visible="visible" :available="available" :show-edit-button="false" @@ -89,7 +93,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`; <QCard class="vn-one"> <VnTitle :url="getUrl(entityId, 'basic-data')" - :text="t('item.summary.basicData')" + :text="t('item.summary.otherData')" /> <VnLv :label="t('item.summary.intrastatCode')" diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index 1d6e6933953..f04563791d5 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -152,7 +152,7 @@ const columns = computed(() => [ }, columnField: { component: null, - } + }, }, { label: t('item.list.category'), @@ -498,5 +498,4 @@ es: Create Item: Crear artículo You can search by id: Puedes buscar por id Preview: Vista previa - Regularize stock: Regularizar stock </i18n> diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue index 4447d1bcfb7..d96fbca2f57 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) => { diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index c4ad0f5370a..907c72acd89 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -219,3 +219,4 @@ item: minSalesQuantity: 'Cantidad mínima de venta' genus: 'Genus' specie: 'Specie' + regularizeStock: Regularize stock diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index d558c38a648..015dea4dd63 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -210,6 +210,7 @@ item: minSalesQuantity: 'Cantidad mínima de venta' genus: 'Genus' specie: 'Specie' + regularizeStock: Regularizar stock buyRequest: ticketId: 'ID Ticket' shipped: 'F. envío' diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index e0c613aedc1..0d5f0146f7b 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -9,7 +9,6 @@ import useCardDescription from 'src/composables/useCardDescription'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import FetchData from 'components/FetchData.vue'; -import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const DEFAULT_ITEMS = 0; @@ -94,9 +93,6 @@ const total = ref(0); @on-fetch="setData" data-key="orderData" > - <template #menu="{ entity }"> - <OrderDescriptorMenu :order="entity" /> - </template> <template #body="{ entity }"> <VnLv :label="t('globals.state')" diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue index ad06dfe43c1..a289688e47e 100644 --- a/src/pages/Order/Card/OrderSummary.vue +++ b/src/pages/Order/Card/OrderSummary.vue @@ -12,6 +12,7 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy import FetchedTags from 'components/ui/FetchedTags.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue'; const { t } = useI18n(); const route = useRoute(); @@ -91,6 +92,9 @@ async function handleConfirm() { <QTooltip>{{ t('order.summary.confirmLines') }}</QTooltip> </QBtn> </template> + <template #menu="{ entity }"> + <OrderDescriptorMenu :order="entity" /> + </template> <template #body="{ entity }"> <QCard class="vn-one"> <VnTitle diff --git a/src/pages/Route/Card/RouteDescriptorMenu.vue b/src/pages/Route/Card/RouteDescriptorMenu.vue index 6092bcd9577..d1a70c86879 100644 --- a/src/pages/Route/Card/RouteDescriptorMenu.vue +++ b/src/pages/Route/Card/RouteDescriptorMenu.vue @@ -52,7 +52,7 @@ async function actualizeVolume() { const params = { isOk: true }; await axios.post(`Routes/${routeId}/updateVolume`, params); quasar.notify({ - message: t('globals.dataUpdated'), + message: t('globals.dataSaved'), type: 'positive', }); } diff --git a/src/pages/Route/Card/RouteSummary.vue b/src/pages/Route/Card/RouteSummary.vue index a0b9711956b..3051972b25d 100644 --- a/src/pages/Route/Card/RouteSummary.vue +++ b/src/pages/Route/Card/RouteSummary.vue @@ -12,6 +12,7 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import RouteDescriptorMenu from './RouteDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -128,6 +129,9 @@ const ticketColumns = ref([ <template #header="{ entity }"> <span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span> </template> + <template #menu="{ entity }"> + <RouteDescriptorMenu :route="entity.route" /> + </template> <template #body="{ entity }"> <QCard class="vn-max"> <VnTitle diff --git a/src/pages/Route/Roadmap/RoadmapSummary.vue b/src/pages/Route/Roadmap/RoadmapSummary.vue index 3fb36b4f7c3..1fbb1897d5b 100644 --- a/src/pages/Route/Roadmap/RoadmapSummary.vue +++ b/src/pages/Route/Roadmap/RoadmapSummary.vue @@ -10,6 +10,7 @@ import VnTitle from 'src/components/common/VnTitle.vue'; import CardSummary from 'components/ui/CardSummary.vue'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; import VnLinkPhone from 'components/ui/VnLinkPhone.vue'; +import RoadmapDescriptorMenu from './RoadmapDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -86,6 +87,9 @@ const filter = { <template #header="{ entity }"> <span>{{ `${entity?.id} - ${entity?.name}` }}</span> </template> + <template #menu="{ entity }"> + <RoadmapDescriptorMenu :route="entity" /> + </template> <template #body="{ entity }"> <QCard class="vn-one"> <VnTitle diff --git a/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue b/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue index 16351fdd42c..447737e9ce8 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue @@ -4,6 +4,7 @@ import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; import VnConfirm from 'components/ui/VnConfirm.vue'; +import { useRoute } from 'vue-router'; const $props = defineProps({ shelving: { @@ -14,8 +15,11 @@ const $props = defineProps({ const router = useRouter(); const quasar = useQuasar(); +const route = useRoute(); const { t } = useI18n(); +const emit = defineEmits(['onRemove']); + function confirmRemove() { quasar.dialog({ component: VnConfirm, @@ -32,11 +36,12 @@ async function remove() { return; } await axios.delete(`Shelvings/${$props.shelving.id}`); - await router.push({ name: 'ShelvingList' }); + if (route.name != 'ShelvingList') await router.push({ name: 'ShelvingList' }); quasar.notify({ message: t('globals.dataDeleted'), type: 'positive', }); + emit('onRemove', {}); } </script> <template> diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue index 0c1eb2a1df8..39fa4639f23 100644 --- a/src/pages/Shelving/Card/ShelvingSummary.vue +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -1,10 +1,11 @@ <script setup> -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnUserLink from 'components/ui/VnUserLink.vue'; +import ShelvingDescriptorMenu from './ShelvingDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -14,7 +15,7 @@ const $props = defineProps({ }); const route = useRoute(); const { t } = useI18n(); - +const summary = ref({}); const entityId = computed(() => $props.id || route.params.id); const filter = { @@ -43,7 +44,13 @@ const filter = { data-key="ShelvingSummary" > <template #header="{ entity }"> - <div>{{ entity.id }} - {{ entity.code }}</div> + <div>{{ entity.code }}</div> + </template> + <template #menu="{ entity }"> + <ShelvingDescriptorMenu + :shelving="entity" + @on-remove="$refs.summary.fetch()" + /> </template> <template #body="{ entity }"> <QCard class="vn-one"> diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 2f5f69e1cec..2c6e34864e5 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -96,7 +96,6 @@ function toTicketUrl(section) { ref="summaryRef" :url="`Tickets/${entityId}/summary`" data-key="TicketSummary" - data-cy="ticketSummary" > <template #header-left> <VnToSummary @@ -114,7 +113,7 @@ function toTicketUrl(section) { {{ entity.nickname }} </div> </template> - <template #header-right="{ entity }"> + <template #header-right> <div> <QBtnDropdown ref="stateBtnDropdownRef" @@ -133,18 +132,11 @@ function toTicketUrl(section) { @update:model-value="changeState" /> </QBtnDropdown> - <QBtn color="white" dense flat icon="more_vert" round size="md"> - <QTooltip> - {{ t('components.cardDescriptor.moreOptions') }} - </QTooltip> - <QMenu> - <QList> - <TicketDescriptorMenu :ticket="entity" /> - </QList> - </QMenu> - </QBtn> </div> </template> + <template #menu="{ entity }"> + <TicketDescriptorMenu :ticket="entity" /> + </template> <template #body="{ entity }"> <QCard class="vn-one"> <VnTitle @@ -258,7 +250,7 @@ function toTicketUrl(section) { <QCard class="vn-one" v-if="entity.notes.length"> <VnTitle :url="toTicketUrl('observation')" - :text="t('globals.pageTitles.notes')" + :text="t('ticket.pageTitles.notes')" /> <QVirtualScroll :items="entity.notes" diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 59c3a8a91fc..9deb2280858 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -11,6 +11,7 @@ import FetchData from 'src/components/FetchData.vue'; import VnRow from 'components/ui/VnRow.vue'; import { toDate, toCurrency } from 'src/filters'; import axios from 'axios'; +import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; const $props = defineProps({ id: { @@ -242,7 +243,9 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <template #header> <span>{{ travel.id }} - {{ travel.ref }}</span> </template> - + <template #menu="{ entity }"> + <TravelDescriptorMenuItems :travel="entity" /> + </template> <template #body> <QCard class="vn-one"> <QCardSection class="q-pa-none"> diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index f09aec81607..c09e8868f9a 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -6,11 +6,10 @@ import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnChangePassword from 'src/components/common/VnChangePassword.vue'; -import { useState } from 'src/composables/useState'; import axios from 'axios'; import VnImg from 'src/components/ui/VnImg.vue'; import EditPictureForm from 'components/EditPictureForm.vue'; -import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; +import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -28,8 +27,6 @@ const image = ref(null); const route = useRoute(); const { t } = useI18n(); -const state = useState(); -const user = state.getUser(); const showEditPhotoForm = ref(false); const toggleEditPictureForm = () => { showEditPhotoForm.value = !showEditPhotoForm.value; @@ -45,18 +42,6 @@ const getIsExcluded = async () => { workerExcluded.value = data.exists; }; -const handleExcluded = async () => { - if (workerExcluded.value) - await axios.delete(`WorkerDisableExcludeds/${entityId.value}`); - else - await axios.post(`WorkerDisableExcludeds`, { - workerFk: entityId.value, - dated: new Date(), - }); - - workerExcluded.value = !workerExcluded.value; -}; - const handlePhotoUpdated = (evt = false) => { image.value.reload(evt); }; @@ -72,25 +57,11 @@ const handlePhotoUpdated = (evt = false) => { @on-fetch="getIsExcluded" > <template #menu="{ entity }"> - <QItem v-ripple clickable @click="handleExcluded"> - <QItemSection> - {{ - workerExcluded - ? t('Click to allow the user to be disabled') - : t('Click to exclude the user from getting disabled') - }} - </QItemSection> - </QItem> - <QItem - v-if="!entity.user.emailVerified && user.id != entity.id" - v-ripple - clickable - @click="$refs.changePassRef.show" - > - <QItemSection> - {{ t('globals.changePass') }} - </QItemSection> - </QItem> + <WorkerDescriptorMenu + :worker="entity" + :is-excluded="workerExcluded" + @show-dialog="$refs.changePassRef.show" + /> </template> <template #before> <div class="relative-position"> @@ -144,14 +115,10 @@ const handlePhotoUpdated = (evt = false) => { :value="entity.user?.emailUser?.email" copy /> - <VnLv :label="t('worker.list.department')"> - <template #value> - <span class="link" v-text="entity.department?.department?.name" /> - <DepartmentDescriptorProxy - :id="entity.department?.department?.id" - /> - </template> - </VnLv> + <VnLv + :label="t('worker.list.department')" + :value="entity.department ? entity.department.department.name : null" + /> <VnLv :value="entity.phone"> <template #label> {{ t('globals.phone') }} @@ -211,8 +178,6 @@ const handlePhotoUpdated = (evt = false) => { <i18n> es: - Go to client: Ir a cliente - Go to user: Ir al usuario Click to allow the user to be disabled: Marcar para deshabilitar Click to exclude the user from getting disabled: Marcar para no deshabilitar </i18n> diff --git a/src/pages/Worker/Card/WorkerDescriptorMenu.vue b/src/pages/Worker/Card/WorkerDescriptorMenu.vue new file mode 100644 index 00000000000..8d82dc839f7 --- /dev/null +++ b/src/pages/Worker/Card/WorkerDescriptorMenu.vue @@ -0,0 +1,65 @@ +<script setup> +import { computed, ref, toRefs } from 'vue'; +import axios from 'axios'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { useState } from 'src/composables/useState'; + +const $props = defineProps({ + worker: { + type: Object, + required: true, + }, + isExcluded: { + type: Boolean, + required: true, + }, +}); +const route = useRoute(); +const { t } = useI18n(); +const state = useState(); +const user = state.getUser(); +const { worker } = toRefs($props); +const workerExcluded = ref($props.isExcluded); +const entityId = computed(() => { + return $props.worker.id || route.params.id; +}); +const emit = defineEmits(['show-dialog']); +const handleExcluded = async () => { + if (workerExcluded.value) + await axios.delete(`WorkerDisableExcludeds/${entityId.value}`); + else + await axios.post(`WorkerDisableExcludeds`, { + workerFk: entityId.value, + dated: new Date(), + }); + + workerExcluded.value = !workerExcluded.value; +}; + +const showChangePasswordDialog = () => { + emit('show-dialog', true); +}; +</script> + +<template> + <QItem v-ripple clickable @click="handleExcluded"> + <QItemSection> + {{ + workerExcluded + ? t('Click to allow the user to be disabled') + : t('Click to exclude the user from getting disabled') + }} + </QItemSection> + </QItem> + <QItem + v-if="!worker.user.emailVerified && user.id == worker.id" + v-ripple + clickable + @click="showChangePasswordDialog" + > + <QItemSection> + {{ t('globals.changePass') }} + </QItemSection> + </QItem> +</template> diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 496f1ca165f..bfb503f6b7f 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -11,6 +11,7 @@ import VnTitle from 'src/components/common/VnTitle.vue'; import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue'; import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; import { useAdvancedSummary } from 'src/composables/useAdvancedSummary'; +import WorkerDescriptorMenu from './WorkerDescriptorMenu.vue'; const route = useRoute(); const { t } = useI18n(); @@ -42,6 +43,9 @@ onBeforeMount(async () => { <template #header="{ entity }"> <div>{{ entity.id }} - {{ entity.firstName }} {{ entity.lastName }}</div> </template> + <template #menu="{ entity }"> + <WorkerDescriptorMenu :worker="entity" :is-excluded="workerExcluded" /> + </template> <template #body="{ entity: worker }"> <QCard class="vn-one"> <VnTitle :url="basicDataUrl" :text="t('globals.summary.basicData')" /> diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 2d65476cea3..c38da614c9f 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -100,6 +100,16 @@ const agencyOptions = ref([]); required="true" clearable /> + <VnInput + v-model="data.priceOptimum" + :label="t('Price optimum')" + type="number" + min="0" + required="true" + clearable + /> + </VnRow> + <VnRow> <VnInput v-model="data.bonus" :label="t('Bonus')" @@ -107,8 +117,6 @@ const agencyOptions = ref([]); min="0" clearable /> - </VnRow> - <VnRow> <VnSelect :label="t('Distribution point')" v-model="data.addressFk" @@ -152,6 +160,7 @@ es: Traveling days: Dias de viaje Closing: Cierre Price: Precio + Price optimum: Precio óptimo Bonus: Bonificación Inflation: Inflación Volumetric: Volumétrico diff --git a/src/pages/Zone/Card/ZoneEventInclusionForm.vue b/src/pages/Zone/Card/ZoneEventInclusionForm.vue index b4096e5a23e..805d03b2715 100644 --- a/src/pages/Zone/Card/ZoneEventInclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventInclusionForm.vue @@ -182,13 +182,19 @@ onMounted(() => { min="0" /> <VnInput - v-model="eventInclusionFormData.bonus" - :label="t('zone.bonus')" + v-model="eventInclusionFormData.priceOptimum" + :label="t('list.priceOptimum')" type="number" min="0" /> </VnRow> <VnRow> + <VnInput + v-model="eventInclusionFormData.bonus" + :label="t('zone.bonus')" + type="number" + min="0" + /> <VnInput v-model="eventInclusionFormData.m3Max" :label="t('zone.m3Max')" diff --git a/src/pages/Zone/Card/ZoneSummary.vue b/src/pages/Zone/Card/ZoneSummary.vue index 384ee1fe945..12480263321 100644 --- a/src/pages/Zone/Card/ZoneSummary.vue +++ b/src/pages/Zone/Card/ZoneSummary.vue @@ -11,6 +11,7 @@ import { getUrl } from 'src/composables/getUrl'; import { toCurrency } from 'filters/index'; import { toTimeFormat } from 'src/filters/date'; import axios from 'axios'; +import ZoneDescriptorMenuItems from './ZoneDescriptorMenuItems.vue'; const route = useRoute(); const { t } = useI18n(); @@ -79,6 +80,9 @@ onMounted(async () => { <template #header="{ entity }"> <div>#{{ entity.id }} - {{ entity.name }}</div> </template> + <template #menu="{ entity }"> + <ZoneDescriptorMenuItems :zone="entity" /> + </template> <template #body="{ entity: zone }"> <QCard class="vn-one"> <VnTitle :url="zoneUrl + `basic-data`" :text="t('summary.basicData')" /> diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index b277ceabb6c..575b12f7af9 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -22,6 +22,7 @@ list: agency: Agencia close: Cierre price: Precio + priceOptimum: Precio óptimo create: Crear zona openSummary: Detalles searchZone: Buscar zonas diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index 8b0a7089667..4dfde08dac5 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -1,19 +1,12 @@ import { RouterView } from 'vue-router'; -export default { - name: 'Claim', - path: '/claim', +const claimCard = { + name: 'ClaimCard', + path: ':id', + component: () => import('src/pages/Claim/Card/ClaimCard.vue'), + redirect: { name: 'ClaimSummary' }, meta: { - title: 'claims', - icon: 'vn:claims', - moduleName: 'Claim', - keyBinding: 'r', - }, - component: RouterView, - redirect: { name: 'ClaimMain' }, - menus: { - main: ['ClaimList'], - card: [ + menu: [ 'ClaimBasicData', 'ClaimLines', 'ClaimPhotos', @@ -23,109 +16,125 @@ export default { 'ClaimLog', ], }, + children: [ + { + path: 'summary', + name: 'ClaimSummary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Claim/Card/ClaimSummary.vue'), + }, + { + path: 'basic-data', + name: 'ClaimBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + acls: [{ model: 'Claim', props: 'findById', accessType: 'READ' }], + }, + component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'), + }, + { + path: 'lines', + name: 'ClaimLines', + meta: { + title: 'lines', + icon: 'vn:details', + }, + component: () => import('src/pages/Claim/Card/ClaimLines.vue'), + }, + { + path: 'photos', + name: 'ClaimPhotos', + meta: { + title: 'photos', + icon: 'image', + }, + component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'), + }, + { + path: 'notes', + name: 'ClaimNotes', + meta: { + title: 'notes', + icon: 'draft', + }, + component: () => import('src/pages/Claim/Card/ClaimNotes.vue'), + }, + { + path: 'development', + name: 'ClaimDevelopment', + meta: { + title: 'development', + icon: 'vn:traceability', + acls: [ + { + model: 'ClaimDevelopment', + props: '*', + accessType: 'WRITE', + }, + ], + }, + component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'), + }, + { + path: 'action', + name: 'ClaimAction', + meta: { + title: 'action', + icon: 'vn:actions', + }, + component: () => import('src/pages/Claim/Card/ClaimAction.vue'), + }, + { + path: 'log', + name: 'ClaimLog', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Claim/Card/ClaimLog.vue'), + }, + ], +} + +export default { + name: 'Claim', + path: '/claim', + meta: { + title: 'claims', + icon: 'vn:claims', + moduleName: 'Claim', + keyBinding: 'r', + menu: ['ClaimList'], + }, + component: RouterView, + redirect: { name: 'ClaimMain' }, children: [ { name: 'ClaimMain', path: '', component: () => import('src/components/common/VnModule.vue'), - redirect: { name: 'ClaimList' }, + redirect: { name: 'ClaimIndexMain' }, children: [ { - name: 'ClaimList', - path: 'list', - meta: { - title: 'list', - icon: 'view_list', - }, + path: '', + name: 'ClaimIndexMain', + redirect: { name: 'ClaimList' }, component: () => import('src/pages/Claim/ClaimList.vue'), - }, - ], - }, - { - name: 'ClaimCard', - path: ':id', - component: () => import('src/pages/Claim/Card/ClaimCard.vue'), - redirect: { name: 'ClaimSummary' }, - children: [ - { - name: 'ClaimSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Claim/Card/ClaimSummary.vue'), - }, - { - name: 'ClaimBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - acls: [{ model: 'Claim', props: 'findById', accessType: 'READ' }], - }, - component: () => import('src/pages/Claim/Card/ClaimBasicData.vue'), - }, - { - name: 'ClaimLines', - path: 'lines', - meta: { - title: 'lines', - icon: 'vn:details', - }, - component: () => import('src/pages/Claim/Card/ClaimLines.vue'), - }, - { - name: 'ClaimPhotos', - path: 'photos', - meta: { - title: 'photos', - icon: 'image', - }, - component: () => import('src/pages/Claim/Card/ClaimPhoto.vue'), - }, - { - name: 'ClaimNotes', - path: 'notes', - meta: { - title: 'notes', - icon: 'draft', - }, - component: () => import('src/pages/Claim/Card/ClaimNotes.vue'), - }, - { - name: 'ClaimDevelopment', - path: 'development', - meta: { - title: 'development', - icon: 'vn:traceability', - acls: [ - { - model: 'ClaimDevelopment', - props: '*', - accessType: 'WRITE', + children: [ + { + name: 'ClaimList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', }, - ], - }, - component: () => import('src/pages/Claim/Card/ClaimDevelopment.vue'), - }, - { - name: 'ClaimAction', - path: 'action', - meta: { - title: 'action', - icon: 'vn:actions', - }, - component: () => import('src/pages/Claim/Card/ClaimAction.vue'), - }, - { - name: 'ClaimLog', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Claim/Card/ClaimLog.vue'), + }, + claimCard, + ], }, ], }, diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js index cc8d86ea8a1..d8649753f3a 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() { @@ -93,8 +93,6 @@ export const useInvoiceOutGlobalStore = defineStore({ }, async makeInvoice(formData, clientsToInvoice) { - this.invoicing = true; - const promises = []; try { this.printer = formData.printer; const params = { @@ -119,11 +117,12 @@ export const useInvoiceOutGlobalStore = defineStore({ ); throw new Error("There aren't addresses to invoice"); } + this.invoicing = false; 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; + this.errors = []; + await this.invoiceClient(); } catch (err) { this.handleError(err); } @@ -182,45 +181,42 @@ export const useInvoiceOutGlobalStore = defineStore({ } }, - async invoiceClient(formData, index) { - 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'; this.invoicing = false; return; } try { + this.invoicing = true; 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); - this.isInvoicing = false; + if (data) this.makePdfAndNotify(data, address); } 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 { + this.invoicing = false; this.addressIndex++; - if (this.status != 'stopping') - await this.invoiceClient(formData, this.addressIndex); + this.invoiceClient(); } }, @@ -231,9 +227,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--; + this.invoiceClient(); } }, diff --git a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js index a64f98c107f..0eb8733551e 100644 --- a/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInCorrective.spec.js @@ -1,7 +1,7 @@ /// <reference types="cypress" /> describe('InvoiceInCorrective', () => { - const createRectificative = '.q-menu > .q-list > :nth-child(6) > .q-item__section'; + const createCorrective = '.q-menu > .q-list > :nth-child(6) > .q-item__section'; const rectificativeSection = '.q-drawer-container .q-list > a:nth-child(6)'; const saveDialog = '.q-card > .q-card__actions > .q-btn--standard '; @@ -13,7 +13,7 @@ describe('InvoiceInCorrective', () => { cy.openActionsDescriptor(); - cy.get(createRectificative).click(); + cy.get(createCorrective).click(); cy.get(saveDialog).click(); cy.wait('@corrective').then((interception) => { const correctingId = interception.response.body; From 76968fe6bd27a984687ae816656a4a6fdd6ad177 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 15 Jan 2025 09:01:37 +0100 Subject: [PATCH 16/21] fix: refs #7087 fixed some tests --- src/components/ui/__tests__/CardSummary.spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ui/__tests__/CardSummary.spec.js b/src/components/ui/__tests__/CardSummary.spec.js index b114797ef92..411ebf9bb60 100644 --- a/src/components/ui/__tests__/CardSummary.spec.js +++ b/src/components/ui/__tests__/CardSummary.spec.js @@ -62,12 +62,14 @@ describe('CardSummary', () => { }); it('should respond to prop changes and refetch data', async () => { + const newUrl = 'CardSummary/35'; + const newKey = 'cardSummaryKey/35'; const fetchSpy = vi.spyOn(vm.arrayData, 'fetch'); - await wrapper.setProps({ url: 'newUrl', filter: { key: 'newValue' } }); + await wrapper.setProps({ url: newUrl, filter: { key: newKey } }); expect(fetchSpy).toHaveBeenCalled(); - expect(vm.store.url).toBe('newUrl'); - expect(vm.store.filter).toEqual({ key: 'newValue' }); + expect(vm.store.url).toBe(newUrl); + expect(vm.store.filter).toEqual({ key: newKey }); }); it('should return true if route path ends with /summary' , () => { From 6a88cad90338bf4d370f77d08a68578f126df104 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 15 Jan 2025 09:55:59 +0100 Subject: [PATCH 17/21] fix: refs #7077 removed unused imports --- src/components/common/VnInputTime.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index b4b24661832..4147f89765d 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -1,13 +1,11 @@ <script setup> import { computed, ref, useAttrs } from 'vue'; -import { useI18n } from 'vue-i18n'; import { date } from 'quasar'; import VnTime from './VnTime.vue'; import { useRequired } from 'src/composables/useRequired'; const $attrs = useAttrs(); const { isRequired, requiredFieldRule } = useRequired($attrs); -const { t } = useI18n(); const model = defineModel({ type: String }); const props = defineProps({ timeOnly: { From 979353119ab3b33d03d420a82ccf613842f3914c Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 15 Jan 2025 09:56:43 +0100 Subject: [PATCH 18/21] refactor: refs #7087 removed unused imports --- src/components/ui/CardSummary.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index a1de3eee392..cf52bcd4063 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -2,7 +2,6 @@ import { ref, computed, watch, onBeforeMount } from 'vue'; import { useRoute } from 'vue-router'; import SkeletonSummary from 'components/ui/SkeletonSummary.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; import { useArrayData } from 'src/composables/useArrayData'; import { isDialogOpened } from 'src/filters'; import VnMoreOptions from './VnMoreOptions.vue'; From a153dd88604d374bbed95eb422ce456689c97aa7 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 15 Jan 2025 12:11:10 +0100 Subject: [PATCH 19/21] fix: refs #8316 use section-searchbar --- src/components/common/VnSection.vue | 1 - src/components/ui/FetchedTags.vue | 3 +- src/pages/Item/ItemList.vue | 2 +- src/pages/Order/Card/OrderCatalog.vue | 43 ++++++++++++--------------- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index 11fd6013f57..510865530ed 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -80,7 +80,6 @@ onBeforeMount(() => { /> <div :id="searchbarId"></div> </slot> - <RightMenu> <template #right-panel v-if="$slots['rightMenu'] || rightFilter"> <slot name="rightMenu"> diff --git a/src/components/ui/FetchedTags.vue b/src/components/ui/FetchedTags.vue index 6e159087c27..b3912f77944 100644 --- a/src/components/ui/FetchedTags.vue +++ b/src/components/ui/FetchedTags.vue @@ -18,8 +18,7 @@ const $props = defineProps({ }, columns: { type: Number, - required: false, - default: null, + default: 3, }, }); diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index f04563791d5..00d0f5c4e2e 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -391,7 +391,7 @@ onBeforeMount(async () => { {{ row?.subName.toUpperCase() }} </div> </div> - <FetchedTags :item="row" :columns="3" /> + <FetchedTags :item="row" /> </template> <template #more-create-dialog="{ data }"> <VnInput diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index da2e88aa990..186f216fb1b 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -15,15 +15,18 @@ const router = useRouter(); const stateStore = useStateStore(); const { t } = useI18n(); const dataKey = 'OrderCatalogList'; -const arrayData = useArrayData(dataKey); -const store = arrayData.store; -const tags = ref([]); -const itemRefs = ref({}); - -let catalogParams = { +const catalogParams = { orderFk: route.params.id, orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }), }; +const arrayData = useArrayData(dataKey, { + url: 'Orders/CatalogFilter', + limit: 50, + userParams: catalogParams, +}); +const store = arrayData.store; +const tags = ref([]); +const itemRefs = ref({}); onMounted(() => { stateStore.rightDrawer = true; @@ -66,7 +69,6 @@ function extractValueTags(items) { ); tagValue.value = resultValueTags; } -const autoLoad = computed(() => !!JSON.parse(route?.query.table ?? '{}')?.categoryFk); watch( () => store.data, @@ -78,16 +80,15 @@ watch( </script> <template> - <VnSearchbar - :data-key="dataKey" - :user-params="catalogParams" - :static-params="['orderFk', 'orderBy']" - :redirect="false" - url="Orders/CatalogFilter" - :label="t('Search items')" - :info="t('You can search items by name or id')" - :search-remove-params="false" - /> + <Teleport to="#section-searchbar" v-if="stateStore.isHeaderMounted()"> + <VnSearchbar + :data-key="dataKey" + :redirect="false" + :label="t('Search items')" + :info="t('You can search items by name or id')" + :search-remove-params="false" + /> + </Teleport> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> <OrderCatalogFilter :data-key="dataKey" @@ -98,13 +99,7 @@ watch( </Teleport> <QPage class="column items-center q-pa-md" data-cy="orderCatalogPage"> <div class="full-width"> - <VnPaginate - :data-key="dataKey" - url="Orders/CatalogFilter" - :limit="50" - :user-params="catalogParams" - :auto-load="autoLoad" - > + <VnPaginate :data-key="dataKey"> <template #body="{ rows }"> <div class="catalog-list"> <div v-if="rows && !rows?.length" class="no-result"> From 82e6cc07907d074e82cb51cdec19fe5919ed06c5 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Thu, 16 Jan 2025 07:18:05 +0100 Subject: [PATCH 20/21] refactor: refs #7077 removed some comments --- src/components/common/__tests__/VnInputTime.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/__tests__/VnInputTime.spec.js b/src/components/common/__tests__/VnInputTime.spec.js index 1ecc01cc398..2692ac71bf2 100644 --- a/src/components/common/__tests__/VnInputTime.spec.js +++ b/src/components/common/__tests__/VnInputTime.spec.js @@ -10,7 +10,7 @@ describe('VnInputTime', () => { wrapper = createWrapper(VnInputTime, { props: { isOutlined: true, - timeOnly: false, // Initial props values + timeOnly: false, }, }); vm = wrapper.vm; From 86169d980c6e390fc0591dfe1dd3c992fedaf795 Mon Sep 17 00:00:00 2001 From: guillermo <guillermo@verdnatura.es> Date: Thu, 16 Jan 2025 09:34:12 +0100 Subject: [PATCH 21/21] feat: refs #7882 Added coords to create a address --- .../components/CustomerAddressCreate.vue | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pages/Customer/components/CustomerAddressCreate.vue b/src/pages/Customer/components/CustomerAddressCreate.vue index bc4d6a1285a..32b4078db88 100644 --- a/src/pages/Customer/components/CustomerAddressCreate.vue +++ b/src/pages/Customer/components/CustomerAddressCreate.vue @@ -11,6 +11,7 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; const { t } = useI18n(); const route = useRoute(); @@ -150,6 +151,22 @@ function onAgentCreated({ id, fiscalName }, data) { </template> </VnSelectDialog> </VnRow> + <VnRow> + <VnInputNumber + :label="t('Longitude')" + clearable + v-model="data.longitude" + :decimal-places="7" + :positive="false" + /> + <VnInputNumber + :label="t('Latitude')" + clearable + v-model="data.latitude" + :decimal-places="7" + :positive="false" + /> + </VnRow> </template> </FormModel> </template> @@ -175,4 +192,6 @@ es: Mobile: Movíl Incoterms: Incoterms Customs agent: Agente de aduanas + Longitude: Longitud + Latitude: Latitud </i18n>