From 321b8dd9f911c2a15c4753f47ebdb7154bb7ee6d Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Fri, 11 Oct 2024 11:01:58 +0200 Subject: [PATCH 001/142] fix: refs #8004 some style issues on all list --- src/components/NavBar.vue | 21 +---------------- src/components/VnTable/VnTable.vue | 19 ++-------------- src/components/common/RightMenu.vue | 16 +++++++++++-- src/components/common/VnInputDate.vue | 19 ++-------------- src/components/common/VnInputTime.vue | 8 +------ src/composables/isMobile.js | 3 +++ src/css/app.scss | 8 ++++++- src/i18n/locale/en.yml | 2 +- src/i18n/locale/es.yml | 2 +- src/pages/Claim/ClaimList.vue | 2 +- src/pages/Customer/CustomerList.vue | 1 - src/pages/Entry/EntryList.vue | 4 ---- src/pages/InvoiceIn/InvoiceInList.vue | 16 +++++++------ src/pages/InvoiceOut/InvoiceOutList.vue | 7 +----- src/pages/Item/ItemList.vue | 2 +- src/pages/Route/RouteExtendedList.vue | 24 -------------------- src/pages/Route/RouteList.vue | 2 +- src/pages/Shelving/ShelvingList.vue | 6 ----- src/pages/Supplier/Card/SupplierAccounts.vue | 1 + src/pages/Supplier/SupplierList.vue | 9 ++++++-- src/pages/Ticket/TicketList.vue | 5 +++- src/pages/Travel/Card/TravelCard.vue | 1 + src/pages/Travel/TravelFilter.vue | 2 +- src/pages/Travel/TravelList.vue | 19 +++++----------- src/pages/Wagon/WagonList.vue | 1 - src/pages/Worker/WorkerList.vue | 1 + src/pages/Zone/ZoneList.vue | 7 +----- src/router/modules/shelving.js | 2 +- src/router/modules/wagon.js | 2 +- src/router/modules/zone.js | 2 +- 30 files changed, 70 insertions(+), 144 deletions(-) create mode 100644 src/composables/isMobile.js diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 00faaebc2f8..5497862c7fb 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -15,12 +15,10 @@ const quasar = useQuasar(); const state = useState(); const user = state.getUser(); const appName = 'Lilium'; +const pinnedModulesRef = ref(); onMounted(() => stateStore.setMounted()); - -const pinnedModulesRef = ref(); </script> - <template> <QHeader color="white" elevated> <QToolbar class="q-py-sm q-px-md"> @@ -55,16 +53,6 @@ const pinnedModulesRef = ref(); <QSpace /> <div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div id="actions-prepend"></div> - <QBtn - flat - v-if="!quasar.platform.is.mobile" - @click="pinnedModulesRef.redirect($route.params.id)" - icon="more_up" - > - <QTooltip> - {{ t('Go to Salix') }} - </QTooltip> - </QBtn> <QBtn :class="{ 'q-pa-none': quasar.platform.is.mobile }" id="pinnedModules" @@ -96,7 +84,6 @@ const pinnedModulesRef = ref(); <VnBreadcrumbs v-if="$q.screen.lt.md" class="q-ml-md" /> </QHeader> </template> - <style lang="scss" scoped> .searchbar { width: max-content; @@ -105,9 +92,3 @@ const pinnedModulesRef = ref(); background-color: var(--vn-section-color); } </style> -<i18n> -en: - Go to Salix: Go to Salix -es: - Go to Salix: Ir a Salix -</i18n> diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 5a30f4d5379..7d45fcfec6d 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -453,7 +453,7 @@ function handleOnDataSaved(_) { <template #header-cell="{ col }"> <QTh v-if="col.visible ?? true"> <div - class="column self-start q-ml-xs ellipsis" + class="column ellipsis" :class="`text-${col?.align ?? 'left'}`" :style="$props.columnSearch ? 'height: 75px' : ''" > @@ -495,7 +495,7 @@ function handleOnDataSaved(_) { <!-- Columns --> <QTd auto-width - class="no-margin q-px-xs" + class="no-margin" :class="[getColAlign(col), col.columnClass]" :style="col.style" v-if="col.visible ?? true" @@ -823,21 +823,6 @@ es: top: 0; padding: 12px 0; } - tbody { - .q-checkbox { - display: flex; - margin-bottom: 9px; - & .q-checkbox__label { - margin-left: 31px; - color: var(--vn-text-color); - } - & .q-checkbox__inner { - position: absolute; - left: 0; - color: var(--vn-label-color); - } - } - } .sticky { position: sticky; right: 0; diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 3aa1891f996..c56a226feb6 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -2,27 +2,34 @@ import { ref, onMounted, useSlots } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; +import isMobile from 'src/composables/isMobile'; const slots = useSlots(); const hasContent = ref(false); const rightPanel = ref(null); onMounted(() => { + console.log('1-stateStore.rightDrawer: ', stateStore.rightDrawer); rightPanel.value = document.querySelector('#right-panel'); if (!rightPanel.value) return; + console.log('2-stateStore.rightDrawer: ', stateStore.rightDrawer); // Check if there's content to display const observer = new MutationObserver(() => { hasContent.value = rightPanel.value.childNodes.length; }); + console.log('3-stateStore.rightDrawer: ', stateStore.rightDrawer); observer.observe(rightPanel.value, { subtree: true, childList: true, attributes: true, }); - if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false; + console.log('4-stateStore.rightDrawer: ', stateStore.rightDrawer); + if ((!slots['right-panel'] && !hasContent.value) || isMobile) + stateStore.rightDrawer = false; + console.log('5-stateStore.rightDrawer: ', stateStore.rightDrawer); }); const { t } = useI18n(); @@ -45,7 +52,12 @@ const stateStore = useStateStore(); </QBtn> </div> </Teleport> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QDrawer + v-model="stateStore.rightDrawer" + side="right" + :width="256" + :show-if-above="!isMobile" + > <QScrollArea class="fit"> <div id="right-panel"></div> <slot v-if="!hasContent" name="right-panel" /> diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 3d5afaf8062..4ce046c0915 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -101,7 +101,7 @@ const styleAttrs = computed(() => { :class="{ required: $attrs.required }" :rules="mixinRules" :clearable="false" - @click="isPopupOpen = true" + @click="isPopupOpen = !isPopupOpen" hide-bottom-space > <template #append> @@ -120,13 +120,6 @@ const styleAttrs = computed(() => { isPopupOpen = false; " /> - <QIcon - v-if="showEvent" - name="event" - class="cursor-pointer" - @click="isPopupOpen = !isPopupOpen" - :title="t('Open date')" - /> </template> <QMenu transition-show="scale" @@ -138,6 +131,7 @@ const styleAttrs = computed(() => { :no-parent-event="true" > <QDate + class="date-picker" v-model="popupDate" :landscape="true" :today-btn="true" @@ -153,15 +147,6 @@ const styleAttrs = computed(() => { </QInput> </div> </template> -<style lang="scss"> -.vn-input-date.q-field--standard.q-field--readonly .q-field__control:before { - border-bottom-style: solid; -} - -.vn-input-date.q-field--outlined.q-field--readonly .q-field__control:before { - border-style: solid; -} -</style> <i18n> es: Open date: Abrir fecha diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index a5e7d3002d5..d8150fd5843 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -79,7 +79,7 @@ function dateToTime(newDate) { :class="{ required: $attrs.required }" style="min-width: 100px" :rules="mixinRules" - @click="isPopupOpen = false" + @click="isPopupOpen = !isPopupOpen" type="time" hide-bottom-space > @@ -99,12 +99,6 @@ function dateToTime(newDate) { isPopupOpen = false; " /> - <QIcon - name="Schedule" - class="cursor-pointer" - @click="isPopupOpen = !isPopupOpen" - :title="t('Open time')" - /> </template> <QMenu transition-show="scale" diff --git a/src/composables/isMobile.js b/src/composables/isMobile.js new file mode 100644 index 00000000000..36f82694ac5 --- /dev/null +++ b/src/composables/isMobile.js @@ -0,0 +1,3 @@ +const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; +const isMobile = regex.test(navigator.userAgent); +export default isMobile; diff --git a/src/css/app.scss b/src/css/app.scss index c77af41f92e..1afa2396fa1 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -240,7 +240,7 @@ input::-webkit-inner-spin-button { .q-table { th, td { - padding: 1px 10px 1px 10px; + padding: 1px 3px 1px 3px; max-width: 100px; div span { overflow: hidden; @@ -299,3 +299,9 @@ input::-webkit-inner-spin-button { } } } + +.q-date__header-today { + border-radius: 12px; + border: 1px solid; + box-shadow: 0 4px 6px #00000000; +} diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index b73395df293..3ddd1dca404 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -118,7 +118,7 @@ globals: workCenters: Work centers modes: Modes zones: Zones - zonesList: Zones + zonesList: List deliveryDays: Delivery days upcomingDeliveries: Upcoming deliveries role: Role diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2552c954927..cdeae09443c 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -120,7 +120,7 @@ globals: workCenters: Centros de trabajo modes: Modos zones: Zonas - zonesList: Zonas + zonesList: Listado deliveryDays: Días de entrega upcomingDeliveries: Próximos repartos role: Role diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index 6d85817dc61..b6acd595094 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -104,6 +104,7 @@ const columns = computed(() => [ title: t('components.smartCard.viewSummary'), icon: 'preview', action: (row) => viewSummary(row.id, ClaimSummary), + isPrimary: true, }, ], }, @@ -134,7 +135,6 @@ const STATE_COLOR = { :columns="columns" redirect="claim" :right-search="false" - auto-load > <template #column-clientFk="{ row }"> <span class="link" @click.stop> diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 63f5149e88e..78c20da4b77 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -419,7 +419,6 @@ function handleLocation(data, location) { :columns="columns" redirect="customer" :right-search="false" - auto-load > <template #more-create-dialog="{ data }"> <VnSelect diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 6f7ff193503..46eff78d752 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -192,9 +192,6 @@ const columns = computed(() => [ ], }, ]); -onMounted(async () => { - stateStore.rightDrawer = true; -}); </script> <template> <VnSearchbar @@ -222,7 +219,6 @@ onMounted(async () => { order="id DESC" :columns="columns" redirect="entry" - auto-load :right-search="false" > <template #column-supplierFk="{ row }"> diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 0cad09378cd..532d9b2980f 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -1,7 +1,6 @@ <script setup> -import { ref, computed, onMounted, onUnmounted } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; import { downloadFile } from 'src/composables/downloadFile'; import { toDate, toCurrency } from 'src/filters/index'; import InvoiceInFilter from './InvoiceInFilter.vue'; @@ -15,19 +14,19 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; -const stateStore = useStateStore(); const { viewSummary } = useSummaryDialog(); const { t } = useI18n(); -onMounted(async () => (stateStore.rightDrawer = true)); -onUnmounted(() => (stateStore.rightDrawer = false)); - const tableRef = ref(); const cols = computed(() => [ { align: 'left', name: 'id', label: 'Id', + isId: true, + chip: { + condition: () => true, + }, }, { align: 'left', @@ -41,6 +40,7 @@ const cols = computed(() => [ }, }, columnClass: 'expand', + cardVisible: true, }, { align: 'left', @@ -67,6 +67,7 @@ const cols = computed(() => [ name: 'isBooked', label: t('invoiceIn.list.isBooked'), columnFilter: false, + cardVisible: true, }, { align: 'left', @@ -78,6 +79,7 @@ const cols = computed(() => [ name: 'amount', label: t('invoiceIn.list.amount'), format: ({ amount }) => toCurrency(amount), + cardVisible: true, }, { align: 'right', @@ -87,6 +89,7 @@ const cols = computed(() => [ title: t('components.smartCard.openSummary'), icon: 'preview', type: 'submit', + isPrimary: true, action: (row) => viewSummary(row.id, InvoiceInSummary), }, { @@ -121,7 +124,6 @@ const cols = computed(() => [ redirect="invoice-in" :columns="cols" :right-search="false" - :disable-option="{ card: true }" :auto-load="!!$route.query.params" > <template #column-supplierFk="{ row }"> diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 3dc5652512f..41f247c2ac5 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, onUnmounted, ref, computed, watchEffect } from 'vue'; +import { ref, computed, watchEffect } from 'vue'; import { useI18n } from 'vue-i18n'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; @@ -10,12 +10,10 @@ import { usePrintService } from 'composables/usePrintService'; import VnTable from 'components/VnTable/VnTable.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; import { toCurrency, toDate } from 'src/filters/index'; -import { useStateStore } from 'stores/useStateStore'; import { QBtn } from 'quasar'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; const { t } = useI18n(); -const stateStore = useStateStore(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); const invoiceOutSerialsOptions = ref([]); @@ -137,8 +135,6 @@ const columns = computed(() => [ ], }, ]); -onMounted(() => (stateStore.rightDrawer = true)); -onUnmounted(() => (stateStore.rightDrawer = false)); function openPdf(id) { try { @@ -210,7 +206,6 @@ watchEffect(selectedRows); order="id DESC" :columns="columns" redirect="invoice-out" - auto-load :table="{ 'row-key': 'id', selection: 'multiple', diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index ae4c9531c6a..657709a9e76 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -2,6 +2,7 @@ import { onMounted, ref, computed, reactive, onUnmounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import isMobile from 'src/composables/isMobile'; import FetchData from 'components/FetchData.vue'; import FetchedTags from 'components/ui/FetchedTags.vue'; @@ -389,7 +390,6 @@ const cloneItem = async (itemFk) => { }; onMounted(async () => { - stateStore.rightDrawer = true; const filteredColumns = columns.value.filter( (col) => col.name !== 'picture' && col.name !== 'actions' ); diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index 51da4ec12d7..3b092164db2 100644 --- a/src/pages/Route/RouteExtendedList.vue +++ b/src/pages/Route/RouteExtendedList.vue @@ -193,30 +193,6 @@ const columns = computed(() => [ columnFilter: false, columnClass: 'shrink', }, - { - align: 'right', - name: 'tableActions', - actions: [ - { - title: t('route.Add tickets'), - icon: 'vn:ticketAdd', - action: (row) => openTicketsDialog(row?.id), - isPrimary: true, - }, - { - title: t('route.components.smartCard.viewSummary'), - icon: 'preview', - action: (row) => viewSummary(row?.id, RouteSummary), - isPrimary: true, - }, - { - title: t('route.Route summary'), - icon: 'arrow_forward', - action: (row) => navigate(row?.id), - isPrimary: true, - }, - ], - }, ]); function navigate(id) { diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index d0feb9a6589..b82d1468ec2 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -26,7 +26,7 @@ const routeFilter = { }; const columns = computed(() => [ { - align: 'left', + align: 'right', isId: true, name: 'id', label: 'Id', diff --git a/src/pages/Shelving/ShelvingList.vue b/src/pages/Shelving/ShelvingList.vue index d29f6ff15b5..cd7c4bcf956 100644 --- a/src/pages/Shelving/ShelvingList.vue +++ b/src/pages/Shelving/ShelvingList.vue @@ -1,8 +1,6 @@ <script setup> import VnPaginate from 'components/ui/VnPaginate.vue'; -import { useStateStore } from 'stores/useStateStore'; import { useI18n } from 'vue-i18n'; -import { onMounted, onUnmounted } from 'vue'; import CardList from 'components/ui/CardList.vue'; import VnLv from 'components/ui/VnLv.vue'; import { useRouter } from 'vue-router'; @@ -12,7 +10,6 @@ import ShelvingSearchbar from 'pages/Shelving/Card/ShelvingSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RightMenu from 'src/components/common/RightMenu.vue'; -const stateStore = useStateStore(); const router = useRouter(); const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -20,9 +17,6 @@ const filter = { include: [{ relation: 'parking' }], }; -onMounted(() => (stateStore.rightDrawer = true)); -onUnmounted(() => (stateStore.rightDrawer = false)); - function navigate(id) { router.push({ path: `/shelving/${id}` }); } diff --git a/src/pages/Supplier/Card/SupplierAccounts.vue b/src/pages/Supplier/Card/SupplierAccounts.vue index 17746647845..816883f4489 100644 --- a/src/pages/Supplier/Card/SupplierAccounts.vue +++ b/src/pages/Supplier/Card/SupplierAccounts.vue @@ -18,6 +18,7 @@ const quasar = useQuasar(); const { notify } = useNotify(); const route = useRoute(); const { t } = useI18n(); +console.log(route.params.id); const bankEntitiesRef = ref(null); const supplier = ref(null); diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index ad668f0c0f5..9fdf606a213 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -12,7 +12,10 @@ const columns = computed(() => [ align: 'left', label: t('supplier.list.tableVisibleColumns.id'), name: 'id', - isTitle: true, + isId: true, + chip: { + condition: () => true, + }, }, { align: 'left', @@ -22,6 +25,7 @@ const columns = computed(() => [ columnFilter: { name: 'search', }, + isTitle: true, }, { align: 'left', @@ -30,6 +34,7 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, + cardVisible: true, }, { align: 'left', @@ -38,6 +43,7 @@ const columns = computed(() => [ columnFilter: { name: 'search', }, + cardVisible: true, }, { align: 'left', @@ -111,7 +117,6 @@ const columns = computed(() => [ }" order="id ASC" :columns="columns" - auto-load /> </template> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index ad97e75c15f..566f489cd8b 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -95,6 +95,7 @@ const columns = computed(() => [ columnField: { component: null, }, + cardVisible: true, format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson), }, { @@ -126,12 +127,14 @@ const columns = computed(() => [ name: 'nickname', label: t('ticketList.nickname'), columnClass: 'expand', + isTitle: true, }, { align: 'left', name: 'addressNickname', label: t('ticketList.addressNickname'), columnClass: 'expand', + cardVisible: true, }, { align: 'left', @@ -152,6 +155,7 @@ const columns = computed(() => [ }, }, columnClass: 'expand', + cardVisible: true, }, { align: 'left', @@ -302,7 +306,6 @@ const getDateColor = (date) => { onMounted(() => { initializeFromQuery(); - stateStore.rightDrawer = true; }); async function makeInvoice(ticket) { diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index 44bd9d430db..81536f75a67 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -1,6 +1,7 @@ <script setup> import VnCard from 'components/common/VnCard.vue'; import TravelDescriptor from './TravelDescriptor.vue'; +import TravelFilter from '../TravelFilter.vue'; const filter = { fields: [ diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 96298853f4c..c024ba92756 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -27,7 +27,7 @@ defineExpose({ states }); <VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ t(`travel.${tag.label}`) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index 05d2e5eda52..9aa48f6355c 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -1,8 +1,7 @@ <script setup> -import { onMounted, ref, computed } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter, useRoute } from 'vue-router'; -import { useStateStore } from 'stores/useStateStore'; import VnTable from 'components/VnTable/VnTable.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import TravelSummary from './Card/TravelSummary.vue'; @@ -15,7 +14,6 @@ import TravelFilter from './TravelFilter.vue'; const { viewSummary } = useSummaryDialog(); const router = useRouter(); const { t } = useI18n(); -const stateStore = useStateStore(); const route = useRoute(); const tableRef = ref(); const $props = defineProps({ @@ -27,9 +25,6 @@ const $props = defineProps({ const entityId = computed(() => $props.id || route.params.id); const travelFilterRef = ref(); -onMounted(async () => { - stateStore.rightDrawer = true; -}); const cloneTravel = (travelData) => { const stringifiedTravelData = JSON.stringify(travelData); @@ -48,9 +43,11 @@ const columns = computed(() => [ { align: 'left', name: 'id', - label: t('travel.travelList.tableVisibleColumns.id'), + label: 'Id', + chip: { + condition: () => true, + }, isId: true, - cardVisible: true, }, { align: 'left', @@ -60,7 +57,7 @@ const columns = computed(() => [ columnField: { component: null, }, - cardVisible: true, + isTitle: true, create: true, }, { @@ -139,7 +136,6 @@ const columns = computed(() => [ columnField: { component: null, }, - cardVisible: true, create: true, format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)), }, @@ -225,7 +221,6 @@ const columns = computed(() => [ :user-params="{ daysOnward: 7 }" order="landed DESC" :columns="columns" - auto-load redirect="travel" :is-editable="false" :use-model="true" @@ -273,7 +268,6 @@ const columns = computed(() => [ </template> </VnTable> </template> - <i18n> en: Add entry: Add entry @@ -287,7 +281,6 @@ es: Clone: Clonar Add entry: Añadir Entrada </i18n> - <style lang="scss" scoped> .is-active { color: #c8e484; diff --git a/src/pages/Wagon/WagonList.vue b/src/pages/Wagon/WagonList.vue index 02e3b6d16c5..1410070dbc8 100644 --- a/src/pages/Wagon/WagonList.vue +++ b/src/pages/Wagon/WagonList.vue @@ -98,7 +98,6 @@ async function remove(row) { url="Wagons" :filter="filter" :columns="columns" - auto-load order="id DESC" :right-search="false" :column-search="false" diff --git a/src/pages/Worker/WorkerList.vue b/src/pages/Worker/WorkerList.vue index 9795cbed070..19e2ad306e6 100644 --- a/src/pages/Worker/WorkerList.vue +++ b/src/pages/Worker/WorkerList.vue @@ -105,6 +105,7 @@ const columns = computed(() => [ title: t('components.smartCard.viewSummary'), icon: 'preview', action: (row) => viewSummary(row.id, WorkerSummary), + isPrimary: true, }, ], }, diff --git a/src/pages/Zone/ZoneList.vue b/src/pages/Zone/ZoneList.vue index d160ea6b574..0a2910d9c42 100644 --- a/src/pages/Zone/ZoneList.vue +++ b/src/pages/Zone/ZoneList.vue @@ -1,7 +1,7 @@ <script setup> import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import { computed, ref, onMounted } from 'vue'; +import { computed, ref } from 'vue'; import axios from 'axios'; import { toCurrency } from 'src/filters'; @@ -9,7 +9,6 @@ import { toTimeFormat } from 'src/filters/date'; import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import { useStateStore } from 'stores/useStateStore'; import ZoneSummary from 'src/pages/Zone/Card/ZoneSummary.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; @@ -24,7 +23,6 @@ const router = useRouter(); const { notify } = useNotify(); const { viewSummary } = useSummaryDialog(); const { openConfirmationModal } = useVnConfirm(); -const stateStore = useStateStore(); const tableRef = ref(); const warehouseOptions = ref([]); @@ -130,8 +128,6 @@ const handleClone = (id) => { () => clone(id) ); }; - -onMounted(() => (stateStore.rightDrawer = true)); </script> <template> @@ -155,7 +151,6 @@ onMounted(() => (stateStore.rightDrawer = true)); :columns="columns" redirect="zone" :right-search="false" - auto-load > <template #more-create-dialog="{ data }"> <VnSelect diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index b7f50a3b606..2eeec4b9872 100644 --- a/src/router/modules/shelving.js +++ b/src/router/modules/shelving.js @@ -25,7 +25,7 @@ export default { path: 'list', name: 'ShelvingList', meta: { - title: 'shelvingList', + title: 'list', icon: 'view_list', }, component: () => import('src/pages/Shelving/ShelvingList.vue'), diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index e25e585eb57..3556f215fec 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -25,7 +25,7 @@ export default { path: 'list', name: 'WagonList', meta: { - title: 'wagonsList', + title: 'list', icon: 'vn:trolley', }, component: () => import('src/pages/Wagon/WagonList.vue'), diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js index 1f27cc76ff1..cac1f2bd7d6 100644 --- a/src/router/modules/zone.js +++ b/src/router/modules/zone.js @@ -38,7 +38,7 @@ export default { name: 'ZoneList', meta: { title: 'zonesList', - icon: 'vn:zone', + icon: 'view_list', }, component: () => import('src/pages/Zone/ZoneList.vue'), }, From 5ac59840a27afcc4708b8095be0a05525489206b Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Fri, 11 Oct 2024 11:03:36 +0200 Subject: [PATCH 002/142] refactor: refs #8004 remove consoleLogs --- src/components/common/RightMenu.vue | 6 ------ src/pages/Supplier/Card/SupplierAccounts.vue | 7 +++---- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index c56a226feb6..2cd6bbd172b 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -9,27 +9,21 @@ const hasContent = ref(false); const rightPanel = ref(null); onMounted(() => { - console.log('1-stateStore.rightDrawer: ', stateStore.rightDrawer); rightPanel.value = document.querySelector('#right-panel'); if (!rightPanel.value) return; - console.log('2-stateStore.rightDrawer: ', stateStore.rightDrawer); - // Check if there's content to display const observer = new MutationObserver(() => { hasContent.value = rightPanel.value.childNodes.length; }); - console.log('3-stateStore.rightDrawer: ', stateStore.rightDrawer); observer.observe(rightPanel.value, { subtree: true, childList: true, attributes: true, }); - console.log('4-stateStore.rightDrawer: ', stateStore.rightDrawer); if ((!slots['right-panel'] && !hasContent.value) || isMobile) stateStore.rightDrawer = false; - console.log('5-stateStore.rightDrawer: ', stateStore.rightDrawer); }); const { t } = useI18n(); diff --git a/src/pages/Supplier/Card/SupplierAccounts.vue b/src/pages/Supplier/Card/SupplierAccounts.vue index 816883f4489..cbb852482dd 100644 --- a/src/pages/Supplier/Card/SupplierAccounts.vue +++ b/src/pages/Supplier/Card/SupplierAccounts.vue @@ -14,11 +14,10 @@ import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; import { useQuasar } from 'quasar'; -const quasar = useQuasar(); -const { notify } = useNotify(); -const route = useRoute(); const { t } = useI18n(); -console.log(route.params.id); +const { notify } = useNotify(); +const quasar = useQuasar(); +const route = useRoute(); const bankEntitiesRef = ref(null); const supplier = ref(null); From e6756aebee9b7f34fe4179eb2e085499958ef0e7 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Fri, 11 Oct 2024 11:39:28 +0200 Subject: [PATCH 003/142] fix: refs #8004 vnTable card with and add permanent labels --- src/components/VnTable/VnTable.vue | 10 ++-------- src/components/ui/VnLv.vue | 2 +- src/pages/Entry/EntryList.vue | 6 ++++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 7d45fcfec6d..a6edf8d8705 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -629,13 +629,7 @@ function handleOnDataSaved(_) { :key="col.name" class="fields" > - <VnLv - :label=" - !col.component && col.label - ? `${col.label}:` - : '' - " - > + <VnLv :label="col.label + ':'"> <template #value> <span @click="stopEventPropagation($event)" @@ -775,7 +769,7 @@ es: .grid-three { display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, max-content)); + grid-template-columns: repeat(auto-fit, minmax(350px, max-content)); max-width: 100%; grid-gap: 20px; margin: 0 auto; diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index ff65f759b85..a198c9c05c3 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -39,7 +39,7 @@ const val = computed(() => $props.value); <template v-else> <div v-if="label || $slots.label" class="label"> <slot name="label"> - <span>{{ label }}</span> + <span style="color: var(--vn-label-color)">{{ label }}</span> </slot> </div> <div class="value"> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index 46eff78d752..5ccda43dee1 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -45,8 +45,10 @@ const columns = computed(() => [ align: 'left', label: t('entry.list.tableVisibleColumns.id'), name: 'id', - isTitle: true, - cardVisible: true, + isId: true, + chip: { + condition: () => true, + }, }, { align: 'left', From b32c8dd9f0676927841fd09e1569ff728853fcbe Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Sun, 13 Oct 2024 11:20:51 +0200 Subject: [PATCH 004/142] fix: refs #8004 more list style issues --- src/components/VnTable/VnTable.vue | 1 + src/components/common/RightMenu.vue | 13 ++------ src/components/ui/CardSummary.vue | 13 +++++--- src/pages/Customer/CustomerList.vue | 20 ++++++------ src/pages/Entry/EntryFilter.vue | 7 ----- src/pages/Order/OrderList.vue | 31 ++++++++++--------- src/pages/Shelving/Card/ShelvingSummary.vue | 2 +- src/pages/Supplier/Card/SupplierSummary.vue | 2 +- src/pages/Travel/Card/TravelCard.vue | 3 +- src/pages/Travel/Card/TravelSummary.vue | 2 +- src/pages/Travel/TravelList.vue | 1 + src/pages/Wagon/WagonList.vue | 1 - .../vnComponent/vnBreadcrumbs.spec.js | 6 +--- .../vnComponent/vnSearchBar.spec.js | 1 + .../wagonType/wagonTypeCreate.spec.js | 0 .../wagonType/wagonTypeEdit.spec.js | 0 .../integration/zone/zoneCreate.spec.js | 2 ++ .../cypress/integration/zone/zoneList.spec.js | 4 +-- 18 files changed, 48 insertions(+), 61 deletions(-) rename test/cypress/integration/{ => wagon}/wagonType/wagonTypeCreate.spec.js (100%) rename test/cypress/integration/{ => wagon}/wagonType/wagonTypeEdit.spec.js (100%) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index a6edf8d8705..3752be9a836 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -149,6 +149,7 @@ const tableModes = [ disable: $props.disableOption?.card, }, ]; + onBeforeMount(() => { setUserParams(route.query[$props.searchUrl]); hasParams.value = params.value && Object.keys(params.value).length !== 0; diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 2cd6bbd172b..81555067cbd 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -4,6 +4,8 @@ import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; import isMobile from 'src/composables/isMobile'; +const { t } = useI18n(); +const stateStore = useStateStore(); const slots = useSlots(); const hasContent = ref(false); const rightPanel = ref(null); @@ -21,13 +23,9 @@ onMounted(() => { childList: true, attributes: true, }); - if ((!slots['right-panel'] && !hasContent.value) || isMobile) stateStore.rightDrawer = false; }); - -const { t } = useI18n(); -const stateStore = useStateStore(); </script> <template> <Teleport to="#actions-append" v-if="stateStore.isHeaderMounted()"> @@ -46,12 +44,7 @@ const stateStore = useStateStore(); </QBtn> </div> </Teleport> - <QDrawer - v-model="stateStore.rightDrawer" - side="right" - :width="256" - :show-if-above="!isMobile" - > + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256"> <QScrollArea class="fit"> <div id="right-panel"></div> <slot v-if="!hasContent" name="right-panel" /> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 11dcbee3b24..133ba7f21aa 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -1,9 +1,9 @@ <script setup> -import { ref, computed, watch, onBeforeMount } from 'vue'; +import { ref, computed, watch, onBeforeMount, onMounted } 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 { useStateStore } from 'src/stores/useStateStore'; const props = defineProps({ url: { @@ -39,6 +39,7 @@ 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, @@ -50,6 +51,10 @@ onBeforeMount(async () => { watch(props, async () => await fetch()); }); +onMounted(() => { + stateStore.rightMenu = false; + console.log('useStateStore: ', useStateStore.rightMenu); +}); async function fetch() { store.url = props.url; store.filter = props.filter ?? {}; @@ -75,7 +80,6 @@ function existSummary(routes) { } } </script> - <template> <div class="summary container"> <QCard class="cardSummary"> @@ -96,7 +100,7 @@ function existSummary(routes) { <span v-else></span> </slot> <slot name="header" :entity="entity" dense> - <VnLv :label="`${entity.id} -`" :value="entity.name" /> + {{ entity.id + ' - ' + entity.name }} </slot> <slot name="header-right"> <span></span> @@ -109,7 +113,6 @@ function existSummary(routes) { </QCard> </div> </template> - <style lang="scss"> .summary.container { display: flex; diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 78c20da4b77..f3b0a071c8a 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -2,22 +2,21 @@ import { ref, computed, markRaw } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import { toDate } from 'src/filters'; + +import RightMenu from 'src/components/common/RightMenu.vue'; +import CustomerSummary from './Card/CustomerSummary.vue'; +import CustomerFilter from './CustomerFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnTable from 'components/VnTable/VnTable.vue'; import VnLocation from 'src/components/common/VnLocation.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import CustomerSummary from './Card/CustomerSummary.vue'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import RightMenu from 'src/components/common/RightMenu.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; -import { toDate } from 'src/filters'; -import CustomerFilter from './CustomerFilter.vue'; const { t } = useI18n(); const router = useRouter(); - const tableRef = ref(); - const columns = computed(() => [ { align: 'left', @@ -406,6 +405,7 @@ function handleLocation(data, location) { ref="tableRef" data-key="Customer" url="Clients/filter" + order="id DESC" :create="{ urlCreate: 'Clients/createWithUser', title: t('globals.pageTitles.customerCreate'), @@ -415,10 +415,9 @@ function handleLocation(data, location) { isEqualizated: false, }, }" - order="id DESC" :columns="columns" - redirect="customer" :right-search="false" + redirect="customer" > <template #more-create-dialog="{ data }"> <VnSelect @@ -453,7 +452,6 @@ function handleLocation(data, location) { </QItem> </template> </VnSelect> - <VnLocation :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]" v-model="data.location" @@ -474,7 +472,7 @@ function handleLocation(data, location) { </template> <i18n> es: - Web user: Usuario Web + Web user: Usuario web </i18n> <style lang="scss" scoped> .col-content { diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue index 3b88072fa21..f50810eb716 100644 --- a/src/pages/Entry/EntryFilter.vue +++ b/src/pages/Entry/EntryFilter.vue @@ -1,8 +1,6 @@ <script setup> import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { onMounted } from 'vue'; -import { useStateStore } from 'stores/useStateStore'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; @@ -20,11 +18,6 @@ const props = defineProps({ const currenciesOptions = ref([]); const companiesOptions = ref([]); - -const stateStore = useStateStore(); -onMounted(async () => { - stateStore.rightDrawer = true; -}); </script> <template> diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 6b6b4182847..94f9ffed759 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -1,21 +1,22 @@ <script setup> -import axios from 'axios'; import { useI18n } from 'vue-i18n'; -import { computed, ref } from 'vue'; +import { computed, ref, onMounted } from 'vue'; import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; -import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; +import { toDateTimeFormat } from 'src/filters/date'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +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 OrderSearchbar from './Card/OrderSearchbar.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; -import OrderFilter from './Card/OrderFilter.vue'; -import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; -import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; -import { toDateTimeFormat } from 'src/filters/date'; -import { onMounted } from 'vue'; -import { useRoute } from 'vue-router'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -166,9 +167,9 @@ const getDateColor = (date) => { today.setHours(0, 0, 0, 0); const timeTicket = new Date(date); timeTicket.setHours(0, 0, 0, 0); - const comparation = today - timeTicket; - if (comparation == 0) return 'bg-warning'; - if (comparation < 0) return 'bg-success'; + const difference = today - timeTicket; + if (difference == 0) return 'bg-warning'; + if (difference < 0) return 'bg-success'; }; onMounted(() => { @@ -203,8 +204,8 @@ onMounted(() => { }, }" :user-params="{ showEmpty: false }" - :right-search="false" :columns="columns" + :right-search="false" redirect="order" > <template #column-clientFk="{ row }"> diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue index 94175b0c165..03762aaae7b 100644 --- a/src/pages/Shelving/Card/ShelvingSummary.vue +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -43,7 +43,7 @@ const filter = { data-key="ShelvingSummary" > <template #header="{ entity }"> - <div>{{ entity.code }}</div> + <div>{{ entity.id }} - {{ entity.code }}</div> </template> <template #body="{ entity }"> <QCard class="vn-one"> diff --git a/src/pages/Supplier/Card/SupplierSummary.vue b/src/pages/Supplier/Card/SupplierSummary.vue index 5791db1eb41..f588fa5861c 100644 --- a/src/pages/Supplier/Card/SupplierSummary.vue +++ b/src/pages/Supplier/Card/SupplierSummary.vue @@ -41,7 +41,7 @@ const getUrl = (section) => `#/supplier/${entityId.value}/${section}`; data-key="SupplierSummary" > <template #header> - <span>{{ supplier.name }} - {{ supplier.id }}</span> + <span>{{ supplier.id }} - {{ supplier.name }}</span> </template> <template #body> diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index 81536f75a67..6e61ecfc4ca 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -36,8 +36,9 @@ const filter = { data-key="Travel" base-url="Travels" search-data-key="TravelList" - :filter="filter" :descriptor="TravelDescriptor" + :filter-panel="TravelFilter" + :filter="filter" :searchbar-props="{ url: 'Travels', label: 'Search travel', diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 4be1984931a..bbc2c467d61 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -252,7 +252,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; data-key="TravelSummary" > <template #header> - <span>{{ travel.ref }} - {{ travel.id }}</span> + <span>{{ travel.id }} - {{ travel.ref }}</span> </template> <template #body> diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index 9aa48f6355c..ccbb2a977f9 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -10,6 +10,7 @@ import { toDate } from 'src/filters'; import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; import RightMenu from 'src/components/common/RightMenu.vue'; import TravelFilter from './TravelFilter.vue'; +import VnInputNumber from 'src/components/common/VnInputNumber.vue'; const { viewSummary } = useSummaryDialog(); const router = useRouter(); diff --git a/src/pages/Wagon/WagonList.vue b/src/pages/Wagon/WagonList.vue index 1410070dbc8..e06bbedcdc4 100644 --- a/src/pages/Wagon/WagonList.vue +++ b/src/pages/Wagon/WagonList.vue @@ -99,7 +99,6 @@ async function remove(row) { :filter="filter" :columns="columns" order="id DESC" - :right-search="false" :column-search="false" :default-mode="'card'" :disable-option="{ table: true }" diff --git a/test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js b/test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js index 3c839c1c7d7..347dae7df5a 100644 --- a/test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js +++ b/test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js @@ -1,6 +1,5 @@ /// <reference types="cypress" /> describe('VnBreadcrumbs', () => { - const firstCard = '.q-infinite-scroll > :nth-child(1)'; const lastBreadcrumb = '.q-breadcrumbs--last > .q-breadcrumbs__el'; beforeEach(() => { cy.login('developer'); @@ -12,10 +11,7 @@ describe('VnBreadcrumbs', () => { }); it('should get the correct breadcrumbs', () => { - cy.visit('#/customer/list'); - cy.get('.q-breadcrumbs__el').should('have.length', 2); - - cy.get(firstCard).click(); + cy.visit('#/customer/1/summary'); cy.get(`${lastBreadcrumb} > .q-icon`).should('have.text', 'launch'); }); }); diff --git a/test/cypress/integration/vnComponent/vnSearchBar.spec.js b/test/cypress/integration/vnComponent/vnSearchBar.spec.js index 580199bc381..c927ce1bb5e 100644 --- a/test/cypress/integration/vnComponent/vnSearchBar.spec.js +++ b/test/cypress/integration/vnComponent/vnSearchBar.spec.js @@ -5,6 +5,7 @@ describe('VnSearchBar', () => { const idGap = '.q-item > .q-item__label'; const vnTableRow = '.q-virtual-scroll__content'; beforeEach(() => { + cy.viewport(1920, 1080); cy.login('developer'); cy.visit('#/customer/list'); }); diff --git a/test/cypress/integration/wagonType/wagonTypeCreate.spec.js b/test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js similarity index 100% rename from test/cypress/integration/wagonType/wagonTypeCreate.spec.js rename to test/cypress/integration/wagon/wagonType/wagonTypeCreate.spec.js diff --git a/test/cypress/integration/wagonType/wagonTypeEdit.spec.js b/test/cypress/integration/wagon/wagonType/wagonTypeEdit.spec.js similarity index 100% rename from test/cypress/integration/wagonType/wagonTypeEdit.spec.js rename to test/cypress/integration/wagon/wagonType/wagonTypeEdit.spec.js diff --git a/test/cypress/integration/zone/zoneCreate.spec.js b/test/cypress/integration/zone/zoneCreate.spec.js index 9618ea84611..cc5de8c6cdd 100644 --- a/test/cypress/integration/zone/zoneCreate.spec.js +++ b/test/cypress/integration/zone/zoneCreate.spec.js @@ -22,6 +22,7 @@ describe('ZoneCreate', () => { ...data, }); cy.get('input[aria-label="Close"]').type('10:00'); + cy.get('body').click(); cy.get('.q-mt-lg > .q-btn--standard').click(); cy.get(notification).should('contains.text', 'Agency cannot be blank'); }); @@ -32,6 +33,7 @@ describe('ZoneCreate', () => { Agency: { val: 'inhouse pickup', type: 'select' }, }); cy.get('input[aria-label="Close"]').type('10:00'); + cy.get('body').click(); cy.get('.q-mt-lg > .q-btn--standard').click(); cy.get(notification).should('contains.text', 'Data created'); }); diff --git a/test/cypress/integration/zone/zoneList.spec.js b/test/cypress/integration/zone/zoneList.spec.js index 92c77a2c645..8d01d4e4e8f 100644 --- a/test/cypress/integration/zone/zoneList.spec.js +++ b/test/cypress/integration/zone/zoneList.spec.js @@ -6,9 +6,7 @@ describe('ZoneList', () => { }); it('should filter by agency', () => { - cy.get( - ':nth-child(1) > .column > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' - ).type('{downArrow}{enter}'); + cy.get('input[aria-label="Agency"]').type('{downArrow}{enter}'); }); it('should open the zone summary', () => { From df775ea5942ad441cafc3be88d370c72cd8b3ac2 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 14 Oct 2024 08:49:01 +0200 Subject: [PATCH 005/142] feat: refs #8004 hide rightFilter --- src/components/common/RightMenu.vue | 5 +++-- src/components/ui/CardSummary.vue | 2 +- src/composables/isMobile.js | 3 --- src/pages/Item/ItemList.vue | 1 - src/stores/useStateStore.js | 5 +++++ 5 files changed, 9 insertions(+), 7 deletions(-) delete mode 100644 src/composables/isMobile.js diff --git a/src/components/common/RightMenu.vue b/src/components/common/RightMenu.vue index 81555067cbd..32dc2874dce 100644 --- a/src/components/common/RightMenu.vue +++ b/src/components/common/RightMenu.vue @@ -2,9 +2,10 @@ import { ref, onMounted, useSlots } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; -import isMobile from 'src/composables/isMobile'; +import { useQuasar } from 'quasar'; const { t } = useI18n(); +const quasar = useQuasar(); const stateStore = useStateStore(); const slots = useSlots(); const hasContent = ref(false); @@ -23,7 +24,7 @@ onMounted(() => { childList: true, attributes: true, }); - if ((!slots['right-panel'] && !hasContent.value) || isMobile) + if ((!slots['right-panel'] && !hasContent.value) || quasar.platform.is.mobile) stateStore.rightDrawer = false; }); </script> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 133ba7f21aa..24c9c18df30 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -52,7 +52,7 @@ onBeforeMount(async () => { }); onMounted(() => { - stateStore.rightMenu = false; + stateStore.rightDrawerChangeValue(false); console.log('useStateStore: ', useStateStore.rightMenu); }); async function fetch() { diff --git a/src/composables/isMobile.js b/src/composables/isMobile.js deleted file mode 100644 index 36f82694ac5..00000000000 --- a/src/composables/isMobile.js +++ /dev/null @@ -1,3 +0,0 @@ -const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; -const isMobile = regex.test(navigator.userAgent); -export default isMobile; diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index 657709a9e76..a1b22c00bdb 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -2,7 +2,6 @@ import { onMounted, ref, computed, reactive, onUnmounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import isMobile from 'src/composables/isMobile'; import FetchData from 'components/FetchData.vue'; import FetchedTags from 'components/ui/FetchedTags.vue'; diff --git a/src/stores/useStateStore.js b/src/stores/useStateStore.js index 328df997877..686e76c77b3 100644 --- a/src/stores/useStateStore.js +++ b/src/stores/useStateStore.js @@ -15,6 +15,10 @@ export const useStateStore = defineStore('stateStore', () => { rightDrawer.value = !rightDrawer.value; } + function rightDrawerChangeValue(value) { + rightDrawer.value = value; + } + function toggleSubToolbar() { subToolbar.value = !subToolbar.value; } @@ -50,5 +54,6 @@ export const useStateStore = defineStore('stateStore', () => { isRightDrawerShown, isSubToolbarShown, toggleSubToolbar, + rightDrawerChangeValue, }; }); From 1dcdc54ab88b771ab6bd60a62ac88943a98b4843 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 6 Nov 2024 13:45:02 +0100 Subject: [PATCH 006/142] feat: refs #6583 add destination opt filter --- src/pages/Ticket/TicketAdvanceFilter.vue | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue index a1d301f3521..b7ae2e65474 100644 --- a/src/pages/Ticket/TicketAdvanceFilter.vue +++ b/src/pages/Ticket/TicketAdvanceFilter.vue @@ -169,6 +169,16 @@ onMounted(async () => await getItemPackingTypes()); </VnSelect> </QItemSection> </QItem> + <QItem> + <QItemSection> + <QCheckbox + :toggle-indeterminate="false" + label="only with destination" + v-model="params.onlyWithDestination" + @update:model-value="searchFn()" + /> + </QItemSection> + </QItem> </template> </VnFilterPanel> </template> From 0cf6ee759011fa614ccdde2d5020eaf69cc00296 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 7 Nov 2024 16:14:25 +0100 Subject: [PATCH 007/142] feat: refs #6583 add icon --- src/pages/Ticket/TicketAdvance.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/Ticket/TicketAdvance.vue b/src/pages/Ticket/TicketAdvance.vue index bdd980c07f6..b4dc42b5939 100644 --- a/src/pages/Ticket/TicketAdvance.vue +++ b/src/pages/Ticket/TicketAdvance.vue @@ -464,6 +464,7 @@ watch( color="primary" name="vn:agency-term" size="xs" + class="q-mr-xs" > <QTooltip class="column"> <span> @@ -482,6 +483,14 @@ watch( </span> </QTooltip> </QIcon> + <QIcon + v-if="row.saleClonedFk" + color="primary" + name="content_copy" + size="xs" + > + <QTooltip>{{ t('advanceTickets.clonedSales') }}</QTooltip> + </QIcon> </template> <template #column-id="{ row }"> <QBtn flat class="link"> From b49c912b66a05173db935c8227d5eb8c9b36cd7f Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 12:51:50 +0100 Subject: [PATCH 008/142] fix: refs #7936 rollback --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 08834ecb820..c2637bac3fe 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -187,7 +187,6 @@ const formatOpt = (row, { model, options }, prop) => { </template> <template #body-cell-taxablebase="{ row }"> <QTd> - {{ currency }} <VnInputNumber :class="{ 'no-pointer-events': isNotEuro(currency), From cc72f220dc2989fad37e27e60771b83d32fa1cbe Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 13:26:14 +0100 Subject: [PATCH 009/142] feat: refs #7936 enhance vn-select --- src/components/common/VnSelect.vue | 14 +++++++++++++- src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue | 5 ++++- src/pages/InvoiceIn/InvoiceInFilter.vue | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 571faad6459..0167451b487 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -312,7 +312,7 @@ function handleKeyDown(event) { <QIcon v-show="value" name="close" - @click.stop=" + @click=" () => { value = null; emit('remove'); @@ -325,6 +325,18 @@ function handleKeyDown(event) { <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> </template> + <template #option="{ opt, itemProps }"> + <QItem v-bind="itemProps"> + <QItemSection> + <QItemLabel v-if="opt[optionValue] == opt[optionLabel]">{{ + opt[optionLabel] + }}</QItemLabel> + <QItemLabel v-else>{{ + `#${opt[optionValue]} - ${opt[optionLabel]}` + }}</QItemLabel> + </QItemSection> + </QItem> + </template> </QSelect> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 92f3fffcaf4..50631f7ab86 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -356,7 +356,10 @@ const createInvoiceInCorrection = async () => { </template> <template #body="{ entity }"> <VnLv :label="t('invoiceIn.list.issued')" :value="toDate(entity.issued)" /> - <VnLv :label="t('invoiceIn.summary.booked')" :value="toDate(entity.booked)" /> + <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> diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index d1c0856b56b..605d6eda8a7 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -50,7 +50,6 @@ const activities = ref([]); dense outlined rounded - :filter-options="['id', 'name']" /> </QItemSection> </QItem> From 185d4f93a74219d70fdc1974f1df3761340c999b Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 13:41:26 +0100 Subject: [PATCH 010/142] fix: refs #7936 serial --- src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue index 4f8c9d70bea..19ed73e5087 100644 --- a/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue +++ b/src/pages/InvoiceIn/Serial/InvoiceInSerialFilter.vue @@ -8,7 +8,11 @@ defineProps({ dataKey: { type: String, required: true } }); const { t } = useI18n(); </script> <template> - <VnFilterPanel :data-key="dataKey" :search-button="true"> + <VnFilterPanel + :data-key="dataKey" + :search-button="true" + :unremovable-params="['daysAgo']" + > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`params.${tag.label}`) }}: </strong> From f5f1cdf0ac03a04a2604f6c6f234cd5f4e5e1142 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 14:06:14 +0100 Subject: [PATCH 011/142] fix: refs #7936 tabulation wip --- src/components/common/VnSelect.vue | 23 ----------------------- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 3 --- 2 files changed, 26 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 0167451b487..7b12b285200 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -259,28 +259,6 @@ async function onScroll({ to, direction, from, index }) { } defineExpose({ opts: myOptions }); - -function handleKeyDown(event) { - if (event.key === 'Tab') { - event.preventDefault(); - - const inputValue = vnSelectRef.value?.inputValue; - - if (inputValue) { - const matchingOption = myOptions.value.find( - (option) => - option[optionLabel.value].toLowerCase() === inputValue.toLowerCase() - ); - - if (matchingOption) { - emit('update:modelValue', matchingOption[optionValue.value]); - } else { - emit('update:modelValue', inputValue); - } - vnSelectRef.value?.hidePopup(); - } - } -} </script> <template> @@ -291,7 +269,6 @@ function handleKeyDown(event) { :option-value="optionValue" v-bind="$attrs" @filter="filterHandler" - @keydown="handleKeyDown" :emit-value="nullishToTrue($attrs['emit-value'])" :map-options="nullishToTrue($attrs['map-options'])" :use-input="nullishToTrue($attrs['use-input'])" diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index c2637bac3fe..526092f023d 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -208,7 +208,6 @@ const formatOpt = (row, { model, options }, prop) => { :option-label="col.optionLabel" :filter-options="['id', 'vat']" :hide-selected="false" - :fill-input="false" :display-value="formatOpt(row, col, 'vat')" > <template #option="scope"> @@ -233,9 +232,7 @@ const formatOpt = (row, { model, options }, prop) => { :option-label="col.optionLabel" :filter-options="['id', 'transaction']" :autofocus="col.tabIndex == 1" - input-debounce="0" :hide-selected="false" - :fill-input="false" :display-value="formatOpt(row, col, 'transaction')" > <template #option="scope"> From 070b831392e2acf2f2e4056c5380c9a6ec88f47c Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 14:48:32 +0100 Subject: [PATCH 012/142] feat: refs #7936 limit decimal places --- src/components/common/VnInputNumber.vue | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 1cad6c245cc..460b39d63b8 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -1,13 +1,26 @@ <script setup> import VnInput from 'src/components/common/VnInput.vue'; -import { ref } from 'vue'; -import { useAttrs } from 'vue'; + +defineProps({ + step: { type: Number, default: 0.01 }, + decimalPlaces: { type: Number, default: 2 }, +}); const model = defineModel({ type: [Number, String] }); -const $attrs = useAttrs(); -const step = ref($attrs.step || 0.01); </script> - <template> - <VnInput v-bind="$attrs" v-model.number="model" type="number" :step="step" /> + <VnInput + v-bind="$attrs" + v-model.number="model" + type="number" + :step="step" + @input=" + (evt) => { + const val = evt.target.value; + const [, decimal] = val.split('.'); + if (val && decimal?.length > decimalPlaces) + model = parseFloat(val).toFixed(decimalPlaces); + } + " + /> </template> From 3f2cd6294874bcc7c09e0a99b3be4ec9f7784f6f Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 14:53:53 +0100 Subject: [PATCH 013/142] fix: refs #7936 decimal places & locale --- src/composables/getTotal.js | 4 ++-- src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue | 2 +- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 5 ----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/composables/getTotal.js b/src/composables/getTotal.js index 41c4330c4ba..24ac3aa27c9 100644 --- a/src/composables/getTotal.js +++ b/src/composables/getTotal.js @@ -1,10 +1,10 @@ import { toCurrency } from 'src/filters'; export function getTotal(rows, key, opts = {}) { - const { currency, cb } = opts; + const { currency, cb, decimalPlaces } = opts; const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0); return currency ? toCurrency(total, currency == 'default' ? undefined : currency) - : total; + : parseFloat(total).toFixed(decimalPlaces ?? 2); } diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index bee50a07dbe..08ce0755d17 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -261,7 +261,7 @@ const formatOpt = (row, { model, options }, prop) => { country: Country es: Code: Código - amount: Cantidad + amount: Valor mercancía net: Neto stems: Tallos country: País diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 526092f023d..100eecda617 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -188,11 +188,6 @@ const formatOpt = (row, { model, options }, prop) => { <template #body-cell-taxablebase="{ row }"> <QTd> <VnInputNumber - :class="{ - 'no-pointer-events': isNotEuro(currency), - }" - :disable="isNotEuro(currency)" - label="" clear-icon="close" v-model="row.taxableBase" clearable From aba33a617ee8d331c010cb67b3656b1049e4ed96 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 26 Nov 2024 16:34:37 +0100 Subject: [PATCH 014/142] fix: refs #7936 changes --- src/components/ui/VnFilterPanel.vue | 23 +++++++++++-- src/i18n/locale/en.yml | 4 +++ src/i18n/locale/es.yml | 4 +++ src/pages/InvoiceIn/InvoiceInFilter.vue | 43 +++++++++++++++---------- src/pages/InvoiceIn/InvoiceInList.vue | 12 +++++++ 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index fb1125fdac7..714fe3f3132 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -6,7 +6,7 @@ import { useRoute } from 'vue-router'; import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; -const { t } = useI18n(); +const { t, te } = useI18n(); const $props = defineProps({ modelValue: { type: Object, @@ -226,6 +226,12 @@ function sanitizer(params) { } return params; } + +const getLocale = (label) => { + const param = label.split('.').at(-1); + const globalLocale = `globals.params.${param}`; + return te(globalLocale) ? t(globalLocale) : t(`params.${param}`); +}; </script> <template> @@ -274,7 +280,12 @@ function sanitizer(params) { :removable="!unremovableParams?.includes(chip.label)" @remove="remove(chip.label)" > - <slot name="tags" :tag="chip" :format-fn="formatValue"> + <slot + name="tags" + :tag="chip" + :format-fn="formatValue" + :get-locale="getLocale" + > <div class="q-gutter-x-xs"> <strong>{{ chip.label }}:</strong> <span>"{{ formatValue(chip.value) }}"</span> @@ -287,6 +298,7 @@ function sanitizer(params) { :params="userParams" :tags="customTags" :format-fn="formatValue" + :get-locale="getLocale" :search-fn="search" /> </div> @@ -294,7 +306,12 @@ function sanitizer(params) { <QSeparator /> </QList> <QList dense class="list q-gutter-y-sm q-mt-sm"> - <slot name="body" :params="sanitizer(userParams)" :search-fn="search"></slot> + <slot + name="body" + :params="sanitizer(userParams)" + :get-locale="getLocale" + :search-fn="search" + ></slot> </QList> </QForm> <QInnerLoading diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 221908dbf10..622ef833bf9 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -331,6 +331,10 @@ globals: fi: FI myTeam: My team departmentFk: Department + companyFk: Company + from: From + to: To + supplierFk: Supplier changePass: Change password deleteConfirmTitle: Delete selected elements changeState: Change state diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 1a075dc50e8..20317aa49d8 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -335,6 +335,10 @@ globals: SSN: NSS fi: NIF myTeam: Mi equipo + companyFk: Empresa + from: Desde + to: Hasta + supplierFk: Proveedor changePass: Cambiar contraseña deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index 605d6eda8a7..1e8b650a1cd 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -21,21 +21,29 @@ const activities = ref([]); @on-fetch="(data) => (activities = data)" /> <VnFilterPanel :data-key="dataKey" :search-button="true"> - <template #tags="{ tag, formatFn }"> + <template #tags="{ tag, formatFn, getLocale }"> <div class="q-gutter-x-xs"> - <strong>{{ t(`params.${tag.label}`) }}: </strong> + <strong>{{ getLocale(tag.label) }}: </strong> <span>{{ formatFn(tag.value) }}</span> </div> </template> <template #body="{ params, searchFn }"> <QItem> <QItemSection> - <VnInputDate :label="t('From')" v-model="params.from" is-outlined /> + <VnInputDate + :label="t('globals.from')" + v-model="params.from" + is-outlined + /> </QItemSection> </QItem> <QItem> <QItemSection> - <VnInputDate :label="t('To')" v-model="params.to" is-outlined /> + <VnInputDate + :label="t('globals.to')" + v-model="params.to" + is-outlined + /> </QItemSection> </QItem> <QItem> @@ -45,7 +53,6 @@ const activities = ref([]); url="Suppliers" :fields="['id', 'nickname']" :label="t('params.supplierFk')" - option-value="id" option-label="nickname" dense outlined @@ -112,6 +119,20 @@ const activities = ref([]); /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnSelect + v-model="params.companyFk" + :label="t('globals.company')" + url="Companies" + option-label="code" + :fields="['id', 'code']" + dense + outlined + rounded + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <QCheckbox @@ -120,8 +141,6 @@ const activities = ref([]); @update:model-value="searchFn()" toggle-indeterminate /> - </QItemSection> - <QItemSection> <QCheckbox :label="t('params.correctingFk')" v-model="params.correctingFk" @@ -141,7 +160,6 @@ en: supplierRef: Supplier ref. supplierFk: Supplier fi: Supplier fiscal id - clientFk: Customer amount: Amount created: Created awb: AWB @@ -152,17 +170,13 @@ en: isBooked: is booked correctedFk: Rectified issued: Issued - to: To - from: From awbCode: AWB correctingFk: Rectificative - supplierActivityFk: Supplier activity es: params: search: Id o nombre proveedor supplierRef: Ref. proveedor supplierFk: Proveedor - clientFk: Cliente fi: CIF proveedor serialNumber: Num. serie serial: Serie @@ -175,11 +189,6 @@ es: dued: Vencida correctedFk: Rectificada correctingFk: Rectificativa - supplierActivityFk: Actividad proveedor - from: Desde - to: Hasta - From: Desde - To: Hasta Amount: Importe Issued: Fecha factura Id or supplier: Id o proveedor diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 8d658bee397..0beb2cdf89b 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -79,6 +79,18 @@ const cols = computed(() => [ label: t('invoiceIn.list.amount'), format: ({ amount }) => toCurrency(amount), }, + { + name: 'companyFk', + label: t('globals.company'), + columnFilter: { + component: 'select', + attrs: { + url: 'Companies', + fields: ['id', 'code'], + optionLabel: 'code', + }, + }, + }, { align: 'right', name: 'tableActions', From 953f6f4af3564ad84ec81c81365a737f80261a2c Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 27 Nov 2024 09:54:56 +0100 Subject: [PATCH 015/142] refactor: refs #7936 locale --- src/components/ui/VnFilterPanel.vue | 4 +- src/i18n/locale/en.yml | 6 ++ src/i18n/locale/es.yml | 4 ++ .../InvoiceIn/Card/InvoiceInBasicData.vue | 2 +- .../InvoiceIn/Card/InvoiceInDescriptor.vue | 10 +-- src/pages/InvoiceIn/Card/InvoiceInSummary.vue | 68 +++++++++---------- src/pages/InvoiceIn/InvoiceInCreate.vue | 6 +- src/pages/InvoiceIn/InvoiceInFilter.vue | 47 +++---------- src/pages/InvoiceIn/InvoiceInList.vue | 18 ++--- src/pages/InvoiceIn/locale/en.yml | 7 +- src/pages/InvoiceIn/locale/es.yml | 6 +- 11 files changed, 85 insertions(+), 93 deletions(-) diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 714fe3f3132..22bd8967565 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -230,7 +230,9 @@ function sanitizer(params) { const getLocale = (label) => { const param = label.split('.').at(-1); const globalLocale = `globals.params.${param}`; - return te(globalLocale) ? t(globalLocale) : t(`params.${param}`); + if (te(globalLocale)) return t(globalLocale); + else if (te(t(`params.${param}`))); + else return t(`${route.meta.moduleName}.params.${param}`); }; </script> diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 622ef833bf9..e89bf3ba3a6 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -335,6 +335,12 @@ globals: from: From to: To supplierFk: Supplier + supplierRef: Supplier ref + serial: Serial + amount: Importe + awbCode: AWB + correctedFk: Rectified + correctingFk: Rectificative changePass: Change password deleteConfirmTitle: Delete selected elements changeState: Change state diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 20317aa49d8..e1080421d02 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -339,6 +339,10 @@ globals: from: Desde to: Hasta supplierFk: Proveedor + supplierRef: Ref. proveedor + serial: Serie + amount: Importe + awbCode: AWB changePass: Cambiar contraseña deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index 209681b7c61..d22ee5b160f 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -262,7 +262,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 50631f7ab86..10575eb692f 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -355,13 +355,13 @@ const createInvoiceInCorrection = async () => { </QItem> </template> <template #body="{ entity }"> - <VnLv :label="t('invoiceIn.list.issued')" :value="toDate(entity.issued)" /> + <VnLv :label="t('Invoicein.list.issued')" :value="toDate(entity.issued)" /> <VnLv - :label="t('invoiceIn.summary.bookedDate')" + :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.amount')" :value="toCurrency(totalAmount)" /> + <VnLv :label="t('Invoicein.list.supplier')"> <template #value> <span class="link"> {{ entity?.supplier?.nickname }} @@ -378,7 +378,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" diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 08fc11f6901..06e6d790ffc 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -26,14 +26,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 +41,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 +51,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 +62,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 +70,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 +81,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 +103,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 +114,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 +123,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 +145,7 @@ const intrastatColumns = ref([ }, { name: 'landed', - label: 'invoiceIn.summary.country', + label: 'InvoiceIn.summary.country', field: (row) => row.country?.code, format: (value) => value, sortable: true, @@ -210,7 +210,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 +221,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}`" /> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> @@ -239,19 +239,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)" /> </QCard> @@ -263,18 +263,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"> @@ -285,11 +285,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 @@ -297,8 +297,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) }} @@ -309,7 +309,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" @@ -357,7 +357,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"> @@ -395,7 +395,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 c809e032bcb..200997f6517 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 1e8b650a1cd..2991a419017 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -27,7 +27,7 @@ const activities = ref([]); <span>{{ formatFn(tag.value) }}</span> </div> </template> - <template #body="{ params, searchFn }"> + <template #body="{ params, searchFn, getLocale }"> <QItem> <QItemSection> <VnInputDate @@ -52,7 +52,7 @@ const activities = ref([]); v-model="params.supplierFk" url="Suppliers" :fields="['id', 'nickname']" - :label="t('params.supplierFk')" + :label="getLocale('supplierFk')" option-label="nickname" dense outlined @@ -63,7 +63,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInput - :label="t('params.supplierRef')" + :label="getLocale('supplierRef')" v-model="params.supplierRef" is-outlined lazy-rules @@ -73,7 +73,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInput - :label="t('params.fi')" + :label="getLocale('fi')" v-model="params.fi" is-outlined lazy-rules @@ -83,7 +83,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInput - :label="t('params.serial')" + :label="getLocale('serial')" v-model="params.serial" is-outlined lazy-rules @@ -93,7 +93,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInput - :label="t('params.account')" + :label="getLocale('account')" v-model="params.account" is-outlined lazy-rules @@ -103,7 +103,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInput - :label="t('params.awb')" + :label="getLocale('globals.params.awbCode')" v-model="params.awbCode" is-outlined lazy-rules @@ -113,7 +113,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInputNumber - :label="t('Amount')" + :label="t('globals.amount')" v-model="params.amount" is-outlined /> @@ -136,7 +136,7 @@ const activities = ref([]); <QItem> <QItemSection> <QCheckbox - :label="t('invoiceIn.isBooked')" + :label="t('InvoiceIn.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate @@ -157,40 +157,11 @@ const activities = ref([]); en: params: search: Id or supplier name - supplierRef: Supplier ref. - supplierFk: Supplier - fi: Supplier fiscal id - amount: Amount - created: Created - awb: AWB - dued: Dued - serialNumber: Serial Number - serial: Serial account: Ledger account - isBooked: is booked - correctedFk: Rectified - issued: Issued - awbCode: AWB correctingFk: Rectificative es: params: search: Id o nombre proveedor - supplierRef: Ref. proveedor - supplierFk: Proveedor - fi: CIF proveedor - serialNumber: Num. serie - serial: Serie - awb: AWB - amount: Importe - issued: Emitida - isBooked: Contabilizada account: Cuenta contable - created: Creada - dued: Vencida - correctedFk: Rectificada correctingFk: Rectificativa - Amount: Importe - Issued: Fecha factura - Id or supplier: Id o proveedor - More options: Más opciones </i18n> diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 0beb2cdf89b..4c9059dbedc 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -32,7 +32,7 @@ const cols = computed(() => [ { align: 'left', name: 'supplierFk', - label: t('invoiceIn.list.supplier'), + label: t('InvoiceIn.list.supplier'), columnFilter: { component: 'select', attrs: { @@ -45,16 +45,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: { @@ -65,18 +65,18 @@ const cols = computed(() => [ { align: 'left', name: 'isBooked', - label: t('invoiceIn.isBooked'), + label: t('InvoiceIn.isBooked'), columnFilter: false, }, { 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), }, { @@ -163,7 +163,7 @@ const cols = computed(() => [ </template> </VnSelect> <VnInput - :label="t('invoiceIn.list.supplierRef')" + :label="t('InvoiceIn.list.supplierRef')" v-model="data.supplierRef" /> <VnSelect @@ -175,7 +175,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 b39511f2976..c917f524e7c 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: @@ -39,3 +39,8 @@ invoiceIn: net: Net stems: Stems country: Country + params: + search: Id or supplier name + account: Ledger account + correctingFk: Rectificative + isBooked: Is booked diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 5f483dd08bb..f7e9b70347c 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: @@ -38,3 +38,7 @@ invoiceIn: net: Neto stems: Tallos country: País + params: + search: Id o nombre proveedor + account: Cuenta contable + correctingFk: Rectificativa From 70ca31aa46361550557d23dff11d53b9b8e72231 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 27 Nov 2024 10:24:09 +0100 Subject: [PATCH 016/142] feat: refs #7936 add company filter --- src/pages/InvoiceIn/InvoiceInFilter.vue | 36 +++++-------------------- src/pages/InvoiceIn/InvoiceInList.vue | 4 +++ src/pages/InvoiceIn/locale/en.yml | 1 + src/pages/InvoiceIn/locale/es.yml | 1 + 4 files changed, 12 insertions(+), 30 deletions(-) diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index 2991a419017..8f69bf2621b 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -1,25 +1,14 @@ <script setup> -import { ref } from 'vue'; -import { useI18n } from 'vue-i18n'; - import VnSelect from 'components/common/VnSelect.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; -import FetchData from 'components/FetchData.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; -const { t } = useI18n(); defineProps({ dataKey: { type: String, required: true } }); -const activities = ref([]); </script> <template> - <FetchData - url="SupplierActivities" - auto-load - @on-fetch="(data) => (activities = data)" - /> <VnFilterPanel :data-key="dataKey" :search-button="true"> <template #tags="{ tag, formatFn, getLocale }"> <div class="q-gutter-x-xs"> @@ -31,7 +20,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInputDate - :label="t('globals.from')" + :label="$t('globals.from')" v-model="params.from" is-outlined /> @@ -40,7 +29,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInputDate - :label="t('globals.to')" + :label="$t('globals.to')" v-model="params.to" is-outlined /> @@ -113,7 +102,7 @@ const activities = ref([]); <QItem> <QItemSection> <VnInputNumber - :label="t('globals.amount')" + :label="$t('globals.amount')" v-model="params.amount" is-outlined /> @@ -123,7 +112,7 @@ const activities = ref([]); <QItemSection> <VnSelect v-model="params.companyFk" - :label="t('globals.company')" + :label="$t('globals.company')" url="Companies" option-label="code" :fields="['id', 'code']" @@ -136,13 +125,13 @@ const activities = ref([]); <QItem> <QItemSection> <QCheckbox - :label="t('InvoiceIn.isBooked')" + :label="$t('InvoiceIn.isBooked')" v-model="params.isBooked" @update:model-value="searchFn()" toggle-indeterminate /> <QCheckbox - :label="t('params.correctingFk')" + :label="getLocale('params.correctingFk')" v-model="params.correctingFk" @update:model-value="searchFn()" toggle-indeterminate @@ -152,16 +141,3 @@ const activities = ref([]); </template> </VnFilterPanel> </template> - -<i18n> -en: - params: - search: Id or supplier name - account: Ledger account - correctingFk: Rectificative -es: - params: - search: Id o nombre proveedor - account: Cuenta contable - correctingFk: Rectificativa -</i18n> diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 4c9059dbedc..82e550acad5 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -14,6 +14,7 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; +import FetchData from 'src/components/FetchData.vue'; const stateStore = useStateStore(); const { viewSummary } = useSummaryDialog(); @@ -23,6 +24,7 @@ onMounted(async () => (stateStore.rightDrawer = true)); onUnmounted(() => (stateStore.rightDrawer = false)); const tableRef = ref(); +const companies = ref([]); const cols = computed(() => [ { align: 'left', @@ -90,6 +92,7 @@ const cols = computed(() => [ optionLabel: 'code', }, }, + format: (row) => row.code, }, { align: 'right', @@ -113,6 +116,7 @@ const cols = computed(() => [ ]); </script> <template> + <FetchData url="Companies" @on-fetch="(data) => (companies = data)" auto-load /> <InvoiceInSearchbar /> <RightMenu> <template #right-panel> diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index c917f524e7c..91d08c3ca6d 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -43,4 +43,5 @@ InvoiceIn: search: Id or supplier name account: Ledger account correctingFk: Rectificative + correctedFk: Corrected isBooked: Is booked diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index f7e9b70347c..80ab9866b33 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -42,3 +42,4 @@ InvoiceIn: search: Id o nombre proveedor account: Cuenta contable correctingFk: Rectificativa + correctedFk: Rectificada From c55530ad1e9a9fda663ca46d8704943cf3cbaade Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 27 Nov 2024 10:36:03 +0100 Subject: [PATCH 017/142] fix: refs #7936 redirection --- src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue | 16 ++++++++-------- src/pages/InvoiceIn/locale/en.yml | 2 ++ src/pages/InvoiceIn/locale/es.yml | 2 ++ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 10575eb692f..40a50772235 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -91,7 +91,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ supplierFk: id }), + table: JSON.stringify({ supplierFk: id }), }, }; }, @@ -100,7 +100,7 @@ const routes = reactive({ return { name: 'InvoiceInList', query: { - params: JSON.stringify({ correctedFk: entityId.value }), + table: JSON.stringify({ correctedFk: entityId.value }), }, }; } @@ -355,13 +355,13 @@ const createInvoiceInCorrection = async () => { </QItem> </template> <template #body="{ entity }"> - <VnLv :label="t('Invoicein.list.issued')" :value="toDate(entity.issued)" /> + <VnLv :label="t('InvoiceIn.list.issued')" :value="toDate(entity.issued)" /> <VnLv - :label="t('Invoicein.summary.bookedDate')" + :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.amount')" :value="toCurrency(totalAmount)" /> + <VnLv :label="t('InvoiceIn.list.supplier')"> <template #value> <span class="link"> {{ entity?.supplier?.nickname }} @@ -378,7 +378,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 +394,7 @@ const createInvoiceInCorrection = async () => { color="primary" :to="routes.getTickets(entity.supplierFk)" > - <QTooltip>{{ t('invoiceOut.card.ticketList') }}</QTooltip> + <QTooltip>{{ t('InvoiceIn.descriptor.ticketList') }}</QTooltip> </QBtn> <QBtn v-if=" diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 91d08c3ca6d..529569aa003 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -9,6 +9,8 @@ InvoiceIn: issued: Issued awb: AWB amount: Amount + descriptor: + ticketList: Ticket list card: client: Client company: Company diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 80ab9866b33..2192442cd91 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -10,6 +10,8 @@ InvoiceIn: issued: Fecha emisión awb: AWB amount: Importe + descriptor: + ticketList: Listado de tickets card: client: Cliente company: Empresa From 8324c0587d0613606ec879dded86c2ecdbc13b7c Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 27 Nov 2024 13:26:54 +0100 Subject: [PATCH 018/142] fix: refs #7936 descriptor & dueday --- src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue | 4 ++-- src/pages/InvoiceIn/Card/InvoiceInDueDay.vue | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index 40a50772235..bf566c0beab 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -126,14 +126,14 @@ const isNotFilled = computed(() => Object.values(correctionFormData).includes(nu onBeforeMount(async () => { await setInvoiceCorrection(entityId.value); const { data } = await axios.get(`InvoiceIns/${entityId.value}/getTotals`); - totalAmount.value = data.totalDueDay; + totalAmount.value = data.totalTaxableBase; }); 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.totalDueDay; + totalAmount.value = data.totalTaxableBase; } }); diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index e8f73848bef..75f9a67a4c6 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -25,6 +25,7 @@ const banks = ref([]); const invoiceInFormRef = ref(); const invoiceId = +route.params.id; const filter = { where: { invoiceInFk: invoiceId } }; +const areRows = ref(false); const columns = computed(() => [ { @@ -230,7 +231,14 @@ async function insert() { </template> </CrudModel> <QPageSticky position="bottom-right" :offset="[25, 25]"> - <QBtn color="primary" icon="add" shortcut="+" size="lg" round @click="insert" /> + <QBtn + color="primary" + icon="add" + shortcut="+" + size="lg" + round + @click="!areRows ? insert() : invoiceInFormRef.insert()" + /> </QPageSticky> </template> <style lang="scss" scoped> From b5d13904d8be87a84af74935535a6a22e7b1d775 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 27 Nov 2024 17:52:41 +0100 Subject: [PATCH 019/142] refactor: refs #7936 update label capitalization and replace invoice type options --- .../InvoiceIn/Card/InvoiceInCorrective.vue | 58 ++++++++++++------- .../InvoiceIn/Card/InvoiceInDescriptor.vue | 33 +++++++---- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue index 7735747f94d..165b3e0dffd 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue @@ -1,9 +1,8 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, capitalize } from 'vue'; import { useRouter } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; -import { useCapitalize } from 'src/composables/useCapitalize'; import CrudModel from 'src/components/CrudModel.vue'; import FetchData from 'src/components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; @@ -31,7 +30,7 @@ const columns = computed(() => [ }, { name: 'type', - label: useCapitalize(t('globals.type')), + label: capitalize(t('globals.type')), field: (row) => row.cplusRectificationTypeFk, options: cplusRectificationTypes.value, model: 'cplusRectificationTypeFk', @@ -43,10 +42,10 @@ const columns = computed(() => [ }, { name: 'class', - label: useCapitalize(t('globals.class')), - field: (row) => row.siiTypeInvoiceOutFk, - options: siiTypeInvoiceOuts.value, - model: 'siiTypeInvoiceOutFk', + label: capitalize(t('globals.class')), + field: (row) => row.siiTypeInvoiceInFk, + options: siiTypeInvoiceIns.value, + model: 'siiTypeInvoiceInFk', optionValue: 'id', optionLabel: 'code', sortable: true, @@ -55,7 +54,7 @@ const columns = computed(() => [ }, { name: 'reason', - label: useCapitalize(t('globals.reason')), + label: capitalize(t('globals.reason')), field: (row) => row.invoiceCorrectionTypeFk, options: invoiceCorrectionTypes.value, model: 'invoiceCorrectionTypeFk', @@ -67,9 +66,8 @@ const columns = computed(() => [ }, ]); const cplusRectificationTypes = ref([]); -const siiTypeInvoiceOuts = ref([]); +const siiTypeInvoiceIns = ref([]); const invoiceCorrectionTypes = ref([]); -const rowsSelected = ref([]); const requiredFieldRule = (val) => val || t('globals.requiredField'); @@ -82,9 +80,9 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` auto-load /> <FetchData - url="SiiTypeInvoiceOuts" + url="SiiTypeInvoiceIns" :where="{ code: { like: 'R%' } }" - @on-fetch="(data) => (siiTypeInvoiceOuts = data)" + @on-fetch="(data) => (siiTypeInvoiceIns = data)" auto-load /> <FetchData @@ -99,17 +97,15 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` url="InvoiceInCorrections" :filter="filter" auto-load - v-model:selected="rowsSelected" primary-key="correctingFk" @save-changes="onSave" + :default-remove="false" > <template #body="{ rows }"> <QTable - v-model:selected="rowsSelected" :columns="columns" :rows="rows" row-key="$index" - selection="single" :grid="$q.screen.lt.sm" :pagination="{ rowsPerPage: 0 }" > @@ -121,8 +117,17 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :options="col.options" :option-value="col.optionValue" :option-label="col.optionLabel" - :readonly="row.invoiceIn.isBooked" - /> + :disable="row.invoiceIn.isBooked" + :filter-options="['description']" + > + <template #option="{ opt }"> + <QItem> + <QItemSection> + <QItemLabel>{{ opt.description }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> </QTd> </template> <template #body-cell-class="{ row, col }"> @@ -134,8 +139,20 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :option-value="col.optionValue" :option-label="col.optionLabel" :rules="[requiredFieldRule]" - :readonly="row.invoiceIn.isBooked" - /> + :filter-options="['code', 'description']" + :disable="row.invoiceIn.isBooked" + > + <template #option="{ opt }"> + <QItem> + <QItemSection> + <QItemLabel + >{{ opt.code }} - + {{ opt.description }}</QItemLabel + > + </QItemSection> + </QItem> + </template> + </VnSelect> </QTd> </template> <template #body-cell-reason="{ row, col }"> @@ -147,7 +164,7 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :option-value="col.optionValue" :option-label="col.optionLabel" :rules="[requiredFieldRule]" - :readonly="row.invoiceIn.isBooked" + :disable="row.invoiceIn.isBooked" /> </QTd> </template> @@ -155,7 +172,6 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` </template> </CrudModel> </template> -<style lang="scss" scoped></style> <i18n> es: Original invoice: Factura origen diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index bf566c0beab..f2c6f93ef2f 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, reactive, computed, onBeforeMount } from 'vue'; +import { ref, reactive, computed, onBeforeMount, capitalize } from 'vue'; import { useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; @@ -15,7 +15,6 @@ 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'; @@ -37,7 +36,7 @@ const totalAmount = ref(); const currentAction = ref(); const config = ref(); const cplusRectificationTypes = ref([]); -const siiTypeInvoiceOuts = ref([]); +const siiTypeInvoiceIns = ref([]); const invoiceCorrectionTypes = ref([]); const actions = { unbook: { @@ -119,7 +118,7 @@ const routes = reactive({ const correctionFormData = reactive({ invoiceReason: 2, invoiceType: 2, - invoiceClass: 6, + invoiceClass: 8, }); const isNotFilled = computed(() => Object.values(correctionFormData).includes(null)); @@ -262,9 +261,9 @@ const createInvoiceInCorrection = async () => { auto-load /> <FetchData - url="SiiTypeInvoiceOuts" + url="siiTypeInvoiceIns" :where="{ code: { like: 'R%' } }" - @on-fetch="(data) => (siiTypeInvoiceOuts = data)" + @on-fetch="(data) => (siiTypeInvoiceIns = data)" auto-load /> <FetchData @@ -438,9 +437,9 @@ const createInvoiceInCorrection = async () => { readonly /> <VnSelect - :label="`${useCapitalize(t('globals.class'))}`" + :label="`${capitalize(t('globals.class'))}`" v-model="correctionFormData.invoiceClass" - :options="siiTypeInvoiceOuts" + :options="siiTypeInvoiceIns" option-value="id" option-label="code" :required="true" @@ -448,15 +447,27 @@ const createInvoiceInCorrection = async () => { </QItemSection> <QItemSection> <VnSelect - :label="`${useCapitalize(t('globals.type'))}`" + :label="`${capitalize(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="`${useCapitalize(t('globals.reason'))}`" + :label="`${capitalize(t('globals.reason'))}`" v-model="correctionFormData.invoiceReason" :options="invoiceCorrectionTypes" option-value="id" From 1b6bb39c3e2febf02afee2346415faefd0bd6002 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 28 Nov 2024 09:23:25 +0100 Subject: [PATCH 020/142] feat: refs #8004 enhance FetchedTags component with column support and styling updates --- src/components/ui/FetchedTags.vue | 39 +++++++++++--- src/css/app.scss | 2 + src/pages/Item/ItemFixedPrice.vue | 2 +- src/pages/Item/ItemList.vue | 54 ++++++------------- .../Item/ItemType/Card/ItemTypeDescriptor.vue | 12 ++--- .../ItemType/Card/ItemTypeDescriptorProxy.vue | 17 ++++++ src/pages/Item/locale/en.yml | 2 +- src/pages/Item/locale/es.yml | 2 +- 8 files changed, 74 insertions(+), 56 deletions(-) create mode 100644 src/pages/Item/ItemType/Card/ItemTypeDescriptorProxy.vue diff --git a/src/components/ui/FetchedTags.vue b/src/components/ui/FetchedTags.vue index a0edf85f832..481e3a475a0 100644 --- a/src/components/ui/FetchedTags.vue +++ b/src/components/ui/FetchedTags.vue @@ -16,7 +16,13 @@ const $props = defineProps({ required: false, default: 'value', }, + columns: { + type: Number, + required: false, + default: null, + }, }); + const tags = computed(() => { return Object.keys($props.item) .filter((i) => i.startsWith(`${$props.tag}`)) @@ -28,10 +34,21 @@ const tags = computed(() => { return acc; }, {}); }); + +const columnStyle = computed(() => { + if ($props.columns) { + return { + 'grid-template-columns': `repeat(${$props.columns}, 1fr)`, + 'max-width': `${$props.columns * 4}rem`, + }; + } + return {}; +}); </script> + <template> <div class="fetchedTags"> - <div class="wrap"> + <div class="wrap" :style="columnStyle"> <div v-for="(val, key) in tags" :key="key" @@ -39,7 +56,7 @@ const tags = computed(() => { :title="`${key}: ${val}`" :class="{ empty: !val }" > - {{ val }} + <span class="text">{{ val }} </span> </div> </div> </div> @@ -49,27 +66,33 @@ const tags = computed(() => { .fetchedTags { align-items: center; .wrap { - width: 100%; flex-wrap: wrap; - display: flex; + display: grid; } .inline-tag { height: 1rem; margin: 0.05rem; - color: $color-font-secondary; + color: var(--vn-label-color); text-align: center; font-size: smaller; padding: 1px; - flex: 1; - border: 1px solid $color-spacer; + border: 1px solid var(--vn-label-color); text-overflow: ellipsis; overflow: hidden; min-width: 4rem; max-width: 4rem; } + + .text { + vertical-align: middle; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: smaller; + } .empty { - border: 1px solid #2b2b2b; + border: 1px solid var(--vn-empty-tag); } } </style> diff --git a/src/css/app.scss b/src/css/app.scss index 2ed5d3c86f9..9124feaf16a 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -11,6 +11,7 @@ body.body--light { --vn-text-color: var(--font-color); --vn-label-color: #5f5f5f; --vn-accent-color: #e7e3e3; + --vn-empty-tag: #acacac; background-color: var(--vn-page-color); @@ -26,6 +27,7 @@ body.body--dark { --vn-text-color: white; --vn-label-color: #a8a8a8; --vn-accent-color: #424242; + --vn-empty-tag: #2d2d2d; background-color: var(--vn-page-color); } diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index 8bf5d33bdca..4e57d18ca06 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -469,7 +469,7 @@ function handleOnDataSave({ CrudModelRef }) { </span> <span class="subName">{{ row.subName }}</span> <ItemDescriptorProxy :id="row.itemFk" /> - <FetchedTags :item="row" /> + <FetchedTags :item="row" :columns="3" /> </template> <template #column-rate2="props"> <QTd class="col"> diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index caccb9949c8..128bb5c2bbb 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -11,6 +11,7 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import ItemSummary from '../Item/Card/ItemSummary.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue'; +import ItemTypeDescriptorProxy from './ItemType/Card/ItemTypeDescriptorProxy.vue'; import { cloneItem } from 'src/pages/Item/composables/cloneItem'; import RightMenu from 'src/components/common/RightMenu.vue'; import ItemListFilter from './ItemListFilter.vue'; @@ -67,7 +68,6 @@ const columns = computed(() => [ }, }, columnFilter: false, - cardVisible: true, }, { label: t('item.list.id'), @@ -105,7 +105,7 @@ const columns = computed(() => [ columnFilter: { name: 'search', }, - cardVisible: true, + columnClass: 'expand', }, { label: t('item.list.stems'), @@ -143,10 +143,13 @@ const columns = computed(() => [ fields: ['id', 'name'], }, }, - columnField: { - component: null, - }, create: true, + visible: false, + }, + { + label: t('item.list.typeName'), + name: 'typeName', + align: 'left', }, { label: t('item.list.category'), @@ -230,7 +233,6 @@ const columns = computed(() => [ { label: t('item.list.weightByPiece'), name: 'weightByPiece', - align: 'left', component: 'input', columnField: { component: null, @@ -305,36 +307,7 @@ const columns = computed(() => [ ], }, ]); - -const redirectToItemCreate = () => { - router.push({ name: 'ItemCreate' }); -}; - -const redirectToItemSummary = (id) => { - router.push({ name: 'ItemSummary', params: { id } }); -}; - -const cloneItem = async (itemFk) => { - try { - const { data } = await axios.post(`Items/${itemFk}/clone`); - if (!data) return; - router.push({ name: 'ItemTags', params: { id: data.id } }); - } catch (err) { - console.error('Error cloning item', err); - } -}; - -onMounted(async () => { - stateStore.rightDrawer = true; - const filteredColumns = columns.value.filter( - (col) => col.name !== 'picture' && col.name !== 'actions' - ); - allColumnNames.value = filteredColumns.map((col) => col.name); -}); - -onUnmounted(() => (stateStore.rightDrawer = false)); </script> - <template> <VnSearchbar data-key="ItemList" @@ -361,7 +334,6 @@ onUnmounted(() => (stateStore.rightDrawer = false)); }" :order="['isActive DESC', 'name', 'id']" :columns="columns" - auto-load redirect="Item" :is-editable="false" :right-search="false" @@ -373,6 +345,13 @@ onUnmounted(() => (stateStore.rightDrawer = false)); <ItemDescriptorProxy :id="row.id" /> </span> </template> + <template #column-typeName="{ row }"> + <span class="link" @click.stop> + {{ row.typeName }} + {{ row.typeFk }} + <ItemTypeDescriptorProxy :id="row.typeFk" /> + </span> + </template> <template #column-userName="{ row }"> <span class="link" @click.stop> {{ row.userName }} @@ -386,11 +365,10 @@ onUnmounted(() => (stateStore.rightDrawer = false)); {{ row?.subName.toUpperCase() }} </div> </div> - <FetchedTags :item="row" :max-length="6" /> + <FetchedTags :item="row" :columns="3" /> </template> </VnTable> </template> - <style lang="scss" scoped> .subName { text-transform: uppercase; diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue index cd12fc238b8..936e95d2f96 100644 --- a/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptor.vue @@ -6,13 +6,11 @@ import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; - import useCardDescription from 'src/composables/useCardDescription'; const $props = defineProps({ id: { type: Number, - required: false, default: null, }, summary: { @@ -24,6 +22,10 @@ const $props = defineProps({ const route = useRoute(); const { t } = useI18n(); +const entityId = computed(() => { + return $props.id || route.params.id; +}); + const itemTypeFilter = { include: [ { relation: 'worker' }, @@ -33,10 +35,6 @@ const itemTypeFilter = { ], }; -const entityId = computed(() => { - return $props.id || route.params.id; -}); - const data = ref(useCardDescription()); const setData = (entity) => (data.value = useCardDescription(entity.code, entity.id)); </script> @@ -48,8 +46,8 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity :filter="itemTypeFilter" :title="data.title" :subtitle="data.subtitle" + data-key="itemTypeDescriptor" @on-fetch="setData" - data-key="entry" > <template #body="{ entity }"> <VnLv :label="t('shared.code')" :value="entity.code" /> diff --git a/src/pages/Item/ItemType/Card/ItemTypeDescriptorProxy.vue b/src/pages/Item/ItemType/Card/ItemTypeDescriptorProxy.vue new file mode 100644 index 00000000000..7cde9aef892 --- /dev/null +++ b/src/pages/Item/ItemType/Card/ItemTypeDescriptorProxy.vue @@ -0,0 +1,17 @@ +<script setup> +import ItemTypeDescriptor from './ItemTypeDescriptor.vue'; +import ItemTypeSummary from './ItemTypeSummary.vue'; + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, +}); +</script> + +<template> + <QPopupProxy> + <ItemTypeDescriptor v-if="$props.id" :id="$props.id" :summary="ItemTypeSummary" /> + </QPopupProxy> +</template> diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 78a1c3ff069..761feeee8d5 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -124,7 +124,7 @@ item: size: Size origin: Origin userName: Buyer - weightByPiece: Weight/Piece + weightByPiece: Weight/stem stemMultiplier: Multiplier producer: Producer landed: Landed diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 5498f445820..6eaf3d9d987 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -125,7 +125,7 @@ item: isActive: Activo size: Medida origin: Origen - weightByPiece: Peso (gramos)/tallo + weightByPiece: Peso/tallo userName: Comprador stemMultiplier: Multiplicador producer: Productor From ff69bf6d57f2b23cbd8fd4f1b5c3231bde47ce70 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 28 Nov 2024 11:13:11 +0100 Subject: [PATCH 021/142] refactor: refs #8004 remove console log from CardSummary component on mount --- 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 f10654c40cf..8395dfd73dd 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -54,7 +54,6 @@ onBeforeMount(async () => { onMounted(() => { stateStore.rightDrawerChangeValue(false); - console.log('useStateStore: ', useStateStore.rightMenu); }); async function fetch() { store.url = props.url; From 011b5814a743867285e2362b68b163c83cfea861 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 28 Nov 2024 11:41:22 +0100 Subject: [PATCH 022/142] fix: refs #7936 improve error handling --- src/components/CrudModel.vue | 3 ++- .../InvoiceIn/Card/InvoiceInCorrective.vue | 18 +++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 85bd5457a66..7dd5129e919 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -176,7 +176,8 @@ async function saveChanges(data) { try { await axios.post($props.saveUrl || $props.url + '/crud', changes); } catch (e) { - return (isLoading.value = false); + isLoading.value = false; + throw e; } originalData.value = JSON.parse(JSON.stringify(formData.value)); if (changes.creates?.length) await vnPaginateRef.value.fetch(); diff --git a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue index 165b3e0dffd..1d0a8d078d0 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInCorrective.vue @@ -1,22 +1,21 @@ <script setup> import { ref, computed, capitalize } from 'vue'; -import { useRouter } from 'vue-router'; +import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import CrudModel from 'src/components/CrudModel.vue'; import FetchData from 'src/components/FetchData.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -const { push, currentRoute } = useRouter(); +const route = useRoute(); const { t } = useI18n(); -const invoiceId = +currentRoute.value.params.id; const arrayData = useArrayData(); const invoiceIn = computed(() => arrayData.store.data); const invoiceInCorrectionRef = ref(); const filter = { include: { relation: 'invoiceIn' }, - where: { correctingFk: invoiceId }, + where: { correctingFk: route.params.id }, }; const columns = computed(() => [ { @@ -70,8 +69,6 @@ const siiTypeInvoiceIns = ref([]); const invoiceCorrectionTypes = ref([]); const requiredFieldRule = (val) => val || t('globals.requiredField'); - -const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary`); </script> <template> <FetchData @@ -98,7 +95,6 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :filter="filter" auto-load primary-key="correctingFk" - @save-changes="onSave" :default-remove="false" > <template #body="{ rows }"> @@ -120,8 +116,8 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :disable="row.invoiceIn.isBooked" :filter-options="['description']" > - <template #option="{ opt }"> - <QItem> + <template #option="{ opt, itemProps }"> + <QItem v-bind="itemProps"> <QItemSection> <QItemLabel>{{ opt.description }}</QItemLabel> </QItemSection> @@ -142,8 +138,8 @@ const onSave = (data) => data.deletes && push(`/invoice-in/${invoiceId}/summary` :filter-options="['code', 'description']" :disable="row.invoiceIn.isBooked" > - <template #option="{ opt }"> - <QItem> + <template #option="{ opt, itemProps }"> + <QItem v-bind="itemProps"> <QItemSection> <QItemLabel >{{ opt.code }} - From 4037b31948c454be4b3389f1a46e9f97d28bd39a Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 28 Nov 2024 12:37:30 +0100 Subject: [PATCH 023/142] feat: refs #7936 add row click navigation to InvoiceInSerial --- src/pages/InvoiceIn/Serial/InvoiceInSerial.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pages/InvoiceIn/Serial/InvoiceInSerial.vue b/src/pages/InvoiceIn/Serial/InvoiceInSerial.vue index 4eb9fa69db3..a8fb3b0c899 100644 --- a/src/pages/InvoiceIn/Serial/InvoiceInSerial.vue +++ b/src/pages/InvoiceIn/Serial/InvoiceInSerial.vue @@ -58,6 +58,14 @@ onBeforeMount(async () => { :right-search="false" :user-params="{ daysAgo }" :disable-option="{ card: true }" + :row-click=" + (row) => { + $router.push({ + name: 'InvoiceInList', + query: { table: JSON.stringify({ serial: row.serial }) }, + }); + } + " auto-load /> </template> From 44cbabfc7ed3e9ae1b5959cbc511c8ae2811603e Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 28 Nov 2024 13:27:53 +0100 Subject: [PATCH 024/142] feat: refs #7936 add number validation to VnInputNumber & new daysAgo filter in InvoiceInFilter --- src/components/common/VnInputNumber.vue | 2 ++ src/components/common/VnSelect.vue | 15 ++++++++++- src/i18n/locale/en.yml | 1 + src/i18n/locale/es.yml | 1 + src/pages/InvoiceIn/InvoiceInFilter.vue | 34 ++++++++++++++++++++++--- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/components/common/VnInputNumber.vue b/src/components/common/VnInputNumber.vue index 460b39d63b8..165cfae3d06 100644 --- a/src/components/common/VnInputNumber.vue +++ b/src/components/common/VnInputNumber.vue @@ -4,6 +4,7 @@ import VnInput from 'src/components/common/VnInput.vue'; defineProps({ step: { type: Number, default: 0.01 }, decimalPlaces: { type: Number, default: 2 }, + positive: { type: Boolean, default: true }, }); const model = defineModel({ type: [Number, String] }); @@ -17,6 +18,7 @@ const model = defineModel({ type: [Number, String] }); @input=" (evt) => { const val = evt.target.value; + if (positive && val < 0) return (model = 0); const [, decimal] = val.split('.'); if (val && decimal?.length > decimalPlaces) model = parseFloat(val).toFixed(decimalPlaces); diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 7b12b285200..f62505d331e 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -94,6 +94,10 @@ const $props = defineProps({ type: String, default: null, }, + isOutlined: { + type: Boolean, + default: false, + }, }); const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])]; @@ -108,6 +112,15 @@ const noOneOpt = ref({ [optionValue.value]: false, [optionLabel.value]: noOneText, }); +const styleAttrs = computed(() => { + return $props.isOutlined + ? { + dense: true, + outlined: true, + rounded: true, + } + : {}; +}); const isLoading = ref(false); const useURL = computed(() => $props.url); const value = computed({ @@ -267,7 +280,7 @@ defineExpose({ opts: myOptions }); :options="myOptions" :option-label="optionLabel" :option-value="optionValue" - v-bind="$attrs" + v-bind="{ ...$attrs, ...styleAttrs }" @filter="filterHandler" :emit-value="nullishToTrue($attrs['emit-value'])" :map-options="nullishToTrue($attrs['map-options'])" diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index fab1d05699e..719a232170b 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -341,6 +341,7 @@ globals: awbCode: AWB correctedFk: Rectified correctingFk: Rectificative + daysOnward: Days onward changePass: Change password deleteConfirmTitle: Delete selected elements changeState: Change state diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index e1080421d02..40b9e42335e 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -343,6 +343,7 @@ globals: serial: Serie amount: Importe awbCode: AWB + daysOnward: Días adelante changePass: Cambiar contraseña deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index 8f69bf2621b..6536920269f 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -4,12 +4,28 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; +import { dateRange } from 'src/filters'; +import { date } from 'quasar'; defineProps({ dataKey: { type: String, required: true } }); +const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + +function handleDaysAgo(params, daysAgo) { + const [from, to] = dateRange(Date.vnNew()); + if (!daysAgo && daysAgo !== 0) { + Object.assign(params, { from: undefined, to: undefined }); + } else { + from.setDate(from.getDate() - daysAgo); + Object.assign(params, { + from: date.formatDate(from, dateFormat), + to: date.formatDate(to, dateFormat), + }); + } +} </script> <template> - <VnFilterPanel :data-key="dataKey" :search-button="true"> + <VnFilterPanel :data-key="dataKey" :search-button="true" :hidden-tags="['daysAgo']"> <template #tags="{ tag, formatFn, getLocale }"> <div class="q-gutter-x-xs"> <strong>{{ getLocale(tag.label) }}: </strong> @@ -35,6 +51,18 @@ defineProps({ dataKey: { type: String, required: true } }); /> </QItemSection> </QItem> + <QItem> + <QItemSection> + <VnInputNumber + :label="$t('globals.daysAgo')" + v-model="params.daysAgo" + is-outlined + :step="0" + @update:model-value="(val) => handleDaysAgo(params, val)" + @remove="(val) => handleDaysAgo(params, val)" + /> + </QItemSection> + </QItem> <QItem> <QItemSection> <VnSelect @@ -116,9 +144,7 @@ defineProps({ dataKey: { type: String, required: true } }); url="Companies" option-label="code" :fields="['id', 'code']" - dense - outlined - rounded + is-outlined /> </QItemSection> </QItem> From 8e3d9b2bf356033b94d109498d559265fc70fca5 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 28 Nov 2024 14:52:39 +0100 Subject: [PATCH 025/142] feat: refs #7936 calculate exchange & update taxable base --- src/composables/getExchange.js | 16 ++++++++++++++++ .../InvoiceIn/Card/InvoiceInBasicData.vue | 1 + src/pages/InvoiceIn/Card/InvoiceInVat.vue | 19 +++++++++++++++---- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/composables/getExchange.js diff --git a/src/composables/getExchange.js b/src/composables/getExchange.js new file mode 100644 index 00000000000..e81e9e895f1 --- /dev/null +++ b/src/composables/getExchange.js @@ -0,0 +1,16 @@ +import axios from 'axios'; +export async function getExchange(amount, currencyFk, dated, decimalPlaces = 2) { + try { + const { data } = await axios.get('ReferenceRates/findOne', { + params: { + filter: { + fields: ['value'], + where: { currencyFk, dated }, + }, + }, + }); + return (amount / data.value).toFixed(decimalPlaces); + } catch (e) { + return null; + } +} diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index d22ee5b160f..fd5ea09312a 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -249,6 +249,7 @@ function deleteFile(dmsFk) { :options="currencies" option-value="id" option-label="code" + sort-by="id" /> <VnSelect diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 100eecda617..977d628b430 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -11,12 +11,13 @@ import CrudModel from 'src/components/CrudModel.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue'; +import { getExchange } from 'src/composables/getExchange'; const { t } = useI18n(); const arrayData = useArrayData(); +const route = useRoute(); const invoiceIn = computed(() => arrayData.store.data); -const invoiceId = +useRoute().params.id; const currency = computed(() => invoiceIn.value?.currency?.code); const expenses = ref([]); const sageTaxTypes = ref([]); @@ -106,7 +107,7 @@ const filter = { 'transactionTypeSageFk', ], where: { - invoiceInFk: invoiceId, + invoiceInFk: route.params.id, }, }; @@ -148,10 +149,10 @@ const formatOpt = (row, { model, options }, prop) => { data-key="InvoiceInTaxes" url="InvoiceInTaxes" :filter="filter" - :data-required="{ invoiceInFk: invoiceId }" + :data-required="{ invoiceInFk: $route.params.id }" auto-load v-model:selected="rowsSelected" - :go-to="`/invoice-in/${invoiceId}/due-day`" + :go-to="`/invoice-in/${$route.params.id}/due-day`" > <template #body="{ rows }"> <QTable @@ -253,6 +254,16 @@ const formatOpt = (row, { model, options }, prop) => { }" :disable="!isNotEuro(currency)" v-model="row.foreignValue" + @update:model-value=" + async (val) => { + const exchange = await getExchange( + val, + row.currencyFk, + invoiceIn.issued + ); + row.taxableBase = exchange; + } + " /> </QTd> </template> From 50dab045f02139f1757bbb2676e63ec6454558c5 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 28 Nov 2024 15:00:29 +0100 Subject: [PATCH 026/142] feat: refs #7936 add currency check before fetching --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 977d628b430..06219aa7760 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -256,12 +256,12 @@ const formatOpt = (row, { model, options }, prop) => { v-model="row.foreignValue" @update:model-value=" async (val) => { - const exchange = await getExchange( + if (!isNotEuro(currency)) return; + row.taxableBase = await getExchange( val, row.currencyFk, invoiceIn.issued ); - row.taxableBase = exchange; } " /> From e2ddc079ec9b78d11a1c020a3d9d1bae3f5ae566 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Fri, 29 Nov 2024 12:42:03 +0100 Subject: [PATCH 027/142] fix: refs #7936 format tax calculation to two decimal places --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 06219aa7760..13b775c774a 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -121,7 +121,7 @@ function taxRate(invoiceInTax) { const taxTypeSage = taxRateSelection?.rate ?? 0; const taxableBase = invoiceInTax?.taxableBase ?? 0; - return (taxTypeSage / 100) * taxableBase; + return ((taxTypeSage / 100) * taxableBase).toFixed(2); } const formatOpt = (row, { model, options }, prop) => { From 907bf3cf3bccb541cd24e17d2ca7ff25a7297d88 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 2 Dec 2024 11:39:01 +0100 Subject: [PATCH 028/142] feat(Account & AccountRole): refs #8197 add VnCardMain --- src/components/common/VnCard.vue | 34 ++--- src/components/common/VnCardMain.vue | 20 +++ src/components/common/VnSectionMain.vue | 3 +- src/composables/useArrayData.js | 9 +- src/pages/Account/AccountList.vue | 56 ++++---- src/pages/Account/Card/AccountCard.vue | 14 +- src/pages/Account/Role/AccountRoles.vue | 63 +++++---- src/pages/Account/Role/Card/RoleCard.vue | 15 +-- .../Account/Role/Card/RoleDescriptor.vue | 2 +- src/pages/Account/Role/Card/RoleSummary.vue | 4 +- src/router/modules/account.js | 120 ++++-------------- src/router/modules/account/accountCard.js | 71 +++++++++++ src/router/modules/account/roleCard.js | 54 ++++++++ src/router/modules/index.js | 2 - src/router/modules/role.js | 76 ----------- src/router/routes.js | 2 - src/utils/getSections.js | 8 ++ 17 files changed, 266 insertions(+), 287 deletions(-) create mode 100644 src/components/common/VnCardMain.vue create mode 100644 src/router/modules/account/accountCard.js create mode 100644 src/router/modules/account/roleCard.js delete mode 100644 src/router/modules/role.js create mode 100644 src/utils/getSections.js diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 0d80f43ce94..88d374c74e3 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -59,32 +59,16 @@ if (props.baseUrl) { } </script> <template> - <QDrawer - v-model="stateStore.leftDrawer" - show-if-above - :width="256" - v-if="stateStore.isHeaderMounted()" - > - <QScrollArea class="fit"> - <component :is="descriptor" /> - <QSeparator /> - <LeftMenu source="card" /> - </QScrollArea> - </QDrawer> <slot name="searchbar" v-if="props.searchDataKey"> <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> </slot> - <RightMenu> - <template #right-panel v-if="props.filterPanel"> - <component :is="props.filterPanel" :data-key="searchRightDataKey" /> - </template> - </RightMenu> - <QPageContainer> - <QPage> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="route.path" /> - </div> - </QPage> - </QPageContainer> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </Teleport> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> </template> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue new file mode 100644 index 00000000000..6e023153739 --- /dev/null +++ b/src/components/common/VnCardMain.vue @@ -0,0 +1,20 @@ +<script setup> +import LeftMenu from '../LeftMenu.vue'; +import { useStateStore } from 'stores/useStateStore'; +const stateStore = useStateStore(); + +defineProps({ + section: { + type: String, + required: true, + }, +}); +</script> +<template> + <slot name="searchbar" /> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <LeftMenu v-if="section == $route.name" /> + </Teleport> + <slot name="body" v-if="section == $route.name" /> + <RouterView v-else /> +</template> diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index 15be6ad9a43..c1b9808b5cf 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -1,6 +1,5 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; -import LeftMenu from 'components/LeftMenu.vue'; import { onMounted } from 'vue'; import { useQuasar } from 'quasar'; @@ -19,7 +18,7 @@ onMounted( <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <LeftMenu /> + <div id="left-panel"></div> </QScrollArea> </QDrawer> <QPageContainer> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index da62eee3eb9..028819a835f 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -75,18 +75,13 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { limit: store.limit, }; - let userParams = { ...store.userParams }; - Object.assign(filter, store.userFilter); - let where; - if (filter?.where || store.filter?.where) - where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); + delete store.filter.where; Object.assign(filter, store.filter); - filter.where = where; const params = { filter }; - Object.assign(params, userParams); + Object.assign(params, store.userParams); if (params.filter) params.filter.skip = store.skip; if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order?.length) params.filter.order = [...store.order]; diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index cbaaf8e26af..0c88e6ac88d 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -5,14 +5,15 @@ import VnTable from 'components/VnTable/VnTable.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import AccountFilter from './AccountFilter.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; +const dataKey = 'AccountList'; +const url = 'VnUsers/preview'; const columns = computed(() => [ { align: 'left', @@ -103,31 +104,34 @@ const exprBuilder = (param, value) => { </script> <template> - <VnSearchbar - data-key="AccountList" - :expr-builder="exprBuilder" - :label="t('account.search')" - :info="t('account.searchInfo')" - :filter="filter" - /> - <RightMenu> - <template #right-panel> - <AccountFilter data-key="AccountList" /> + <VnCardMain :section="dataKey"> + <template #searchbar> + <VnSearchbar + :data-key="dataKey" + :expr-builder="exprBuilder" + :label="t('account.search')" + :info="t('account.searchInfo')" + :filter="filter" + :url="url" + /> </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="AccountList" - url="VnUsers/preview" - :filter="filter" - order="id DESC" - :columns="columns" - default-mode="table" - redirect="account" - :use-model="true" - :right-search="false" - auto-load - /> + <template #body> + <VnTable + :style="{ display: !!$route.name.endsWith('List') ? '' : 'none' }" + ref="tableRef" + :data-key="dataKey" + :url="url" + :filter="filter" + order="id DESC" + :columns="columns" + default-mode="table" + redirect="account" + :use-model="true" + :right-search="true" + :expr-builder="exprBuilder" + /> + </template> + </VnCardMain> </template> <i18n> diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index 119a7fd07ed..f69bba77842 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,20 +1,8 @@ <script setup> -import { useI18n } from 'vue-i18n'; import VnCard from 'components/common/VnCard.vue'; import AccountDescriptor from './AccountDescriptor.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Account" - :descriptor="AccountDescriptor" - search-data-key="AccountList" - :searchbar-props="{ - url: 'VnUsers/preview', - label: t('account.search'), - info: t('account.searchInfo'), - }" - /> + <VnCard data-key="Account" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 5a27e2ed607..683de061675 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -6,8 +6,11 @@ import { useRoute } from 'vue-router'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; + const route = useRoute(); const { t } = useI18n(); +const { viewSummary } = useSummaryDialog(); const $props = defineProps({ id: { type: Number, @@ -15,8 +18,10 @@ const $props = defineProps({ }, }); const tableRef = ref(); +const url = 'VnRoles'; +const dataKey = 'AccountRoleList'; + const entityId = computed(() => $props.id || route.params.id); -const { viewSummary } = useSummaryDialog(); const columns = computed(() => [ { align: 'left', @@ -63,6 +68,7 @@ const columns = computed(() => [ }, ]); const exprBuilder = (param, value) => { + console.log('param: ', param); switch (param) { case 'search': return /^\d+$/.test(value) @@ -81,30 +87,37 @@ const exprBuilder = (param, value) => { </script> <template> - <VnSearchbar - data-key="AccountRolesList" - :expr-builder="exprBuilder" - :label="t('role.searchRoles')" - :info="t('role.searchInfo')" - /> - <VnTable - ref="tableRef" - data-key="AccountRolesList" - :url="`VnRoles`" - :create="{ - urlCreate: 'VnRoles', - title: t('Create rol'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { - editorFk: entityId, - }, - }" - order="id ASC" - :disable-option="{ card: true }" - :columns="columns" - default-mode="table" - redirect="account/role" - /> + <VnCardMain :section="dataKey"> + <template #searchbar> + <VnSearchbar + :url="url" + :data-key="dataKey" + :expr-builder="exprBuilder" + :label="t('role.searchRoles')" + :info="t('role.searchInfo')" + /> + </template> + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :url="url" + :create="{ + urlCreate: 'VnRoles', + title: t('Create rol'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: { + editorFk: entityId, + }, + }" + order="id ASC" + :disable-option="{ card: true }" + :columns="columns" + default-mode="table" + redirect="account/role" + /> + </template> + </VnCardMain> </template> <i18n> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index a2d5710f47a..da6ac61d87a 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -1,20 +1,7 @@ <script setup> -import { useI18n } from 'vue-i18n'; import VnCard from 'components/common/VnCard.vue'; import RoleDescriptor from './RoleDescriptor.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Role" - :descriptor="RoleDescriptor" - search-data-key="AccountRolesList" - :searchbar-props="{ - url: 'VnRoles', - label: t('role.searchRoles'), - info: t('role.searchInfo'), - searchUrl: 'table', - }" - /> + <VnCard data-key="Role" :descriptor="RoleDescriptor" /> </template> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 693fcdf48a6..b4b4fe3168d 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -43,7 +43,7 @@ const removeRole = async () => { :filter="filter" module="Role" @on-fetch="setData" - data-key="accountData" + data-key="Role" :title="data.title" :subtitle="data.subtitle" :summary="$props.summary" diff --git a/src/pages/Account/Role/Card/RoleSummary.vue b/src/pages/Account/Role/Card/RoleSummary.vue index fef85f9193a..f0daa77fb8f 100644 --- a/src/pages/Account/Role/Card/RoleSummary.vue +++ b/src/pages/Account/Role/Card/RoleSummary.vue @@ -27,10 +27,10 @@ const filter = { <template> <CardSummary ref="summary" - :url="`VnRoles`" + :url="`VnRoles/${entityId}`" :filter="filter" @on-fetch="(data) => (role = data)" - data-key="RoleSummary" + data-key="Role" > <template #header> {{ role.id }} - {{ role.name }} </template> <template #body> diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 7200131dafa..ece0ab2bb89 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,4 +1,7 @@ import { RouterView } from 'vue-router'; +import accountCard from './account/accountCard'; +import roleCard from './account/roleCard'; +import getSections from 'src/utils/getSections'; export default { path: '/account', @@ -22,39 +25,48 @@ export default { 'AccountAcls', 'AccountConnections', ], - card: [ - 'AccountBasicData', - 'AccountInheritedRoles', - 'AccountMailForwarding', - 'AccountMailAlias', - 'AccountPrivileges', - 'AccountLog', - ], + card: getSections(accountCard.children), }, children: [ { path: '', name: 'AccountMain', component: () => import('src/components/common/VnSectionMain.vue'), - redirect: { name: 'AccountList' }, + redirect: { name: 'AccountIndexMain' }, children: [ { - path: 'list', - name: 'AccountList', - meta: { - title: 'list', - icon: 'view_list', - }, + path: '', + name: 'AccountIndexMain', + redirect: { name: 'AccountList' }, component: () => import('src/pages/Account/AccountList.vue'), + children: [ + { + name: 'AccountList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + accountCard, + ], }, { - path: 'role-list', + path: 'role', name: 'AccountRoles', + redirect: { name: 'AccountRoleList' }, meta: { title: 'roles', icon: 'group', }, component: () => import('src/pages/Account/Role/AccountRoles.vue'), + children: [ + { + name: 'AccountRoleList', + path: 'list', + }, + roleCard, + ], }, { path: 'alias-list', @@ -120,81 +132,5 @@ export default { }, ], }, - { - name: 'AccountCard', - path: ':id', - component: () => import('src/pages/Account/Card/AccountCard.vue'), - redirect: { name: 'AccountSummary' }, - children: [ - { - name: 'AccountSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Account/Card/AccountSummary.vue'), - }, - { - name: 'AccountBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Card/AccountBasicData.vue'), - }, - { - name: 'AccountInheritedRoles', - path: 'inherited-roles', - meta: { - title: 'inheritedRoles', - icon: 'group', - }, - component: () => - import('src/pages/Account/Card/AccountInheritedRoles.vue'), - }, - { - name: 'AccountMailForwarding', - path: 'mail-forwarding', - meta: { - title: 'mailForwarding', - icon: 'forward', - }, - component: () => - import('src/pages/Account/Card/AccountMailForwarding.vue'), - }, - { - name: 'AccountMailAlias', - path: 'mail-alias', - meta: { - title: 'mailAlias', - icon: 'email', - }, - component: () => - import('src/pages/Account/Card/AccountMailAlias.vue'), - }, - { - name: 'AccountPrivileges', - path: 'privileges', - meta: { - title: 'privileges', - icon: 'badge', - }, - component: () => - import('src/pages/Account/Card/AccountPrivileges.vue'), - }, - { - name: 'AccountLog', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Account/Card/AccountLog.vue'), - }, - ], - }, ], }; diff --git a/src/router/modules/account/accountCard.js b/src/router/modules/account/accountCard.js new file mode 100644 index 00000000000..0d8850f10aa --- /dev/null +++ b/src/router/modules/account/accountCard.js @@ -0,0 +1,71 @@ +export default { + name: 'AccountCard', + path: ':id', + redirect: { name: 'AccountSummary' }, + component: () => import('src/pages/Account/Card/AccountCard.vue'), + children: [ + { + name: 'AccountSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Card/AccountSummary.vue'), + }, + { + name: 'AccountBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Card/AccountBasicData.vue'), + }, + { + name: 'AccountInheritedRoles', + path: 'inherited-roles', + meta: { + title: 'inheritedRoles', + icon: 'group', + }, + component: () => import('src/pages/Account/Card/AccountInheritedRoles.vue'), + }, + { + name: 'AccountMailForwarding', + path: 'mail-forwarding', + meta: { + title: 'mailForwarding', + icon: 'forward', + }, + component: () => import('src/pages/Account/Card/AccountMailForwarding.vue'), + }, + { + name: 'AccountMailAlias', + path: 'mail-alias', + meta: { + title: 'mailAlias', + icon: 'email', + }, + component: () => import('src/pages/Account/Card/AccountMailAlias.vue'), + }, + { + name: 'AccountPrivileges', + path: 'privileges', + meta: { + title: 'privileges', + icon: 'badge', + }, + component: () => import('src/pages/Account/Card/AccountPrivileges.vue'), + }, + { + name: 'AccountLog', + path: 'log', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Account/Card/AccountLog.vue'), + }, + ], +}; diff --git a/src/router/modules/account/roleCard.js b/src/router/modules/account/roleCard.js new file mode 100644 index 00000000000..2a538756873 --- /dev/null +++ b/src/router/modules/account/roleCard.js @@ -0,0 +1,54 @@ +export default { + name: 'RoleCard', + path: ':id', + component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), + redirect: { name: 'RoleSummary' }, + children: [ + { + name: 'RoleSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Role/Card/RoleSummary.vue'), + }, + { + name: 'RoleBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Role/Card/RoleBasicData.vue'), + }, + { + name: 'SubRoles', + path: 'sub-roles', + meta: { + title: 'subRoles', + icon: 'group', + }, + component: () => import('src/pages/Account/Role/Card/SubRoles.vue'), + }, + + { + name: 'InheritedRoles', + path: 'inherited-roles', + meta: { + title: 'inheritedRoles', + icon: 'account_tree', + }, + component: () => import('src/pages/Account/Role/Card/InheritedRoles.vue'), + }, + { + name: 'RoleLog', + path: 'log', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Account/Role/Card/RoleLog.vue'), + }, + ], +}; diff --git a/src/router/modules/index.js b/src/router/modules/index.js index bf7e46b000d..fb1bdc46667 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -21,7 +21,6 @@ import Zone from './zone'; import Account from './account'; import Monitor from './monitor'; import MailAlias from './mailAlias'; -import Role from './role'; export default [ Item, @@ -47,5 +46,4 @@ export default [ Account, MailAlias, Monitor, - Role, ]; diff --git a/src/router/modules/role.js b/src/router/modules/role.js deleted file mode 100644 index 47cd10b1889..00000000000 --- a/src/router/modules/role.js +++ /dev/null @@ -1,76 +0,0 @@ -import { RouterView } from 'vue-router'; - -export default { - path: 'account/role', - name: 'Role', - meta: { - title: 'role', - icon: 'vn:greuge', - moduleName: 'Role', - }, - component: RouterView, - redirect: { name: 'AccountRoles' }, - menus: { - main: [], - card: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'], - }, - children: [ - { - name: 'RoleCard', - path: ':id', - component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), - redirect: { name: 'RoleSummary' }, - children: [ - { - name: 'RoleSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => - import('src/pages/Account/Role/Card/RoleSummary.vue'), - }, - { - name: 'RoleBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Role/Card/RoleBasicData.vue'), - }, - { - name: 'SubRoles', - path: 'sub-roles', - meta: { - title: 'subRoles', - icon: 'group', - }, - component: () => import('src/pages/Account/Role/Card/SubRoles.vue'), - }, - - { - name: 'InheritedRoles', - path: 'inherited-roles', - meta: { - title: 'inheritedRoles', - icon: 'account_tree', - }, - component: () => - import('src/pages/Account/Role/Card/InheritedRoles.vue'), - }, - { - name: 'RoleLog', - path: 'log', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Account/Role/Card/RoleLog.vue'), - }, - ], - }, - ], -}; diff --git a/src/router/routes.js b/src/router/routes.js index cced308b5a3..d332be94194 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -10,7 +10,6 @@ import wagon from './modules/wagon'; import supplier from './modules/Supplier'; import travel from './modules/travel'; import department from './modules/department'; -import role from './modules/role'; import ItemType from './modules/itemType'; import shelving from 'src/router/modules/shelving'; import order from 'src/router/modules/order'; @@ -95,7 +94,6 @@ const routes = [ ItemType, zone, account, - role, mailAlias, { path: '/:catchAll(.*)*', diff --git a/src/utils/getSections.js b/src/utils/getSections.js new file mode 100644 index 00000000000..f70daf4685c --- /dev/null +++ b/src/utils/getSections.js @@ -0,0 +1,8 @@ +export default (sections) => { + const names = []; + for (const section of sections) { + if (section.path == 'summary') continue; + names.push(section.name); + } + return names; +}; From 68fc5653241e7cb67cb580599dcc67bc9fd28394 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:44:09 +0100 Subject: [PATCH 029/142] feat(VnPaginate): refs #8197 hold data when change to Card --- src/components/common/VnSelect.vue | 2 +- src/components/ui/VnFilterPanel.vue | 2 +- src/components/ui/VnPaginate.vue | 6 ++++-- src/components/ui/VnSearchbar.vue | 2 +- src/composables/useArrayData.js | 10 +++++++--- src/pages/Account/AccountList.vue | 2 +- src/pages/Account/Card/AccountCard.vue | 2 +- src/pages/Account/Card/AccountDescriptor.vue | 2 +- src/stores/useArrayDataStore.js | 6 ++++++ 9 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index f24f054a5fb..db47231f48a 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -201,7 +201,7 @@ async function fetchFilter(val) { const fetchOptions = { where, include, limit }; if (fields) fetchOptions.fields = fields; if (sortBy) fetchOptions.order = sortBy; - arrayData.reset(['skip', 'filter.skip', 'page']); + arrayData.resetPagination(); const { data } = await arrayData.applyFilter({ filter: fetchOptions }); setOptions(data); diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index b188bde4820..716d8331fcf 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -138,7 +138,7 @@ async function clearFilters() { try { isLoading.value = true; store.userParamsChanged = true; - arrayData.reset(['skip', 'filter.skip', 'page']); + arrayData.resetPagination(); // Filtrar los params no removibles const removableFilters = Object.keys(userParams.value).filter((param) => $props.unremovableParams.includes(param) diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 3649ba8f551..c5fbbb7314b 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -104,7 +104,9 @@ onMounted(async () => { mounted.value = true; }); -onBeforeUnmount(() => arrayData.reset()); +onBeforeUnmount(() => { + arrayData.resetPagination(); +}); watch( () => props.data, @@ -132,7 +134,7 @@ const addFilter = async (filter, params) => { async function fetch(params) { useArrayData(props.dataKey, params); - arrayData.reset(['filter.skip', 'skip', 'page']); + arrayData.resetPagination(); await arrayData.fetch({ append: false, updateRouter: mounted.value }); return emitStoreData(); } diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index da2d370fe09..a5690f35a9c 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -101,7 +101,7 @@ onMounted(() => { async function search() { const staticParams = Object.entries(store.userParams); - arrayData.reset(['skip', 'page']); + arrayData.resetPagination(); const filter = { params: { diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 028819a835f..ee66f6be7d8 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -142,6 +142,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts); } + function resetPagination() { + if (arrayDataStore.get(key)) arrayDataStore.resetPagination(key); + } + function cancelRequest() { if (canceller) { canceller.abort(); @@ -165,7 +169,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; - reset(['skip', 'filter.skip', 'page']); + resetPagination(); await fetch({}); return { filter, params }; @@ -192,7 +196,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { } store.order = order; - reset(['skip', 'filter.skip', 'page']); + resetPagination(); fetch({}); index++; @@ -275,7 +279,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const pushUrl = { path: to }; if (to.endsWith('/list') || to.endsWith('/')) pushUrl.query = newUrl.query; - else destroy(); return router.push(pushUrl); } } @@ -302,5 +305,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { isLoading, deleteOption, reset, + resetPagination, }; } diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 0c88e6ac88d..4b8e8fb2836 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -6,6 +6,7 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; + const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); @@ -117,7 +118,6 @@ const exprBuilder = (param, value) => { </template> <template #body> <VnTable - :style="{ display: !!$route.name.endsWith('List') ? '' : 'none' }" ref="tableRef" :data-key="dataKey" :url="url" diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index f69bba77842..ba9040852cf 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -4,5 +4,5 @@ import AccountDescriptor from './AccountDescriptor.vue'; </script> <template> - <VnCard data-key="Account" :descriptor="AccountDescriptor" /> + <VnCard data-key="AccountId" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 3156f8e1ec3..4e10e1366d6 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -41,7 +41,7 @@ const hasAccount = ref(false); /> <CardDescriptor ref="descriptor" - :url="`VnUsers/preview`" + url="VnUsers/preview" :filter="filter" module="Account" @on-fetch="setData" diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 6a0e7dfa8d1..be65de19a65 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -49,10 +49,16 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { }); } + function resetPagination(key) { + reset(key, ['skip', 'filter.skip', 'page']); + } + return { + state, get, set, clear, reset, + resetPagination, }; }); From 1b2af7cb84e0b033ba3425a552f627e92feaaf09 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:44:21 +0100 Subject: [PATCH 030/142] chore: refs #8197 remove console log --- src/pages/Account/Role/AccountRoles.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 683de061675..74c4ab8a558 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -68,7 +68,6 @@ const columns = computed(() => [ }, ]); const exprBuilder = (param, value) => { - console.log('param: ', param); switch (param) { case 'search': return /^\d+$/.test(value) From 2d2501838b63303ae4bbbbfe5d2688f17b8c15cc Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 4 Dec 2024 09:49:48 +0100 Subject: [PATCH 031/142] revert: refs #8197 arrayData changes --- src/composables/useArrayData.js | 9 +++++++-- src/pages/Account/Card/AccountSummary.vue | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index ee66f6be7d8..c36eb99900c 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -75,13 +75,18 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { limit: store.limit, }; + let userParams = { ...store.userParams }; + Object.assign(filter, store.userFilter); - delete store.filter.where; + let where; + if (filter?.where || store.filter?.where) + where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); Object.assign(filter, store.filter); + filter.where = where; const params = { filter }; - Object.assign(params, store.userParams); + Object.assign(params, userParams); if (params.filter) params.filter.skip = store.skip; if (store?.order && typeof store?.order == 'string') store.order = [store.order]; if (store.order?.length) params.filter.order = [...store.order]; diff --git a/src/pages/Account/Card/AccountSummary.vue b/src/pages/Account/Card/AccountSummary.vue index 5a21e18a5c9..e6c21ed34af 100644 --- a/src/pages/Account/Card/AccountSummary.vue +++ b/src/pages/Account/Card/AccountSummary.vue @@ -30,7 +30,7 @@ const filter = { <template> <CardSummary - data-key="AccountSummary" + data-key="AccountId" ref="AccountSummary" url="VnUsers/preview" :filter="filter" From 2ab6380f97a19f6862b742e2469afbffc1149bf1 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 4 Dec 2024 13:46:42 +0100 Subject: [PATCH 032/142] feat: refs #7936 use default invoice data --- src/pages/InvoiceIn/InvoiceInList.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 82e550acad5..e9255398beb 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -2,6 +2,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useStateStore } from 'stores/useStateStore'; +import { useState } from 'src/composables/useState'; import { downloadFile } from 'src/composables/downloadFile'; import { toDate, toCurrency } from 'src/filters/index'; import InvoiceInFilter from './InvoiceInFilter.vue'; @@ -17,6 +18,7 @@ import VnInputDate from 'src/components/common/VnInputDate.vue'; import FetchData from 'src/components/FetchData.vue'; const stateStore = useStateStore(); +const user = useState().getUser(); const { viewSummary } = useSummaryDialog(); const { t } = useI18n(); @@ -132,7 +134,7 @@ const cols = computed(() => [ urlCreate: 'InvoiceIns', title: t('globals.createInvoiceIn'), onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, + formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() }, }" redirect="invoice-in" :columns="cols" From 482505b8007148427be0a41bcd1a6802b9503664 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 4 Dec 2024 17:40:29 +0100 Subject: [PATCH 033/142] feat: refs #7936 add useAccountShortToStandard composable --- src/composables/useAccountShortToStandard.js | 4 ++++ src/pages/InvoiceIn/Card/InvoiceInVat.vue | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 src/composables/useAccountShortToStandard.js diff --git a/src/composables/useAccountShortToStandard.js b/src/composables/useAccountShortToStandard.js new file mode 100644 index 00000000000..ca221433e73 --- /dev/null +++ b/src/composables/useAccountShortToStandard.js @@ -0,0 +1,4 @@ +export function useAccountShortToStandard(val) { + if (!val || !/^\d+(\.\d*)$/.test(val)) return; + return val?.replace('.', '0'.repeat(11 - val.length)); +} diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 13b775c774a..c52136beaef 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -12,6 +12,7 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import CreateNewExpenseForm from 'src/components/CreateNewExpenseForm.vue'; import { getExchange } from 'src/composables/getExchange'; +import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; const { t } = useI18n(); @@ -172,6 +173,13 @@ const formatOpt = (row, { model, options }, prop) => { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" + :hide-selected="false" + :display-value="formatOpt(row, col, 'name')" + @keydown.tab=" + row[col.model] = useAccountShortToStandard( + $event.target.value + ) + " > <template #option="scope"> <QItem v-bind="scope.itemProps"> From d916b47f4c8e9b385a2e4a839680471137cad16a Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 5 Dec 2024 10:40:50 +0100 Subject: [PATCH 034/142] feat: refs #7936 enhance getTotal fn & add unit tests --- src/composables/getTotal.js | 5 +- .../InvoiceIn/Card/InvoiceInIntrastat.vue | 2 +- .../__tests__/composables/getTotal.spec.js | 55 +++++++++++++++++++ .../InvoiceIn/InvoiceInIntrastat.spec.js | 34 ------------ .../pages/InvoiceIn/InvoiceInVat.spec.js | 38 ------------- 5 files changed, 59 insertions(+), 75 deletions(-) create mode 100644 test/vitest/__tests__/composables/getTotal.spec.js delete mode 100644 test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js delete mode 100644 test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js diff --git a/src/composables/getTotal.js b/src/composables/getTotal.js index 24ac3aa27c9..91b884bda24 100644 --- a/src/composables/getTotal.js +++ b/src/composables/getTotal.js @@ -1,10 +1,11 @@ import { toCurrency } from 'src/filters'; export function getTotal(rows, key, opts = {}) { - const { currency, cb, decimalPlaces } = opts; + const { currency, cb, decimalPlaces, int } = opts; const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0); + const decimals = int ? 0 : decimalPlaces ?? 2; return currency ? toCurrency(total, currency == 'default' ? undefined : currency) - : parseFloat(total).toFixed(decimalPlaces ?? 2); + : parseFloat(total).toFixed(decimals); } diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index 08ce0755d17..b821025c29f 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -154,7 +154,7 @@ const formatOpt = (row, { model, options }, prop) => { {{ getTotal(rows, 'net') }} </QTd> <QTd> - {{ getTotal(rows, 'stems') }} + {{ getTotal(rows, 'stems', { int: true }) }} </QTd> <QTd /> </QTr> diff --git a/test/vitest/__tests__/composables/getTotal.spec.js b/test/vitest/__tests__/composables/getTotal.spec.js new file mode 100644 index 00000000000..7081c433462 --- /dev/null +++ b/test/vitest/__tests__/composables/getTotal.spec.js @@ -0,0 +1,55 @@ +import { vi, describe, expect, it } from 'vitest'; +import { getTotal } from 'src/composables/getTotal'; + +vi.mock('src/filters', () => ({ + toCurrency: vi.fn((value, currency) => `${currency} ${value.toFixed(2)}`), +})); + +describe('getTotal()', () => { + const rows = [ + { amount: 10.5, tax: 2.1 }, + { amount: 20.75, tax: 3.25 }, + { amount: 30.25, tax: 4.75 }, + ]; + + it('should calculate the total for a given key', () => { + const total = getTotal(rows, 'amount'); + expect(total).toBe('61.50'); + }); + + it('should calculate the total with a callback function', () => { + const total = getTotal(rows, null, { cb: (row) => row.amount + row.tax }); + expect(total).toBe('71.60'); + }); + + it('should format the total as currency', () => { + const total = getTotal(rows, 'amount', { currency: 'USD' }); + expect(total).toBe('USD 61.50'); + }); + + it('should format the total as currency with default currency', () => { + const total = getTotal(rows, 'amount', { currency: 'default' }); + expect(total).toBe('undefined 61.50'); + }); + + it('should calculate the total with integer formatting', () => { + const total = getTotal(rows, 'amount', { int: true }); + expect(total).toBe('62'); + }); + + it('should calculate the total with custom decimal places', () => { + const total = getTotal(rows, 'amount', { decimalPlaces: 1 }); + expect(total).toBe('61.5'); + }); + + it('should handle rows with missing keys', () => { + const rowsWithMissingKeys = [{ amount: 10.5 }, { amount: 20.75 }, {}]; + const total = getTotal(rowsWithMissingKeys, 'amount'); + expect(total).toBe('31.25'); + }); + + it('should handle empty rows', () => { + const total = getTotal([], 'amount'); + expect(total).toBe('0.00'); + }); +}); diff --git a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js deleted file mode 100644 index adfb054c658..00000000000 --- a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { vi, describe, expect, it, beforeAll } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import InvoiceInIntrastat from 'src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'; - -describe('InvoiceInIntrastat', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(InvoiceInIntrastat, { - global: { - stubs: ['vnPaginate'], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - vi.spyOn(axios, 'get').mockResolvedValue({ data: [{}] }); - }); - - describe('getTotal()', () => { - it('should correctly handle the sum', () => { - const invoceInIntrastat = [ - { amount: 10, stems: 162 }, - { amount: 20, stems: 21 }, - ]; - - const totalAmount = vm.getTotal(invoceInIntrastat, 'amount'); - const totalStems = vm.getTotal(invoceInIntrastat, 'stems'); - - expect(totalAmount).toBe(10 + 20); - expect(totalStems).toBe(162 + 21); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js deleted file mode 100644 index 76453f65aa6..00000000000 --- a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { vi, describe, expect, it, beforeAll } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; -import InvoiceInVat from 'src/pages/InvoiceIn/Card/InvoiceInVat.vue'; - -describe('InvoiceInVat', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(InvoiceInVat, { - global: { - stubs: [], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - }); - - describe('taxRate()', () => { - it('should correctly compute the tax rate', () => { - const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 }; - vm.sageTaxTypes = [ - { id: 1, rate: 10 }, - { id: 2, rate: 20 }, - ]; - const result = vm.taxRate(invoiceInTax); - expect(result).toBe((10 / 100) * 100); - }); - - it('should return 0 if there is not tax rate', () => { - const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 }; - vm.sageTaxTypes = []; - - const result = vm.taxRate(invoiceInTax); - expect(result).toBe(0); - }); - }); -}); From 6a7cf5e8e858aee10a00e82319e32c300de65747 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 5 Dec 2024 12:34:29 +0100 Subject: [PATCH 035/142] feat: refs #7936 add unit tests --- src/components/LeftMenu.vue | 1 + .../__tests__/composables/getExchange.spec.js | 45 +++++++++++++++++++ .../useAccountShortToStandard.spec.js | 9 ++++ 3 files changed, 55 insertions(+) create mode 100644 test/vitest/__tests__/composables/getExchange.spec.js create mode 100644 test/vitest/__tests__/composables/useAccountShortToStandard.spec.js diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index ab2931dfd29..31ad9ebed5d 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -177,6 +177,7 @@ function normalize(text) { class="full-width" filled dense + autofocus /> </QItem> <QSeparator /> diff --git a/test/vitest/__tests__/composables/getExchange.spec.js b/test/vitest/__tests__/composables/getExchange.spec.js new file mode 100644 index 00000000000..dba31458ee1 --- /dev/null +++ b/test/vitest/__tests__/composables/getExchange.spec.js @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from 'vitest'; +import axios from 'axios'; +import { getExchange } from 'src/composables/getExchange'; + +vi.mock('axios'); + +describe('getExchange()', () => { + it('should return the correct exchange rate', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBe('83.33'); + }); + + it('should return the correct exchange rate with custom decimal places', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const decimalPlaces = 3; + const result = await getExchange(amount, currencyFk, dated, decimalPlaces); + + expect(result).toBe('83.333'); + }); + + it('should return null if the API call fails', async () => { + axios.get.mockRejectedValue(new Error('Network error')); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBeNull(); + }); +}); diff --git a/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js b/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js new file mode 100644 index 00000000000..d2458581210 --- /dev/null +++ b/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest'; +import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; + +describe('useAccountShortToStandard()', () => { + it('should pad the decimal part with zeros for short numbers', () => { + expect(useAccountShortToStandard('123.45')).toBe('1230000045'); + expect(useAccountShortToStandard('123.')).toBe('1230000000'); + }); +}); From bd12c3bc663f0be5a1d781f01b981dffc0c165ab Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 5 Dec 2024 15:55:40 +0100 Subject: [PATCH 036/142] feat: refs #7936 update option labels in InvoiceIn components for better clarity --- .../InvoiceIn/Card/InvoiceInIntrastat.vue | 24 ++++----------- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 30 ++----------------- 2 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index b821025c29f..be9e3bf4456 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -26,7 +26,7 @@ const columns = computed(() => [ options: intrastats.value, model: 'intrastatFk', optionValue: 'id', - optionLabel: 'description', + optionLabel: (row) => `${row.id}: ${row.description}`, sortable: true, tabIndex: 1, align: 'left', @@ -68,12 +68,6 @@ const columns = computed(() => [ align: 'left', }, ]); - -const formatOpt = (row, { model, options }, prop) => { - const obj = row[model]; - const option = options.find(({ id }) => id == obj); - return option ? `${obj}:${option[prop]}` : ''; -}; </script> <template> <FetchData @@ -118,12 +112,9 @@ const formatOpt = (row, { model, options }, prop) => { <VnSelect v-model="row[col.model]" :options="col.options" - option-value="id" - option-label="description" + :option-value="col.optionValue" + :option-label="col.optionLabel" :filter-options="['id', 'description']" - :hide-selected="false" - :fill-input="false" - :display-value="formatOpt(row, col, 'description')" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -138,8 +129,8 @@ const formatOpt = (row, { model, options }, prop) => { <VnSelect v-model="row[col.model]" :options="col.options" - option-value="id" - option-label="code" + :option-value="col.optionValue" + :option-label="col.optionLabel" /> </QTd> </template> @@ -248,11 +239,6 @@ const formatOpt = (row, { model, options }, prop) => { } } </style> -<style lang="scss" scoped> -:deep(.q-table tr .q-td:nth-child(2) input) { - display: none; -} -</style> <i18n> en: amount: Amount diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index c52136beaef..dcca997e80c 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -41,9 +41,8 @@ const columns = computed(() => [ options: expenses.value, model: 'expenseFk', optionValue: 'id', - optionLabel: 'id', + optionLabel: (row) => `${row.id}: ${row.name}`, sortable: true, - tabIndex: 1, align: 'left', }, { @@ -52,7 +51,6 @@ const columns = computed(() => [ field: (row) => row.taxableBase, model: 'taxableBase', sortable: true, - tabIndex: 2, align: 'left', }, { @@ -62,9 +60,8 @@ const columns = computed(() => [ options: sageTaxTypes.value, model: 'taxTypeSageFk', optionValue: 'id', - optionLabel: 'id', + optionLabel: (row) => `${row.id}: ${row.vat}`, sortable: true, - tabindex: 3, align: 'left', }, { @@ -74,16 +71,14 @@ const columns = computed(() => [ options: sageTransactionTypes.value, model: 'transactionTypeSageFk', optionValue: 'id', - optionLabel: 'id', + optionLabel: (row) => `${row.id}: ${row.transaction}`, sortable: true, - tabIndex: 4, align: 'left', }, { name: 'rate', label: t('Rate'), sortable: true, - tabIndex: 5, field: (row) => taxRate(row, row.taxTypeSageFk), align: 'left', }, @@ -91,7 +86,6 @@ const columns = computed(() => [ name: 'foreignvalue', label: t('Foreign value'), sortable: true, - tabIndex: 6, field: (row) => row.foreignValue, align: 'left', }, @@ -124,12 +118,6 @@ function taxRate(invoiceInTax) { return ((taxTypeSage / 100) * taxableBase).toFixed(2); } - -const formatOpt = (row, { model, options }, prop) => { - const obj = row[model]; - const option = options.find(({ id }) => id == obj); - return option ? `${obj}:${option[prop]}` : ''; -}; </script> <template> <FetchData @@ -173,8 +161,6 @@ const formatOpt = (row, { model, options }, prop) => { :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" - :hide-selected="false" - :display-value="formatOpt(row, col, 'name')" @keydown.tab=" row[col.model] = useAccountShortToStandard( $event.target.value @@ -211,8 +197,6 @@ const formatOpt = (row, { model, options }, prop) => { :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'vat']" - :hide-selected="false" - :display-value="formatOpt(row, col, 'vat')" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -235,9 +219,6 @@ const formatOpt = (row, { model, options }, prop) => { :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'transaction']" - :autofocus="col.tabIndex == 1" - :hide-selected="false" - :display-value="formatOpt(row, col, 'transaction')" > <template #option="scope"> <QItem v-bind="scope.itemProps"> @@ -428,11 +409,6 @@ const formatOpt = (row, { model, options }, prop) => { .bg { background-color: var(--vn-light-gray); } - -:deep(.q-table tr td:nth-child(n + 4):nth-child(-n + 5) input) { - display: none; -} - @media (max-width: $breakpoint-xs) { .q-dialog { .q-card { From fdd947fbbbd5f7dafbc7eb2531f1786cc8bdef52 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 5 Dec 2024 15:59:56 +0100 Subject: [PATCH 037/142] feat: refs #7936 improve optionLabel logic in InvoiceInVat component for better handling of numeric values --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index dcca997e80c..2b3c42e0dc7 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -41,7 +41,11 @@ const columns = computed(() => [ options: expenses.value, model: 'expenseFk', optionValue: 'id', - optionLabel: (row) => `${row.id}: ${row.name}`, + optionLabel: (row) => { + if (isNaN(row)) return `${row.id}: ${row.name}`; + let label = expenses.value.find((expense) => expense.id == row); + return `${label.id}: ${label.name}`; + }, sortable: true, align: 'left', }, From fb1928db7ea8d3909bceca29a44c2d1bdaefdbde Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 5 Dec 2024 16:41:32 +0100 Subject: [PATCH 038/142] feat: refs #7936 simplify optionLabel wip --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index 2b3c42e0dc7..dcca997e80c 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -41,11 +41,7 @@ const columns = computed(() => [ options: expenses.value, model: 'expenseFk', optionValue: 'id', - optionLabel: (row) => { - if (isNaN(row)) return `${row.id}: ${row.name}`; - let label = expenses.value.find((expense) => expense.id == row); - return `${label.id}: ${label.name}`; - }, + optionLabel: (row) => `${row.id}: ${row.name}`, sortable: true, align: 'left', }, From 1d86b2912944b9c1abc5d7b9e040a93966c1077d Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 9 Dec 2024 11:21:18 +0100 Subject: [PATCH 039/142] feat: refs #8197 vnTableFilter --- src/components/VnTable/VnFilter.vue | 5 +- src/components/VnTable/VnTable.vue | 138 +++-------------------- src/components/VnTable/VnTableFilter.vue | 85 ++++++++++++++ src/components/common/VnCard.vue | 11 +- src/components/common/VnCardMain.vue | 2 + src/components/ui/VnFilterPanel.vue | 93 +++++---------- src/components/ui/VnPaginate.vue | 10 +- src/composables/useArrayData.js | 8 +- src/composables/useFilterParams.js | 65 +++++++++++ src/pages/Account/AccountList.vue | 30 +++-- src/utils/getUserParams.js | 0 11 files changed, 233 insertions(+), 214 deletions(-) create mode 100644 src/components/VnTable/VnTableFilter.vue create mode 100644 src/composables/useFilterParams.js create mode 100644 src/utils/getUserParams.js diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 86802ee92ae..d859d12aa28 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -32,7 +32,10 @@ const $props = defineProps({ defineExpose({ addFilter, props: $props }); const model = defineModel(undefined, { required: true }); -const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); +const arrayData = useArrayData( + $props.dataKey, + $props.searchUrl ? { searchUrl: $props.searchUrl } : null +); const columnFilter = computed(() => $props.column?.columnFilter); const updateEvent = { 'update:modelValue': addFilter }; diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 94147708408..324c49cde98 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -1,20 +1,21 @@ <script setup> -import { ref, onBeforeMount, onMounted, computed, watch } from 'vue'; +import { ref, onBeforeMount, onMounted, computed, watch, useAttrs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; import { useQuasar } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; +import { useFilterParams } from 'src/composables/useFilterParams'; import CrudModel from 'src/components/CrudModel.vue'; import FormModelPopup from 'components/FormModelPopup.vue'; -import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnFilter from 'components/VnTable/VnFilter.vue'; import VnTableChip from 'components/VnTable/VnChip.vue'; import VnVisibleColumn from 'src/components/VnTable/VnVisibleColumn.vue'; import VnLv from 'components/ui/VnLv.vue'; import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; +import VnTableFilter from './VnTableFilter.vue'; const $props = defineProps({ columns: { @@ -33,6 +34,10 @@ const $props = defineProps({ type: Boolean, default: true, }, + rightSearchIcon: { + type: Boolean, + default: true, + }, rowClick: { type: [Function, Boolean], default: null, @@ -101,10 +106,6 @@ const $props = defineProps({ type: String, default: '90vh', }, - chipLocale: { - type: String, - default: null, - }, footer: { type: Boolean, default: false, @@ -119,22 +120,21 @@ const stateStore = useStateStore(); const route = useRoute(); const router = useRouter(); const quasar = useQuasar(); +const $attrs = useAttrs(); const CARD_MODE = 'card'; const TABLE_MODE = 'table'; const mode = ref(CARD_MODE); const selected = ref([]); const hasParams = ref(false); -const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}'); -const params = ref({ ...routeQuery, ...routeQuery.filter?.where }); -const orders = ref(parseOrder(routeQuery.filter?.order)); const CrudModelRef = ref({}); const showForm = ref(false); const splittedColumns = ref({ columns: [] }); const columnsVisibilitySkipped = ref(); const createForm = ref(); -const tableFilterRef = ref([]); const tableRef = ref(); +const params = ref(useFilterParams($attrs['data-key']).params); +const orders = ref(useFilterParams($attrs['data-key']).orders); const tableModes = [ { @@ -163,7 +163,7 @@ onMounted(() => { stateStore.rightDrawer = quasar.screen.gt.xs; columnsVisibilitySkipped.value = [ ...splittedColumns.value.columns - .filter((c) => c.visible == false) + .filter((c) => c.visible === false) .map((c) => c.name), ...['tableActions'], ]; @@ -183,41 +183,8 @@ watch( { immediate: true } ); -watch( - () => route.query[$props.searchUrl], - (val) => setUserParams(val), - { immediate: true, deep: true } -); - const isTableMode = computed(() => mode.value == TABLE_MODE); - -function setUserParams(watchedParams, watchedOrder) { - if (!watchedParams) return; - - if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); - const filter = - typeof watchedParams?.filter == 'string' - ? JSON.parse(watchedParams?.filter ?? '{}') - : watchedParams?.filter; - const where = filter?.where; - const order = watchedOrder ?? filter?.order; - - watchedParams = { ...watchedParams, ...where }; - delete watchedParams.filter; - delete params.value?.filter; - params.value = { ...params.value, ...sanitizer(watchedParams) }; - orders.value = parseOrder(order); -} - -function sanitizer(params) { - for (const [key, value] of Object.entries(params)) { - if (value && typeof value == 'object') { - const param = Object.values(value)[0]; - if (typeof param == 'string') params[key] = param.replaceAll('%', ''); - } - } - return params; -} +const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon); function splitColumns(columns) { splittedColumns.value = { @@ -298,17 +265,6 @@ function getColAlign(col) { return 'text-' + (col.align ?? 'left'); } -function parseOrder(urlOrders) { - const orderObject = {}; - if (!urlOrders) return orderObject; - if (typeof urlOrders == 'string') urlOrders = [urlOrders]; - for (const [index, orders] of urlOrders.entries()) { - const [name, direction] = orders.split(' '); - orderObject[name] = { direction, index: index + 1 }; - } - return orderObject; -} - const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); defineExpose({ create: createForm, @@ -349,71 +305,11 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } </script> <template> - <QDrawer + <VnTableFilter v-if="$props.rightSearch" - v-model="stateStore.rightDrawer" - side="right" - :width="256" - show-if-above - > - <QScrollArea class="fit"> - <VnFilterPanel - :data-key="$attrs['data-key']" - :search-button="true" - v-model="params" - :search-url="searchUrl" - :redirect="!!redirect" - @set-user-params="setUserParams" - :disable-submit-event="true" - @remove=" - (key) => - tableFilterRef - .find((f) => f.props?.column.name == key) - ?.addFilter() - " - > - <template #body> - <div - class="row no-wrap flex-center" - v-for="col of splittedColumns.columns.filter( - (c) => c.columnFilter ?? true - )" - :key="col.id" - > - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> - <VnTableOrder - v-if=" - col?.columnFilter !== false && - col?.name !== 'tableActions' - " - v-model="orders[col.orderBy ?? col.name]" - :name="col.orderBy ?? col.name" - :data-key="$attrs['data-key']" - :search-url="searchUrl" - :vertical="true" - /> - </div> - <slot - name="moreFilterPanel" - :params="params" - :columns="splittedColumns.columns" - /> - </template> - <template #tags="{ tag, formatFn }" v-if="chipLocale"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - </VnFilterPanel> - </QScrollArea> - </QDrawer> + :data-key="$attrs['data-key']" + :columns="columns" + /> <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" @@ -467,7 +363,7 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { :options="tableModes.filter((mode) => !mode.disable)" /> <QBtn - v-if="$props.rightSearch" + v-if="showRightIcon" icon="filter_alt" class="bg-vn-section-color q-ml-sm" dense diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue new file mode 100644 index 00000000000..2d1758786e7 --- /dev/null +++ b/src/components/VnTable/VnTableFilter.vue @@ -0,0 +1,85 @@ +<script setup> +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useStateStore } from 'stores/useStateStore'; + +import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; +import VnFilter from 'components/VnTable/VnFilter.vue'; +import VnTableOrder from 'src/components/VnTable/VnOrder.vue'; + +defineProps({ + columns: { + type: Array, + required: true, + }, + chipLocale: { + type: String, + default: null, + }, + searchUrl: { + type: [String, Boolean], + default: 'table', + }, +}); +const { t } = useI18n(); +const stateStore = useStateStore(); + +const tableFilterRef = ref([]); + +function columnName(col) { + const column = { ...col, ...col.columnFilter }; + let name = column.name; + if (column.alias) name = column.alias + '.' + name; + return name; +} +</script> +<template> + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QScrollArea class="fit"> + <VnFilterPanel + v-bind="$attrs" + :search-button="true" + :disable-submit-event="true" + > + <template #body="{ params, orders }"> + <div + class="row no-wrap flex-center" + v-for="col of columns.filter((c) => c.columnFilter ?? true)" + :key="col.id" + > + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + <VnTableOrder + v-if=" + col?.columnFilter !== false && + col?.name !== 'tableActions' + " + v-model="orders[col.orderBy ?? col.name]" + :name="col.orderBy ?? col.name" + :data-key="$attrs['data-key']" + :search-url="searchUrl" + :vertical="true" + /> + </div> + <slot + name="moreFilterPanel" + :params="params" + :orders="orders" + :columns="columns" + /> + </template> + <template #tags="{ tag, formatFn }" v-if="chipLocale"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + </VnFilterPanel> + </QScrollArea> + </QDrawer> +</template> diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 88d374c74e3..16a077a79f1 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -4,10 +4,7 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; -import VnSubToolbar from '../ui/VnSubToolbar.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import LeftMenu from 'components/LeftMenu.vue'; -import RightMenu from 'components/common/RightMenu.vue'; const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, @@ -29,10 +26,7 @@ const url = computed(() => { } return props.customUrl; }); -const searchRightDataKey = computed(() => { - if (!props.searchDataKey) return route.name; - return props.searchDataKey; -}); + const arrayData = useArrayData(props.dataKey, { url: url.value, filter: props.filter, @@ -59,9 +53,6 @@ if (props.baseUrl) { } </script> <template> - <slot name="searchbar" v-if="props.searchDataKey"> - <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> - </slot> <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <component :is="descriptor" /> <QSeparator /> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 6e023153739..3ebfcfb8bdc 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -12,9 +12,11 @@ defineProps({ </script> <template> <slot name="searchbar" /> + {{ stateStore.isHeaderMounted() }} <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> <slot name="body" v-if="section == $route.name" /> <RouterView v-else /> + <slot name="rightPanel" /> </template> diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index 7319dc866cd..b59df89904d 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -1,10 +1,10 @@ <script setup> -import { onMounted, ref, computed, watch } from 'vue'; +import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; -import { useRoute } from 'vue-router'; import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; +import { useFilterParams } from 'src/composables/useFilterParams'; const { t } = useI18n(); const $props = defineProps({ @@ -55,6 +55,10 @@ const $props = defineProps({ type: Boolean, default: true, }, + arrayData: { + type: Object, + default: null, + }, }); const emit = defineEmits([ @@ -67,52 +71,19 @@ const emit = defineEmits([ 'setUserParams', ]); -const arrayData = useArrayData($props.dataKey, { - exprBuilder: $props.exprBuilder, - searchUrl: $props.searchUrl, - navigate: $props.redirect ? {} : null, -}); -const route = useRoute(); +const arrayData = + $props.arrayData ?? + useArrayData($props.dataKey, { + exprBuilder: $props.exprBuilder, + searchUrl: $props.searchUrl, + navigate: $props.redirect ? {} : null, + }); + const store = arrayData.store; -const userParams = ref({}); +const userParams = ref(useFilterParams($props.dataKey).params); +const userOrders = ref(useFilterParams($props.dataKey).orders); -defineExpose({ search, sanitizer, params: userParams }); - -onMounted(() => { - if (!userParams.value) userParams.value = $props.modelValue ?? {}; - emit('init', { params: userParams.value }); -}); - -function setUserParams(watchedParams) { - if (!watchedParams || Object.keys(watchedParams).length == 0) return; - - if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); - if (typeof watchedParams?.filter == 'string') - watchedParams.filter = JSON.parse(watchedParams.filter); - - watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; - const order = watchedParams.filter?.order; - - delete watchedParams.filter; - userParams.value = sanitizer(watchedParams); - emit('setUserParams', userParams.value, order); -} - -watch( - () => route.query[$props.searchUrl], - (val, oldValue) => (val || oldValue) && setUserParams(val) -); - -watch( - () => arrayData.store.userParams, - (val, oldValue) => (val || oldValue) && setUserParams(val), - { immediate: true } -); - -watch( - () => $props.modelValue, - (val) => (userParams.value = val ?? {}) -); +defineExpose({ search, params: userParams, remove }); const isLoading = ref(false); async function search(evt) { @@ -123,10 +94,9 @@ async function search(evt) { isLoading.value = true; const filter = { ...userParams.value, ...$props.modelValue }; store.userParamsChanged = true; - const { params: newParams } = await arrayData.addFilter({ + await arrayData.addFilter({ params: filter, }); - userParams.value = newParams; if (!$props.showAll && !Object.values(filter).length) store.data = []; emit('search'); @@ -149,9 +119,8 @@ async function clearFilters() { for (const key of removableFilters) { newParams[key] = userParams.value[key]; } - userParams.value = {}; - userParams.value = { ...newParams }; // Actualizar los params con los removibles - await arrayData.applyFilter({ params: userParams.value }); + + await arrayData.applyFilter({ params: { ...newParams } }); if (!$props.showAll) { store.data = []; @@ -213,21 +182,6 @@ function formatValue(value) { return `"${value}"`; } - -function sanitizer(params) { - for (const [key, value] of Object.entries(params)) { - if (key === 'and' && Array.isArray(value)) { - value.forEach((item) => { - Object.assign(params, item); - }); - delete params[key]; - } else if (value && typeof value === 'object') { - const param = Object.values(value)[0]; - if (typeof param == 'string') params[key] = param.replaceAll('%', ''); - } - } - return params; -} </script> <template> @@ -296,7 +250,12 @@ function sanitizer(params) { <QSeparator /> </QList> <QList dense class="list q-gutter-y-sm q-mt-sm"> - <slot name="body" :params="sanitizer(userParams)" :search-fn="search"></slot> + <slot + name="body" + :params="userParams" + :orders="userOrders" + :search-fn="search" + ></slot> </QList> </QForm> <QInnerLoading diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index c5fbbb7314b..13361bd0639 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -106,6 +106,7 @@ onMounted(async () => { onBeforeUnmount(() => { arrayData.resetPagination(); + arrayData.reset(['currentFilter', 'userParams', 'userFilter']); }); watch( @@ -197,7 +198,14 @@ async function onLoad(index, done) { done(isDone); } -defineExpose({ fetch, update, addFilter, paginate }); +defineExpose({ + fetch, + update, + addFilter, + paginate, + userParams: arrayData.store.userParams, + currentFilter: arrayData.store.currentFilter, +}); </script> <template> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index c36eb99900c..c0c744852ee 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -25,11 +25,14 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const searchUrl = store.searchUrl; if (query[searchUrl]) { const params = JSON.parse(query[searchUrl]); - const filter = params?.filter && JSON.parse(params?.filter ?? '{}'); + const filter = + params?.filter && typeof params?.filter == 'object' + ? params?.filter + : JSON.parse(params?.filter ?? '{}'); delete params.filter; store.userParams = { ...store.userParams, ...params }; - store.userFilter = { ...filter, ...store.userFilter }; + store.filter = { ...filter, ...store.userFilter }; if (filter?.order) store.order = filter.order; } }); @@ -74,7 +77,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const filter = { limit: store.limit, }; - let userParams = { ...store.userParams }; Object.assign(filter, store.userFilter); diff --git a/src/composables/useFilterParams.js b/src/composables/useFilterParams.js new file mode 100644 index 00000000000..2878e4b76ab --- /dev/null +++ b/src/composables/useFilterParams.js @@ -0,0 +1,65 @@ +import { useArrayData } from 'src/composables/useArrayData'; +import { onBeforeMount, ref, watch } from 'vue'; + +export function useFilterParams(key) { + if (!key) throw new Error('ArrayData: A key is required to use this composable'); + const params = ref({}); + const orders = ref({}); + const arrayData = ref({}); + + onBeforeMount(() => { + arrayData.value = useArrayData(key); + }); + + watch( + () => arrayData.value.store?.currentFilter, + (val, oldValue) => (val || oldValue) && setUserParams(val), + { immediate: true, deep: true } + ); + + function parseOrder(urlOrders) { + const orderObject = {}; + if (urlOrders) { + if (typeof urlOrders == 'string') urlOrders = [urlOrders]; + for (const [index, orders] of urlOrders.entries()) { + const [name, direction] = orders.split(' '); + orderObject[name] = { direction, index: index + 1 }; + } + } + orders.value = orderObject; + } + + function setUserParams(watchedParams) { + if (!watchedParams || Object.keys(watchedParams).length == 0) return; + + if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); + if (typeof watchedParams?.filter == 'string') + watchedParams.filter = JSON.parse(watchedParams.filter); + + watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; + parseOrder(watchedParams.filter?.order); + + delete watchedParams.filter; + params.value = sanitizer(watchedParams); + } + + function sanitizer(params) { + for (const [key, value] of Object.entries(params)) { + if (key === 'and' && Array.isArray(value)) { + value.forEach((item) => { + Object.assign(params, item); + }); + delete params[key]; + } else if (value && typeof value === 'object') { + const param = Object.values(value)[0]; + if (typeof param == 'string') params[key] = param.replaceAll('%', ''); + } + } + return params; + } + + return { + params, + orders, + }; +} diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 4b8e8fb2836..a0e2a3842ab 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,11 +1,13 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { ref, computed } from 'vue'; +import { ref, computed, onBeforeMount } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnTableFilter from 'src/components/VnTable/VnTableFilter.vue'; +import { useArrayData } from 'src/composables/useArrayData'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -84,7 +86,17 @@ const columns = computed(() => [ ], }, ]); -const exprBuilder = (param, value) => { + +onBeforeMount(() => { + useArrayData(dataKey, { + url, + userFilter: filter, + order: 'id DESC', + exprBuilder, + searchUrl: 'table', + }); +}); +function exprBuilder(param, value) { switch (param) { case 'search': return /^\d+$/.test(value) @@ -101,7 +113,7 @@ const exprBuilder = (param, value) => { case 'roleFk': return { [param]: value }; } -}; +} </script> <template> @@ -109,28 +121,24 @@ const exprBuilder = (param, value) => { <template #searchbar> <VnSearchbar :data-key="dataKey" - :expr-builder="exprBuilder" :label="t('account.search')" :info="t('account.searchInfo')" - :filter="filter" - :url="url" /> </template> <template #body> <VnTable ref="tableRef" :data-key="dataKey" - :url="url" - :filter="filter" - order="id DESC" :columns="columns" default-mode="table" redirect="account" :use-model="true" - :right-search="true" - :expr-builder="exprBuilder" + :right-search="false" /> </template> + <template #rightPanel> + <VnTableFilter :data-key="dataKey" :columns="columns" /> + </template> </VnCardMain> </template> diff --git a/src/utils/getUserParams.js b/src/utils/getUserParams.js new file mode 100644 index 00000000000..e69de29bb2d From 2ae0d90e32738bdfc816b3a6a5e64ca94a45e658 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 9 Dec 2024 14:15:33 +0100 Subject: [PATCH 040/142] chore: refs #8197 replace name --- src/components/common/VnCardMain.vue | 1 - src/router/modules/index.js | 2 +- src/router/modules/{Supplier.js => supplier.js} | 0 src/router/routes.js | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) rename src/router/modules/{Supplier.js => supplier.js} (100%) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 3ebfcfb8bdc..7a56aa5cf16 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -12,7 +12,6 @@ defineProps({ </script> <template> <slot name="searchbar" /> - {{ stateStore.isHeaderMounted() }} <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> diff --git a/src/router/modules/index.js b/src/router/modules/index.js index fb1bdc46667..77076d04a20 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -8,7 +8,7 @@ import Worker from './worker'; import Shelving from './shelving'; import Wagon from './wagon'; import Route from './route'; -import Supplier from './Supplier'; +import Supplier from './supplier'; import Travel from './travel'; import Order from './order'; import Department from './department'; diff --git a/src/router/modules/Supplier.js b/src/router/modules/supplier.js similarity index 100% rename from src/router/modules/Supplier.js rename to src/router/modules/supplier.js diff --git a/src/router/routes.js b/src/router/routes.js index d332be94194..131021c9a23 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -7,7 +7,7 @@ import worker from './modules/worker'; import invoiceOut from './modules/invoiceOut'; import invoiceIn from './modules/invoiceIn'; import wagon from './modules/wagon'; -import supplier from './modules/Supplier'; +import supplier from './modules/supplier'; import travel from './modules/travel'; import department from './modules/department'; import ItemType from './modules/itemType'; From 799c78cdff9d512ad544b24a3d6c6881524acdc4 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 9 Dec 2024 17:07:12 +0100 Subject: [PATCH 041/142] feat: refs #7936 show id & value --- src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue | 4 +++- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index be9e3bf4456..f16a7b17b07 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -165,7 +165,9 @@ const columns = computed(() => [ v-model="props.row['intrastatFk']" :options="intrastats" option-value="id" - option-label="description" + :option-label=" + (row) => `${row.id}:${row.description}` + " :filter-options="['id', 'description']" > <template #option="scope"> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index dcca997e80c..e8087001c4c 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -118,6 +118,8 @@ function taxRate(invoiceInTax) { return ((taxTypeSage / 100) * taxableBase).toFixed(2); } + +const formatLabel = (row, prop) => `${row.id}: ${row[prop]}`; </script> <template> <FetchData @@ -296,7 +298,7 @@ function taxRate(invoiceInTax) { v-model="props.row['expenseFk']" :options="expenses" option-value="id" - option-label="name" + :option-label="(row) => `${row.id}:${row.name}`" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" > @@ -330,7 +332,7 @@ function taxRate(invoiceInTax) { v-model="props.row['taxTypeSageFk']" :options="sageTaxTypes" option-value="id" - option-label="vat" + :option-label="(row) => `${row.id}:${row.vat}`" :filter-options="['id', 'vat']" > <template #option="scope"> @@ -353,7 +355,9 @@ function taxRate(invoiceInTax) { v-model="props.row['transactionTypeSageFk']" :options="sageTransactionTypes" option-value="id" - option-label="transaction" + :option-label=" + (row) => `${row.id}:${row.transaction}` + " :filter-options="['id', 'transaction']" > <template #option="scope"> From 43629a3bc3e2b12ab3fe77d87bbe0cf205e5cd86 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 9 Dec 2024 17:27:30 +0100 Subject: [PATCH 042/142] fix: refs #7936 test --- src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue | 1 + src/pages/InvoiceIn/Card/InvoiceInVat.vue | 1 + .../integration/invoiceIn/invoiceInIntrastat.spec.js | 11 +++++------ .../integration/invoiceIn/invoiceInVat.spec.js | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index f16a7b17b07..fee65daaa5c 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -115,6 +115,7 @@ const columns = computed(() => [ :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'description']" + data-cy="intrastat-code" > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index e8087001c4c..d9eacf686f6 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -199,6 +199,7 @@ const formatLabel = (row, prop) => `${row.id}: ${row[prop]}`; :option-value="col.optionValue" :option-label="col.optionLabel" :filter-options="['id', 'vat']" + data-cy="vat-sageiva" > <template #option="scope"> <QItem v-bind="scope.itemProps"> diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js index f6dac4c733f..4c255054843 100644 --- a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js @@ -2,7 +2,7 @@ describe('InvoiceInIntrastat', () => { const firstRow = 'tbody > :nth-child(1)'; const thirdRow = 'tbody > :nth-child(3)'; - const firstRowCode = `${firstRow} > :nth-child(2)`; + const codes = `[data-cy="intrastat-code"]`; const firstRowAmount = `${firstRow} > :nth-child(3)`; beforeEach(() => { @@ -11,13 +11,12 @@ describe('InvoiceInIntrastat', () => { }); it('should edit the first line', () => { - cy.selectOption(firstRowCode, 'Plantas vivas: Esqueje/injerto, Vid'); + cy.selectOption(`${firstRow} ${codes}`, 'Plantas vivas: Esqueje/injerto, Vid'); cy.get(firstRowAmount).clear(); cy.saveCard(); - cy.get(`${firstRowCode} span`).should( - 'have.text', - '6021010:Plantas vivas: Esqueje/injerto, Vid' - ); + cy.get(codes) + .eq(0) + .should('have.value', '6021010: Plantas vivas: Esqueje/injerto, Vid'); }); it('should add a new row', () => { diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js index b84d743d11b..f8b403a458f 100644 --- a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -2,6 +2,7 @@ describe('InvoiceInVat', () => { const thirdRow = 'tbody > :nth-child(3)'; const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)'; + const vats = '[data-cy="vat-sageiva"]'; const dialogInputs = '.q-dialog label input'; const addBtn = 'tbody tr:nth-child(1) td:nth-child(2) .--add-icon'; const randomInt = Math.floor(Math.random() * 100); @@ -14,9 +15,9 @@ describe('InvoiceInVat', () => { }); it('should edit the sage iva', () => { - cy.selectOption(firstLineVat, 'H.P. IVA 21% CEE'); + cy.selectOption(`${firstLineVat} ${vats}`, 'H.P. IVA 21% CEE'); cy.saveCard(); - cy.get(`${firstLineVat} span`).should('have.text', '8:H.P. IVA 21% CEE'); + cy.get(vats).eq(0).should('have.value', '8: H.P. IVA 21% CEE'); }); it('should add a new row', () => { From 2939ddcfb7606f2bc15b087d110999fe990a88d3 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 10 Dec 2024 10:35:05 +0100 Subject: [PATCH 043/142] fix: refs #7936 rollback --- src/components/common/VnSelect.vue | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index d39d1143f10..1082df4f91d 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -279,6 +279,28 @@ async function onScroll({ to, direction, from, index }) { } defineExpose({ opts: myOptions }); + +function handleKeyDown(event) { + if (event.key === 'Tab') { + event.preventDefault(); + + const inputValue = vnSelectRef.value?.inputValue; + + if (inputValue) { + const matchingOption = myOptions.value.find( + (option) => + option[optionLabel.value].toLowerCase() === inputValue.toLowerCase() + ); + + if (matchingOption) { + emit('update:modelValue', matchingOption[optionValue.value]); + } else { + emit('update:modelValue', inputValue); + } + vnSelectRef.value?.hidePopup(); + } + } +} </script> <template> @@ -303,6 +325,7 @@ defineExpose({ opts: myOptions }); :input-debounce="useURL ? '300' : '0'" :loading="isLoading" @virtual-scroll="onScroll" + @keydown="handleKeyDown" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'" > <template #append> From cc7432b2534aa1c98b51eb5b311dbaa9088a2214 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 10 Dec 2024 10:38:43 +0100 Subject: [PATCH 044/142] refactor: refs #7936 simplify getTotal fn --- src/composables/getTotal.js | 5 ++--- src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue | 2 +- test/vitest/__tests__/composables/getTotal.spec.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/composables/getTotal.js b/src/composables/getTotal.js index 91b884bda24..24ac3aa27c9 100644 --- a/src/composables/getTotal.js +++ b/src/composables/getTotal.js @@ -1,11 +1,10 @@ import { toCurrency } from 'src/filters'; export function getTotal(rows, key, opts = {}) { - const { currency, cb, decimalPlaces, int } = opts; + const { currency, cb, decimalPlaces } = opts; const total = rows.reduce((acc, row) => acc + +(cb ? cb(row) : row[key] || 0), 0); - const decimals = int ? 0 : decimalPlaces ?? 2; return currency ? toCurrency(total, currency == 'default' ? undefined : currency) - : parseFloat(total).toFixed(decimals); + : parseFloat(total).toFixed(decimalPlaces ?? 2); } diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue index fee65daaa5c..1c4091169e6 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -146,7 +146,7 @@ const columns = computed(() => [ {{ getTotal(rows, 'net') }} </QTd> <QTd> - {{ getTotal(rows, 'stems', { int: true }) }} + {{ getTotal(rows, 'stems', { decimalPlaces: 0 }) }} </QTd> <QTd /> </QTr> diff --git a/test/vitest/__tests__/composables/getTotal.spec.js b/test/vitest/__tests__/composables/getTotal.spec.js index 7081c433462..789e3fbcfe0 100644 --- a/test/vitest/__tests__/composables/getTotal.spec.js +++ b/test/vitest/__tests__/composables/getTotal.spec.js @@ -33,7 +33,7 @@ describe('getTotal()', () => { }); it('should calculate the total with integer formatting', () => { - const total = getTotal(rows, 'amount', { int: true }); + const total = getTotal(rows, 'amount', { decimalPlaces: 0 }); expect(total).toBe('62'); }); From 8a1cda6914dcb4f0055abda075ec7c7af1c6d802 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 11 Dec 2024 10:10:59 +0100 Subject: [PATCH 045/142] feat: refs #7936 add autocomplete on tab fn --- src/pages/InvoiceIn/Card/InvoiceInVat.vue | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue index d9eacf686f6..f7ef7d525be 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInVat.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -119,7 +119,17 @@ function taxRate(invoiceInTax) { return ((taxTypeSage / 100) * taxableBase).toFixed(2); } -const formatLabel = (row, prop) => `${row.id}: ${row[prop]}`; +function autocompleteExpense(evt, row, col) { + const val = evt.target.value; + if (!val) return; + + const param = isNaN(val) ? row[col.model] : val; + const lookup = expenses.value.find( + ({ id }) => id == useAccountShortToStandard(param) + ); + + if (lookup) row[col.model] = lookup; +} </script> <template> <FetchData @@ -163,11 +173,7 @@ const formatLabel = (row, prop) => `${row.id}: ${row[prop]}`; :option-label="col.optionLabel" :filter-options="['id', 'name']" :tooltip="t('Create a new expense')" - @keydown.tab=" - row[col.model] = useAccountShortToStandard( - $event.target.value - ) - " + @keydown.tab="autocompleteExpense($event, row, col)" > <template #option="scope"> <QItem v-bind="scope.itemProps"> From 2a6717d0595cffd275455f920f06077fe50ae36f Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 11 Dec 2024 15:04:31 +0100 Subject: [PATCH 046/142] feat: refs #8113 add mapKey prop to VnPaginate and integrate into useArrayData for enhanced data mapping --- src/components/ui/VnPaginate.vue | 5 +++ src/composables/useArrayData.js | 37 ++++++++++++---- src/pages/Ticket/TicketAdvance.vue | 1 + src/stores/useArrayDataStore.js | 2 + .../__tests__/components/Paginate.spec.js | 43 ++++++++----------- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 3649ba8f551..920836a939c 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -74,6 +74,10 @@ const props = defineProps({ type: Boolean, default: false, }, + mapKey: { + type: String, + default: '', + }, }); const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']); @@ -96,6 +100,7 @@ const arrayData = useArrayData(props.dataKey, { exprBuilder: props.exprBuilder, keepOpts: props.keepOpts, searchUrl: props.searchUrl, + mapKey: props.mapKey, }); const store = arrayData.store; diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index da62eee3eb9..6e685ee20ad 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -49,6 +49,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { 'exprBuilder', 'searchUrl', 'navigate', + 'mapKey', ]; if (typeof userOptions === 'object') { for (const option in userOptions) { @@ -119,17 +120,12 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const { limit } = filter; store.hasMoreData = limit && response.data.length >= limit; - if (append) { - if (!store.data) store.data = []; - for (const row of response.data) store.data.push(row); - } else { - store.data = response.data; - if (!isDialogOpened()) updateRouter && updateStateParams(); - } + processData(response.data, { map: !!store.mapKey, append }); + if (!append && !isDialogOpened()) updateRouter && updateStateParams(); store.isLoading = false; - canceller = null; + return response; } @@ -288,6 +284,31 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { router.replace(newUrl); } + function processData(data, { map = true, append = true }) { + if (!append) { + store.data = []; + store.map = new Map(); + } + + if (!Array.isArray(data)) store.data = data; + else if (!map && append) for (const row of data) store.data.push(row); + else + for (const row of data) { + const key = row[store.mapKey]; + const val = { ...row, key }; + if (store.map.has(key)) { + const { position } = store.map.get(key); + val.position = position; + store.map.set(key, val); + store.data[position] = val; + } else { + val.position = store.map.size; + store.map.set(key, val); + store.data.push(val); + } + } + } + const totalRows = computed(() => (store.data && store.data.length) || 0); const isLoading = computed(() => store.isLoading || false); diff --git a/src/pages/Ticket/TicketAdvance.vue b/src/pages/Ticket/TicketAdvance.vue index 8de602b374c..a867285e781 100644 --- a/src/pages/Ticket/TicketAdvance.vue +++ b/src/pages/Ticket/TicketAdvance.vue @@ -441,6 +441,7 @@ watch( <QPage class="column items-center q-pa-md"> <VnTable data-key="advanceTickets" + :map-key="false" ref="vnTableRef" url="Tickets/getTicketsAdvance" search-url="advanceTickets" diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 6a0e7dfa8d1..d0a1c3a8fb1 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -17,6 +17,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { searchUrl: 'params', navigate: null, page: 1, + mapKey: 'id', }; function get(key) { @@ -46,6 +47,7 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { function getDefaultState() { return Object.assign(JSON.parse(JSON.stringify(defaultOpts)), { data: ref(), + map: ref(new Map()), }); } diff --git a/test/vitest/__tests__/components/Paginate.spec.js b/test/vitest/__tests__/components/Paginate.spec.js index 345903c1a56..a67dfcdc638 100644 --- a/test/vitest/__tests__/components/Paginate.spec.js +++ b/test/vitest/__tests__/components/Paginate.spec.js @@ -4,7 +4,11 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue'; describe('VnPaginate', () => { const expectedUrl = '/api/customers'; - + const defaultData = [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; let vm; beforeAll(() => { const options = { @@ -28,11 +32,7 @@ describe('VnPaginate', () => { describe('paginate()', () => { it('should call to the paginate() method and set the data on the rows property', async () => { vi.spyOn(vm.arrayData, 'loadMore'); - vm.store.data = [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; + vm.store.data = defaultData; await vm.paginate(); @@ -42,26 +42,25 @@ describe('VnPaginate', () => { it('should call to the paginate() method and then call it again to paginate', async () => { vi.spyOn(axios, 'get').mockResolvedValue({ - data: [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ], + data: defaultData, }); vm.store.hasMoreData = true; await vm.$nextTick(); - vm.store.data = [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; + vm.store.data = defaultData; await vm.paginate(); expect(vm.store.skip).toEqual(3); expect(vm.store.data.length).toEqual(6); + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 4, name: 'Peter Parker' }, + { id: 5, name: 'Clark Kent' }, + { id: 6, name: 'Barry Allen' }, + ], + }); await vm.paginate(); expect(vm.store.skip).toEqual(6); @@ -85,11 +84,7 @@ describe('VnPaginate', () => { const index = 1; const done = vi.fn(); - vm.store.data = [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; + vm.store.data = defaultData; await vm.onLoad(index, done); @@ -105,11 +100,7 @@ describe('VnPaginate', () => { ], }); - vm.store.data = [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; + vm.store.data = defaultData; expect(vm.pagination.page).toEqual(1); From e075e1307663028b675e580251b4462859348afc Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 11 Dec 2024 17:42:31 +0100 Subject: [PATCH 047/142] feat: refs #7936 add dueDated field --- src/pages/InvoiceIn/InvoiceInList.vue | 16 +++++++++++++--- src/pages/InvoiceIn/locale/en.yml | 1 + src/pages/InvoiceIn/locale/es.yml | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index e9255398beb..6469f446d1f 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -28,6 +28,12 @@ onUnmounted(() => (stateStore.rightDrawer = false)); const tableRef = ref(); const companies = ref([]); const cols = computed(() => [ + { + align: 'left', + name: 'isBooked', + label: t('InvoiceIn.isBooked'), + columnFilter: false, + }, { align: 'left', name: 'id', @@ -68,9 +74,13 @@ const cols = computed(() => [ }, { align: 'left', - name: 'isBooked', - label: t('InvoiceIn.isBooked'), - columnFilter: false, + label: t('InvoiceIn.list.dueDated'), + name: 'dueDated', + component: null, + columnFilter: { + component: 'date', + }, + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.dueDated)), }, { align: 'left', diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 529569aa003..ef7e31ac3d7 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -7,6 +7,7 @@ InvoiceIn: supplierRef: Supplier ref. file: File issued: Issued + dueDated: Due dated awb: AWB amount: Amount descriptor: diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 2192442cd91..ed59434894d 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -5,9 +5,9 @@ InvoiceIn: ref: Referencia supplier: Proveedor supplierRef: Ref. proveedor - shortIssued: F. emisión + issued: F. emisión + dueDated: F. vencimiento file: Fichero - issued: Fecha emisión awb: AWB amount: Importe descriptor: From c5f4e8decd2d4008df70f4ffa118db0b3deb3a1f Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 12 Dec 2024 10:56:42 +0100 Subject: [PATCH 048/142] fix: refs #7936 exclude disabled els on tab --- src/components/common/VnSelect.vue | 2 +- src/pages/InvoiceIn/Card/InvoiceInDueDay.vue | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 5b905efc76e..dc8fd582ed6 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -301,7 +301,7 @@ function handleKeyDown(event) { } const focusableElements = document.querySelectorAll( - 'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])' + 'a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), details:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])' ); const currentIndex = Array.prototype.indexOf.call( focusableElements, diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue index 75f9a67a4c6..d2c6d0a2d4b 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -144,8 +144,6 @@ async function insert() { }" :disable="!isNotEuro(currency)" v-model="row.foreignValue" - clearable - clear-icon="close" /> </QTd> </template> From 3984327b51272ef78f9f64282aebdd8fec250286 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Thu, 12 Dec 2024 11:03:28 +0100 Subject: [PATCH 049/142] feat: refs #8197 working rightMenu --- src/components/VnTable/VnTableFilter.vue | 85 ++++++++++-------------- src/components/common/VnCardMain.vue | 7 +- src/pages/Account/AccountList.vue | 2 +- src/pages/Account/Role/AccountRoles.vue | 1 + 4 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/components/VnTable/VnTableFilter.vue b/src/components/VnTable/VnTableFilter.vue index 2d1758786e7..f23c657cf12 100644 --- a/src/components/VnTable/VnTableFilter.vue +++ b/src/components/VnTable/VnTableFilter.vue @@ -1,7 +1,6 @@ <script setup> import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnFilter from 'components/VnTable/VnFilter.vue'; @@ -22,7 +21,6 @@ defineProps({ }, }); const { t } = useI18n(); -const stateStore = useStateStore(); const tableFilterRef = ref([]); @@ -34,52 +32,41 @@ function columnName(col) { } </script> <template> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> - <QScrollArea class="fit"> - <VnFilterPanel - v-bind="$attrs" - :search-button="true" - :disable-submit-event="true" + <VnFilterPanel v-bind="$attrs" :search-button="true" :disable-submit-event="true"> + <template #body="{ params, orders }"> + <div + class="row no-wrap flex-center" + v-for="col of columns.filter((c) => c.columnFilter ?? true)" + :key="col.id" > - <template #body="{ params, orders }"> - <div - class="row no-wrap flex-center" - v-for="col of columns.filter((c) => c.columnFilter ?? true)" - :key="col.id" - > - <VnFilter - ref="tableFilterRef" - :column="col" - :data-key="$attrs['data-key']" - v-model="params[columnName(col)]" - :search-url="searchUrl" - /> - <VnTableOrder - v-if=" - col?.columnFilter !== false && - col?.name !== 'tableActions' - " - v-model="orders[col.orderBy ?? col.name]" - :name="col.orderBy ?? col.name" - :data-key="$attrs['data-key']" - :search-url="searchUrl" - :vertical="true" - /> - </div> - <slot - name="moreFilterPanel" - :params="params" - :orders="orders" - :columns="columns" - /> - </template> - <template #tags="{ tag, formatFn }" v-if="chipLocale"> - <div class="q-gutter-x-xs"> - <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> - <span>{{ formatFn(tag.value) }}</span> - </div> - </template> - </VnFilterPanel> - </QScrollArea> - </QDrawer> + <VnFilter + ref="tableFilterRef" + :column="col" + :data-key="$attrs['data-key']" + v-model="params[columnName(col)]" + :search-url="searchUrl" + /> + <VnTableOrder + v-if="col?.columnFilter !== false && col?.name !== 'tableActions'" + v-model="orders[col.orderBy ?? col.name]" + :name="col.orderBy ?? col.name" + :data-key="$attrs['data-key']" + :search-url="searchUrl" + :vertical="true" + /> + </div> + <slot + name="moreFilterPanel" + :params="params" + :orders="orders" + :columns="columns" + /> + </template> + <template #tags="{ tag, formatFn }" v-if="chipLocale"> + <div class="q-gutter-x-xs"> + <strong>{{ t(`${chipLocale}.${tag.label}`) }}: </strong> + <span>{{ formatFn(tag.value) }}</span> + </div> + </template> + </VnFilterPanel> </template> diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 7a56aa5cf16..ab664917ae7 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -1,6 +1,7 @@ <script setup> import LeftMenu from '../LeftMenu.vue'; import { useStateStore } from 'stores/useStateStore'; +import RightMenu from './RightMenu.vue'; const stateStore = useStateStore(); defineProps({ @@ -17,5 +18,9 @@ defineProps({ </Teleport> <slot name="body" v-if="section == $route.name" /> <RouterView v-else /> - <slot name="rightPanel" /> + <RightMenu> + <template #right-panel v-if="$slots['rightMenu']"> + <slot name="rightMenu" /> + </template> + </RightMenu> </template> diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index a0e2a3842ab..ed2030d2964 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -136,7 +136,7 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - <template #rightPanel> + <template #rightMenu> <VnTableFilter :data-key="dataKey" :columns="columns" /> </template> </VnCardMain> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 74c4ab8a558..9aebef64c5c 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -114,6 +114,7 @@ const exprBuilder = (param, value) => { :columns="columns" default-mode="table" redirect="account/role" + :right-search="false" /> </template> </VnCardMain> From aeee8611474369b8fd7c844318b9753ccef27095 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 12 Dec 2024 11:20:54 +0100 Subject: [PATCH 050/142] feat: refs #7936 show country code & isVies fields --- src/pages/InvoiceIn/Card/InvoiceInSummary.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 06e6d790ffc..5ad4959f61d 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -229,6 +229,10 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :value="entity.currency?.code" /> <VnLv :label="t('InvoiceIn.serial')" :value="`${entity.serial}`" /> + <VnLv + :label="t('globals.country')" + :value="entity.supplier?.country?.code" + /> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> @@ -254,6 +258,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :label="t('InvoiceIn.summary.bookedDate')" :value="toDate(entity.booked)" /> + <VnLv label="Is vies" :value="entity.supplier?.isVies" /> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> From 965f5fbead6e96ac9aaf927ba962218942471d70 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 12 Dec 2024 12:09:23 +0100 Subject: [PATCH 051/142] fix: refs #7936 test --- test/cypress/integration/invoiceIn/invoiceInList.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index fa0d1c5e462..d9ab3f7e790 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -1,7 +1,7 @@ /// <reference types="cypress" /> describe('InvoiceInList', () => { const firstRow = 'tbody.q-virtual-scroll__content tr:nth-child(1)'; - const firstId = `${firstRow} > td:nth-child(1) span`; + const firstId = `${firstRow} > td:nth-child(2) span`; const firstDetailBtn = `${firstRow} .q-btn:nth-child(1)`; const summaryHeaders = '.summaryBody .header-link'; From f95a1f97399e64ee47f502eb8f4116d461387bf7 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 12 Dec 2024 12:50:57 +0100 Subject: [PATCH 052/142] feat: refs #7936 update 'isVies' label to use global translation key --- src/i18n/locale/en.yml | 2 +- src/i18n/locale/es.yml | 2 +- src/pages/Customer/Card/CustomerFiscalData.vue | 3 +-- src/pages/Customer/Card/CustomerSummary.vue | 2 +- src/pages/Customer/CustomerList.vue | 2 +- src/pages/Customer/locale/en.yml | 1 - src/pages/Customer/locale/es.yml | 1 - src/pages/InvoiceIn/Card/InvoiceInSummary.vue | 2 +- src/pages/Supplier/Card/SupplierFiscalData.vue | 5 +---- 9 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 93ed345b928..3481fe4a9b1 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -348,6 +348,7 @@ globals: deleteConfirmTitle: Delete selected elements changeState: Change state raid: 'Raid {daysInForward} days' + isVies: Vies errors: statusUnauthorized: Access denied statusInternalServerError: An internal server error has ocurred @@ -741,7 +742,6 @@ supplier: sageTransactionTypeFk: Sage transaction type supplierActivityFk: Supplier activity isTrucker: Trucker - isVies: Vies billingData: payMethodFk: Billing data payDemFk: Payment deadline diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 9e189431d14..8807a1adc6e 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -350,6 +350,7 @@ globals: deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado raid: 'Redada {daysInForward} días' + isVies: Vies errors: statusUnauthorized: Acceso denegado statusInternalServerError: Ha ocurrido un error interno del servidor @@ -734,7 +735,6 @@ supplier: sageTransactionTypeFk: Tipo de transacción sage supplierActivityFk: Actividad proveedor isTrucker: Transportista - isVies: Vies billingData: payMethodFk: Forma de pago payDemFk: Plazo de pago diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index 673c7dda97c..aff7deda41a 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -110,7 +110,7 @@ function handleLocation(data, location) { <VnRow> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> <div> - <QCheckbox :label="t('Vies')" v-model="data.isVies" /> + <QCheckbox :label="t('globals.isVies')" v-model="data.isVies" /> <QIcon name="info" class="cursor-info q-ml-sm" size="sm"> <QTooltip> {{ t('whenActivatingIt') }} @@ -169,7 +169,6 @@ es: Active: Activo Frozen: Congelado Has to invoice: Factura - Vies: Vies Notify by email: Notificar vía e-mail Invoice by address: Facturar por consignatario Is equalizated: Recargo de equivalencia diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index ae4c7f3aba7..6650ea39515 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -173,7 +173,7 @@ const sumRisk = ({ clientRisks }) => { :label="t('customer.summary.notifyByEmail')" :value="entity.isToBeMailed" /> - <VnLv :label="t('customer.summary.vies')" :value="entity.isVies" /> + <VnLv :label="t('globals.isVies')" :value="entity.isVies" /> </VnRow> </QCard> <QCard class="vn-one"> diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index e86e35966c3..69122f20097 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -263,7 +263,7 @@ const columns = computed(() => [ }, { align: 'left', - label: t('customer.extendedList.tableVisibleColumns.isVies'), + label: t('globals.isVies'), name: 'isVies', columnFilter: { inWhere: true, diff --git a/src/pages/Customer/locale/en.yml b/src/pages/Customer/locale/en.yml index 07ec53964bd..18cee730941 100644 --- a/src/pages/Customer/locale/en.yml +++ b/src/pages/Customer/locale/en.yml @@ -88,7 +88,6 @@ customer: businessTypeFk: Business type sageTaxTypeFk: Sage tax type sageTransactionTypeFk: Sage tr. type - isVies: Vies isTaxDataChecked: Verified data isFreezed: Freezed hasToInvoice: Invoice diff --git a/src/pages/Customer/locale/es.yml b/src/pages/Customer/locale/es.yml index 36a266497fa..b544f8ad797 100644 --- a/src/pages/Customer/locale/es.yml +++ b/src/pages/Customer/locale/es.yml @@ -90,7 +90,6 @@ customer: businessTypeFk: Tipo de negocio sageTaxTypeFk: Tipo de impuesto Sage sageTransactionTypeFk: Tipo tr. sage - isVies: Vies isTaxDataChecked: Datos comprobados isFreezed: Congelado hasToInvoice: Factura diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 5ad4959f61d..115a3320868 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -258,7 +258,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :label="t('InvoiceIn.summary.bookedDate')" :value="toDate(entity.booked)" /> - <VnLv label="Is vies" :value="entity.supplier?.isVies" /> + <VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" /> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> diff --git a/src/pages/Supplier/Card/SupplierFiscalData.vue b/src/pages/Supplier/Card/SupplierFiscalData.vue index 1a79be8bc27..44235717f86 100644 --- a/src/pages/Supplier/Card/SupplierFiscalData.vue +++ b/src/pages/Supplier/Card/SupplierFiscalData.vue @@ -180,10 +180,7 @@ function handleLocation(data, location) { :label="t('supplier.fiscalData.isTrucker')" /> <div class="row items-center"> - <QCheckbox - v-model="data.isVies" - :label="t('supplier.fiscalData.isVies')" - /> + <QCheckbox v-model="data.isVies" :label="t('globals.isVies')" /> <QIcon name="info" size="xs" class="cursor-pointer q-ml-sm"> <QTooltip> {{ From c164c39a2d70f802c1b5440baf5bb61ef9475604 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Thu, 12 Dec 2024 15:52:36 +0100 Subject: [PATCH 053/142] feat: refs #7050 7050 add test to isEmpty() --- .../components/common/CrudModel.spec.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index 6ce93e59c68..e8cf434737f 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -1,5 +1,6 @@ import { createWrapper } from 'app/test/vitest/helper'; import CrudModel from 'components/CrudModel.vue'; +import c from 'croppie'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; describe('CrudModel', () => { @@ -117,4 +118,47 @@ describe('CrudModel', () => { }); }); }); + + describe('isEmpty()', () => { + let dummyObj; + let dummyArray; + let result; + it('should return true if object si null', async () => { + dummyObj = null; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + }); + + it('should return true if object si undefined', async () => { + dummyObj = undefined; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + }); + + it('should return true if object is empty', async () => { + dummyObj ={}; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(true); + + dummyArray = []; + result = vm.isEmpty(dummyArray); + + expect(result).toBe(true); + }); + + it('should return false if object is not empty', async () => { + dummyObj = {a:1, b:2, c:3}; + result = vm.isEmpty(dummyObj); + + expect(result).toBe(false); + + dummyArray = [1,2,3]; + result = vm.isEmpty(dummyArray); + + expect(result).toBe(false); + }) + }); }); From 77f4949ba6297c4a585bd60e7ac43f51f6852682 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Thu, 12 Dec 2024 15:53:56 +0100 Subject: [PATCH 054/142] feat: refs #7050 7050 add object check --- src/components/CrudModel.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 7fdb54bc4f8..cff18f2dea9 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -270,10 +270,8 @@ function getChanges() { function isEmpty(obj) { if (obj == null) return true; - if (obj === undefined) return true; - if (Object.keys(obj).length === 0) return true; - - if (obj.length > 0) return false; + if (Array.isArray(obj)) return !obj.length; + return Object.keys(obj).length === 0 ; } async function reload(params) { From 0706c9d58ed0c28ed38f58f20f83a0b91b0252ca Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Thu, 12 Dec 2024 16:04:57 +0100 Subject: [PATCH 055/142] fix: refs #7050 delete import added by mistake --- test/vitest/__tests__/components/common/CrudModel.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index e8cf434737f..2d7493cca5d 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -1,6 +1,5 @@ import { createWrapper } from 'app/test/vitest/helper'; import CrudModel from 'components/CrudModel.vue'; -import c from 'croppie'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; describe('CrudModel', () => { From 35cef37d4f31f09e975e1ac023a9a666cfa282d9 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 13 Dec 2024 08:04:05 +0100 Subject: [PATCH 056/142] fix: refs #8201 use arrayData to fix the error --- src/pages/Supplier/Card/SupplierDescriptor.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 28cfe49ce71..9d469bcc137 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -9,7 +9,7 @@ import VnLv from 'src/components/ui/VnLv.vue'; import { toDateString } from 'src/filters'; import useCardDescription from 'src/composables/useCardDescription'; import { getUrl } from 'src/composables/getUrl'; -import { useState } from 'src/composables/useState'; +import { useArrayData } from 'src/composables/useArrayData'; const $props = defineProps({ id: { @@ -26,7 +26,7 @@ const $props = defineProps({ const route = useRoute(); const { t } = useI18n(); const url = ref(); -const state = useState(); +const arrayData = useArrayData(); const filter = { fields: [ @@ -77,7 +77,7 @@ const setData = (entity) => { data.value = useCardDescription(entity.ref, entity.id); }; -const supplier = computed(() => state.get('supplier')); +const supplier = computed(() => arrayData.store.data); const getEntryQueryParams = (supplier) => { if (!supplier) return null; @@ -127,6 +127,7 @@ const getEntryQueryParams = (supplier) => { <VnLv :label="t('supplier.summary.account')" :value="entity.account" /> </template> <template #icons> + {{ console.log('supplier: ', supplier) }} <QCardActions v-if="supplier" class="q-gutter-x-md"> <QIcon v-if="!supplier.isActive" From 1a0789b77fc71cbf78839a331925e992c9475c07 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 13 Dec 2024 08:05:03 +0100 Subject: [PATCH 057/142] refactor: refs #8201 deleted log --- src/pages/Supplier/Card/SupplierDescriptor.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 9d469bcc137..a1a2a09919f 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -127,7 +127,6 @@ const getEntryQueryParams = (supplier) => { <VnLv :label="t('supplier.summary.account')" :value="entity.account" /> </template> <template #icons> - {{ console.log('supplier: ', supplier) }} <QCardActions v-if="supplier" class="q-gutter-x-md"> <QIcon v-if="!supplier.isActive" From 21b4913e197d6ec89a73c4bf9904ffe9cd887aa8 Mon Sep 17 00:00:00 2001 From: jgallego <jgallego@verdnatura.es> Date: Fri, 13 Dec 2024 08:33:05 +0100 Subject: [PATCH 058/142] feat: refs #7235 update invoice out global form to fetch config based on serial type --- src/pages/InvoiceOut/InvoiceOutGlobalForm.vue | 3 +++ src/stores/invoiceOutGlobal.js | 23 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue index 3fd3104bfd4..e6c68952342 100644 --- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue +++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue @@ -115,6 +115,9 @@ onMounted(async () => { <VnSelect :label="t('invoiceOutSerialType')" v-model="formData.serialType" + @update:model-value=" + invoiceOutGlobalStore.fetchInvoiceOutConfig(formData) + " :options="serialTypesOptions" option-value="type" option-label="type" diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js index 332494aa887..cc8d86ea8a1 100644 --- a/src/stores/invoiceOutGlobal.js +++ b/src/stores/invoiceOutGlobal.js @@ -19,7 +19,7 @@ export const useInvoiceOutGlobalStore = defineStore({ maxShipped: null, clientId: null, printer: null, - serialType: null, + serialType: 'global', }, addresses: [], minInvoicingDate: null, @@ -41,7 +41,6 @@ export const useInvoiceOutGlobalStore = defineStore({ async fetchAllData() { try { - const userInfo = await useUserConfig().fetch(); const date = Date.vnNew(); this.formInitialData.maxShipped = new Date( date.getFullYear(), @@ -53,7 +52,7 @@ export const useInvoiceOutGlobalStore = defineStore({ await Promise.all([ this.fetchParallelism(), - this.fetchInvoiceOutConfig(userInfo.companyFk), + this.fetchInvoiceOutConfig(), ]); this.initialDataLoading = false; @@ -62,21 +61,23 @@ export const useInvoiceOutGlobalStore = defineStore({ } }, - async fetchInvoiceOutConfig(companyFk) { + async fetchInvoiceOutConfig(formData = this.formInitialData) { try { - this.formInitialData.companyFk = companyFk; - const params = { companyFk: companyFk }; + const userInfo = await useUserConfig().fetch(); + const params = { + companyFk: userInfo.companyFk, + serialType: formData.serialType, + }; const { data } = await axios.get('InvoiceOuts/getInvoiceDate', { params, }); - const stringDate = data.issued.substring(0, 10); - this.minInvoicingDate = stringDate; - this.formInitialData.invoiceDate = stringDate; - - this.minInvoicingDate = new Date(data.issued); + this.minInvoicingDate = data?.issued + ? new Date(data.issued) + : Date.vnNew(); this.formInitialData.invoiceDate = this.minInvoicingDate; + formData.invoiceDate = this.minInvoicingDate; } catch (err) { console.error('Error fetching invoice out global initial data'); throw new Error(); From 43c24e0383cdca8ae82c20aeae3d19da24811118 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 13 Dec 2024 14:20:17 +0100 Subject: [PATCH 059/142] fix: refs #8201 added onDataSaved emi to refetch when cahnges are made --- src/pages/Account/Card/AccountDescriptor.vue | 7 +++---- src/pages/Supplier/Card/SupplierBasicData.vue | 7 +++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index 3156f8e1ec3..ed7adc45536 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -53,7 +53,6 @@ const hasAccount = ref(false); <AccountDescriptorMenu :has-account="hasAccount" /> </template> <template #before> - <!-- falla id :id="entityId.value" collection="user" size="160x160" --> <VnImg :id="entityId" collection="user" resolution="520x520" class="photo"> <template #error> <div @@ -75,8 +74,8 @@ 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 #actions="{ entity }"> - <QCardActions class="q-gutter-x-md"> + <template #icons="{ entity }"> + <QCardActions v-if="accountData" class="q-gutter-x-md"> <QIcon v-if="!entity.active" color="primary" @@ -91,7 +90,7 @@ const hasAccount = ref(false); <QIcon color="primary" name="contact_mail" - v-if="entity.hasAccount" + v-if="hasAccount" flat round size="sm" diff --git a/src/pages/Supplier/Card/SupplierBasicData.vue b/src/pages/Supplier/Card/SupplierBasicData.vue index 842109656d3..4b4988789ef 100644 --- a/src/pages/Supplier/Card/SupplierBasicData.vue +++ b/src/pages/Supplier/Card/SupplierBasicData.vue @@ -6,14 +6,20 @@ import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; +import { useArrayData } from 'src/composables/useArrayData'; const route = useRoute(); const { t } = useI18n(); +const arrayData = useArrayData(); const companySizes = [ { id: 'small', name: t('globals.small'), size: '1-5' }, { id: 'medium', name: t('globals.medium'), size: '6-50' }, { id: 'big', name: t('globals.big'), size: '>50' }, ]; + +const onSave = () => { + arrayData.fetch({}); +}; </script> <template> <FormModel @@ -22,6 +28,7 @@ const companySizes = [ model="supplier" auto-load :clear-store-on-unmount="false" + @on-data-saved="onSave" > <template #form="{ data, validate }"> <VnRow> From c4db8f781890c4f6ad1b7301d10f546708048b90 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Mon, 16 Dec 2024 08:41:28 +0100 Subject: [PATCH 060/142] feat: refs #8293 include zone data in each record --- src/pages/Claim/ClaimList.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index d561a69f7f6..f02bdf08317 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -10,6 +10,7 @@ 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'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -95,7 +96,12 @@ const columns = computed(() => [ optionLabel: 'description', }, }, - orderBy: 'priority', + orderBy: 'cs.priority', + }, + { + align: 'left', + label: t('claim.zone'), + name: 'zoneFk' }, { align: 'right', @@ -131,7 +137,7 @@ const STATE_COLOR = { <VnTable data-key="ClaimList" url="Claims/filter" - :order="['priority ASC', 'created ASC']" + :order="['cs.priority ASC', 'created ASC']" :columns="columns" redirect="claim" :right-search="false" @@ -148,6 +154,12 @@ const STATE_COLOR = { <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> From dadf3206e0dc12b4f14abed77cc52658e9629ae8 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Mon, 16 Dec 2024 08:42:37 +0100 Subject: [PATCH 061/142] feat: refs #8293 add zone filter --- src/pages/Claim/ClaimFilter.vue | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index f7e2ffbf670..c846cbbf931 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -129,6 +129,18 @@ defineExpose({ states }); outlined rounded /> + <VnSelect + :label="t('claim.zone')" + v-model="params.zoneFk" + @update:model-value="searchFn()" + url="Zones" + option-value="id" + option-label="name" + :use-like="false" + outlined + rounded + dense + ></VnSelect> <QCheckbox v-model="params.myTeam" :label="t('params.myTeam')" @@ -153,6 +165,7 @@ en: created: Created myTeam: My team itemFk: Item + zoneFk: Zone es: params: search: Contiene @@ -165,6 +178,7 @@ es: created: Creada myTeam: Mi equipo itemFk: Artículo + zoneFk: Zona Client Name: Nombre del cliente Salesperson: Comercial Item: Artículo From 5d744ca456e39708124e78c866d33efe14d71f06 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 09:41:50 +0100 Subject: [PATCH 062/142] feat: refs #8197 better leftMenu and VnCardMain improvements --- src/components/LeftMenu.vue | 25 ++++----- src/components/common/VnBreadcrumbs.vue | 2 +- src/components/common/VnCardMain.vue | 65 ++++++++++++++++++++--- src/pages/Account/AccountList.vue | 40 +++++--------- src/pages/Account/Role/AccountRoles.vue | 20 +++---- src/pages/Account/locale/en.yml | 2 +- src/router/modules/account.js | 10 ++-- src/router/modules/account/accountCard.js | 10 ++++ src/router/modules/account/roleCard.js | 3 ++ src/utils/getSections.js | 8 --- 10 files changed, 108 insertions(+), 77 deletions(-) delete mode 100644 src/utils/getSections.js diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index ab2931dfd29..eed2e192bed 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -92,13 +92,11 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - if (route.menus) { - const mainMenus = route.menus[props.source]; - const matches = findMatches(mainMenus, route); + if (!route?.meta?.menu) return; + const matches = findMatches(route.meta.menu, route); - for (const child of matches) { - navigation.addMenuItem(module, child, parent); - } + for (const child of matches) { + navigation.addMenuItem(module, child, parent); } } @@ -120,15 +118,14 @@ function getRoutes() { } if (props.source === 'card') { - const currentRoute = route.matched[1]; - const currentModule = toLowerCamel(currentRoute.name); - const moduleDef = routes.find( - (route) => toLowerCamel(route.name) === currentModule - ); + let menuRoute; + let index = route.matched.length - 1; - if (!moduleDef) return; - - addChildren(currentModule, moduleDef, items.value); + while (!menuRoute && index > 0) { + if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; + index--; + } + addChildren('', menuRoute, items.value); } } diff --git a/src/components/common/VnBreadcrumbs.vue b/src/components/common/VnBreadcrumbs.vue index 02226e4975a..334ab4d2115 100644 --- a/src/components/common/VnBreadcrumbs.vue +++ b/src/components/common/VnBreadcrumbs.vue @@ -15,7 +15,7 @@ let root = ref(null); watchEffect(() => { matched.value = currentRoute.value.matched.filter( - (matched) => Object.keys(matched.meta).length + (matched) => !!matched?.meta?.title || !!matched?.meta?.icon ); breadcrumbs.value.length = 0; if (!matched.value[0]) return; diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index ab664917ae7..b222748b9f7 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -2,25 +2,78 @@ import LeftMenu from '../LeftMenu.vue'; import { useStateStore } from 'stores/useStateStore'; import RightMenu from './RightMenu.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import VnTableFilter from '../VnTable/VnTableFilter.vue'; +import { onBeforeMount } from 'vue'; +import { useArrayData } from 'src/composables/useArrayData'; const stateStore = useStateStore(); -defineProps({ +const $props = defineProps({ section: { type: String, required: true, }, + dataKey: { + type: String, + default: null, + }, + searchBar: { + type: Boolean, + default: true, + }, + prefix: { + type: String, + default: null, + }, + rightFilter: { + type: Boolean, + default: true, + }, + columns: { + type: Array, + default: null, + }, + arrayDataProps: { + type: Object, + default: null, + }, +}); + +onBeforeMount(() => { + if ($props.dataKey) + useArrayData($props.dataKey, { + searchUrl: 'table', + ...$props.arrayDataProps, + }); }); </script> <template> - <slot name="searchbar" /> + <slot name="searchbar"> + <VnSearchbar + v-if="searchBar" + v-bind="arrayDataProps" + :data-key="dataKey" + :label="$t(`${prefix}.search`)" + :info="$t(`${prefix}.searchInfo`)" + /> + </slot> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> <LeftMenu v-if="section == $route.name" /> </Teleport> - <slot name="body" v-if="section == $route.name" /> - <RouterView v-else /> + <RightMenu> - <template #right-panel v-if="$slots['rightMenu']"> - <slot name="rightMenu" /> + <template #right-panel v-if="$slots['rightMenu'] || rightFilter"> + <slot name="rightMenu"> + <VnTableFilter + v-if="rightFilter && columns" + :data-key="dataKey" + :columns="columns" + /> + </slot> </template> </RightMenu> + + <slot name="body" v-if="section == $route.name" /> + <RouterView v-else /> </template> diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index ed2030d2964..34a653e61db 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,22 +1,17 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { ref, computed, onBeforeMount } from 'vue'; +import { computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnCardMain from 'src/components/common/VnCardMain.vue'; -import VnTableFilter from 'src/components/VnTable/VnTableFilter.vue'; -import { useArrayData } from 'src/composables/useArrayData'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); -const tableRef = ref(); const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; const dataKey = 'AccountList'; -const url = 'VnUsers/preview'; const columns = computed(() => [ { align: 'left', @@ -87,15 +82,6 @@ const columns = computed(() => [ }, ]); -onBeforeMount(() => { - useArrayData(dataKey, { - url, - userFilter: filter, - order: 'id DESC', - exprBuilder, - searchUrl: 'table', - }); -}); function exprBuilder(param, value) { switch (param) { case 'search': @@ -117,17 +103,20 @@ function exprBuilder(param, value) { </script> <template> - <VnCardMain :section="dataKey"> - <template #searchbar> - <VnSearchbar - :data-key="dataKey" - :label="t('account.search')" - :info="t('account.searchInfo')" - /> - </template> + <VnCardMain + :section="dataKey" + :data-key="dataKey" + :columns="columns" + prefix="account" + :array-data-props="{ + url: 'VnUsers/preview', + userFilter: filter, + order: 'id DESC', + exprBuilder, + }" + > <template #body> <VnTable - ref="tableRef" :data-key="dataKey" :columns="columns" default-mode="table" @@ -136,9 +125,6 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - <template #rightMenu> - <VnTableFilter :data-key="dataKey" :columns="columns" /> - </template> </VnCardMain> </template> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 9aebef64c5c..8cc392f1bb8 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -3,7 +3,6 @@ import { useI18n } from 'vue-i18n'; import { computed, ref } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import { useRoute } from 'vue-router'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; import VnCardMain from 'src/components/common/VnCardMain.vue'; @@ -86,21 +85,17 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain :section="dataKey"> - <template #searchbar> - <VnSearchbar - :url="url" - :data-key="dataKey" - :expr-builder="exprBuilder" - :label="t('role.searchRoles')" - :info="t('role.searchInfo')" - /> - </template> + <VnCardMain + :section="dataKey" + :data-key="dataKey" + :columns="columns" + prefix="role" + :array-data-props="{ url, exprBuilder, order: 'id ASC' }" + > <template #body> <VnTable ref="tableRef" :data-key="dataKey" - :url="url" :create="{ urlCreate: 'VnRoles', title: t('Create rol'), @@ -109,7 +104,6 @@ const exprBuilder = (param, value) => { editorFk: entityId, }, }" - order="id ASC" :disable-option="{ card: true }" :columns="columns" default-mode="table" diff --git a/src/pages/Account/locale/en.yml b/src/pages/Account/locale/en.yml index f2f563923c3..88a6b11e990 100644 --- a/src/pages/Account/locale/en.yml +++ b/src/pages/Account/locale/en.yml @@ -66,7 +66,7 @@ account: mailInputInfo: All emails will be forwarded to the specified address. role: newRole: New role - searchRoles: Search role + search: Search role searchInfo: Search role by id or name description: Description id: Id diff --git a/src/router/modules/account.js b/src/router/modules/account.js index ece0ab2bb89..2ee7c915d9d 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,7 +1,6 @@ import { RouterView } from 'vue-router'; import accountCard from './account/accountCard'; import roleCard from './account/roleCard'; -import getSections from 'src/utils/getSections'; export default { path: '/account', @@ -11,11 +10,7 @@ export default { icon: 'face', moduleName: 'Account', keyBinding: 'u', - }, - component: RouterView, - redirect: { name: 'AccountMain' }, - menus: { - main: [ + menu: [ 'AccountList', 'AccountAliasList', 'AccountRoles', @@ -25,8 +20,9 @@ export default { 'AccountAcls', 'AccountConnections', ], - card: getSections(accountCard.children), }, + component: RouterView, + redirect: { name: 'AccountMain' }, children: [ { path: '', diff --git a/src/router/modules/account/accountCard.js b/src/router/modules/account/accountCard.js index 0d8850f10aa..3ba687adfda 100644 --- a/src/router/modules/account/accountCard.js +++ b/src/router/modules/account/accountCard.js @@ -3,6 +3,16 @@ export default { path: ':id', redirect: { name: 'AccountSummary' }, component: () => import('src/pages/Account/Card/AccountCard.vue'), + meta: { + menu: [ + 'AccountBasicData', + 'AccountInheritedRoles', + 'AccountMailForwarding', + 'AccountMailAlias', + 'AccountPrivileges', + 'AccountLog', + ], + }, children: [ { name: 'AccountSummary', diff --git a/src/router/modules/account/roleCard.js b/src/router/modules/account/roleCard.js index 2a538756873..c36ce71b9da 100644 --- a/src/router/modules/account/roleCard.js +++ b/src/router/modules/account/roleCard.js @@ -3,6 +3,9 @@ export default { path: ':id', component: () => import('src/pages/Account/Role/Card/RoleCard.vue'), redirect: { name: 'RoleSummary' }, + meta: { + menu: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'], + }, children: [ { name: 'RoleSummary', diff --git a/src/utils/getSections.js b/src/utils/getSections.js deleted file mode 100644 index f70daf4685c..00000000000 --- a/src/utils/getSections.js +++ /dev/null @@ -1,8 +0,0 @@ -export default (sections) => { - const names = []; - for (const section of sections) { - if (section.path == 'summary') continue; - names.push(section.name); - } - return names; -}; From 18fd41b82bf8ddf8317aad06f912115985ed9531 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 09:47:46 +0100 Subject: [PATCH 063/142] refactor: refs #8197 adapt AccountAlias --- src/pages/Account/AccountAliasList.vue | 77 ++++++++++++------------- src/router/modules/account.js | 15 ++++- src/router/modules/account/aliasCard.js | 36 ++++++++++++ src/router/modules/index.js | 2 - src/router/modules/mailAlias.js | 57 ------------------ src/router/routes.js | 2 - 6 files changed, 85 insertions(+), 104 deletions(-) create mode 100644 src/router/modules/account/aliasCard.js delete mode 100644 src/router/modules/mailAlias.js diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index c6728329747..721a009e512 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -2,21 +2,12 @@ import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; -import { useStateStore } from 'stores/useStateStore'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; const tableRef = ref(); const { t } = useI18n(); -const stateStore = useStateStore(); +const dataKey = 'AccountAliasList'; -const exprBuilder = (param, value) => { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? { id: value } - : { alias: { like: `%${value}%` } }; - } -}; const columns = computed(() => [ { align: 'left', @@ -40,40 +31,46 @@ const columns = computed(() => [ create: true, }, ]); + +const exprBuilder = (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? { id: value } + : { alias: { like: `%${value}%` } }; + } +}; </script> <template> - <template v-if="stateStore.isHeaderMounted()"> - <Teleport to="#searchbar"> - <VnSearchbar - data-key="AccountAliasList" - url="MailAliases" - :expr-builder="exprBuilder" - :label="t('mailAlias.search')" - :info="t('mailAlias.searchInfo')" - /> - </Teleport> - </template> - <VnTable - ref="tableRef" - data-key="AccountAliasList" - url="MailAliases" - :create="{ - urlCreate: 'MailAliases', - title: 'Create MailAlias', - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: {}, - }" - order="id DESC" + <VnCardMain + :section="dataKey" + :data-key="dataKey" :columns="columns" - :disable-option="{ card: true }" - default-mode="table" - redirect="account/alias" - :is-editable="true" - :use-model="true" - /> + prefix="mailAlias" + :array-data-props="{ url: 'MailAliases', order: 'id DESC', exprBuilder }" + > + <template #body> + <VnTable + :data-key="dataKey" + ref="tableRef" + :create="{ + urlCreate: 'MailAliases', + title: 'Create MailAlias', + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + }" + :columns="columns" + :disable-option="{ card: true }" + default-mode="table" + redirect="account/alias" + :is-editable="true" + :use-model="true" + :right-search="false" + /> + </template> + </VnCardMain> </template> - <i18n> es: Id: Id diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 2ee7c915d9d..6f5ca90f3cf 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -1,6 +1,7 @@ import { RouterView } from 'vue-router'; import accountCard from './account/accountCard'; import roleCard from './account/roleCard'; +import aliasCard from './account/aliasCard'; export default { path: '/account', @@ -12,8 +13,8 @@ export default { keyBinding: 'u', menu: [ 'AccountList', - 'AccountAliasList', 'AccountRoles', + 'AccountAlias', 'AccountAccounts', 'AccountLdap', 'AccountSamba', @@ -65,13 +66,21 @@ export default { ], }, { - path: 'alias-list', - name: 'AccountAliasList', + path: 'alias', + name: 'AccountAlias', + redirect: { name: 'AccountAliasList' }, meta: { title: 'alias', icon: 'email', }, component: () => import('src/pages/Account/AccountAliasList.vue'), + children: [ + { + name: 'AccountAliasList', + path: 'list', + }, + aliasCard, + ], }, { path: 'acls', diff --git a/src/router/modules/account/aliasCard.js b/src/router/modules/account/aliasCard.js new file mode 100644 index 00000000000..cbbd31e5103 --- /dev/null +++ b/src/router/modules/account/aliasCard.js @@ -0,0 +1,36 @@ +export default { + name: 'AliasCard', + path: ':id', + component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), + redirect: { name: 'AliasSummary' }, + meta: { menu: ['AliasBasicData', 'AliasUsers'] }, + children: [ + { + name: 'AliasSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Account/Alias/Card/AliasSummary.vue'), + }, + { + name: 'AliasBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => import('src/pages/Account/Alias/Card/AliasBasicData.vue'), + }, + { + name: 'AliasUsers', + path: 'users', + meta: { + title: 'aliasUsers', + icon: 'group', + }, + component: () => import('src/pages/Account/Alias/Card/AliasUsers.vue'), + }, + ], +}; diff --git a/src/router/modules/index.js b/src/router/modules/index.js index 77076d04a20..f28fed1c2c3 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -20,7 +20,6 @@ import ItemType from './itemType'; import Zone from './zone'; import Account from './account'; import Monitor from './monitor'; -import MailAlias from './mailAlias'; export default [ Item, @@ -44,6 +43,5 @@ export default [ ItemType, Zone, Account, - MailAlias, Monitor, ]; diff --git a/src/router/modules/mailAlias.js b/src/router/modules/mailAlias.js deleted file mode 100644 index 8e0f8abdcb9..00000000000 --- a/src/router/modules/mailAlias.js +++ /dev/null @@ -1,57 +0,0 @@ -import { RouterView } from 'vue-router'; - -export default { - path: 'account/alias', - name: 'Alias', - meta: { - title: 'alias', - icon: 'email', - moduleName: 'Alias', - }, - component: RouterView, - redirect: { name: 'AccountAliasList' }, - menus: { - main: [], - card: ['AliasBasicData', 'AliasUsers'], - }, - children: [ - { - name: 'AliasCard', - path: ':id', - component: () => import('src/pages/Account/Alias/Card/AliasCard.vue'), - redirect: { name: 'AliasSummary' }, - children: [ - { - name: 'AliasSummary', - path: 'summary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasSummary.vue'), - }, - { - name: 'AliasBasicData', - path: 'basic-data', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasBasicData.vue'), - }, - { - name: 'AliasUsers', - path: 'users', - meta: { - title: 'aliasUsers', - icon: 'group', - }, - component: () => - import('src/pages/Account/Alias/Card/AliasUsers.vue'), - }, - ], - }, - ], -}; diff --git a/src/router/routes.js b/src/router/routes.js index 131021c9a23..b9120f8c414 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -20,7 +20,6 @@ import agency from 'src/router/modules/agency'; import zone from 'src/router/modules/zone'; import account from './modules/account'; import monitor from 'src/router/modules/monitor'; -import mailAlias from './modules/mailAlias'; const routes = [ { @@ -94,7 +93,6 @@ const routes = [ ItemType, zone, account, - mailAlias, { path: '/:catchAll(.*)*', name: 'NotFound', From fb8997b3df17f0842dc4cbd6e7040cd29139bd66 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 16 Dec 2024 09:52:40 +0100 Subject: [PATCH 064/142] fix: refs #8004 update label for daysOnward in TravelFilter component and add translations --- src/pages/Travel/TravelFilter.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index c5ce649552c..d27dbcd4667 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -125,7 +125,7 @@ defineExpose({ states }); is-outlined /> <VnInput - :label="t('travel.travelList.tableVisibleColumns.daysOnward')" + :label="t('travel.daysOnward')" v-model="params.daysOnward" lazy-rules is-outlined @@ -148,6 +148,7 @@ en: landed: Landed landingHour: Landing Hour totalEntries: Σ + daysOnward: Days Onward es: travel: Id: Id @@ -160,4 +161,5 @@ es: landed: F.Entrega landingHour: Hora de entrega totalEntries: Σ + daysOnward: Días en adelante </i18n> From 1e76d5fd3fb45bba11060ea9ab0898e2fc4fa79d Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Mon, 16 Dec 2024 12:03:57 +0100 Subject: [PATCH 065/142] refactor: ignore params when searching by id on searchbar --- src/components/ui/VnSearchbar.vue | 19 +++++++++++++------ src/pages/Order/Card/OrderCatalog.vue | 3 ++- src/pages/Zone/Card/ZoneLocationsTree.vue | 8 +++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index da2d370fe09..92babfcc6fc 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -51,10 +51,6 @@ const props = defineProps({ type: Object, default: null, }, - staticParams: { - type: Array, - default: () => [], - }, exprBuilder: { type: Function, default: null, @@ -67,6 +63,10 @@ const props = defineProps({ type: Function, default: undefined, }, + searchRemoveParams: { + type: Boolean, + default: true, + }, }); const searchText = ref(); @@ -105,12 +105,18 @@ async function search() { const filter = { params: { - ...Object.fromEntries(staticParams), search: searchText.value, }, - ...{ filter: props.filter }, + filter: props.filter, }; + if (!props.searchRemoveParams || !searchText.value) { + filter.params = { + ...Object.fromEntries(staticParams), + search: searchText.value, + }; + } + if (props.whereFilter) { filter.filter = { where: props.whereFilter(searchText.value), @@ -130,6 +136,7 @@ async function search() { dense standout autofocus + data-cy="vnSearchBar" > <template #prepend> <QIcon diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index 948970cc3c3..744f87297ac 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -1,7 +1,7 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; import { useRoute, useRouter } from 'vue-router'; -import { onMounted, onUnmounted, ref, computed, watch, provide, nextTick } from 'vue'; +import { onMounted, onUnmounted, ref, computed, watch, provide } from 'vue'; import axios from 'axios'; import { useI18n } from 'vue-i18n'; import VnPaginate from 'src/components/ui/VnPaginate.vue'; @@ -101,6 +101,7 @@ provide('onItemSaved', onItemSaved); url="Orders/CatalogFilter" :label="t('Search items')" :info="t('You can search items by name or id')" + :search-remove-params="false" /> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QScrollArea class="fit text-grey-8"> diff --git a/src/pages/Zone/Card/ZoneLocationsTree.vue b/src/pages/Zone/Card/ZoneLocationsTree.vue index 650047e40c7..5c87faf999e 100644 --- a/src/pages/Zone/Card/ZoneLocationsTree.vue +++ b/src/pages/Zone/Card/ZoneLocationsTree.vue @@ -163,7 +163,13 @@ onUnmounted(() => { <QBtn color="primary" icon="search" dense flat @click="reFetch()" /> </template> </VnInput> - <VnSearchbar v-if="!showSearchBar" :data-key="datakey" :url="url" :redirect="false" /> + <VnSearchbar + v-if="!showSearchBar" + :data-key="datakey" + :url="url" + :redirect="false" + :search-remove-params="false" + /> <QTree ref="treeRef" :nodes="nodes" From 0ab12d7b5d9b964fae83e64ab84367eebd473bbf Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 12:26:41 +0100 Subject: [PATCH 066/142] fix: refs #8197 vnTableFilter in vnTable --- src/components/VnTable/VnTable.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 521c19d3243..5f073fb65ec 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -305,11 +305,17 @@ function handleSelection({ evt, added, rows: selectedRows }, rows) { } </script> <template> - <VnTableFilter + <QDrawer v-if="$props.rightSearch" - :data-key="$attrs['data-key']" - :columns="columns" - /> + v-model="stateStore.rightDrawer" + side="right" + :width="256" + show-if-above + > + <QScrollArea class="fit"> + <VnTableFilter :data-key="$attrs['data-key']" :columns="columns" /> + </QScrollArea> + </QDrawer> <CrudModel v-bind="$attrs" :class="$attrs['class'] ?? 'q-px-md'" From 95420e96d1681f4838f584a9c60a1112cd29744f Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 12:27:13 +0100 Subject: [PATCH 067/142] refactor: refs #8197 adapt AccountAcls to VnCardMain --- src/pages/Account/AccountAcls.vue | 60 ++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index d80f835ecc2..73771a34107 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -1,16 +1,15 @@ <script setup> import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; -import { useStateStore } from 'stores/useStateStore'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; import { useQuasar } from 'quasar'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; import FetchData from 'src/components/FetchData.vue'; import { useValidator } from 'src/composables/useValidator'; +import VnCardMain from 'src/components/common/VnCardMain.vue'; defineProps({ id: { @@ -21,13 +20,13 @@ defineProps({ const { notify } = useNotify(); const { t } = useI18n(); -const stateStore = useStateStore(); const quasar = useQuasar(); const tableRef = ref(); const roles = ref(); const validationsStore = useValidator(); const { models } = validationsStore; +const dataKey = 'AccountAcls'; const exprBuilder = (param, value) => { switch (param) { case 'search': @@ -134,38 +133,41 @@ const deleteAcl = async ({ id }) => { </script> <template> - <VnSearchbar - data-key="AccountAcls" - url="ACLs" - :expr-builder="exprBuilder" - :label="t('acls.search')" - :info="t('acls.searchInfo')" - /> - <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> - </QDrawer> <FetchData url="VnRoles?fields=['name']" auto-load @on-fetch="(data) => (roles = data)" /> - <VnTable - ref="tableRef" - data-key="AccountAcls" - :url="`ACLs`" - :create="{ - urlCreate: 'ACLs', - title: 'Create ACL', - onDataSaved: () => tableRef.reload(), - formInitialData: {}, - }" - order="id DESC" - :disable-option="{ card: true }" + <VnCardMain + :section="dataKey" + :data-key="dataKey" :columns="columns" - default-mode="table" - :right-search="true" - :is-editable="true" - :use-model="true" - /> + prefix="acls" + :array-data-props="{ + url: 'ACLs', + order: 'id DESC', + exprBuilder, + }" + > + <template #body> + <VnTable + ref="tableRef" + data-key="AccountAcls" + :create="{ + urlCreate: 'ACLs', + title: 'Create ACL', + onDataSaved: () => tableRef.reload(), + formInitialData: {}, + }" + :disable-option="{ card: true }" + :columns="columns" + default-mode="table" + :right-search="false" + :is-editable="true" + :use-model="true" + /> + </template> + </VnCardMain> </template> <i18n> From 331c1bc51d9c37c28f5b046d06cf77aa1a7321a0 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 16 Dec 2024 12:51:51 +0100 Subject: [PATCH 068/142] refactor: refs #8004 replace VnSelect with VnSelectWorker in CustomerList component --- src/pages/Customer/CustomerList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index c0005d49e8e..55c00e918c3 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -448,7 +448,7 @@ function handleLocation(data, location) { </QItemSection> </QItem> </template> - </VnSelect> + </VnSelectWorker> <VnLocation :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]" v-model="data.location" From bef0f25e88772bb85e636b388db395a87396cfad Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 16 Dec 2024 13:28:49 +0100 Subject: [PATCH 069/142] test: refs #8004 remove only modifier from 'Client list create new client' test case in ticketList.spec.js --- test/cypress/integration/ticket/ticketList.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index c1d1a065578..b30b4cdad45 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -37,7 +37,7 @@ describe('TicketList', () => { cy.dataCy('ticketSummary').should('exist'); }); - it.only('Client list create new client', () => { + it('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); cy.dataCy('vnTableCreateBtn').click(); const data = { From dc665d43fe2da557a056e8e1feb5e21e9fcd481c Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 13:33:59 +0100 Subject: [PATCH 070/142] feat: refs #8197 default leftMenu --- src/components/common/VnCardMain.vue | 7 ------- src/components/common/VnSectionMain.vue | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index b222748b9f7..5b8e6b5e87a 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -1,12 +1,9 @@ <script setup> -import LeftMenu from '../LeftMenu.vue'; -import { useStateStore } from 'stores/useStateStore'; import RightMenu from './RightMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; import { onBeforeMount } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; -const stateStore = useStateStore(); const $props = defineProps({ section: { @@ -58,10 +55,6 @@ onBeforeMount(() => { /> </slot> - <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> - <LeftMenu v-if="section == $route.name" /> - </Teleport> - <RightMenu> <template #right-panel v-if="$slots['rightMenu'] || rightFilter"> <slot name="rightMenu"> diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index c1b9808b5cf..4e800fa8aaa 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -1,7 +1,8 @@ <script setup> import { useStateStore } from 'stores/useStateStore'; -import { onMounted } from 'vue'; +import { onMounted, ref } from 'vue'; import { useQuasar } from 'quasar'; +import LeftMenu from '../LeftMenu.vue'; const stateStore = useStateStore(); const $props = defineProps({ @@ -13,12 +14,31 @@ const $props = defineProps({ onMounted( () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) ); + +const targetId = 'left-panel'; +const teleportRef = ref({}); +const hasContent = ref(); +let observer; + +onMounted(() => { + if (teleportRef.value) { + const checkContent = () => { + hasContent.value = teleportRef.value.innerHTML.trim() !== ''; + }; + + observer = new MutationObserver(checkContent); + observer.observe(teleportRef.value, { childList: true, subtree: true }); + + checkContent(); + } +}); </script> <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <div id="left-panel"></div> + <div :id="targetId" ref="teleportRef"></div> + <LeftMenu v-if="!hasContent" /> </QScrollArea> </QDrawer> <QPageContainer> From 2985583353af48889ced7f861f41ebd1406ccd42 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Mon, 16 Dec 2024 13:44:09 +0100 Subject: [PATCH 071/142] style: refs #8004 update layout and styling in FetchedTags and ItemList components --- src/components/ui/FetchedTags.vue | 6 +++--- src/pages/Item/ItemList.vue | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ui/FetchedTags.vue b/src/components/ui/FetchedTags.vue index 481e3a475a0..6e159087c27 100644 --- a/src/components/ui/FetchedTags.vue +++ b/src/components/ui/FetchedTags.vue @@ -61,16 +61,17 @@ const columnStyle = computed(() => { </div> </div> </template> - <style lang="scss" scoped> .fetchedTags { align-items: center; .wrap { - flex-wrap: wrap; display: grid; } .inline-tag { + display: flex; + align-items: center; + justify-content: center; height: 1rem; margin: 0.05rem; color: var(--vn-label-color); @@ -85,7 +86,6 @@ const columnStyle = computed(() => { } .text { - vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/src/pages/Item/ItemList.vue b/src/pages/Item/ItemList.vue index dbbd11ce111..4aa3b13fe41 100644 --- a/src/pages/Item/ItemList.vue +++ b/src/pages/Item/ItemList.vue @@ -370,6 +370,7 @@ const columns = computed(() => [ .subName { text-transform: uppercase; color: var(--vn-label-color); + font-size: small; } </style> <i18n> From dc83d50e96e1882b98499eb5addc1acbf55ad998 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 16 Dec 2024 14:30:23 +0100 Subject: [PATCH 072/142] refactor: refs #8197 backward compatible --- src/components/LeftMenu.vue | 31 ++++++++--- src/components/common/VnCard.vue | 45 +++++++++++---- src/components/common/VnCardBeta.vue | 65 ++++++++++++++++++++++ src/pages/Account/Alias/Card/AliasCard.vue | 4 +- src/pages/Account/Card/AccountCard.vue | 4 +- src/pages/Account/Role/Card/RoleCard.vue | 4 +- 6 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 src/components/common/VnCardBeta.vue diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index eed2e192bed..09e126213fd 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -92,8 +92,10 @@ function findMatches(search, item) { } function addChildren(module, route, parent) { - if (!route?.meta?.menu) return; - const matches = findMatches(route.meta.menu, route); + const menus = route?.meta?.menu ?? route?.menus?.[props.source]; //backwards compatible + if (!menus) return; + + const matches = findMatches(menus, route); for (const child of matches) { navigation.addMenuItem(module, child, parent); @@ -118,17 +120,28 @@ function getRoutes() { } if (props.source === 'card') { - let menuRoute; - let index = route.matched.length - 1; + const currentRoute = route.matched[1]; + const currentModule = toLowerCamel(currentRoute.name); + let moduleDef = routes.find( + (route) => toLowerCamel(route.name) === currentModule + ); - while (!menuRoute && index > 0) { - if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; - index--; - } - addChildren('', menuRoute, items.value); + if (!moduleDef) return; + if (!moduleDef?.menus) moduleDef = betaGetRoutes(); + addChildren(currentModule, moduleDef, items.value); } } +function betaGetRoutes() { + let menuRoute; + let index = route.matched.length - 1; + while (!menuRoute && index > 0) { + if (route.matched[index]?.meta?.menu) menuRoute = route.matched[index]; + index--; + } + return menuRoute; +} + async function togglePinned(item, event) { if (event.defaultPrevented) return; event.preventDefault(); diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 16a077a79f1..0d80f43ce94 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -4,7 +4,10 @@ import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; +import VnSubToolbar from '../ui/VnSubToolbar.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; import LeftMenu from 'components/LeftMenu.vue'; +import RightMenu from 'components/common/RightMenu.vue'; const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, @@ -26,7 +29,10 @@ const url = computed(() => { } return props.customUrl; }); - +const searchRightDataKey = computed(() => { + if (!props.searchDataKey) return route.name; + return props.searchDataKey; +}); const arrayData = useArrayData(props.dataKey, { url: url.value, filter: props.filter, @@ -53,13 +59,32 @@ if (props.baseUrl) { } </script> <template> - <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> - <component :is="descriptor" /> - <QSeparator /> - <LeftMenu source="card" /> - </Teleport> - <VnSubToolbar /> - <div :class="[useCardSize(), $attrs.class]"> - <RouterView :key="route.path" /> - </div> + <QDrawer + v-model="stateStore.leftDrawer" + show-if-above + :width="256" + v-if="stateStore.isHeaderMounted()" + > + <QScrollArea class="fit"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </QScrollArea> + </QDrawer> + <slot name="searchbar" v-if="props.searchDataKey"> + <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> + </slot> + <RightMenu> + <template #right-panel v-if="props.filterPanel"> + <component :is="props.filterPanel" :data-key="searchRightDataKey" /> + </template> + </RightMenu> + <QPageContainer> + <QPage> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> + </QPage> + </QPageContainer> </template> diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue new file mode 100644 index 00000000000..16a077a79f1 --- /dev/null +++ b/src/components/common/VnCardBeta.vue @@ -0,0 +1,65 @@ +<script setup> +import { onBeforeMount, computed } from 'vue'; +import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'; +import { useArrayData } from 'src/composables/useArrayData'; +import { useStateStore } from 'stores/useStateStore'; +import useCardSize from 'src/composables/useCardSize'; +import LeftMenu from 'components/LeftMenu.vue'; +const props = defineProps({ + dataKey: { type: String, required: true }, + baseUrl: { type: String, default: undefined }, + customUrl: { type: String, default: undefined }, + filter: { type: Object, default: () => {} }, + descriptor: { type: Object, required: true }, + filterPanel: { type: Object, default: undefined }, + searchDataKey: { type: String, default: undefined }, + searchbarProps: { type: Object, default: undefined }, + redirectOnError: { type: Boolean, default: false }, +}); + +const stateStore = useStateStore(); +const route = useRoute(); +const router = useRouter(); +const url = computed(() => { + if (props.baseUrl) { + return `${props.baseUrl}/${route.params.id}`; + } + return props.customUrl; +}); + +const arrayData = useArrayData(props.dataKey, { + url: url.value, + filter: props.filter, +}); + +onBeforeMount(async () => { + try { + if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; + await arrayData.fetch({ append: false, updateRouter: false }); + } catch { + const { matched: matches } = router.currentRoute.value; + const { path } = matches.at(-1); + router.push({ path: path.replace(/:id.*/, '') }); + } +}); + +if (props.baseUrl) { + onBeforeRouteUpdate(async (to, from) => { + if (to.params.id !== from.params.id) { + arrayData.store.url = `${props.baseUrl}/${to.params.id}`; + await arrayData.fetch({ append: false, updateRouter: false }); + } + }); +} +</script> +<template> + <Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()"> + <component :is="descriptor" /> + <QSeparator /> + <LeftMenu source="card" /> + </Teleport> + <VnSubToolbar /> + <div :class="[useCardSize(), $attrs.class]"> + <RouterView :key="route.path" /> + </div> +</template> diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue index 65951b3bf61..3a814edc051 100644 --- a/src/pages/Account/Alias/Card/AliasCard.vue +++ b/src/pages/Account/Alias/Card/AliasCard.vue @@ -1,12 +1,12 @@ <script setup> import { useI18n } from 'vue-i18n'; -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import AliasDescriptor from './AliasDescriptor.vue'; const { t } = useI18n(); </script> <template> - <VnCard + <VnCardBeta data-key="Alias" base-url="MailAliases" :descriptor="AliasDescriptor" diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index ba9040852cf..35ff7e73229 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -1,8 +1,8 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import AccountDescriptor from './AccountDescriptor.vue'; </script> <template> - <VnCard data-key="AccountId" :descriptor="AccountDescriptor" /> + <VnCardBeta data-key="AccountId" :descriptor="AccountDescriptor" /> </template> diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue index da6ac61d87a..7664deca8da 100644 --- a/src/pages/Account/Role/Card/RoleCard.vue +++ b/src/pages/Account/Role/Card/RoleCard.vue @@ -1,7 +1,7 @@ <script setup> -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import RoleDescriptor from './RoleDescriptor.vue'; </script> <template> - <VnCard data-key="Role" :descriptor="RoleDescriptor" /> + <VnCardBeta data-key="Role" :descriptor="RoleDescriptor" /> </template> From fe1d9e93a33d2027f7922402bedd50ce3c0ea843 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Tue, 17 Dec 2024 06:27:03 +0100 Subject: [PATCH 073/142] refactor: refs #8201 added onMounted to stablish the value to show icons --- src/pages/Claim/Card/ClaimDevelopment.vue | 14 +------------- src/pages/Customer/Card/CustomerDescriptor.vue | 12 +++++++++--- src/pages/Department/Card/DepartmentBasicData.vue | 2 +- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/pages/Claim/Card/ClaimDevelopment.vue b/src/pages/Claim/Card/ClaimDevelopment.vue index e288d861450..d17c6b4e6a5 100644 --- a/src/pages/Claim/Card/ClaimDevelopment.vue +++ b/src/pages/Claim/Card/ClaimDevelopment.vue @@ -164,19 +164,7 @@ const columns = computed(() => [ :autofocus="col.tabIndex == 1" input-debounce="0" hide-selected - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel>{{ scope.opt?.name }}</QItemLabel> - <QItemLabel caption> - {{ scope.opt?.nickname }} - {{ scope.opt?.code }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelectWorker> + /> <VnSelect v-else v-model="row[col.model]" diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 98e53d568a2..a17b7c7961f 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed } from 'vue'; +import { ref, computed, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; @@ -14,7 +14,13 @@ import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import { useState } from 'src/composables/useState'; const state = useState(); -const customer = computed(() => state.get('customer')); +const customer = ref(); + +onMounted(async () => { + console.log('state.get(customer): ', state.get('customer')); + customer.value = state.get('customer'); + if (customer.value) customer.value.webAccess = data.value?.account?.isActive; +}); const $props = defineProps({ id: { @@ -38,7 +44,6 @@ const entityId = computed(() => { const data = ref(useCardDescription()); const setData = (entity) => { data.value = useCardDescription(entity?.name, entity?.id); - if (customer.value) customer.value.webAccess = data.value?.account?.isActive; }; const debtWarning = computed(() => { return customer.value?.debt > customer.value?.credit ? 'negative' : 'primary'; @@ -94,6 +99,7 @@ const debtWarning = computed(() => { /> </template> <template #icons> + {{ console.log('customer: ', customer) }} <QCardActions v-if="customer" class="q-gutter-x-md"> <QIcon v-if="!customer.isActive" diff --git a/src/pages/Department/Card/DepartmentBasicData.vue b/src/pages/Department/Card/DepartmentBasicData.vue index 22ce06821ee..b13aed2d3f4 100644 --- a/src/pages/Department/Card/DepartmentBasicData.vue +++ b/src/pages/Department/Card/DepartmentBasicData.vue @@ -52,7 +52,7 @@ const { t } = useI18n(); <VnSelectWorker :label="t('department.bossDepartment')" v-model="data.workerFk" - :rules="validate('department.workerFk')" + :rules="validate('department.bossDepartment')" /> <VnSelect :label="t('department.selfConsumptionCustomer')" From 14ca6d73f1a992bdbfe7134e6fac5f73cdfa9fe2 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Tue, 17 Dec 2024 07:36:16 +0100 Subject: [PATCH 074/142] feat: refs #7074 tests for fns setData(), parseDms() and showFormDialog() --- .../components/common/VnDmsList.spec.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 test/vitest/__tests__/components/common/VnDmsList.spec.js diff --git a/test/vitest/__tests__/components/common/VnDmsList.spec.js b/test/vitest/__tests__/components/common/VnDmsList.spec.js new file mode 100644 index 00000000000..49228ddf8fc --- /dev/null +++ b/test/vitest/__tests__/components/common/VnDmsList.spec.js @@ -0,0 +1,93 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnDmsList from 'src/components/common/VnDmsList.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('VnDmsList', () => { + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vm = createWrapper(VnDmsList, { + props: { + model: 'WorkerDms/1110/filter', + defaultDmsCode: 'hhrrData', + filter: 'wd.workerFk', + updateModel: 'Workers', + deleteModel: 'WorkerDms', + downloadModel: 'WorkerDms' + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('setData()', () => { + const data = [ + { + userFk: 1, + name: 'Jessica', + lastName: 'Jones', + file: '4.jpg', + created: '2021-07-28 21:00:00' + }, + { + userFk: 2, + name: 'Bruce', + lastName: 'Banner', + created: '2022-07-28 21:00:00', + dms: { + userFk: 2, + name: 'Bruce', + lastName: 'BannerDMS', + created: '2022-07-28 21:00:00', + file: '4.jpg', + } + }, + { + userFk: 3, + name: 'Natasha', + lastName: 'Romanoff', + file: '4.jpg', + created: '2021-10-28 21:00:00' + } + ] + + it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => { + vm.setData(data); + expect([vm.rows][0][0].lastName).toEqual('BannerDMS'); + expect([vm.rows][0][1].lastName).toEqual('Romanoff'); + + }); + }); + + describe('parseDms()', () => { + const dms = { + userFk: 1, + name: 'DMS 1' + }; + + const resultDms = { ...dms, userId:1}; + + it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { + const parsedDms = vm.parseDms(dms); + expect(parsedDms).toEqual(resultDms); + }); + }); + + describe('showFormDialog()', () => { + const dms = { + userFk: 1, + name: 'DMS 1' + }; + + const resultDms = { ...dms, userId:1}; + + it('should call fn parseDms() and set show true if dms is defined', () => { + vm.showFormDialog(dms); + expect(vm.formDialog.show).toEqual(true); + expect(vm.formDialog.dms).toEqual(resultDms); + }); + }); +}); \ No newline at end of file From 586d5eff3e5a6e34aa9fac9de09958ebe6888bed Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 17 Dec 2024 09:05:22 +0100 Subject: [PATCH 075/142] chore: refs #8197 unnecessary file --- src/utils/getUserParams.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/utils/getUserParams.js diff --git a/src/utils/getUserParams.js b/src/utils/getUserParams.js deleted file mode 100644 index e69de29bb2d..00000000000 From a33cec4a34091ddc32dcac132104e7dc3b2f63e3 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 11:12:21 +0100 Subject: [PATCH 076/142] feat: refs #7936 add optionCaption --- src/components/common/VnSelect.vue | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index dc8fd582ed6..ed21b089b92 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'; import { useArrayData } from 'src/composables/useArrayData'; import { useRequired } from 'src/composables/useRequired'; import dataByOrder from 'src/utils/dataByOrder'; +import { QItemLabel } from 'quasar'; const emit = defineEmits(['update:modelValue', 'update:options', 'remove']); const $attrs = useAttrs(); @@ -33,6 +34,10 @@ const $props = defineProps({ type: String, default: 'id', }, + optionCaption: { + type: String, + default: null, + }, optionFilter: { type: String, default: null, @@ -373,13 +378,16 @@ function handleKeyDown(event) { </template> <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> - <QItemSection> - <QItemLabel v-if="opt[optionValue] == opt[optionLabel]">{{ - opt[optionLabel] - }}</QItemLabel> - <QItemLabel v-else>{{ - `#${opt[optionValue]} - ${opt[optionLabel]}` - }}</QItemLabel> + <QItemSection v-if="opt[optionValue] == opt[optionLabel]"> + <QItemLabel>{{ opt[optionLabel] }}</QItemLabel> + </QItemSection> + <QItemSection v-else> + <QItemLabel> + {{ opt[optionLabel] }} + </QItemLabel> + <QItemLabel caption v-if="optionCaption !== false"> + {{ `#${opt[optionCaption] || opt[optionValue]}` }} + </QItemLabel> </QItemSection> </QItem> </template> From ad9063704c322fd73eed4f4e9382bfe28c466107 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 11:54:12 +0100 Subject: [PATCH 077/142] feat: refs #7936 enhance downloadFile function to support opening in a new tab --- src/composables/downloadFile.js | 19 ++++++++++++++----- .../InvoiceIn/Card/InvoiceInDescriptor.vue | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/composables/downloadFile.js b/src/composables/downloadFile.js index 4588265a2fd..8c9e21a4fe7 100644 --- a/src/composables/downloadFile.js +++ b/src/composables/downloadFile.js @@ -2,16 +2,25 @@ import { useSession } from 'src/composables/useSession'; import { getUrl } from './getUrl'; import axios from 'axios'; import { exportFile } from 'quasar'; +import useOpenURL from './useOpenURL'; const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); -export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { +export async function downloadFile( + id, + model = 'dms', + urlPath = '/downloadFile', + url, + newTab = true +) { const appUrl = (await getUrl('', 'lilium')).replace('/#/', ''); - const response = await axios.get( - url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, - { responseType: 'blob' } - ); + const targetUrl = + url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`; + + if (newTab) return useOpenURL(targetUrl); + + const response = await axios.get(targetUrl, { responseType: 'blob' }); const contentDisposition = response.headers['content-disposition']; const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue index f2c6f93ef2f..cb8a45833aa 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -206,7 +206,8 @@ const isAgricultural = () => { }; function showPdfInvoice() { - if (isAgricultural()) openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`); + if (isAgricultural()) + openReport(`InvoiceIns/${entityId.value}/invoice-in-pdf`, null, '_blank'); } function sendPdfInvoiceConfirmation() { From 22daf36c6cd5a2ebea2e4aa28b4953805e023b62 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 11:58:57 +0100 Subject: [PATCH 078/142] fix: refs #7936 rollback --- src/composables/downloadFile.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/composables/downloadFile.js b/src/composables/downloadFile.js index 8c9e21a4fe7..4588265a2fd 100644 --- a/src/composables/downloadFile.js +++ b/src/composables/downloadFile.js @@ -2,25 +2,16 @@ import { useSession } from 'src/composables/useSession'; import { getUrl } from './getUrl'; import axios from 'axios'; import { exportFile } from 'quasar'; -import useOpenURL from './useOpenURL'; const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); -export async function downloadFile( - id, - model = 'dms', - urlPath = '/downloadFile', - url, - newTab = true -) { +export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { const appUrl = (await getUrl('', 'lilium')).replace('/#/', ''); - const targetUrl = - url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`; - - if (newTab) return useOpenURL(targetUrl); - - const response = await axios.get(targetUrl, { responseType: 'blob' }); + const response = await axios.get( + url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, + { responseType: 'blob' } + ); const contentDisposition = response.headers['content-disposition']; const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); From 4ab3934da37ce04218cff749a51ace8251ad02f0 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Tue, 17 Dec 2024 12:19:35 +0100 Subject: [PATCH 079/142] fix: hotfix mix --- src/pages/Item/ItemFixedPrice.vue | 358 +++++++++++----------- src/pages/Supplier/SupplierListFilter.vue | 4 +- src/pages/Travel/Card/TravelSummary.vue | 27 +- src/pages/Travel/TravelFilter.vue | 4 +- src/pages/Travel/TravelList.vue | 14 +- 5 files changed, 203 insertions(+), 204 deletions(-) diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index 09fccfd6d21..f44237020e1 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -361,7 +361,7 @@ function handleOnDataSave({ CrudModelRef }) { @on-fetch="(data) => (warehousesOptions = data)" auto-load url="Warehouses" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" /> <RightMenu> <template #right-panel> @@ -394,191 +394,185 @@ function handleOnDataSave({ CrudModelRef }) { /> </template> </VnSubToolbar> - <QPage> - <VnTable - @on-fetch=" - (data) => - data.forEach((item) => { - item.hasMinPrice = `${item.hasMinPrice !== 0}`; - }) - " - :default-remove="false" - :default-reset="false" - :default-save="false" - data-key="ItemFixedPrices" - url="FixedPrices/filter" - :order="['itemFk DESC', 'name DESC']" - save-url="FixedPrices/crud" - ref="tableRef" - dense - :filter="{ - where: { - warehouseFk: user.warehouseFk, - }, - }" - :columns="columns" - default-mode="table" - auto-load - :is-editable="true" - :right-search="false" - :table="{ - 'row-key': 'id', - selection: 'multiple', - }" - :crud-model="{ - disableInfiniteScroll: true, - }" - v-model:selected="rowsSelected" - :create-as-dialog="false" - :create="{ - onDataSaved: handleOnDataSave, - }" - :use-model="true" - :disable-option="{ card: true }" - > - <template #header-selection="scope"> - <QCheckbox v-model="scope.selected" /> - </template> - <template #body-selection="scope"> - {{ scope }} - <QCheckbox flat v-model="scope.selected" /> - </template> + <VnTable + @on-fetch=" + (data) => + data.forEach((item) => { + item.hasMinPrice = `${item.hasMinPrice !== 0}`; + }) + " + :default-remove="false" + :default-reset="false" + :default-save="false" + data-key="ItemFixedPrices" + url="FixedPrices/filter" + :order="['itemFk DESC', 'name DESC']" + save-url="FixedPrices/crud" + ref="tableRef" + dense + :filter="{ + where: { + warehouseFk: user.warehouseFk, + }, + }" + :columns="columns" + default-mode="table" + auto-load + :is-editable="true" + :right-search="false" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + v-model:selected="rowsSelected" + :create-as-dialog="false" + :create="{ + onDataSaved: handleOnDataSave, + }" + :disable-option="{ card: true }" + > + <template #header-selection="scope"> + <QCheckbox v-model="scope.selected" /> + </template> + <template #body-selection="scope"> + {{ scope }} + <QCheckbox flat v-model="scope.selected" /> + </template> - <template #column-itemFk="props"> - <VnSelect - style="max-width: 100px" - url="Items/withName" - hide-selected - option-label="id" - option-value="id" - v-model="props.row.itemFk" - v-on="getRowUpdateInputEvents(props, true, 'select')" + <template #column-itemFk="props"> + <VnSelect + style="max-width: 100px" + url="Items/withName" + hide-selected + option-label="id" + option-value="id" + v-model="props.row.itemFk" + v-on="getRowUpdateInputEvents(props, true, 'select')" + > + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> #{{ scope.opt?.id }} </QItemLabel> + <QItemLabel caption>{{ scope.opt?.name }}</QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </template> + <template #column-name="{ row }"> + <span class="link"> + {{ row.name }} + </span> + <span class="subName">{{ row.subName }}</span> + <ItemDescriptorProxy :id="row.itemFk" /> + <FetchedTags :item="row" /> + </template> + <template #column-rate2="props"> + <QTd class="col"> + <VnInput + type="currency" + style="width: 75px" + v-model.number="props.row.rate2" + v-on="getRowUpdateInputEvents(props)" > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel> #{{ scope.opt?.id }} </QItemLabel> - <QItemLabel caption>{{ scope.opt?.name }}</QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </template> - <template #column-name="{ row }"> - <span class="link"> - {{ row.name }} - </span> - <span class="subName">{{ row.subName }}</span> - <ItemDescriptorProxy :id="row.itemFk" /> - <FetchedTags :item="row" /> - </template> - <template #column-rate2="props"> - <QTd class="col"> - <VnInput - type="currency" - style="width: 75px" - v-model.number="props.row.rate2" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </QTd> - </template> - <template #column-rate3="props"> - <QTd class="col"> - <VnInput - style="width: 75px" - type="currency" - v-model.number="props.row.rate3" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </QTd> - </template> - <template #column-minPrice="props"> - <QTd class="col"> - <div class="row" style="align-items: center"> - <QCheckbox - :model-value="props.row.hasMinPrice" - @update:model-value="updateMinPrice($event, props)" - :false-value="'false'" - :true-value="'true'" - /> - <VnInput - class="col" - type="currency" - mask="###.##" - :disable="props.row.hasMinPrice === 1" - v-model.number="props.row.minPrice" - v-on="getRowUpdateInputEvents(props)" - > - <template #append>€</template> - </VnInput> - </div> - </QTd> - </template> - <template #column-started="props"> - <VnInputDate - class="vnInputDate" - :show-event="true" - v-model="props.row.started" - v-on="getRowUpdateInputEvents(props, false, 'date')" - v-bind="dateStyle(isBigger(props.row.started))" - /> - </template> - <template #column-ended="props"> - <VnInputDate - class="vnInputDate" - :show-event="true" - v-model="props.row.ended" - v-on="getRowUpdateInputEvents(props, false, 'date')" - v-bind="dateStyle(isLower(props.row.ended))" - /> - </template> - <template #column-warehouseFk="props"> - <QTd class="col"> - <VnSelect - style="max-width: 150px" - :options="warehousesOptions" - hide-selected - option-label="name" - option-value="id" - v-model="props.row.warehouseFk" - v-on="getRowUpdateInputEvents(props, false, 'select')" + <template #append>€</template> + </VnInput> + </QTd> + </template> + <template #column-rate3="props"> + <QTd class="col"> + <VnInput + style="width: 75px" + type="currency" + v-model.number="props.row.rate3" + v-on="getRowUpdateInputEvents(props)" + > + <template #append>€</template> + </VnInput> + </QTd> + </template> + <template #column-minPrice="props"> + <QTd class="col"> + <div class="row" style="align-items: center"> + <QCheckbox + :model-value="props.row.hasMinPrice" + @update:model-value="updateMinPrice($event, props)" + :false-value="'false'" + :true-value="'true'" /> - </QTd> - </template> - <template #column-deleteAction="{ row, rowIndex }"> - <QIcon - name="delete" - size="sm" - class="cursor-pointer fill-icon-on-hover" - color="primary" - @click.stop=" - openConfirmationModal( - t('globals.rowWillBeRemoved'), - t('Do you want to clone this item?'), - () => removePrice(row.id, rowIndex) - ) - " - > - <QTooltip class="text-no-wrap"> - {{ t('globals.delete') }} - </QTooltip> - </QIcon> - </template> - </VnTable> - - <QDialog ref="editTableCellDialogRef"> - <EditTableCellValueForm - edit-url="FixedPrices/editFixedPrice" - :rows="rowsSelected" - :fields-options="editTableFieldsOptions" - @on-data-saved="onEditCellDataSaved()" + <VnInput + class="col" + type="currency" + mask="###.##" + :disable="props.row.hasMinPrice === 1" + v-model.number="props.row.minPrice" + v-on="getRowUpdateInputEvents(props)" + > + <template #append>€</template> + </VnInput> + </div> + </QTd> + </template> + <template #column-started="props"> + <VnInputDate + class="vnInputDate" + :show-event="true" + v-model="props.row.started" + v-on="getRowUpdateInputEvents(props, false, 'date')" + v-bind="dateStyle(isBigger(props.row.started))" /> - </QDialog> - </QPage> + </template> + <template #column-ended="props"> + <VnInputDate + class="vnInputDate" + :show-event="true" + v-model="props.row.ended" + v-on="getRowUpdateInputEvents(props, false, 'date')" + v-bind="dateStyle(isLower(props.row.ended))" + /> + </template> + <template #column-warehouseFk="props"> + <QTd class="col"> + <VnSelect + style="max-width: 150px" + :options="warehousesOptions" + hide-selected + option-label="name" + option-value="id" + v-model="props.row.warehouseFk" + v-on="getRowUpdateInputEvents(props, false, 'select')" + /> + </QTd> + </template> + <template #column-deleteAction="{ row, rowIndex }"> + <QIcon + name="delete" + size="sm" + class="cursor-pointer fill-icon-on-hover" + color="primary" + @click.stop=" + openConfirmationModal( + t('globals.rowWillBeRemoved'), + t('Do you want to clone this item?'), + () => removePrice(row.id, rowIndex) + ) + " + > + <QTooltip class="text-no-wrap"> + {{ t('globals.delete') }} + </QTooltip> + </QIcon> + </template> + </VnTable> + + <QDialog ref="editTableCellDialogRef"> + <EditTableCellValueForm + edit-url="FixedPrices/editFixedPrice" + :rows="rowsSelected" + :fields-options="editTableFieldsOptions" + @on-data-saved="onEditCellDataSaved()" + /> + </QDialog> </template> <style lang="scss"> .q-table th, diff --git a/src/pages/Supplier/SupplierListFilter.vue b/src/pages/Supplier/SupplierListFilter.vue index 7f838d3f537..b170a35cc8b 100644 --- a/src/pages/Supplier/SupplierListFilter.vue +++ b/src/pages/Supplier/SupplierListFilter.vue @@ -23,13 +23,13 @@ const countriesOptions = ref([]); <template> <FetchData url="Provinces" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" + :filter="{ fields: ['id', 'name'], order: 'name ASC'}" @on-fetch="(data) => (provincesOptions = data)" auto-load /> <FetchData url="countries" - :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" + :filter="{ fields: ['id', 'name'], order: 'name ASC'}" @on-fetch="(data) => (countriesOptions = data)" auto-load /> diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index be1a124065d..cfc082ad9dc 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -8,7 +8,7 @@ import VnLv from 'src/components/ui/VnLv.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue'; import FetchData from 'src/components/FetchData.vue'; - +import VnRow from 'components/ui/VnRow.vue'; import { toDate, toCurrency } from 'src/filters'; import axios from 'axios'; @@ -256,16 +256,20 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; :label="t('globals.warehouseOut')" :value="travel.warehouseOut?.name" /> - <QCheckbox - :label="t('travel.basicData.isRaid')" - v-model="travel.isRaid" - :disable="true" - /> - <QCheckbox - :label="t('travel.summary.delivered')" - v-model="travel.isDelivered" - :disable="true" - /> + <VnRow> + <QCheckbox + :label="t('travel.basicData.isRaid')" + v-model="travel.isRaid" + :disable="true" + /> + </VnRow> + <VnRow> + <QCheckbox + :label="t('travel.summary.delivered')" + v-model="travel.isDelivered" + :disable="true" + /> + </VnRow> </QCard> <QCard class="vn-one"> <QCardSection class="q-pa-none"> @@ -320,7 +324,6 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <QTd> <QCheckbox v-if="col.name === 'isConfirmed'" - :label="t('travel.summary.received')" :true-value="1" :false-value="0" v-model="row[col.name]" diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 1ac83b9e073..31e1e937301 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -33,6 +33,7 @@ defineExpose({ states }); </template> <template #body="{ params, searchFn }"> <div class="q-pa-sm q-gutter-y-sm"> + {{ params }} <VnInput :label="t('travel.Id')" v-model="params.id" @@ -77,7 +78,6 @@ defineExpose({ states }); :label="t('travel.shipped')" v-model="params.shipped" @update:model-value="searchFn()" - dense outlined rounded /> @@ -153,7 +153,7 @@ es: Id: Id ref: Referencia agency: Agencia - warehouseInFk: Alm.Salida + warehouseInFk: Alm.Entrada shipped: F.Envío shipmentHour: Hora de envío warehouseOut: Alm.Entrada diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index 0e69250a8b1..70e81aae2a1 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -54,7 +54,9 @@ const columns = computed(() => [ name: 'id', label: t('globals.id'), isId: true, - cardVisible: true, + chip: { + condition: () => true, + }, }, { align: 'left', @@ -64,7 +66,7 @@ const columns = computed(() => [ columnField: { component: null, }, - cardVisible: true, + isTitle: true, create: true, }, { @@ -103,14 +105,14 @@ const columns = computed(() => [ }, { align: 'left', - name: 'shipped', label: t('globals.shipped'), + name: 'shipped', + create: true, + cardVisible: true, component: 'date', columnField: { component: null, }, - cardVisible: true, - create: true, format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)), }, { @@ -201,7 +203,7 @@ const columns = computed(() => [ /> <RightMenu> <template #right-panel> - <TravelFilter data-key="TravelList" ref="travelFilterRef" /> + <TravelFilter data-key="TravelList" /> </template> </RightMenu> <VnTable From 5eb842f1b4bf8743fadce9873847aaa9e15ac847 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Tue, 17 Dec 2024 12:25:57 +0100 Subject: [PATCH 080/142] fix: remove params --- src/pages/Travel/TravelFilter.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue index 31e1e937301..287ac5ad293 100644 --- a/src/pages/Travel/TravelFilter.vue +++ b/src/pages/Travel/TravelFilter.vue @@ -33,7 +33,6 @@ defineExpose({ states }); </template> <template #body="{ params, searchFn }"> <div class="q-pa-sm q-gutter-y-sm"> - {{ params }} <VnInput :label="t('travel.Id')" v-model="params.id" From 9d60083360e9f38656e98477d52dd3ed80fdcab1 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 12:46:53 +0100 Subject: [PATCH 081/142] fix: refs #6583 update checkbox for filtering by destination in TicketAdvanceFilter --- src/pages/Ticket/TicketAdvanceFilter.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue index 3a485794668..6d5c7726e17 100644 --- a/src/pages/Ticket/TicketAdvanceFilter.vue +++ b/src/pages/Ticket/TicketAdvanceFilter.vue @@ -171,7 +171,7 @@ onMounted(async () => await getItemPackingTypes()); <QItem> <QItemSection> <QCheckbox - :toggle-indeterminate="false" + toggle-indeterminate label="only with destination" v-model="params.onlyWithDestination" @update:model-value="searchFn()" @@ -192,6 +192,7 @@ en: ipt: Destination IPT isFullMovable: 100% movable warehouseFk: Warehouse + onlyWithDestination: Only with destination es: Horizontal: Horizontal Vertical: Vertical @@ -203,4 +204,5 @@ es: ipt: IPT destino isFullMovable: 100% movible warehouseFk: Almacén + onlyWithDestination: Solo con destino </i18n> From 9c17541085ea2c28500f5f2c126199138f48c13c Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Tue, 17 Dec 2024 12:51:57 +0100 Subject: [PATCH 082/142] fix: refs #8813 fixed ClaimLines format --- src/pages/Claim/Card/ClaimLines.vue | 13 ++++++++----- src/pages/Claim/Card/ClaimSummary.vue | 11 ++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index 60c470d22b3..90a68beaeb0 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -57,6 +57,7 @@ function onFetch(rows, newRows) { const price = row.quantity * sale.price; const discount = (sale.discount * price) / 100; amountClaimed.value = amountClaimed.value + (price - discount); + } } @@ -207,9 +208,10 @@ async function saveWhenHasChanges() { selection="multiple" v-model:selected="selected" :grid="$q.screen.lt.md" + > <template #body-cell-claimed="{ row }"> - <QTd auto-width align="right" class="text-primary"> + <QTd auto-width align="right" class="text-primary shrink"> <QInput v-model.number="row.quantity" type="number" @@ -220,7 +222,7 @@ async function saveWhenHasChanges() { </QTd> </template> <template #body-cell-description="{ row, value }"> - <QTd auto-width align="right" class="text-primary"> + <QTd auto-width align="right" class="link expand"> {{ value }} <ItemDescriptorProxy :id="row.sale.itemFk" @@ -228,7 +230,7 @@ async function saveWhenHasChanges() { </QTd> </template> <template #body-cell-discount="{ row, value, rowIndex }"> - <QTd auto-width align="right" class="text-primary"> + <QTd auto-width align="right" class="text-primary shrink"> {{ value }} <VnDiscount :quantity="row.quantity" @@ -264,7 +266,7 @@ async function saveWhenHasChanges() { </QItemSection> <QItemSection side> <template v-if="column.name === 'claimed'"> - <QItemLabel class="text-primary"> + <QItemLabel class="text-primary shrink"> <QInput v-model.number=" props.row.quantity @@ -282,7 +284,7 @@ async function saveWhenHasChanges() { <template v-else-if="column.name === 'discount'" > - <QItemLabel class="text-primary"> + <QItemLabel class="text-primary shrink"> {{ column.value }} <VnDiscount :quantity="props.row.quantity" @@ -330,6 +332,7 @@ async function saveWhenHasChanges() { .grid-style-transition { transition: transform 0.28s, background-color 0.28s; } + </style> <i18n> diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 200ab4bea40..645fdfe14bf 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -341,16 +341,13 @@ function claimUrl(section) { </template> <template #body="props"> <QTr :props="props"> - <QTd v-for="col in props.cols" :key="col.name" :props="props"> + <QTd v-for="col in props.cols" :key="col.name" :props="props" class=""> <span v-if="col.name != 'description'">{{ t(col.value) }}</span> - <QBtn - v-if="col.name == 'description'" - flat - color="blue" - >{{ col.value }}</QBtn - > + <span class="link" v-if="col.name == 'description'">{{ + t(col.value) + }}</span> <ItemDescriptorProxy v-if="col.name == 'description'" :id="props.row.sale.itemFk" From 646efe52ff465201fdd942fd859c67ef0576e509 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Tue, 17 Dec 2024 13:04:00 +0100 Subject: [PATCH 083/142] fix: fix use-model --- src/pages/Item/ItemFixedPrice.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index f44237020e1..5ebf8a47791 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -424,6 +424,7 @@ function handleOnDataSave({ CrudModelRef }) { 'row-key': 'id', selection: 'multiple', }" + :use-model="true" v-model:selected="rowsSelected" :create-as-dialog="false" :create="{ From 109841eee5441a7e1fd9aa5c679700ed13c89017 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 13:20:48 +0100 Subject: [PATCH 084/142] feat: refs #6583 add default param --- src/pages/Ticket/TicketAdvance.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Ticket/TicketAdvance.vue b/src/pages/Ticket/TicketAdvance.vue index 9e52de23e6e..9b6669acbed 100644 --- a/src/pages/Ticket/TicketAdvance.vue +++ b/src/pages/Ticket/TicketAdvance.vue @@ -37,6 +37,7 @@ const userParams = reactive({ ipt: 'H', futureIpt: 'H', isFullMovable: true, + onlyWithDestination: true, }); const ticketColumns = computed(() => [ From 959608c49006046687abefe50896643a0d53dd82 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 13:35:46 +0100 Subject: [PATCH 085/142] feat: refs #6583 add locale --- src/pages/Ticket/locale/en.yml | 1 + src/pages/Ticket/locale/es.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index 56cc798ba37..54716a65098 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -53,6 +53,7 @@ advanceTickets: errorsList: Errors list search: Search advance tickets searchInfo: Search advance tickets by ID or client ID + clonedSales: Turn ticket futureTickets: problems: Problems shipped: Date diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index bb068ac5bf2..ae605ea1733 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -91,6 +91,7 @@ advanceTickets: errorsList: Lista de errores search: Buscar por tickets adelantados searchInfo: Buscar tickets adelantados por el identificador o el identificador del cliente + clonedSales: Ticket de turno futureTickets: problems: Problemas shipped: Fecha From 19c7337479f160ab890240087e7ff5794398e725 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 13:36:52 +0100 Subject: [PATCH 086/142] feat: refs #6583 add locale --- src/pages/Ticket/locale/en.yml | 2 +- src/pages/Ticket/locale/es.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index 54716a65098..2e2110f200b 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -53,7 +53,7 @@ advanceTickets: errorsList: Errors list search: Search advance tickets searchInfo: Search advance tickets by ID or client ID - clonedSales: Turn ticket + clonedSales: has turn lines futureTickets: problems: Problems shipped: Date diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index ae605ea1733..48fe31281ca 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -91,7 +91,7 @@ advanceTickets: errorsList: Lista de errores search: Buscar por tickets adelantados searchInfo: Buscar tickets adelantados por el identificador o el identificador del cliente - clonedSales: Ticket de turno + clonedSales: tiene lineas de turno futureTickets: problems: Problemas shipped: Fecha From 9b83490cf1ced175c19839da40b093c961430bb1 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 13:37:46 +0100 Subject: [PATCH 087/142] feat: refs #6583 add locale --- src/pages/Ticket/locale/es.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index 48fe31281ca..17fa17168e4 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -91,7 +91,7 @@ advanceTickets: errorsList: Lista de errores search: Buscar por tickets adelantados searchInfo: Buscar tickets adelantados por el identificador o el identificador del cliente - clonedSales: tiene lineas de turno + clonedSales: tiene líneas de turno futureTickets: problems: Problemas shipped: Fecha From 7b118d662135cfdfaf4b12a38be32e21c4a31095 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 13:38:06 +0100 Subject: [PATCH 088/142] feat: refs #6583 add locale --- src/pages/Ticket/locale/en.yml | 2 +- src/pages/Ticket/locale/es.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index 2e2110f200b..f11b32c3a5e 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -53,7 +53,7 @@ advanceTickets: errorsList: Errors list search: Search advance tickets searchInfo: Search advance tickets by ID or client ID - clonedSales: has turn lines + clonedSales: Has turn lines futureTickets: problems: Problems shipped: Date diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index 17fa17168e4..945da8367f8 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -91,7 +91,7 @@ advanceTickets: errorsList: Lista de errores search: Buscar por tickets adelantados searchInfo: Buscar tickets adelantados por el identificador o el identificador del cliente - clonedSales: tiene líneas de turno + clonedSales: Tiene líneas de turno futureTickets: problems: Problemas shipped: Fecha From 5bf3e2c80ac22c266f565d977211ea319873c680 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 17 Dec 2024 14:35:37 +0100 Subject: [PATCH 089/142] test: refs #8315 fix claimDevelopment fixtures --- .../integration/claim/claimDevelopment.spec.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/cypress/integration/claim/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js index eb39f340a7d..df9d09a49db 100755 --- a/test/cypress/integration/claim/claimDevelopment.spec.js +++ b/test/cypress/integration/claim/claimDevelopment.spec.js @@ -3,6 +3,8 @@ describe('ClaimDevelopment', () => { const claimId = 1; const firstLineReason = 'tbody > :nth-child(1) > :nth-child(2)'; const thirdRow = 'tbody > :nth-child(3)'; + const lastReason = 'Incompetencia'; + const newReason = 'Calor'; beforeEach(() => { cy.viewport(1920, 1080); @@ -14,22 +16,22 @@ describe('ClaimDevelopment', () => { }); it('should reset line', () => { - cy.selectOption(firstLineReason, 'Novato'); + cy.selectOption(firstLineReason, newReason); cy.resetCard(); - cy.getValue(firstLineReason).should('equal', 'Prisas'); + cy.getValue(firstLineReason).should('equal', lastReason); }); it('should edit line', () => { - cy.selectOption(firstLineReason, 'Novato'); + cy.selectOption(firstLineReason, newReason); cy.saveCard(); cy.login('developer'); cy.visit(`/#/claim/${claimId}/development`); - cy.getValue(firstLineReason).should('equal', 'Novato'); + cy.getValue(firstLineReason).should('equal', newReason); //Restart data - cy.selectOption(firstLineReason, 'Prisas'); + cy.selectOption(firstLineReason, lastReason); cy.saveCard(); }); @@ -42,7 +44,7 @@ describe('ClaimDevelopment', () => { const rowData = [ false, - 'Novato', + newReason, 'Roces', 'Compradores', 'administrativeNick', From cbb1bb6f60f2755cbb80061dc0c2c1128684325a Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 17 Dec 2024 14:37:15 +0100 Subject: [PATCH 090/142] test: refs #8315 fix clientList --- test/cypress/integration/client/clientList.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index ce07deb1619..703bb145495 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/client/clientList.spec.js @@ -26,7 +26,7 @@ describe('Client list', () => { 'Web user': { val: `user_test_${randomInt}` }, Street: { val: `C/ STREET ${randomInt}` }, Email: { val: `user.test${randomInt}@cypress.com` }, - 'Sales person': { val: 'employee', type: 'select' }, + 'Sales person': { val: 'salesPerson', type: 'select' }, Location: { val: '46000, Valencia(Province one), España', type: 'select' }, 'Business type': { val: 'Otros', type: 'select' }, }; From 997a6d18bc0de3f83c180bbaa1a42031fd228e42 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 17 Dec 2024 14:48:16 +0100 Subject: [PATCH 091/142] fix: refs #8315 ticketBoxing test --- src/pages/Ticket/Card/TicketBoxing.vue | 126 ++++++++++++------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/src/pages/Ticket/Card/TicketBoxing.vue b/src/pages/Ticket/Card/TicketBoxing.vue index 7c127efdaad..bd8cad03fa8 100644 --- a/src/pages/Ticket/Card/TicketBoxing.vue +++ b/src/pages/Ticket/Card/TicketBoxing.vue @@ -86,71 +86,67 @@ async function getVideoList(expeditionId, timed) { <template> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> - <QScrollArea class="fit"> - <QList bordered separator style="max-width: 318px"> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QItemLabel class="text-h6"> - {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ - time.max - }}) - </QItemLabel> - <QRange - v-model="time" - @change="getVideoList(lastExpedition, time)" - :min="0" - :max="24" - :step="1" - :left-label-value="time.min + ':00'" - :right-label-value="time.max + ':00'" - label - markers - snap - color="primary" - /> - </QItemSection> - </QItem> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QSelect - color="primary" - v-model="slide" - :options="videoList" - :label="t('ticket.boxing.selectVideo')" - emit-value - map-options - > - <template #prepend> - <QIcon name="schedule" /> - </template> - </QSelect> - </QItemSection> - </QItem> - <QItem - v-for="expedition in expeditions" - :key="expedition.id" - @click="getVideoList(expedition.id)" - clickable - v-ripple - > - <QItemSection> - <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> - </QItemSection> - <QItemSection> - <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> - <QItemLabel> - {{ - date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') - }} - </QItemLabel> - <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> - <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> - <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> - <QItemLabel>{{ expedition.userName }}</QItemLabel> - </QItemSection> - </QItem> - </QList> - </QScrollArea> + <QList bordered separator style="max-width: 318px"> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QItemLabel class="text-h6"> + {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ + time.max + }}) + </QItemLabel> + <QRange + v-model="time" + @change="getVideoList(lastExpedition, time)" + :min="0" + :max="24" + :step="1" + :left-label-value="time.min + ':00'" + :right-label-value="time.max + ':00'" + label + markers + snap + color="primary" + /> + </QItemSection> + </QItem> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QSelect + color="primary" + v-model="slide" + :options="videoList" + :label="t('ticket.boxing.selectVideo')" + emit-value + map-options + > + <template #prepend> + <QIcon name="schedule" /> + </template> + </QSelect> + </QItemSection> + </QItem> + <QItem + v-for="expedition in expeditions" + :key="expedition.id" + @click="getVideoList(expedition.id)" + clickable + v-ripple + > + <QItemSection> + <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> + </QItemSection> + <QItemSection> + <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> + <QItemLabel> + {{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }} + </QItemLabel> + <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> + <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> + <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> + <QItemLabel>{{ expedition.userName }}</QItemLabel> + </QItemSection> + </QItem> + </QList> </Teleport> <QCard> From 75b49490f6aecb22db8823e08b7c43d7db2637d1 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Tue, 17 Dec 2024 16:18:31 +0100 Subject: [PATCH 092/142] fix: refs #7323 update date on outside --- src/pages/Worker/Card/WorkerTimeControl.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 0bba2f891a4..491e5e1802b 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -103,9 +103,12 @@ const formattedWeekTotalHours = computed(() => const onInputChange = async (date) => { if (!date) return; - const { year, month, day } = date.scope.timestamp; + const { timestamp, outside } = date.scope; + const { year, month, day } = timestamp; const _date = new Date(year, month - 1, day); setDate(_date); + + if (outside) getMailStates(_date); }; const setDate = async (date) => { From 7028d95b1aa6b88c273dd8b0fe4073174cde6397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= <carlosap@verdnatura.es> Date: Tue, 17 Dec 2024 18:33:58 +0100 Subject: [PATCH 093/142] fix: refs #7146 front rutas --- src/pages/Route/Card/RouteAutonomousFilter.vue | 4 ++-- src/pages/Route/Card/RouteDescriptor.vue | 4 ++-- src/pages/Route/Card/RouteForm.vue | 8 ++++---- src/pages/Route/Card/RouteSummary.vue | 2 +- src/pages/Route/RouteAutonomous.vue | 4 ++-- src/pages/Route/RouteExtendedList.vue | 2 +- src/pages/Route/RouteRoadmap.vue | 2 +- src/pages/Route/RouteTickets.vue | 2 +- src/pages/Route/locale/en.yml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pages/Route/Card/RouteAutonomousFilter.vue b/src/pages/Route/Card/RouteAutonomousFilter.vue index eb3fa6f2c80..3d08e135545 100644 --- a/src/pages/Route/Card/RouteAutonomousFilter.vue +++ b/src/pages/Route/Card/RouteAutonomousFilter.vue @@ -29,7 +29,7 @@ const exprBuilder = (param, value) => { return { 'a.supplierName': value }; case 'routeFk': return { 'a.routeFk': value }; - case 'created': + case 'dated': case 'agencyFk': case 'packages': case 'm3': @@ -145,7 +145,7 @@ const exprBuilder = (param, value) => { <QItem class="q-my-sm"> <QItemSection> <VnInputDate - v-model="params.created" + v-model="params.dated" :label="t('Date')" is-outlined /> diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index cbabaf64888..fa621843eca 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -28,7 +28,7 @@ const filter = { 'id', 'workerFk', 'agencyModeFk', - 'created', + 'dated', 'm3', 'warehouseFk', 'description', @@ -78,7 +78,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity @on-fetch="setData" > <template #body="{ entity }"> - <VnLv :label="t('Date')" :value="toDate(entity?.created)" /> + <VnLv :label="t('Date')" :value="toDate(entity?.dated)" /> <VnLv :label="t('Agency')" :value="entity?.agencyMode?.name" /> <VnLv :label="t('Zone')" :value="entity?.zone?.name" /> <VnLv diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue index 8c89718fa94..5bd0c5928bf 100644 --- a/src/pages/Route/Card/RouteForm.vue +++ b/src/pages/Route/Card/RouteForm.vue @@ -19,7 +19,7 @@ const shelvingId = ref(route.params?.id || null); const isNew = Boolean(!shelvingId.value); const defaultInitialData = { agencyModeFk: null, - created: null, + dated: null, description: '', vehicleFk: null, workerFk: null, @@ -32,7 +32,7 @@ const routeFilter = { 'id', 'workerFk', 'agencyModeFk', - 'created', + 'dated', 'm3', 'warehouseFk', 'description', @@ -134,7 +134,7 @@ const onSave = (data, response) => { option-label="name" :input-debounce="0" /> - <VnInputDate v-model="data.created" :label="t('Created')" /> + <VnInputDate v-model="data.dated" :label="t('Dated')" /> </VnRow> <template v-if="!isNew"> <VnRow> @@ -188,7 +188,7 @@ es: Hour finished: Hora fin Description: Descripción Is served: Se ha servido - Created: Creado + Dated: Fecha The km can not exceed: La distancia debe ser inferior a {maxDistance} en: The km can not exceed: Distance must be lesser than {maxDistance} diff --git a/src/pages/Route/Card/RouteSummary.vue b/src/pages/Route/Card/RouteSummary.vue index 3f9b1a2a9b3..a0b9711956b 100644 --- a/src/pages/Route/Card/RouteSummary.vue +++ b/src/pages/Route/Card/RouteSummary.vue @@ -139,7 +139,7 @@ const ticketColumns = ref([ <QCard class="vn-one"> <VnLv :label="t('route.summary.date')" - :value="toDate(entity?.route.created)" + :value="toDate(entity?.route.dated)" /> <VnLv :label="t('route.summary.agency')" diff --git a/src/pages/Route/RouteAutonomous.vue b/src/pages/Route/RouteAutonomous.vue index 4a691dbefed..e45af30c750 100644 --- a/src/pages/Route/RouteAutonomous.vue +++ b/src/pages/Route/RouteAutonomous.vue @@ -46,10 +46,10 @@ const columns = computed(() => [ }, { align: 'left', - name: 'created', + name: 'dated', label: t('Date'), columnFilter: false, - format: ({ created }) => toDate(created), + format: ({ dated }) => toDate(dated), }, { align: 'left', diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue index 38e907ce048..221fc47545a 100644 --- a/src/pages/Route/RouteExtendedList.vue +++ b/src/pages/Route/RouteExtendedList.vue @@ -111,7 +111,7 @@ const columns = computed(() => [ }, { align: 'left', - name: 'created', + name: 'dated', label: t('route.Date'), columnFilter: false, cardVisible: true, diff --git a/src/pages/Route/RouteRoadmap.vue b/src/pages/Route/RouteRoadmap.vue index 3687442f57b..168e52b23c2 100644 --- a/src/pages/Route/RouteRoadmap.vue +++ b/src/pages/Route/RouteRoadmap.vue @@ -45,7 +45,7 @@ const columns = computed(() => [ columnFilter: { inWhere: true, }, - format: ({ created }) => toDate(created), + format: ({ dated }) => toDate(dated), cardVisible: true, }, { diff --git a/src/pages/Route/RouteTickets.vue b/src/pages/Route/RouteTickets.vue index 3bdad4feccf..56e3143b4e0 100644 --- a/src/pages/Route/RouteTickets.vue +++ b/src/pages/Route/RouteTickets.vue @@ -109,7 +109,7 @@ const ticketList = ref([]); const cloneRoutes = () => { axios.post('Routes/clone', { - created: startingDate.value, + dated: startingDate.value, ids: selectedRows.value.map((row) => row?.id), }); refreshKey.value++; diff --git a/src/pages/Route/locale/en.yml b/src/pages/Route/locale/en.yml index d113fda6767..420d18dfe8d 100644 --- a/src/pages/Route/locale/en.yml +++ b/src/pages/Route/locale/en.yml @@ -5,7 +5,7 @@ route: Description: Description hourStarted: H.Start hourFinished: H.End - createRoute: Create route + dated: Dated From: From To: To Date: Date From 9d955f45bbdb0947781106c734ef0bbe8841fc9b Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Wed, 18 Dec 2024 07:06:34 +0100 Subject: [PATCH 094/142] test: refs #7050 add tests to fns resetData() and saveChanges() --- .../components/common/CrudModel.spec.js | 101 +++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index 2d7493cca5d..b3cdbede7d9 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -1,9 +1,10 @@ -import { createWrapper } from 'app/test/vitest/helper'; +import { createWrapper, axios } from 'app/test/vitest/helper'; import CrudModel from 'components/CrudModel.vue'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; describe('CrudModel', () => { let vm; + let data; beforeAll(() => { vm = createWrapper(CrudModel, { global: { @@ -31,6 +32,7 @@ describe('CrudModel', () => { beforeEach(() => { vm.fetch([]); + vm.watchChanges = null; }); afterEach(() => { @@ -160,4 +162,101 @@ describe('CrudModel', () => { expect(result).toBe(false); }) }); + + describe('resetData()', () => { + + it('should add $index to elements in data[] and sets originalData and formData with data', async () => { + data = [{ + name: 'Tony', + lastName: 'Stark', + age: 42, + }]; + + vm.resetData(data); + + expect(vm.originalData).toEqual(data); + expect(vm.originalData[0].$index).toEqual(0); + expect(vm.formData).toEqual(data); + expect(vm.formData[0].$index).toEqual(0); + expect(vm.watchChanges).not.toBeNull(); + }); + + it('should dont do nothing if data is null', async () => { + vm.resetData(null); + + expect(vm.watchChanges).toBeNull(); + }); + + it('should set originalData and formatData with data and generate watchChanges', async () => { + data = { + name: 'Tony', + lastName: 'Stark', + age: 42, + }; + + vm.resetData(data); + + expect(vm.originalData).toEqual(data); + expect(vm.formData).toEqual(data); + expect(vm.watchChanges).not.toBeNull(); + }); + }); + + describe('saveChanges()', () => { + data = [{ + name: 'Tony', + lastName: 'Stark', + age: 42, + }]; + + it('should call saveFn if exists', async () => { + const saveFnMock = vi.fn(); + + const localVm = createWrapper(CrudModel, { + global: { + stubs: [ + 'vnPaginate', + 'useState', + 'arrayData', + 'useStateStore', + 'vue-i18n', + ], + mocks: { + validate: vi.fn(), + }, + }, + propsData: { + dataRequired: { + fk: 1, + }, + dataKey: 'crudModelKey', + model: 'crudModel', + url: 'crudModelUrl', + saveFn: saveFnMock, + }, + }); + + localVm.vm.saveChanges(data); + expect(saveFnMock).toHaveBeenCalledOnce(); + expect(localVm.vm.isLoading).toBe(false); + expect(localVm.vm.hasChanges).toBe(false); + }); + + it('should not call saveFn if not exists', async () => { + const postMock =vi.spyOn(axios, 'post'); + + vm.formData = [{ + name: 'Bruce', + lastName: 'Wayne', + age: 45, + }] + + await vm.saveChanges(data); + + expect(postMock).toHaveBeenCalledWith(vm.url + '/crud', data); + expect(vm.isLoading).toBe(false); + expect(vm.hasChanges).toBe(false); + expect(vm.originalData).toEqual(JSON.parse(JSON.stringify(vm.formData))); + }); + }); }); From 4b35d4b41e2e34c1709577df79c01b035fe512a9 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Wed, 18 Dec 2024 07:59:56 +0100 Subject: [PATCH 095/142] refactor: refs #8293 remove redundant attributes --- src/pages/Claim/ClaimFilter.vue | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/pages/Claim/ClaimFilter.vue b/src/pages/Claim/ClaimFilter.vue index c846cbbf931..c28e95cb8f9 100644 --- a/src/pages/Claim/ClaimFilter.vue +++ b/src/pages/Claim/ClaimFilter.vue @@ -30,7 +30,7 @@ defineExpose({ states }); <span>{{ formatFn(tag.value) }}</span> </div> </template> - <template #body="{ params, searchFn }"> + <template #body="{ params }"> <div class="q-pa-sm q-gutter-y-sm"> <VnInput :label="t('claim.customerId')" @@ -49,12 +49,9 @@ defineExpose({ states }); <VnSelect :label="t('Salesperson')" v-model="params.salesPersonFk" - @update:model-value="searchFn()" url="Workers/activeWithInheritedRole" :filter="{ where: { role: 'salesPerson' } }" :use-like="false" - option-value="id" - option-label="name" option-filter="firstName" dense outlined @@ -63,12 +60,9 @@ defineExpose({ states }); <VnSelect :label="t('claim.attendedBy')" v-model="params.attenderFk" - @update:model-value="searchFn()" url="Workers/activeWithInheritedRole" :filter="{ where: { role: 'salesPerson' } }" :use-like="false" - option-value="id" - option-label="name" option-filter="firstName" dense outlined @@ -77,9 +71,7 @@ defineExpose({ states }); <VnSelect :label="t('claim.state')" v-model="params.claimStateFk" - @update:model-value="searchFn()" :options="states" - option-value="id" option-label="description" dense outlined @@ -87,7 +79,6 @@ defineExpose({ states }); /> <VnInputDate v-model="params.created" - @update:model-value="searchFn()" :label="t('claim.created')" outlined rounded @@ -96,10 +87,7 @@ defineExpose({ states }); <VnSelect :label="t('Item')" v-model="params.itemFk" - @update:model-value="searchFn()" url="Items/withName" - option-value="id" - option-label="name" :use-like="false" sort-by="id DESC" outlined @@ -118,12 +106,9 @@ defineExpose({ states }); <VnSelect :label="t('claim.responsible')" v-model="params.claimResponsibleFk" - @update:model-value="searchFn()" url="Workers/activeWithInheritedRole" :filter="{ where: { role: 'salesPerson' } }" :use-like="false" - option-value="id" - option-label="name" option-filter="firstName" dense outlined @@ -132,19 +117,15 @@ defineExpose({ states }); <VnSelect :label="t('claim.zone')" v-model="params.zoneFk" - @update:model-value="searchFn()" url="Zones" - option-value="id" - option-label="name" :use-like="false" outlined rounded dense - ></VnSelect> + /> <QCheckbox v-model="params.myTeam" :label="t('params.myTeam')" - @update:model-value="searchFn()" toggle-indeterminate /> </div> From 8ab10dda1b8e6461ed5392baa4d6ceb9bf28f286 Mon Sep 17 00:00:00 2001 From: Jtubau <jtubau@verdnatura.es> Date: Wed, 18 Dec 2024 08:07:44 +0100 Subject: [PATCH 096/142] refactor: refs #7074 move dms constant to global scope --- .../__tests__/components/common/VnDmsList.spec.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/vitest/__tests__/components/common/VnDmsList.spec.js b/test/vitest/__tests__/components/common/VnDmsList.spec.js index 49228ddf8fc..9649943a237 100644 --- a/test/vitest/__tests__/components/common/VnDmsList.spec.js +++ b/test/vitest/__tests__/components/common/VnDmsList.spec.js @@ -4,6 +4,10 @@ import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; describe('VnDmsList', () => { let vm; + const dms = { + userFk: 1, + name: 'DMS 1' + }; beforeAll(() => { vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); @@ -63,11 +67,6 @@ describe('VnDmsList', () => { }); describe('parseDms()', () => { - const dms = { - userFk: 1, - name: 'DMS 1' - }; - const resultDms = { ...dms, userId:1}; it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { @@ -77,11 +76,6 @@ describe('VnDmsList', () => { }); describe('showFormDialog()', () => { - const dms = { - userFk: 1, - name: 'DMS 1' - }; - const resultDms = { ...dms, userId:1}; it('should call fn parseDms() and set show true if dms is defined', () => { From 4ac5a603bb9cfa1a422216de70b2e6bb6cd1a66a Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Wed, 18 Dec 2024 09:26:13 +0100 Subject: [PATCH 097/142] refactor: refs #8813 removed unused class property --- src/pages/Claim/Card/ClaimSummary.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 645fdfe14bf..8939a0785b3 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -341,11 +341,11 @@ function claimUrl(section) { </template> <template #body="props"> <QTr :props="props"> - <QTd v-for="col in props.cols" :key="col.name" :props="props" class=""> + <QTd v-for="col in props.cols" :key="col.name" :props="props"> <span v-if="col.name != 'description'">{{ t(col.value) }}</span> - <span class="link" v-if="col.name == 'description'">{{ + <span class="link" v-if="col.name === 'description'">{{ t(col.value) }}</span> <ItemDescriptorProxy From 410052d6ec28e4cc8b1145134cd69e386b2bc35f Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:04:21 +0100 Subject: [PATCH 098/142] refactor: refs #8201 deleted logs --- src/pages/Customer/Card/CustomerDescriptor.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index a17b7c7961f..dc5f08d3757 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -17,7 +17,6 @@ const state = useState(); const customer = ref(); onMounted(async () => { - console.log('state.get(customer): ', state.get('customer')); customer.value = state.get('customer'); if (customer.value) customer.value.webAccess = data.value?.account?.isActive; }); @@ -99,7 +98,6 @@ const debtWarning = computed(() => { /> </template> <template #icons> - {{ console.log('customer: ', customer) }} <QCardActions v-if="customer" class="q-gutter-x-md"> <QIcon v-if="!customer.isActive" From 9783be1ff00dd2709f9e3633956e85797be49616 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:45:02 +0100 Subject: [PATCH 099/142] fix: fixed recipient param --- src/pages/Worker/Card/WorkerTimeControl.vue | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 491e5e1802b..96e7cd44146 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -100,15 +100,23 @@ const formattedWeekTotalHours = computed(() => secondsToHoursMinutes(weekTotalHours.value) ); +// const onInputChange = async (date) => { +// if (!date) return; + +// const { timestamp, outside } = date.scope; +// const { year, month, day } = timestamp; +// const _date = new Date(year, month - 1, day); +// setDate(_date); + +// if (outside) getMailStates(_date); +// }; + const onInputChange = async (date) => { if (!date) return; - const { timestamp, outside } = date.scope; - const { year, month, day } = timestamp; + const { year, month, day } = date.scope.timestamp; const _date = new Date(year, month - 1, day); setDate(_date); - - if (outside) getMailStates(_date); }; const setDate = async (date) => { @@ -381,12 +389,13 @@ const isUnsatisfied = async (reason) => { const resendEmail = async () => { const params = { - recipient: worker.value?.user?.email, + recipient: worker.value[0]?.user?.emailUser?.email, week: selectedWeekNumber.value, year: selectedDate.value.getFullYear(), workerId: Number(route.params.id), state: 'SENDED', }; + console.log('params: ', params); await axios.post('WorkerTimeControls/weekly-hour-record-email', params); await getMailStates(selectedDate.value); notify(t('Email sended'), 'positive'); From 06d3a025fcd355981a41887a4a4382ca584941af Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:45:42 +0100 Subject: [PATCH 100/142] fix: deleted code --- src/pages/Worker/Card/WorkerTimeControl.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index 96e7cd44146..f4c81102f7e 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -395,7 +395,6 @@ const resendEmail = async () => { workerId: Number(route.params.id), state: 'SENDED', }; - console.log('params: ', params); await axios.post('WorkerTimeControls/weekly-hour-record-email', params); await getMailStates(selectedDate.value); notify(t('Email sended'), 'positive'); From 76788fe8892e2744698283ce749787f4549d46a0 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Wed, 18 Dec 2024 10:46:50 +0100 Subject: [PATCH 101/142] fix: changes --- src/pages/Worker/Card/WorkerTimeControl.vue | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index f4c81102f7e..c480d5bd89c 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -100,23 +100,15 @@ const formattedWeekTotalHours = computed(() => secondsToHoursMinutes(weekTotalHours.value) ); -// const onInputChange = async (date) => { -// if (!date) return; - -// const { timestamp, outside } = date.scope; -// const { year, month, day } = timestamp; -// const _date = new Date(year, month - 1, day); -// setDate(_date); - -// if (outside) getMailStates(_date); -// }; - const onInputChange = async (date) => { if (!date) return; - const { year, month, day } = date.scope.timestamp; + const { timestamp, outside } = date.scope; + const { year, month, day } = timestamp; const _date = new Date(year, month - 1, day); setDate(_date); + + if (outside) getMailStates(_date); }; const setDate = async (date) => { From 17527cb4e7e29a474077758649536b1f8177c592 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 11:22:04 +0100 Subject: [PATCH 102/142] test: refs #8315 fix VnSelect in e2e --- src/components/common/VnSelect.vue | 1 + src/components/ui/VnConfirm.vue | 1 + src/pages/Zone/Card/ZoneCreateWarehouse.vue | 20 ++++---- .../integration/client/clientList.spec.js | 4 +- .../integration/entry/stockBought.spec.js | 2 +- .../integration/item/ItemFixedPrice.spec.js | 4 +- .../route/roadMap/roadmapList.spec.js | 2 +- .../integration/route/routeList.spec.js | 2 +- .../integration/worker/workerPda.spec.js | 2 +- .../integration/zone/zoneWarehouse.spec.js | 27 +++++------ test/cypress/support/commands.js | 48 +++++++++++++++---- 11 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index ed21b089b92..758fb92287b 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -343,6 +343,7 @@ function handleKeyDown(event) { @virtual-scroll="onScroll" @keydown="handleKeyDown" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'" + :data-url="url" > <template #append> <QIcon diff --git a/src/components/ui/VnConfirm.vue b/src/components/ui/VnConfirm.vue index 0b191338303..a02b56bdb4e 100644 --- a/src/components/ui/VnConfirm.vue +++ b/src/components/ui/VnConfirm.vue @@ -98,6 +98,7 @@ function cancel() { /> <QBtn :label="t('globals.confirm')" + :title="t('globals.confirm')" color="primary" :loading="isLoading" @click="confirm()" diff --git a/src/pages/Zone/Card/ZoneCreateWarehouse.vue b/src/pages/Zone/Card/ZoneCreateWarehouse.vue index a46ec2e6c56..88f6a77017c 100644 --- a/src/pages/Zone/Card/ZoneCreateWarehouse.vue +++ b/src/pages/Zone/Card/ZoneCreateWarehouse.vue @@ -30,17 +30,15 @@ const warehousesOptions = ref([]); > <template #form-inputs> <VnRow> - <div class="col"> - <VnSelect - :label="t('list.warehouse')" - v-model="ZoneWarehouseFormData.warehouseFk" - :options="warehousesOptions" - option-value="id" - option-label="name" - hide-selected - :required="true" - /> - </div> + <VnSelect + :label="t('list.warehouse')" + v-model="ZoneWarehouseFormData.warehouseFk" + :options="warehousesOptions" + option-value="id" + option-label="name" + hide-selected + :required="true" + /> </VnRow> </template> </FormPopup> diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index 703bb145495..dcded63b0fe 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/client/clientList.spec.js @@ -16,7 +16,7 @@ describe('Client list', () => { }); it('Client list create new client', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); const randomInt = Math.floor(Math.random() * 90) + 10; const data = { @@ -27,7 +27,7 @@ describe('Client list', () => { Street: { val: `C/ STREET ${randomInt}` }, Email: { val: `user.test${randomInt}@cypress.com` }, 'Sales person': { val: 'salesPerson', type: 'select' }, - Location: { val: '46000, Valencia(Province one), España', type: 'select' }, + Location: { val: '46000', type: 'select' }, 'Business type': { val: 'Otros', type: 'select' }, }; cy.fillInForm(data); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js index 66e06b79ee3..078ad19cc28 100644 --- a/test/cypress/integration/entry/stockBought.spec.js +++ b/test/cypress/integration/entry/stockBought.spec.js @@ -11,7 +11,7 @@ describe('EntryStockBought', () => { cy.get('.q-notification__message').should('have.text', 'Data saved'); }); it('Should add a new reserved space for buyerBoss', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.get('input[aria-label="Reserve"]').type('1'); cy.get('input[aria-label="Date"]').eq(1).clear(); cy.get('input[aria-label="Date"]').eq(1).type('01-01'); diff --git a/test/cypress/integration/item/ItemFixedPrice.spec.js b/test/cypress/integration/item/ItemFixedPrice.spec.js index 824ecf7a065..92dc27fda7a 100644 --- a/test/cypress/integration/item/ItemFixedPrice.spec.js +++ b/test/cypress/integration/item/ItemFixedPrice.spec.js @@ -19,7 +19,7 @@ describe('Handle Items FixedPrice', () => { cy.selectOption('.list > :nth-child(2)', 'Alstroemeria'); cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.selectOption(`${firstRow} > :nth-child(2)`, '#13'); cy.get(`${firstRow} > :nth-child(4)`).find('input').type(1); cy.get(`${firstRow} > :nth-child(5)`).find('input').type('2'); @@ -29,7 +29,7 @@ describe('Handle Items FixedPrice', () => { }); it('Create and delete ', function () { cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.selectOption(`${firstRow} > :nth-child(2)`, '#11'); cy.get(`${firstRow} > :nth-child(4)`).type('1'); cy.get(`${firstRow} > :nth-child(5)`).type('2'); diff --git a/test/cypress/integration/route/roadMap/roadmapList.spec.js b/test/cypress/integration/route/roadMap/roadmapList.spec.js index ba602fdf67f..2f5e5672f46 100644 --- a/test/cypress/integration/route/roadMap/roadmapList.spec.js +++ b/test/cypress/integration/route/roadMap/roadmapList.spec.js @@ -5,7 +5,7 @@ describe('RoadMap', () => { cy.visit(`/#/route/roadmap`); }); it('Route list create roadmap and redirect', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.get('input[name="name"]').eq(1).type('roadMapTestOne{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); cy.url().should('include', '/summary'); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index 8020d3ea9f2..4da43ce8e63 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -9,7 +9,7 @@ describe('Route', () => { const getRowColumn = (row, column) => `:nth-child(${row}) > :nth-child(${column})`; it('Route list create route', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.get('input[name="description"]').type('routeTestOne{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); cy.url().should('include', '/summary'); diff --git a/test/cypress/integration/worker/workerPda.spec.js b/test/cypress/integration/worker/workerPda.spec.js index dc1ca622420..31ec19eda0e 100644 --- a/test/cypress/integration/worker/workerPda.spec.js +++ b/test/cypress/integration/worker/workerPda.spec.js @@ -7,7 +7,7 @@ describe('WorkerPda', () => { }); it('assign pda', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.addBtnClick(); cy.get(select).click(); cy.get(select).type('{downArrow}{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); diff --git a/test/cypress/integration/zone/zoneWarehouse.spec.js b/test/cypress/integration/zone/zoneWarehouse.spec.js index 3ffa3f69d70..817e26312d5 100644 --- a/test/cypress/integration/zone/zoneWarehouse.spec.js +++ b/test/cypress/integration/zone/zoneWarehouse.spec.js @@ -1,10 +1,10 @@ describe('ZoneWarehouse', () => { const data = { - Warehouse: { val: 'Algemesi', type: 'select' }, + Warehouse: { val: 'Warehouse One', type: 'select' }, }; - const deviceProductionField = - '.vn-row > :nth-child(1) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'; - const dataError = "ER_DUP_ENTRY: Duplicate entry '2-2' for key 'zoneFk'"; + + const dataError = 'ER_DUP_ENTRY: Duplicate entry'; + const saveBtn = '.q-btn--standard > .q-btn__content > .block'; beforeEach(() => { cy.viewport(1280, 720); @@ -13,22 +13,21 @@ describe('ZoneWarehouse', () => { }); it('should throw an error if the warehouse chosen is already put in the zone', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get(deviceProductionField).click(); - cy.get(deviceProductionField).type('{upArrow}{enter}'); - cy.get('.q-notification__message').should('have.text', dataError); + cy.addBtnClick(); + cy.selectOption('[data-cy="Warehouse_select"]', 'Warehouse Two'); + cy.get(saveBtn).click(); + cy.checkNotification(dataError); }); - it('should create a warehouse', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get(deviceProductionField).click(); + it('should create & remove a warehouse', () => { + cy.addBtnClick(); cy.fillInForm(data); + cy.get(saveBtn).click(); cy.get('.q-mt-lg > .q-btn--standard').click(); - }); - it('should delete a warehouse', () => { cy.get('tbody > :nth-child(2) > :nth-child(2) > .q-icon').click(); - cy.get('.q-card__actions > .q-btn--flat > .q-btn__content').click(); + cy.get('[title="Confirm"]').click(); + cy.reload(); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 2b13a714445..df2c00e03dd 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -58,8 +58,9 @@ Cypress.Commands.add('domContentLoad', (element, timeout = 5000) => { cy.waitUntil(() => cy.document().then((doc) => doc.readyState === 'complete')); }); Cypress.Commands.add('waitForElement', (element, timeout = 5000) => { - cy.waitUntil(() => cy.get(element).then(($el) => $el.is(':visible'))); + cy.get(element, { timeout }).should('be.visible').and('not.be.disabled'); }); + Cypress.Commands.add('getValue', (selector) => { cy.get(selector).then(($el) => { if ($el.find('.q-checkbox__inner').length > 0) { @@ -86,15 +87,40 @@ Cypress.Commands.add('getValue', (selector) => { }); // Fill Inputs -Cypress.Commands.add('selectOption', (selector, option, timeout) => { - cy.waitForElement(selector); +Cypress.Commands.add('selectOption', (selector, option, timeout = 5000) => { + cy.waitForElement(selector, timeout); cy.get(selector).click(); - cy.wait(timeout || 1000); - cy.get('.q-menu .q-item').contains(option).click(); + cy.get(selector).invoke('data', 'url').as('dataUrl'); + cy.get(selector) + .clear() + .type(option) + .then(() => { + cy.get('.q-menu', { timeout }) + .should('be.visible') // Asegurarse de que el menú está visible + .and('exist') // Verificar que el menú existe + .then(() => { + cy.get('@dataUrl').then((url) => { + if (url) { + cy.log('url: ', url); + // Esperar a que el menú no esté visible (desaparezca) + cy.get('.q-menu').should('not.be.visible'); + // Ahora esperar a que el menú vuelva a aparecer + cy.get('.q-menu').should('be.visible').and('exist'); + } + }); + }); + }); + + // Finalmente, seleccionar la opción deseada + cy.get('.q-menu:visible') // Asegurarse de que estamos dentro del menú visible + .find('.q-item') // Encontrar los elementos de las opciones + .contains(option) // Verificar que existe una opción que contenga el texto deseado + .click(); // Hacer clic en la opción }); + Cypress.Commands.add('countSelectOptions', (selector, option) => { cy.waitForElement(selector); - cy.get(selector).click(); + cy.get(selector).click({ force: true }); cy.get('.q-menu .q-item').should('have.length', option); }); @@ -110,8 +136,7 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { const { type, val } = field; switch (type) { case 'select': - cy.get(el).click(); - cy.get('.q-menu .q-item').contains(val).click(); + cy.selectOption(el, val); break; case 'date': cy.get(el).type(val.split('-').join('')); @@ -347,3 +372,10 @@ Cypress.Commands.add('searchByLabel', (label, value) => { Cypress.Commands.add('dataCy', (tag, attr = 'data-cy') => { return cy.get(`[${attr}="${tag}"]`); }); + +Cypress.Commands.add('addBtnClick', () => { + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon') + .should('exist') + .and('be.visible') + .click(); +}); From b851262ff886d9998c6694f3a90979c73da5a1df Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 11:33:57 +0100 Subject: [PATCH 103/142] build: init version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39d49519bec..b5e62af1133 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.52.0", + "version": "25.02.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", From b54f39f1a066dde5a59a8b3e4911e1636c2daebe Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:00 +0100 Subject: [PATCH 104/142] feat: refs #8197 default sectionName --- src/components/common/VnCardMain.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnCardMain.vue index 5b8e6b5e87a..e6afea4b6a3 100644 --- a/src/components/common/VnCardMain.vue +++ b/src/components/common/VnCardMain.vue @@ -2,7 +2,7 @@ import RightMenu from './RightMenu.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnTableFilter from '../VnTable/VnTableFilter.vue'; -import { onBeforeMount } from 'vue'; +import { onBeforeMount, computed } from 'vue'; import { useArrayData } from 'src/composables/useArrayData'; const $props = defineProps({ @@ -36,6 +36,8 @@ const $props = defineProps({ }, }); +const sectionValue = computed(() => $props.section ?? $props.dataKey); + onBeforeMount(() => { if ($props.dataKey) useArrayData($props.dataKey, { @@ -67,6 +69,6 @@ onBeforeMount(() => { </template> </RightMenu> - <slot name="body" v-if="section == $route.name" /> + <slot name="body" v-if="sectionValue == $route.name" /> <RouterView v-else /> </template> From 76b73cc6167e27f88d004625bcccb3f0c159dfd1 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:10 +0100 Subject: [PATCH 105/142] perf: refs #8197 perf --- src/components/common/VnSectionMain.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnSectionMain.vue index 4e800fa8aaa..505b3a8b56e 100644 --- a/src/components/common/VnSectionMain.vue +++ b/src/components/common/VnSectionMain.vue @@ -15,7 +15,6 @@ onMounted( () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) ); -const targetId = 'left-panel'; const teleportRef = ref({}); const hasContent = ref(); let observer; @@ -37,7 +36,7 @@ onMounted(() => { <template> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit text-grey-8"> - <div :id="targetId" ref="teleportRef"></div> + <div id="left-panel" ref="teleportRef"></div> <LeftMenu v-if="!hasContent" /> </QScrollArea> </QDrawer> From 32fd07dd14e401d6fed3608cc274dcf92094c342 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:04:31 +0100 Subject: [PATCH 106/142] feat: refs #8197 default sectionName --- src/pages/Account/AccountAcls.vue | 1 - src/pages/Account/AccountAliasList.vue | 1 - src/pages/Account/AccountList.vue | 1 - src/pages/Account/Role/AccountRoles.vue | 1 - 4 files changed, 4 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index 73771a34107..b457bb7f034 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -139,7 +139,6 @@ const deleteAcl = async ({ id }) => { @on-fetch="(data) => (roles = data)" /> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="acls" diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index 721a009e512..9631c96399d 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -44,7 +44,6 @@ const exprBuilder = (param, value) => { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="mailAlias" diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 34a653e61db..5296cc1d668 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -104,7 +104,6 @@ function exprBuilder(param, value) { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="account" diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 8cc392f1bb8..4e67a691bef 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -86,7 +86,6 @@ const exprBuilder = (param, value) => { <template> <VnCardMain - :section="dataKey" :data-key="dataKey" :columns="columns" prefix="role" From 5db434676f14151034950bc2b849f6a33a30bf89 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 18 Dec 2024 13:05:40 +0100 Subject: [PATCH 107/142] feat: refs #7936 make fields required --- src/pages/InvoiceIn/Card/InvoiceInBasicData.vue | 5 +++++ src/pages/InvoiceIn/InvoiceInList.vue | 1 + 2 files changed, 6 insertions(+) diff --git a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue index fd5ea09312a..83b1aa25e1d 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -116,6 +116,7 @@ function deleteFile(dmsFk) { <template #form="{ data }"> <VnRow> <VnSelect + :required="true" :label="t('supplierFk')" v-model="data.supplierFk" option-value="id" @@ -244,6 +245,8 @@ function deleteFile(dmsFk) { </VnRow> <VnRow> <VnSelect + :required="true" + :is-clearable="false" :label="t('Currency')" v-model="data.currencyFk" :options="currencies" @@ -253,6 +256,8 @@ function deleteFile(dmsFk) { /> <VnSelect + :required="true" + :is-clearable="false" v-if="companiesRef" :label="t('Company')" v-model="data.companyFk" diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 6469f446d1f..0af2e68b25b 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -114,6 +114,7 @@ const cols = computed(() => [ title: t('components.smartCard.openSummary'), icon: 'preview', type: 'submit', + isPrimary: true, action: (row) => viewSummary(row.id, InvoiceInSummary), }, { From 8c3c318099bc7d76696d5e3d43c758b65011b396 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:16:45 +0100 Subject: [PATCH 108/142] test(VnTable): refs #8197 mock useFilterParams --- test/vitest/__tests__/components/VnTable.spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/vitest/__tests__/components/VnTable.spec.js b/test/vitest/__tests__/components/VnTable.spec.js index 162df727dd6..74ba0698765 100644 --- a/test/vitest/__tests__/components/VnTable.spec.js +++ b/test/vitest/__tests__/components/VnTable.spec.js @@ -1,4 +1,4 @@ -import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { describe, expect, it, beforeAll, beforeEach, vi } from 'vitest'; import { createWrapper } from 'app/test/vitest/helper'; import VnTable from 'src/components/VnTable/VnTable.vue'; @@ -13,6 +13,15 @@ describe('VnTable', () => { }, }); vm = wrapper.vm; + + vi.mock('src/composables/useFilterParams', () => { + return { + useFilterParams: vi.fn(() => ({ + params: {}, + orders: {}, + })), + }; + }); }); beforeEach(() => (vm.selected = [])); From fccca9ea477c74237adfffcc5566a0c592560e05 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 13:42:57 +0100 Subject: [PATCH 109/142] refactor: refs #8197 rename VnSectionMain to VnModule and VnCardMain to VnSection --- src/components/common/VnCardBeta.vue | 2 + .../{VnSectionMain.vue => VnModule.vue} | 0 .../common/{VnCardMain.vue => VnSection.vue} | 0 src/pages/Account/AccountAcls.vue | 6 +- src/pages/Account/AccountAliasList.vue | 6 +- src/pages/Account/AccountList.vue | 6 +- src/pages/Account/Role/AccountRoles.vue | 6 +- src/pages/Ticket/Card/TicketBoxing.vue | 136 +++++++++--------- src/router/modules/account.js | 2 +- src/router/modules/claim.js | 2 +- src/router/modules/customer.js | 2 +- src/router/modules/entry.js | 2 +- src/router/modules/invoiceIn.js | 2 +- src/router/modules/invoiceOut.js | 2 +- src/router/modules/item.js | 2 +- src/router/modules/monitor.js | 2 +- src/router/modules/order.js | 2 +- src/router/modules/route.js | 2 +- src/router/modules/shelving.js | 2 +- src/router/modules/supplier.js | 2 +- src/router/modules/travel.js | 2 +- src/router/modules/wagon.js | 4 +- src/router/modules/worker.js | 2 +- src/router/modules/zone.js | 2 +- 24 files changed, 98 insertions(+), 98 deletions(-) rename src/components/common/{VnSectionMain.vue => VnModule.vue} (100%) rename src/components/common/{VnCardMain.vue => VnSection.vue} (100%) diff --git a/src/components/common/VnCardBeta.vue b/src/components/common/VnCardBeta.vue index 16a077a79f1..349956be9f7 100644 --- a/src/components/common/VnCardBeta.vue +++ b/src/components/common/VnCardBeta.vue @@ -5,6 +5,8 @@ import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; import LeftMenu from 'components/LeftMenu.vue'; +import VnSubToolbar from '../ui/VnSubToolbar.vue'; + const props = defineProps({ dataKey: { type: String, required: true }, baseUrl: { type: String, default: undefined }, diff --git a/src/components/common/VnSectionMain.vue b/src/components/common/VnModule.vue similarity index 100% rename from src/components/common/VnSectionMain.vue rename to src/components/common/VnModule.vue diff --git a/src/components/common/VnCardMain.vue b/src/components/common/VnSection.vue similarity index 100% rename from src/components/common/VnCardMain.vue rename to src/components/common/VnSection.vue diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index b457bb7f034..b4eeb0648d4 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -9,7 +9,7 @@ import VnTable from 'components/VnTable/VnTable.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; import FetchData from 'src/components/FetchData.vue'; import { useValidator } from 'src/composables/useValidator'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; defineProps({ id: { @@ -138,7 +138,7 @@ const deleteAcl = async ({ id }) => { auto-load @on-fetch="(data) => (roles = data)" /> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="acls" @@ -166,7 +166,7 @@ const deleteAcl = async ({ id }) => { :use-model="true" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue index 9631c96399d..f6016fb6c18 100644 --- a/src/pages/Account/AccountAliasList.vue +++ b/src/pages/Account/AccountAliasList.vue @@ -2,7 +2,7 @@ import { useI18n } from 'vue-i18n'; import { ref, computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const tableRef = ref(); const { t } = useI18n(); @@ -43,7 +43,7 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="mailAlias" @@ -68,7 +68,7 @@ const exprBuilder = (param, value) => { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> es: diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 5296cc1d668..997e3104142 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -4,7 +4,7 @@ import { computed } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -103,7 +103,7 @@ function exprBuilder(param, value) { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="account" @@ -124,7 +124,7 @@ function exprBuilder(param, value) { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue index 4e67a691bef..3c3d6b243fc 100644 --- a/src/pages/Account/Role/AccountRoles.vue +++ b/src/pages/Account/Role/AccountRoles.vue @@ -5,7 +5,7 @@ import VnTable from 'components/VnTable/VnTable.vue'; import { useRoute } from 'vue-router'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import RoleSummary from './Card/RoleSummary.vue'; -import VnCardMain from 'src/components/common/VnCardMain.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const route = useRoute(); const { t } = useI18n(); @@ -85,7 +85,7 @@ const exprBuilder = (param, value) => { </script> <template> - <VnCardMain + <VnSection :data-key="dataKey" :columns="columns" prefix="role" @@ -110,7 +110,7 @@ const exprBuilder = (param, value) => { :right-search="false" /> </template> - </VnCardMain> + </VnSection> </template> <i18n> diff --git a/src/pages/Ticket/Card/TicketBoxing.vue b/src/pages/Ticket/Card/TicketBoxing.vue index 1a728739645..5675fe1b30f 100644 --- a/src/pages/Ticket/Card/TicketBoxing.vue +++ b/src/pages/Ticket/Card/TicketBoxing.vue @@ -1,15 +1,18 @@ <script setup> import axios from 'axios'; import { date, useQuasar } from 'quasar'; -import { computed, onMounted, reactive, ref } from 'vue'; +import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import { useStateStore } from 'stores/useStateStore'; const router = useRouter(); +const stateStore = useStateStore(); const { t } = useI18n(); const quasar = useQuasar(); onMounted(async () => { + stateStore.rightDrawer = true; await fetch(); }); @@ -84,74 +87,69 @@ async function getVideoList(expeditionId, timed) { </script> <template> - <QDrawer show-if-above side="right"> - <QScrollArea class="fit"> - <QList bordered separator style="max-width: 318px"> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QItemLabel class="text-h6"> - {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ - time.max - }}) - </QItemLabel> - <QRange - v-model="time" - @change="getVideoList(lastExpedition, time)" - :min="0" - :max="24" - :step="1" - :left-label-value="time.min + ':00'" - :right-label-value="time.max + ':00'" - label - markers - snap - color="primary" - /> - </QItemSection> - </QItem> - <QItem v-if="lastExpedition && videoList.length"> - <QItemSection> - <QSelect - color="primary" - v-model="slide" - :options="videoList" - :label="t('ticket.boxing.selectVideo')" - emit-value - map-options - > - <template #prepend> - <QIcon name="schedule" /> - </template> - </QSelect> - </QItemSection> - </QItem> - <QItem - v-for="expedition in expeditions" - :key="expedition.id" - @click="getVideoList(expedition.id)" - clickable - v-ripple - > - <QItemSection> - <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> - </QItemSection> - <QItemSection> - <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> - <QItemLabel> - {{ - date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') - }} - </QItemLabel> - <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> - <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> - <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> - <QItemLabel>{{ expedition.userName }}</QItemLabel> - </QItemSection> - </QItem> - </QList> - </QScrollArea> - </QDrawer> - + <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted()"> + <QList bordered separator style="max-width: 318px"> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QItemLabel class="text-h6"> + {{ t('ticket.boxing.selectTime') }} ({{ time.min }}-{{ + time.max + }}) + </QItemLabel> + <QRange + v-model="time" + @change="getVideoList(lastExpedition, time)" + :min="0" + :max="24" + :step="1" + :left-label-value="time.min + ':00'" + :right-label-value="time.max + ':00'" + label + markers + snap + color="primary" + /> + </QItemSection> + </QItem> + <QItem v-if="lastExpedition && videoList.length"> + <QItemSection> + <QSelect + color="primary" + v-model="slide" + :options="videoList" + :label="t('ticket.boxing.selectVideo')" + emit-value + map-options + > + <template #prepend> + <QIcon name="schedule" /> + </template> + </QSelect> + </QItemSection> + </QItem> + <QItem + v-for="expedition in expeditions" + :key="expedition.id" + @click="getVideoList(expedition.id)" + clickable + v-ripple + > + <QItemSection> + <QItemLabel class="text-h6">#{{ expedition.id }}</QItemLabel> + </QItemSection> + <QItemSection> + <QItemLabel caption>{{ t('globals.created') }}</QItemLabel> + <QItemLabel> + {{ date.formatDate(expedition.created, 'YYYY-MM-DD HH:mm:ss') }} + </QItemLabel> + <QItemLabel caption>{{ t('globals.item') }}</QItemLabel> + <QItemLabel>{{ expedition.packagingItemFk }}</QItemLabel> + <QItemLabel caption>{{ t('ticket.boxing.worker') }}</QItemLabel> + <QItemLabel>{{ expedition.userName }}</QItemLabel> + </QItemSection> + </QItem> + </QList> + </Teleport> <QCard> <QCarousel animated v-model="slide" height="max-content"> <QCarouselSlide diff --git a/src/router/modules/account.js b/src/router/modules/account.js index 6f5ca90f3cf..466db953945 100644 --- a/src/router/modules/account.js +++ b/src/router/modules/account.js @@ -28,7 +28,7 @@ export default { { path: '', name: 'AccountMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'AccountIndexMain' }, children: [ { diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index b58a58e8dad..8b0a7089667 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -27,7 +27,7 @@ export default { { name: 'ClaimMain', path: '', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ClaimList' }, children: [ { diff --git a/src/router/modules/customer.js b/src/router/modules/customer.js index 1b707f1a238..9e7f6fe703b 100644 --- a/src/router/modules/customer.js +++ b/src/router/modules/customer.js @@ -39,7 +39,7 @@ export default { { path: '', name: 'CustomerMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'CustomerList' }, children: [ { diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index 3add239df63..26ce773c5d1 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -25,7 +25,7 @@ export default { { path: '', name: 'EntryMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'EntryList' }, children: [ { diff --git a/src/router/modules/invoiceIn.js b/src/router/modules/invoiceIn.js index 168d64f373a..788b27d37d6 100644 --- a/src/router/modules/invoiceIn.js +++ b/src/router/modules/invoiceIn.js @@ -25,7 +25,7 @@ export default { { path: '', name: 'InvoiceInMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'InvoiceInList' }, children: [ { diff --git a/src/router/modules/invoiceOut.js b/src/router/modules/invoiceOut.js index 5e83b0859a3..53d27d0e8ee 100644 --- a/src/router/modules/invoiceOut.js +++ b/src/router/modules/invoiceOut.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'InvoiceOutMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'InvoiceOutList' }, children: [ { diff --git a/src/router/modules/item.js b/src/router/modules/item.js index 0f810434c09..e2afd6c7bb7 100644 --- a/src/router/modules/item.js +++ b/src/router/modules/item.js @@ -36,7 +36,7 @@ export default { { path: '', name: 'ItemMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ItemList' }, children: [ { diff --git a/src/router/modules/monitor.js b/src/router/modules/monitor.js index 2af60c09cd0..89ba4078f99 100644 --- a/src/router/modules/monitor.js +++ b/src/router/modules/monitor.js @@ -19,7 +19,7 @@ export default { { path: '', name: 'MonitorMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), props: (route) => ({ leftDrawer: route.name === 'MonitorClientsActions' }), redirect: { name: 'MonitorTickets' }, children: [ diff --git a/src/router/modules/order.js b/src/router/modules/order.js index bfa37fce50c..77af812cf79 100644 --- a/src/router/modules/order.js +++ b/src/router/modules/order.js @@ -19,7 +19,7 @@ export default { { path: '', name: 'OrderMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'OrderList' }, children: [ { diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 9a7b16df3a2..a6c4f30a223 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -25,7 +25,7 @@ export default { { path: '/route', name: 'RouteMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'RouteList' }, children: [ { diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js index b7f50a3b606..dd254db6946 100644 --- a/src/router/modules/shelving.js +++ b/src/router/modules/shelving.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'ShelvingMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ShelvingList' }, children: [ { diff --git a/src/router/modules/supplier.js b/src/router/modules/supplier.js index c08fb596114..647f4bdd33e 100644 --- a/src/router/modules/supplier.js +++ b/src/router/modules/supplier.js @@ -30,7 +30,7 @@ export default { { path: '', name: 'SupplierMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'SupplierList' }, children: [ { diff --git a/src/router/modules/travel.js b/src/router/modules/travel.js index dff693d2fae..49272be1e0a 100644 --- a/src/router/modules/travel.js +++ b/src/router/modules/travel.js @@ -18,7 +18,7 @@ export default { { path: '', name: 'TravelMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'TravelList' }, children: [ { diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index e25e585eb57..5c7e881c2cb 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -18,7 +18,7 @@ export default { { path: '/wagon', name: 'WagonMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WagonList' }, children: [ { @@ -62,7 +62,7 @@ export default { { path: '/wagon/type', name: 'WagonTypeMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WagonTypeList' }, children: [ { diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index 9250197342d..c732664ecbe 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -35,7 +35,7 @@ export default { { path: '', name: 'WorkerMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'WorkerList' }, children: [ { diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js index c5ebe762ee9..334ba2b51ad 100644 --- a/src/router/modules/zone.js +++ b/src/router/modules/zone.js @@ -30,7 +30,7 @@ export default { { path: '/zone', name: 'ZoneMain', - component: () => import('src/components/common/VnSectionMain.vue'), + component: () => import('src/components/common/VnModule.vue'), redirect: { name: 'ZoneList' }, children: [ { From 41390fec58cf09812b0bb64e38294e20b8531ecc Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Wed, 18 Dec 2024 13:52:43 +0100 Subject: [PATCH 110/142] fix: refs #8314 space between label and value --- src/pages/Order/Card/OrderVolume.vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/Order/Card/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue index 27ee24197b9..fb104157e70 100644 --- a/src/pages/Order/Card/OrderVolume.vue +++ b/src/pages/Order/Card/OrderVolume.vue @@ -71,9 +71,11 @@ onMounted(async () => (stateStore.rightDrawer = false)); auto-load /> <QCard v-if="volumeSummary" class="order-volume-summary q-pa-lg"> - <VnLv :label="t('total')" :value="`${volumeSummary?.totalVolume} m³`" /> + <VnLv + :label="`${t('total')}: `" + :value="`${volumeSummary?.totalVolume} m³`" /> <VnLv - :label="t('boxes')" + :label="`${t('boxes')}: `" :value="`${dashIfEmpty(volumeSummary?.totalBoxes)} U`" /> </QCard> @@ -111,12 +113,12 @@ onMounted(async () => (stateStore.rightDrawer = false)); </VnTable> </template> -<style lang="scss"> +<style lang="scss" scoped> .order-volume-summary { .vn-label-value { display: flex; justify-content: flex-end; - gap: 2%; + gap: 0.5%; .label { color: var(--vn-label-color); From 84ac4dd2102a825e0732128d22c1a7b3906744d1 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 14:21:59 +0100 Subject: [PATCH 111/142] fix: refs #8197 vnPaginate onFetch emit --- src/components/ui/VnPaginate.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 90089593e05..42f558f89b8 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -106,6 +106,7 @@ const store = arrayData.store; onMounted(async () => { if (props.autoLoad && !store.data?.length) await fetch(); + else emit('onFetch', store.data); mounted.value = true; }); From a940eb9861b9193d659cf0cc69e43f3b81fba3c0 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Wed, 18 Dec 2024 14:22:19 +0100 Subject: [PATCH 112/142] refactor: refs #8197 adapt Ticket to VnCardMain --- src/pages/Ticket/Card/TicketCard.vue | 20 +- src/pages/Ticket/TicketList.vue | 431 ++++++++++++++------------- src/router/modules/ticket.js | 379 +++++++++++------------ 3 files changed, 413 insertions(+), 417 deletions(-) diff --git a/src/pages/Ticket/Card/TicketCard.vue b/src/pages/Ticket/Card/TicketCard.vue index 73b6f5543c2..6886a8e577a 100644 --- a/src/pages/Ticket/Card/TicketCard.vue +++ b/src/pages/Ticket/Card/TicketCard.vue @@ -1,23 +1,7 @@ <script setup> -import { useI18n } from 'vue-i18n'; - -import VnCard from 'components/common/VnCard.vue'; +import VnCardBeta from 'components/common/VnCardBeta.vue'; import TicketDescriptor from './TicketDescriptor.vue'; -import TicketFilter from '../TicketFilter.vue'; - -const { t } = useI18n(); </script> <template> - <VnCard - data-key="Ticket" - base-url="Tickets" - :filter-panel="TicketFilter" - :descriptor="TicketDescriptor" - search-data-key="TicketList" - :searchbar-props="{ - url: 'Tickets/filter', - label: t('card.search'), - info: t('card.searchInfo'), - }" - /> + <VnCardBeta data-key="Ticket" base-url="Tickets" :descriptor="TicketDescriptor" /> </template> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index eb03a492721..823f74fc51a 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -8,13 +8,11 @@ import { useQuasar } from 'quasar'; import { toDate, toCurrency, dashIfEmpty } from 'src/filters/index'; import useNotify from 'src/composables/useNotify'; import TicketSummary from './Card/TicketSummary.vue'; -import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnRow from 'src/components/ui/VnRow.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import TicketFilter from './TicketFilter.vue'; import VnInput from 'src/components/common/VnInput.vue'; import FetchData from 'src/components/FetchData.vue'; @@ -23,6 +21,7 @@ import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue'; import { toTimeFormat } from 'src/filters/date'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import TicketProblems from 'src/components/TicketProblems.vue'; +import VnSection from 'src/components/common/VnSection.vue'; const route = useRoute(); const router = useRouter(); @@ -66,6 +65,7 @@ const dialogData = ref(); const companiesOptions = ref([]); const accountingOptions = ref([]); const amountToReturn = ref(); +const dataKey = 'TicketList'; const columns = computed(() => [ { @@ -452,223 +452,228 @@ function setReference(data) { @on-fetch="(data) => (accountingOptions = data)" auto-load /> - <VnSearchbar - data-key="TicketList" - :label="t('Search ticket')" - :info="t('You can search by ticket id or alias')" - data-cy="ticketListSearchBar" - /> - <RightMenu> - <template #right-panel> + <VnSection + :data-key="dataKey" + :columns="columns" + prefix="card" + :array-data-props="{ + url: 'Tickets/filter', + order: ['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id'], + exprBuilder, + }" + > + <template #rightMenu> <TicketFilter data-key="TicketList" /> </template> - </RightMenu> - <VnTable - ref="tableRef" - data-key="TicketList" - url="Tickets/filter" - :create="{ - urlCreate: 'Tickets/new', - title: t('ticketList.createTicket'), - onDataSaved: ({ id }) => tableRef.redirect(id), - formInitialData: { clientId: null }, - }" - default-mode="table" - :order="['shippedDate DESC', 'shippedHour ASC', 'zoneLanding ASC', 'id']" - :columns="columns" - :user-params="userParams" - :right-search="false" - redirect="ticket" - v-model:selected="selectedRows" - :table="{ - 'row-key': 'id', - selection: 'multiple', - }" - data-cy="ticketListTable" - > - <template #column-statusIcons="{ row }"> - <TicketProblems :row="row" /> - </template> - <template #column-salesPersonFk="{ row }"> - <span class="link" @click.stop> - {{ dashIfEmpty(row.userName) }} - <CustomerDescriptorProxy :id="row.salesPersonFk" /> - </span> - </template> - <template #column-shippedDate="{ row }"> - <span v-if="getDateColor(row.shipped)"> - <QChip :class="getDateColor(row.shipped)" dense square> - {{ toDate(row.shippedDate) }} - </QChip> - </span> - </template> - <template #column-nickname="{ row }"> - <span class="link" @click.stop> - {{ row.nickname }} - <CustomerDescriptorProxy :id="row.clientFk" /> - </span> - </template> - <template #column-addressNickname="{ row }"> - <span class="link" @click.stop> - {{ row.addressNickname }} - <CustomerDescriptorProxy :id="row.clientFk" /> - </span> - </template> - <template #column-stateFk="{ row }"> - <span v-if="row.refFk"> - <span class="link" @click.stop> - {{ row.refFk }} - <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> - </span> - </span> - <span v-else-if="getColor(row)"> - <QChip :class="getColor(row)" dense square> - {{ row.state }} - </QChip> - </span> - <span v-else> - {{ row.state }} - </span> - </template> - <template #column-zoneFk="{ row }"> - <span class="link" @click.stop> - {{ dashIfEmpty(row.zoneName) }} - <ZoneDescriptorProxy :id="row.zoneFk" /> - </span> - </template> - <template #column-totalWithVat="{ row }"> - <QChip - v-if="row.totalWithVat > 0 && row.totalWithVat < 50" - class="bg-warning" - dense - square + <template #body> + <VnTable + ref="tableRef" + :data-key="dataKey" + :create="{ + urlCreate: 'Tickets/new', + title: t('ticketList.createTicket'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: { clientId: null }, + }" + default-mode="table" + :columns="columns" + :user-params="userParams" + :right-search="false" + redirect="ticket" + v-model:selected="selectedRows" + :table="{ + 'row-key': 'id', + selection: 'multiple', + }" + data-cy="ticketListTable" > - {{ row.totalWithVat }} - </QChip> - </template> - <template #more-create-dialog="{ data }"> - <VnRow> - <VnSelect - url="Clients" - :fields="['id', 'name']" - :label="t('ticketList.client')" - v-model="data.clientId" - :options="clientsOptions" - option-value="id" - option-label="name" - hide-selected - required - @update:model-value="(client) => onClientSelected(data)" - :sort-by="'id ASC'" - > - <template #option="scope"> - <QItem v-bind="scope.itemProps"> - <QItemSection> - <QItemLabel> - {{ scope.opt.name }} - </QItemLabel> - <QItemLabel caption> - {{ `#${scope.opt.id}` }} - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </VnRow> - <VnRow> - <VnSelect - :label="t('basicData.address')" - v-model="data.addressId" - :options="addressesOptions" - option-value="id" - option-label="nickname" - hide-selected - map-options - required - :disable="!data.clientId" - :sort-by="'isActive DESC'" - @update:model-value="() => fetchAvailableAgencies(data)" - > - <template #option="scope"> - <QItem - v-bind="scope.itemProps" - :class="{ disabled: !scope.opt.isActive }" + <template #column-statusIcons="{ row }"> + <TicketProblems :row="row" /> + </template> + <template #column-salesPersonFk="{ row }"> + <span class="link" @click.stop> + {{ dashIfEmpty(row.userName) }} + <CustomerDescriptorProxy :id="row.salesPersonFk" /> + </span> + </template> + <template #column-shippedDate="{ row }"> + <span v-if="getDateColor(row.shipped)"> + <QChip :class="getDateColor(row.shipped)" dense square> + {{ toDate(row.shippedDate) }} + </QChip> + </span> + </template> + <template #column-nickname="{ row }"> + <span class="link" @click.stop> + {{ row.nickname }} + <CustomerDescriptorProxy :id="row.clientFk" /> + </span> + </template> + <template #column-addressNickname="{ row }"> + <span class="link" @click.stop> + {{ row.addressNickname }} + <CustomerDescriptorProxy :id="row.clientFk" /> + </span> + </template> + <template #column-stateFk="{ row }"> + <span v-if="row.refFk"> + <span class="link" @click.stop> + {{ row.refFk }} + <InvoiceOutDescriptorProxy :id="row.invoiceOutId" /> + </span> + </span> + <span v-else-if="getColor(row)"> + <QChip :class="getColor(row)" dense square> + {{ row.state }} + </QChip> + </span> + <span v-else> + {{ row.state }} + </span> + </template> + <template #column-zoneFk="{ row }"> + <span class="link" @click.stop> + {{ dashIfEmpty(row.zoneName) }} + <ZoneDescriptorProxy :id="row.zoneFk" /> + </span> + </template> + <template #column-totalWithVat="{ row }"> + <QChip + v-if="row.totalWithVat > 0 && row.totalWithVat < 50" + class="bg-warning" + dense + square + > + {{ row.totalWithVat }} + </QChip> + </template> + <template #more-create-dialog="{ data }"> + <VnRow> + <VnSelect + url="Clients" + :fields="['id', 'name']" + :label="t('ticketList.client')" + v-model="data.clientId" + :options="clientsOptions" + option-value="id" + option-label="name" + hide-selected + required + @update:model-value="(client) => onClientSelected(data)" + :sort-by="'id ASC'" > - <QItemSection style="min-width: min-content" avatar> - <QIcon - v-if=" - scope.opt.isActive && - selectedClient?.defaultAddressFk === scope.opt.id - " - size="sm" - color="grey" - name="star" - class="fill-icon" - /> - </QItemSection> - <QItemSection> - <QItemLabel - :class="{ - 'color-vn-label': !scope.opt?.isActive, - }" + <template #option="scope"> + <QItem v-bind="scope.itemProps"> + <QItemSection> + <QItemLabel> + {{ scope.opt.name }} + </QItemLabel> + <QItemLabel caption> + {{ `#${scope.opt.id}` }} + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </VnRow> + <VnRow> + <VnSelect + :label="t('basicData.address')" + v-model="data.addressId" + :options="addressesOptions" + option-value="id" + option-label="nickname" + hide-selected + map-options + required + :disable="!data.clientId" + :sort-by="'isActive DESC'" + @update:model-value="() => fetchAvailableAgencies(data)" + > + <template #option="scope"> + <QItem + v-bind="scope.itemProps" + :class="{ disabled: !scope.opt.isActive }" > - {{ - `${ - !scope.opt?.isActive - ? t('basicData.inactive') - : '' - } ` - }} - <span> - {{ scope.opt?.nickname }}: - {{ scope.opt?.street }}, {{ scope.opt?.city }} - </span> - </QItemLabel> - </QItemSection> - </QItem> - </template> - </VnSelect> - </VnRow> - <VnRow> - <div class="col"> - <VnInputDate - placeholder="dd-mm-aaa" - :label="t('globals.landed')" - v-model="data.landed" - @update:model-value="() => fetchAvailableAgencies(data)" - /> - </div> - </VnRow> - <VnRow> - <div class="col"> - <VnSelect - url="Warehouses" - :sort-by="['name']" - :label="t('globals.warehouse')" - v-model="data.warehouseId" - :options="warehousesOptions" - option-value="id" - option-label="name" - hide-selected - required - @update:model-value="() => fetchAvailableAgencies(data)" - /> - </div> - </VnRow> - <VnRow> - <div class="col"> - <VnSelect - :label="t('globals.agency')" - v-model="data.agencyModeId" - :options="agenciesOptions" - option-value="agencyModeFk" - option-label="agencyMode" - hide-selected - /> - </div> - </VnRow> + <QItemSection style="min-width: min-content" avatar> + <QIcon + v-if=" + scope.opt.isActive && + selectedClient?.defaultAddressFk === + scope.opt.id + " + size="sm" + color="grey" + name="star" + class="fill-icon" + /> + </QItemSection> + <QItemSection> + <QItemLabel + :class="{ + 'color-vn-label': !scope.opt?.isActive, + }" + > + {{ + `${ + !scope.opt?.isActive + ? t('basicData.inactive') + : '' + } ` + }} + <span> + {{ scope.opt?.nickname }}: + {{ scope.opt?.street }}, + {{ scope.opt?.city }} + </span> + </QItemLabel> + </QItemSection> + </QItem> + </template> + </VnSelect> + </VnRow> + <VnRow> + <div class="col"> + <VnInputDate + placeholder="dd-mm-aaa" + :label="t('globals.landed')" + v-model="data.landed" + @update:model-value="() => fetchAvailableAgencies(data)" + /> + </div> + </VnRow> + <VnRow> + <div class="col"> + <VnSelect + url="Warehouses" + :sort-by="['name']" + :label="t('globals.warehouse')" + v-model="data.warehouseId" + :options="warehousesOptions" + option-value="id" + option-label="name" + hide-selected + required + @update:model-value="() => fetchAvailableAgencies(data)" + /> + </div> + </VnRow> + <VnRow> + <div class="col"> + <VnSelect + :label="t('globals.agency')" + v-model="data.agencyModeId" + :options="agenciesOptions" + option-value="agencyModeFk" + option-label="agencyMode" + hide-selected + /> + </div> + </VnRow> + </template> + </VnTable> </template> - </VnTable> + </VnSection> <QPageSticky :offset="[20, 80]" style="z-index: 2"> <QBtn v-if="hasSelectedRows" diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index 6e407b88b57..600b64c1454 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -1,19 +1,12 @@ import { RouterView } from 'vue-router'; -export default { - name: 'Ticket', - path: '/ticket', +const ticketCard = { + name: 'TicketCard', + path: ':id', + component: () => import('src/pages/Ticket/Card/TicketCard.vue'), + redirect: { name: 'TicketSummary' }, meta: { - title: 'tickets', - icon: 'vn:ticket', - moduleName: 'Ticket', - keyBinding: 't', - }, - component: RouterView, - redirect: { name: 'TicketMain' }, - menus: { - main: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], - card: [ + menu: [ 'TicketBasicData', 'TicketSale', 'TicketLog', @@ -32,21 +25,200 @@ export default { 'TicketSms', ], }, + children: [ + { + path: 'summary', + name: 'TicketSummary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), + }, + { + path: 'basic-data', + name: 'TicketBasicData', + meta: { + title: 'basicData', + icon: 'vn:settings', + }, + component: () => + import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), + }, + { + path: 'sale', + name: 'TicketSale', + meta: { + title: 'sale', + icon: 'vn:lines', + }, + component: () => import('src/pages/Ticket/Card/TicketSale.vue'), + }, + { + path: 'request', + name: 'TicketPurchaseRequest', + meta: { + title: 'purchaseRequest', + icon: 'vn:buyrequest', + }, + component: () => import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), + }, + { + path: 'tracking', + name: 'TicketTracking', + meta: { + title: 'tracking', + icon: 'vn:eye', + }, + component: () => import('src/pages/Ticket/Card/TicketTracking.vue'), + }, + { + path: 'log', + name: 'TicketLog', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Ticket/Card/TicketLog.vue'), + }, + { + path: 'observation', + name: 'TicketNotes', + meta: { + title: 'notes', + icon: 'vn:notes', + }, + component: () => import('src/pages/Ticket/Card/TicketNotes.vue'), + }, + { + path: 'picture', + name: 'TicketPicture', + meta: { + title: 'pictures', + icon: 'vn:photo', + }, + component: () => import('src/pages/Ticket/Card/TicketPicture.vue'), + }, + { + path: 'volume', + name: 'TicketVolume', + meta: { + title: 'volume', + icon: 'vn:volume', + }, + component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), + }, + { + path: 'expedition', + name: 'TicketExpedition', + meta: { + title: 'expedition', + icon: 'vn:package', + }, + component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'), + }, + { + path: 'service', + name: 'TicketService', + meta: { + title: 'services', + icon: 'vn:services', + }, + component: () => import('src/pages/Ticket/Card/TicketService.vue'), + }, + { + path: 'package', + name: 'TicketPackage', + meta: { + title: 'packages', + icon: 'vn:bucket', + }, + component: () => import('src/pages/Ticket/Card/TicketPackage.vue'), + }, + { + path: 'components', + name: 'TicketComponents', + meta: { + title: 'components', + icon: 'vn:components', + }, + component: () => import('src/pages/Ticket/Card/TicketComponents.vue'), + }, + + { + path: 'sale-tracking', + name: 'TicketSaleTracking', + meta: { + title: 'saleTracking', + icon: 'assignment', + }, + component: () => import('src/pages/Ticket/Card/TicketSaleTracking.vue'), + }, + { + path: 'dms', + name: 'TicketDms', + meta: { + title: 'dms', + icon: 'cloud_upload', + }, + component: () => import('src/pages/Ticket/Card/TicketDms.vue'), + }, + { + path: 'boxing', + name: 'TicketBoxing', + meta: { + title: 'boxing', + icon: 'science', + }, + component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), + }, + { + path: 'sms', + name: 'TicketSms', + meta: { + title: 'sms', + icon: 'sms', + }, + component: () => import('src/pages/Ticket/Card/TicketSms.vue'), + }, + ], +}; + +export default { + name: 'Ticket', + path: '/ticket', + meta: { + title: 'tickets', + icon: 'vn:ticket', + moduleName: 'Ticket', + keyBinding: 't', + menu: ['TicketList', 'TicketAdvance', 'TicketWeekly', 'TicketFuture'], + }, + component: RouterView, + redirect: { name: 'TicketMain' }, children: [ { name: 'TicketMain', path: '', - component: () => import('src/components/common/VnSectionMain.vue'), - redirect: { name: 'TicketList' }, + component: () => import('src/components/common/VnModule.vue'), + redirect: { name: 'TicketIndexMain' }, children: [ { - path: 'list', - name: 'TicketList', - meta: { - title: 'list', - icon: 'view_list', - }, + path: '', + name: 'TicketIndexMain', + redirect: { name: 'TicketList' }, component: () => import('src/pages/Ticket/TicketList.vue'), + children: [ + { + name: 'TicketList', + path: 'list', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + ticketCard, + ], }, { path: 'create', @@ -86,170 +258,5 @@ export default { }, ], }, - { - name: 'TicketCard', - path: ':id', - component: () => import('src/pages/Ticket/Card/TicketCard.vue'), - redirect: { name: 'TicketSummary' }, - children: [ - { - path: 'summary', - name: 'TicketSummary', - meta: { - title: 'summary', - icon: 'launch', - }, - component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), - }, - { - path: 'basic-data', - name: 'TicketBasicData', - meta: { - title: 'basicData', - icon: 'vn:settings', - }, - component: () => - import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), - }, - { - path: 'sale', - name: 'TicketSale', - meta: { - title: 'sale', - icon: 'vn:lines', - }, - component: () => import('src/pages/Ticket/Card/TicketSale.vue'), - }, - { - path: 'request', - name: 'TicketPurchaseRequest', - meta: { - title: 'purchaseRequest', - icon: 'vn:buyrequest', - }, - component: () => - import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), - }, - { - path: 'tracking', - name: 'TicketTracking', - meta: { - title: 'tracking', - icon: 'vn:eye', - }, - component: () => import('src/pages/Ticket/Card/TicketTracking.vue'), - }, - { - path: 'log', - name: 'TicketLog', - meta: { - title: 'log', - icon: 'history', - }, - component: () => import('src/pages/Ticket/Card/TicketLog.vue'), - }, - { - path: 'observation', - name: 'TicketNotes', - meta: { - title: 'notes', - icon: 'vn:notes', - }, - component: () => import('src/pages/Ticket/Card/TicketNotes.vue'), - }, - { - path: 'picture', - name: 'TicketPicture', - meta: { - title: 'pictures', - icon: 'vn:photo', - }, - component: () => import('src/pages/Ticket/Card/TicketPicture.vue'), - }, - { - path: 'volume', - name: 'TicketVolume', - meta: { - title: 'volume', - icon: 'vn:volume', - }, - component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), - }, - { - path: 'expedition', - name: 'TicketExpedition', - meta: { - title: 'expedition', - icon: 'vn:package', - }, - component: () => import('src/pages/Ticket/Card/TicketExpedition.vue'), - }, - { - path: 'service', - name: 'TicketService', - meta: { - title: 'services', - icon: 'vn:services', - }, - component: () => import('src/pages/Ticket/Card/TicketService.vue'), - }, - { - path: 'package', - name: 'TicketPackage', - meta: { - title: 'packages', - icon: 'vn:bucket', - }, - component: () => import('src/pages/Ticket/Card/TicketPackage.vue'), - }, - { - path: 'components', - name: 'TicketComponents', - meta: { - title: 'components', - icon: 'vn:components', - }, - component: () => import('src/pages/Ticket/Card/TicketComponents.vue'), - }, - - { - path: 'sale-tracking', - name: 'TicketSaleTracking', - meta: { - title: 'saleTracking', - icon: 'assignment', - }, - component: () => - import('src/pages/Ticket/Card/TicketSaleTracking.vue'), - }, - { - path: 'dms', - name: 'TicketDms', - meta: { - title: 'dms', - icon: 'cloud_upload', - }, - component: () => import('src/pages/Ticket/Card/TicketDms.vue'), - }, - { - path: 'boxing', - name: 'TicketBoxing', - meta: { - title: 'boxing', - icon: 'science', - }, - component: () => import('src/pages/Ticket/Card/TicketBoxing.vue'), - }, - { - path: 'sms', - name: 'TicketSms', - meta: { - title: 'sms', - icon: 'sms', - }, - component: () => import('src/pages/Ticket/Card/TicketSms.vue'), - }, - ], - }, ], }; From bda4fe62af0b5a487ce96ab4425ed67451a08ae2 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 18 Dec 2024 16:43:26 +0100 Subject: [PATCH 113/142] feat: refs #7189 add Accept-Language header to axios requests --- src/boot/axios.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/boot/axios.js b/src/boot/axios.js index aee38e88725..3f9fadee5cd 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -3,12 +3,12 @@ import { useSession } from 'src/composables/useSession'; import { Router } from 'src/router'; import useNotify from 'src/composables/useNotify.js'; import { useStateQueryStore } from 'src/stores/useStateQueryStore'; +import { i18n } from 'src/boot/i18n'; const session = useSession(); const { notify } = useNotify(); const stateQuery = useStateQueryStore(); const baseUrl = '/api/'; - axios.defaults.baseURL = baseUrl; const axiosNoError = axios.create({ baseURL: baseUrl }); @@ -16,6 +16,7 @@ const onRequest = (config) => { const token = session.getToken(); if (token.length && !config.headers.Authorization) { config.headers.Authorization = token; + config.headers['Accept-Language'] = i18n.global.locale.value; } stateQuery.add(config); return config; From 777bdec0c772def71c08e0f46025c4163adea427 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 18 Dec 2024 16:49:06 +0100 Subject: [PATCH 114/142] test: refs #7189 enable skipped test --- test/cypress/integration/vnComponent/VnLog.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cypress/integration/vnComponent/VnLog.spec.js b/test/cypress/integration/vnComponent/VnLog.spec.js index 4db724e997c..80b9d07dfd0 100644 --- a/test/cypress/integration/vnComponent/VnLog.spec.js +++ b/test/cypress/integration/vnComponent/VnLog.spec.js @@ -9,15 +9,15 @@ describe('VnLog', () => { cy.visit(`/#/claim/${1}/log`); cy.openRightMenu(); }); - // Se tiene que cambiar el Accept-Language a 'en', ya hay una tarea para eso #7189. - xit('should filter by insert actions', () => { + + it('should filter by insert actions', () => { cy.checkOption(':nth-child(7) > .q-checkbox'); cy.get('.q-page').click(); cy.validateContent(chips[0], 'Document'); cy.validateContent(chips[1], 'Beginning'); }); - xit('should filter by entity', () => { + it('should filter by entity', () => { cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim'); cy.get('.q-page').click(); cy.validateContent(chips[0], 'Claim'); From 21ebc854057d6e34be33d20d63f9b73d93f66bd2 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 18 Dec 2024 17:15:43 +0100 Subject: [PATCH 115/142] test: refs #7189 add Accept-Language header to axios request tests --- test/vitest/__tests__/boot/axios.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/vitest/__tests__/boot/axios.spec.js b/test/vitest/__tests__/boot/axios.spec.js index 19d396ec529..b3b6f98c66e 100644 --- a/test/vitest/__tests__/boot/axios.spec.js +++ b/test/vitest/__tests__/boot/axios.spec.js @@ -1,4 +1,3 @@ -import { Notify } from 'quasar'; import { onRequest, onResponseError } from 'src/boot/axios'; import { describe, expect, it, vi } from 'vitest'; @@ -27,6 +26,7 @@ describe('Axios boot', () => { expect(resultConfig).toEqual( expect.objectContaining({ headers: { + 'Accept-Language': 'en-US', Authorization: 'DEFAULT_TOKEN', }, }) From adbb574025c2905c274fbd5fa857dd73fc3ac04a Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Wed, 18 Dec 2024 17:53:41 +0100 Subject: [PATCH 116/142] fix: refs #7189 update user language on sessionStorage --- src/boot/i18n.js | 4 +++- src/components/UserPanel.vue | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/boot/i18n.js b/src/boot/i18n.js index b23b6d5fde4..85d0772a3e1 100644 --- a/src/boot/i18n.js +++ b/src/boot/i18n.js @@ -1,9 +1,11 @@ import { boot } from 'quasar/wrappers'; import { createI18n } from 'vue-i18n'; import messages from 'src/i18n'; +import { useState } from 'src/composables/useState'; +const user = useState().getUser(); const i18n = createI18n({ - locale: navigator.language || navigator.userLanguage, + locale: user.value.lang || navigator.language || navigator.userLanguage, fallbackLocale: 'en', globalInjection: true, messages, diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue index 810f6304487..a0ef73a1fe1 100644 --- a/src/components/UserPanel.vue +++ b/src/components/UserPanel.vue @@ -87,10 +87,10 @@ async function saveDarkMode(value) { async function saveLanguage(value) { const query = `/VnUsers/${user.value.id}`; try { - await axios.patch(query, { - lang: value, - }); + await axios.patch(query, { lang: value }); + user.value.lang = value; + useState().setUser(user.value); onDataSaved(); } catch (error) { onDataError(); From e704a4214ce49071f9496a05e23139dc989dfed1 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 19 Dec 2024 10:32:51 +0100 Subject: [PATCH 117/142] refactor: refs #8004 remove unused stateStore import in InvoiceInList.vue --- src/pages/InvoiceIn/InvoiceInList.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index 48f15755c63..db6e7d21418 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -1,7 +1,6 @@ <script setup> import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useStateStore } from 'stores/useStateStore'; import { useState } from 'src/composables/useState'; import { downloadFile } from 'src/composables/downloadFile'; import { toDate, toCurrency } from 'src/filters/index'; @@ -17,7 +16,6 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import FetchData from 'src/components/FetchData.vue'; -const stateStore = useStateStore(); const user = useState().getUser(); const { viewSummary } = useSummaryDialog(); const { t } = useI18n(); From 648b1269ee4ee1fab6903282fff73339bbe663f3 Mon Sep 17 00:00:00 2001 From: pablone <pablone@verdnatura.es> Date: Thu, 19 Dec 2024 10:35:04 +0100 Subject: [PATCH 118/142] refactor: refs #8004 remove unused travelFilterRef and chip definition in TravelList.vue --- src/pages/Travel/TravelList.vue | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index 4c471915a69..4d2912bf76e 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -25,8 +25,6 @@ const $props = defineProps({ }); const entityId = computed(() => $props.id || route.params.id); -const travelFilterRef = ref(); - const cloneTravel = (travelData) => { const stringifiedTravelData = JSON.stringify(travelData); redirectToCreateView(stringifiedTravelData); @@ -53,9 +51,6 @@ const columns = computed(() => [ condition: () => true, }, isId: true, - chip: { - condition: () => true, - }, }, { align: 'left', From 0103927ce7e3365359f54d45c36fa6c93a23fe50 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 19 Dec 2024 11:03:15 +0100 Subject: [PATCH 119/142] fix: handle non-object options --- src/components/common/VnSelect.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 758fb92287b..1a69bc6c19a 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -379,7 +379,8 @@ function handleKeyDown(event) { </template> <template #option="{ opt, itemProps }"> <QItem v-bind="itemProps"> - <QItemSection v-if="opt[optionValue] == opt[optionLabel]"> + <QItemSection v-if="typeof opt !== 'object'"> {{ opt }}</QItemSection> + <QItemSection v-else-if="opt[optionValue] == opt[optionLabel]"> <QItemLabel>{{ opt[optionLabel] }}</QItemLabel> </QItemSection> <QItemSection v-else> From 5125b576634b9c829b16fb4fe41ac12e3edde5ff Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 19 Dec 2024 12:15:46 +0100 Subject: [PATCH 120/142] fix: refs #7133 handleSalesModelValue function to handle empty input --- src/pages/Customer/Card/CustomerBasicData.vue | 19 +++++++++++-------- src/pages/Customer/CustomerFilter.vue | 19 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index 768c66f327d..e9a349e0b94 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -16,14 +16,17 @@ const { t } = useI18n(); const businessTypes = ref([]); const contactChannels = ref([]); -const handleSalesModelValue = (val) => ({ - or: [ - { id: val }, - { name: val }, - { nickname: { like: '%' + val + '%' } }, - { code: { like: `${val}%` } }, - ], -}); +const handleSalesModelValue = (val) => { + if (!val) val = ''; + return { + or: [ + { id: val }, + { name: val }, + { nickname: { like: '%' + val + '%' } }, + { code: { like: `${val}%` } }, + ], + }; +}; const exprBuilder = (param, value) => { return { diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 96f67054289..c62ec7dbb0d 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -12,14 +12,17 @@ defineProps({ required: true, }, }); -const handleSalesModelValue = (val) => ({ - or: [ - { id: val }, - { name: val }, - { nickname: { like: '%' + val + '%' } }, - { code: { like: `${val}%` } }, - ], -}); +const handleSalesModelValue = (val) => { + if (!val) val = ''; + return { + or: [ + { id: val }, + { name: val }, + { nickname: { like: '%' + val + '%' } }, + { code: { like: `${val}%` } }, + ], + }; +}; const exprBuilder = (param, value) => { return { From 3fb9b0de47521752bf39c8f39acf1e44184fe349 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Thu, 19 Dec 2024 12:32:15 +0100 Subject: [PATCH 121/142] refactor: refs #8320 moved front tests to their respective sections --- src/boot/specs/axios.spec.js | 65 ++++++ src/components/VnTable/specs/VnTable.spec.js | 47 ++++ .../common/specs/VnChangePassword.spec.js | 70 ++++++ .../common/specs/VnDiscount.spec.js | 28 +++ src/components/common/specs/VnLog.spec.js | 134 +++++++++++ .../common/specs/VnSmsDialog.spec.js | 58 +++++ src/components/specs/CrudModel.spec.js | 120 ++++++++++ src/components/specs/Leftmenu.spec.js | 94 ++++++++ src/components/ui/specs/Paginate.spec.js | 118 ++++++++++ src/components/ui/specs/VnLinkPhone.spec.js | 50 +++++ src/components/ui/specs/VnSms.spec.js | 25 +++ src/composables/specs/downloadFile.spec.js | 36 +++ src/composables/specs/getExchange.spec.js | 45 ++++ src/composables/specs/getTotal.spec.js | 55 +++++ .../specs/useAccountShortToStandard.spec.js | 9 + src/composables/specs/useAcl.spec.js | 110 +++++++++ src/composables/specs/useArrayData.spec.js | 98 ++++++++ src/composables/specs/useRole.spec.js | 73 ++++++ src/composables/specs/useSession.spec.js | 211 ++++++++++++++++++ src/composables/specs/useTokenConfig.spec.js | 31 +++ .../Card/specs/ClaimDescriptorMenu.spec.js | 33 +++ src/pages/Claim/Card/specs/ClaimLines.spec.js | 75 +++++++ .../Claim/Card/specs/ClaimLinesImport.spec.js | 47 ++++ src/pages/Claim/Card/specs/ClaimPhoto.spec.js | 114 ++++++++++ .../Payments/specs/CustomerPayments.spec.js | 38 ++++ src/pages/Login/specs/Login.spec.js | 49 ++++ .../Ticket/Card/specs/TicketBoxing.spec.js | 50 +++++ src/pages/Ticket/specs/TicketAdvance.spec.js | 120 ++++++++++ src/pages/Wagon/specs/WagonCreate.spec.js | 97 ++++++++ .../specs/WorkerNotificationsManager.spec.js | 33 +++ src/stores/specs/useStateQueryStore.spec.js | 58 +++++ 31 files changed, 2191 insertions(+) create mode 100644 src/boot/specs/axios.spec.js create mode 100644 src/components/VnTable/specs/VnTable.spec.js create mode 100644 src/components/common/specs/VnChangePassword.spec.js create mode 100644 src/components/common/specs/VnDiscount.spec.js create mode 100644 src/components/common/specs/VnLog.spec.js create mode 100644 src/components/common/specs/VnSmsDialog.spec.js create mode 100644 src/components/specs/CrudModel.spec.js create mode 100644 src/components/specs/Leftmenu.spec.js create mode 100644 src/components/ui/specs/Paginate.spec.js create mode 100644 src/components/ui/specs/VnLinkPhone.spec.js create mode 100644 src/components/ui/specs/VnSms.spec.js create mode 100644 src/composables/specs/downloadFile.spec.js create mode 100644 src/composables/specs/getExchange.spec.js create mode 100644 src/composables/specs/getTotal.spec.js create mode 100644 src/composables/specs/useAccountShortToStandard.spec.js create mode 100644 src/composables/specs/useAcl.spec.js create mode 100644 src/composables/specs/useArrayData.spec.js create mode 100644 src/composables/specs/useRole.spec.js create mode 100644 src/composables/specs/useSession.spec.js create mode 100644 src/composables/specs/useTokenConfig.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimLines.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimLinesImport.spec.js create mode 100644 src/pages/Claim/Card/specs/ClaimPhoto.spec.js create mode 100644 src/pages/Customer/Payments/specs/CustomerPayments.spec.js create mode 100644 src/pages/Login/specs/Login.spec.js create mode 100644 src/pages/Ticket/Card/specs/TicketBoxing.spec.js create mode 100644 src/pages/Ticket/specs/TicketAdvance.spec.js create mode 100644 src/pages/Wagon/specs/WagonCreate.spec.js create mode 100644 src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js create mode 100644 src/stores/specs/useStateQueryStore.spec.js diff --git a/src/boot/specs/axios.spec.js b/src/boot/specs/axios.spec.js new file mode 100644 index 00000000000..b3b6f98c66e --- /dev/null +++ b/src/boot/specs/axios.spec.js @@ -0,0 +1,65 @@ +import { onRequest, onResponseError } from 'src/boot/axios'; +import { describe, expect, it, vi } from 'vitest'; + +vi.mock('src/composables/useSession', () => ({ + useSession: () => ({ + getToken: () => 'DEFAULT_TOKEN', + isLoggedIn: () => vi.fn(), + destroy: () => vi.fn(), + }), +})); + +vi.mock('src/stores/useStateQueryStore', () => ({ + useStateQueryStore: () => ({ + add: () => vi.fn(), + remove: () => vi.fn(), + }), +})); + +describe('Axios boot', () => { + describe('onRequest()', async () => { + it('should set the "Authorization" property on the headers', async () => { + const config = { headers: {} }; + + const resultConfig = onRequest(config); + + expect(resultConfig).toEqual( + expect.objectContaining({ + headers: { + 'Accept-Language': 'en-US', + Authorization: 'DEFAULT_TOKEN', + }, + }) + ); + }); + }); + + describe('onResponseError()', async () => { + it('should call to the Notify plugin with a message error for an status code "500"', async () => { + const error = { + response: { + status: 500, + }, + }; + + const result = onResponseError(error); + expect(result).rejects.toEqual(expect.objectContaining(error)); + }); + + it('should call to the Notify plugin with a message from the response property', async () => { + const error = { + response: { + status: 401, + data: { + error: { + message: 'Invalid user or password', + }, + }, + }, + }; + + const result = onResponseError(error); + expect(result).rejects.toEqual(expect.objectContaining(error)); + }); + }); +}); diff --git a/src/components/VnTable/specs/VnTable.spec.js b/src/components/VnTable/specs/VnTable.spec.js new file mode 100644 index 00000000000..162df727dd6 --- /dev/null +++ b/src/components/VnTable/specs/VnTable.spec.js @@ -0,0 +1,47 @@ +import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnTable from 'src/components/VnTable/VnTable.vue'; + +describe('VnTable', () => { + let wrapper; + let vm; + + beforeAll(() => { + wrapper = createWrapper(VnTable, { + propsData: { + columns: [], + }, + }); + vm = wrapper.vm; + }); + + beforeEach(() => (vm.selected = [])); + + describe('handleSelection()', () => { + const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }]; + const selectedRows = [{ $index: 1 }]; + it('should add rows to selected when shift key is pressed and rows are added except last one', () => { + vm.handleSelection( + { evt: { shiftKey: true }, added: true, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([{ $index: 0 }]); + }); + + it('should not add rows to selected when shift key is not pressed', () => { + vm.handleSelection( + { evt: { shiftKey: false }, added: true, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([]); + }); + + it('should not add rows to selected when rows are not added', () => { + vm.handleSelection( + { evt: { shiftKey: true }, added: false, rows: selectedRows }, + rows + ); + expect(vm.selected).toEqual([]); + }); + }); +}); diff --git a/src/components/common/specs/VnChangePassword.spec.js b/src/components/common/specs/VnChangePassword.spec.js new file mode 100644 index 00000000000..f5a967bb50b --- /dev/null +++ b/src/components/common/specs/VnChangePassword.spec.js @@ -0,0 +1,70 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; +import { vi, beforeEach, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { Notify } from 'quasar'; + +describe('VnSmsDialog', () => { + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + vm = createWrapper(VnChangePassword, { + propsData: { + submitFn: vi.fn(), + }, + }).vm; + }); + + beforeEach(() => { + Notify.create = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should notify when new password is empty', async () => { + vm.passwords.newPassword = ''; + vm.passwords.repeatPassword = 'password'; + + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'You must enter a new password', + type: 'negative', + }) + ); + }); + + it("should notify when passwords don't match", async () => { + vm.passwords.newPassword = 'password1'; + vm.passwords.repeatPassword = 'password2'; + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: `Passwords don't match`, + type: 'negative', + }) + ); + }); + + describe('if passwords match', () => { + it('should call submitFn and emit password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', undefined); + }); + + it('should call submitFn and emit password and old password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + vm.passwords.oldPassword = 'oldPassword'; + + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', 'oldPassword'); + }); + }); +}); diff --git a/src/components/common/specs/VnDiscount.spec.js b/src/components/common/specs/VnDiscount.spec.js new file mode 100644 index 00000000000..5d5be61ac7e --- /dev/null +++ b/src/components/common/specs/VnDiscount.spec.js @@ -0,0 +1,28 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnDiscount from 'components/common/vnDiscount.vue'; + +describe('VnDiscount', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(VnDiscount, { + props: { + data: {}, + price: 100, + quantity: 2, + discount: 10, + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('total', () => { + it('should calculate total correctly', () => { + expect(vm.total).toBe(180); + }); + }); +}); \ No newline at end of file diff --git a/src/components/common/specs/VnLog.spec.js b/src/components/common/specs/VnLog.spec.js new file mode 100644 index 00000000000..53d2732a074 --- /dev/null +++ b/src/components/common/specs/VnLog.spec.js @@ -0,0 +1,134 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnLog from 'src/components/common/VnLog.vue'; + +describe('VnLog', () => { + let vm; + const fakeLogTreeData = [ + { + id: 2, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'ClaimObservation', + oldInstance: {}, + newInstance: { + claimFk: 1, + text: 'Waiting for customer', + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + { + id: 1, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'Claim', + oldInstance: { + pickup: null, + }, + newInstance: { + pickup: 'agency', + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + ]; + const mockValidations = { + Claim: { + locale: { + name: 'reclamación', + }, + }, + ClaimObservation: { + locale: { + name: 'observación', + }, + }, + ClaimDms: { + locale: { + name: 'documento', + }, + }, + ClaimBeginning: { + locale: { + name: 'comienzo', + }, + }, + }; + + beforeAll(async () => { + axios.get.mockImplementation(() => { + return { data: fakeLogTreeData }; + }); + + vm = createWrapper(VnLog, { + global: { + stubs: [], + mocks: {}, + }, + propsData: { + model: 'Claim', + }, + }).vm; + vm.validations = mockValidations; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should correctly set logTree', async () => { + vm.logTree = vm.getLogTree(fakeLogTreeData); + expect(vm.logTree[0].originFk).toEqual(1); + expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); + }); + + it('should correctly set the selectedFilters when filtering', () => { + vm.searchInput = '1'; + vm.userSelect = '21'; + vm.checkboxOptions.insert.selected = true; + vm.checkboxOptions.update.selected = true; + + vm.selectFilter('search'); + vm.selectFilter('userSelect'); + + expect(vm.selectedFilters.changedModelId).toEqual('1'); + expect(vm.selectedFilters.userFk).toEqual('21'); + expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); + }); + + it('should correctly set the date from', () => { + vm.dateFrom = '18-09-2023'; + vm.selectFilter('date', 'from'); + expect(vm.selectedFilters.creationDate.between).toEqual([ + new Date('2023-09-18T00:00:00.000Z'), + new Date('2023-09-18T21:59:59.999Z'), + ]); + }); +}); diff --git a/src/components/common/specs/VnSmsDialog.spec.js b/src/components/common/specs/VnSmsDialog.spec.js new file mode 100644 index 00000000000..0b34739826a --- /dev/null +++ b/src/components/common/specs/VnSmsDialog.spec.js @@ -0,0 +1,58 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import VnSmsDialog from 'components/common/VnSmsDialog.vue'; +import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; + + +describe('VnSmsDialog', () => { + let vm; + const orderId = 1; + const shipped = new Date(); + const phone = '012345678'; + const promise = (response) => {return response;}; + const template = 'minAmount'; + const locale = 'en'; + + beforeAll(() => { + vm = createWrapper(VnSmsDialog, { + propsData: { + data: { + orderId, + shipped + }, + template, + locale, + phone, + promise + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('updateMessage()', () => { + it('should update the message value with the correct template and parameters', () => { + vm.updateMessage(); + + expect(vm.message).toEqual(`A minimum amount of 50€ (VAT excluded) is required for your order ${orderId} of ${shipped} to receive it without additional shipping costs.`); + }); + }); + + describe('send()', async () => { + it('should send the message', async () => { + vi.spyOn(vm.props, 'promise'); + vm.message = 'Example message'; + const response = { + orderId, + shipped, + destination: phone, + message: vm.message + }; + await vm.send(); + + expect(vm.isLoading).toEqual(false); + expect(vm.props.promise).toHaveBeenCalledWith(response); + }); + }); +}); diff --git a/src/components/specs/CrudModel.spec.js b/src/components/specs/CrudModel.spec.js new file mode 100644 index 00000000000..6ce93e59c68 --- /dev/null +++ b/src/components/specs/CrudModel.spec.js @@ -0,0 +1,120 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import CrudModel from 'components/CrudModel.vue'; +import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; + +describe('CrudModel', () => { + let vm; + beforeAll(() => { + vm = createWrapper(CrudModel, { + global: { + stubs: [ + 'vnPaginate', + 'useState', + 'arrayData', + 'useStateStore', + 'vue-i18n', + ], + mocks: { + validate: vi.fn(), + }, + }, + propsData: { + dataRequired: { + fk: 1, + }, + dataKey: 'crudModelKey', + model: 'crudModel', + url: 'crudModelUrl', + }, + }).vm; + }); + + beforeEach(() => { + vm.fetch([]); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('insert()', () => { + it('should new element in list with index 0 if formData not has data', () => { + vm.insert(); + + expect(vm.formData.length).toEqual(1); + expect(vm.formData[0].fk).toEqual(1); + expect(vm.formData[0].$index).toEqual(0); + }); + }); + + describe('getChanges()', () => { + it('should return correct updates and creates', async () => { + vm.fetch([ + { id: 1, name: 'New name one' }, + { id: 2, name: 'New name two' }, + { id: 3, name: 'Bruce Wayne' }, + ]); + + vm.originalData = [ + { id: 1, name: 'Tony Starks' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; + + vm.insert(); + const result = vm.getChanges(); + + const expected = { + creates: [ + { + $index: 3, + fk: 1, + }, + ], + updates: [ + { + data: { + name: 'New name one', + }, + where: { + id: 1, + }, + }, + { + data: { + name: 'New name two', + }, + where: { + id: 2, + }, + }, + ], + }; + + expect(result).toEqual(expected); + }); + }); + + describe('getDifferences()', () => { + it('should return the differences between two objects', async () => { + const obj1 = { + a: 1, + b: 2, + c: 3, + }; + const obj2 = { + a: null, + b: 4, + d: 5, + }; + + const result = vm.getDifferences(obj1, obj2); + + expect(result).toEqual({ + a: null, + b: 4, + d: 5, + }); + }); + }); +}); diff --git a/src/components/specs/Leftmenu.spec.js b/src/components/specs/Leftmenu.spec.js new file mode 100644 index 00000000000..10d9d66fbfb --- /dev/null +++ b/src/components/specs/Leftmenu.spec.js @@ -0,0 +1,94 @@ +import { vi, describe, expect, it, beforeAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import Leftmenu from 'components/LeftMenu.vue'; + +import { useNavigationStore } from 'src/stores/useNavigationStore'; + +vi.mock('src/router/modules', () => ({ + default: [ + { + path: '/customer', + name: 'Customer', + meta: { + title: 'customers', + icon: 'vn:client', + }, + menus: { + main: ['CustomerList', 'CustomerCreate'], + card: ['CustomerBasicData'], + }, + children: [ + { + path: '', + name: 'CustomerMain', + children: [ + { + path: 'list', + name: 'CustomerList', + meta: { + title: 'list', + icon: 'view_list', + }, + }, + { + path: 'create', + name: 'CustomerCreate', + meta: { + title: 'createCustomer', + icon: 'vn:addperson', + }, + }, + ], + }, + ], + }, + ], +})); + +describe('Leftmenu', () => { + let vm; + let navigation; + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + + vm = createWrapper(Leftmenu, { + propsData: { + source: 'main', + }, + }).vm; + + navigation = useNavigationStore(); + navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true)); + navigation.getModules = vi.fn().mockReturnValue({ + value: [ + { + name: 'customer', + title: 'customer.pageTitles.customers', + icon: 'vn:customer', + module: 'customer', + }, + ], + }); + }); + + it('should return a proper formated object with two child items', async () => { + const expectedMenuItem = [ + { + children: null, + name: 'CustomerList', + title: 'globals.pageTitles.list', + icon: 'view_list', + }, + { + children: null, + name: 'CustomerCreate', + title: 'globals.pageTitles.createCustomer', + icon: 'vn:addperson', + }, + ]; + const firstMenuItem = vm.items[0]; + expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem)); + }); +}); diff --git a/src/components/ui/specs/Paginate.spec.js b/src/components/ui/specs/Paginate.spec.js new file mode 100644 index 00000000000..a67dfcdc638 --- /dev/null +++ b/src/components/ui/specs/Paginate.spec.js @@ -0,0 +1,118 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnPaginate from 'src/components/ui/VnPaginate.vue'; + +describe('VnPaginate', () => { + const expectedUrl = '/api/customers'; + const defaultData = [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + { id: 3, name: 'Bruce Wayne' }, + ]; + let vm; + beforeAll(() => { + const options = { + attrs: { + url: expectedUrl, + dataKey: 'CustomerList', + order: 'id DESC', + limit: 3, + }, + }; + vm = createWrapper(VnPaginate, options).vm; + }); + + afterEach(() => { + vm.store.data = []; + vm.store.skip = 0; + vm.pagination.page = 1; + vm.hasMoreData = true; + }); + + describe('paginate()', () => { + it('should call to the paginate() method and set the data on the rows property', async () => { + vi.spyOn(vm.arrayData, 'loadMore'); + vm.store.data = defaultData; + + await vm.paginate(); + + expect(vm.arrayData.loadMore).toHaveBeenCalledWith(); + expect(vm.store.data.length).toEqual(3); + }); + + it('should call to the paginate() method and then call it again to paginate', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: defaultData, + }); + vm.store.hasMoreData = true; + await vm.$nextTick(); + + vm.store.data = defaultData; + + await vm.paginate(); + + expect(vm.store.skip).toEqual(3); + expect(vm.store.data.length).toEqual(6); + + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 4, name: 'Peter Parker' }, + { id: 5, name: 'Clark Kent' }, + { id: 6, name: 'Barry Allen' }, + ], + }); + await vm.paginate(); + + expect(vm.store.skip).toEqual(6); + expect(vm.store.data.length).toEqual(9); + }); + }); + + describe('onLoad()', () => { + it('should call to the done() callback and not increment the pagination', async () => { + const index = 1; + const done = vi.fn(); + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(1); + expect(done).toHaveBeenCalledWith(false); + }); + + it('should increment the pagination and then call to the done() callback', async () => { + expect(vm.pagination.page).toEqual(1); + + const index = 1; + const done = vi.fn(); + vm.store.data = defaultData; + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(2); + expect(done).toHaveBeenCalledWith(false); + }); + + it('should call to the done() callback with true as argument to finish pagination', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [ + { id: 1, name: 'Tony Stark' }, + { id: 2, name: 'Jessica Jones' }, + ], + }); + + vm.store.data = defaultData; + + expect(vm.pagination.page).toEqual(1); + + const index = 1; + const done = vi.fn(); + + vm.hasMoreData = false; + + await vm.onLoad(index, done); + + expect(vm.pagination.page).toEqual(2); + expect(done).toHaveBeenCalledWith(false); + }); + }); +}); diff --git a/src/components/ui/specs/VnLinkPhone.spec.js b/src/components/ui/specs/VnLinkPhone.spec.js new file mode 100644 index 00000000000..a34ef90a514 --- /dev/null +++ b/src/components/ui/specs/VnLinkPhone.spec.js @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeAll, vi } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import parsePhone from 'src/filters/parsePhone'; + +describe('parsePhone filter', () => { + beforeAll(async () => { + vi.spyOn(axios, 'get').mockReturnValue({ data: { prefix: '34' } }); + }); + + it('no phone', async () => { + const phone = await parsePhone(null, '34'); + expect(phone).toBe(undefined); + }); + + it("adds prefix +34 if it doesn't have one", async () => { + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('maintains prefix +34 if it is already correct', async () => { + const phone = await parsePhone('+34123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('converts prefix 0034 to +34', async () => { + const phone = await parsePhone('0034123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('converts prefix 34 without symbol to +34', async () => { + const phone = await parsePhone('34123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('replaces incorrect prefix with the correct one', async () => { + const phone = await parsePhone('+44123456789', '34'); + expect(phone).toBe('44123456789'); + }); + + it('adds default prefix on error', async () => { + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url.includes('Prefixes')) + return Promise.reject(new Error('Network error')); + else if (url.includes('PbxConfigs')) + return Promise.resolve({ data: { defaultPrefix: '39' } }); + }); + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('39123456789'); + }); +}); diff --git a/src/components/ui/specs/VnSms.spec.js b/src/components/ui/specs/VnSms.spec.js new file mode 100644 index 00000000000..e0f8c1868bd --- /dev/null +++ b/src/components/ui/specs/VnSms.spec.js @@ -0,0 +1,25 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnSms from 'src/components/ui/VnSms.vue'; + +describe('VnSms', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(VnSms, { + global: { + stubs: ['VnPaginate'], + mocks: {}, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should format number correctly', () => { + const formattedNumber = vm.formatNumber('123456789012'); + expect(formattedNumber).toBe('1234 56789012'); + }); +}); diff --git a/src/composables/specs/downloadFile.spec.js b/src/composables/specs/downloadFile.spec.js new file mode 100644 index 00000000000..f53b56b3e3d --- /dev/null +++ b/src/composables/specs/downloadFile.spec.js @@ -0,0 +1,36 @@ +import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import { downloadFile } from 'src/composables/downloadFile'; +import { useSession } from 'src/composables/useSession'; +const session = useSession(); +const token = session.getToken(); + +describe('downloadFile', () => { + const baseUrl = 'http://localhost:9000'; + let defaulCreateObjectURL; + + beforeAll(() => { + defaulCreateObjectURL = window.URL.createObjectURL; + window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); + }); + + afterAll(() => (window.URL.createObjectURL = defaulCreateObjectURL)); + + it('should open a new window to download the file', async () => { + const res = { + data: new Blob(['file content'], { type: 'application/octet-stream' }), + headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, + }; + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); + else if (url.includes('downloadFile')) return Promise.resolve(res); + }); + + await downloadFile(1); + + expect(axios.get).toHaveBeenCalledWith( + `${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, + { responseType: 'blob' } + ); + }); +}); diff --git a/src/composables/specs/getExchange.spec.js b/src/composables/specs/getExchange.spec.js new file mode 100644 index 00000000000..dba31458ee1 --- /dev/null +++ b/src/composables/specs/getExchange.spec.js @@ -0,0 +1,45 @@ +import { describe, expect, it, vi } from 'vitest'; +import axios from 'axios'; +import { getExchange } from 'src/composables/getExchange'; + +vi.mock('axios'); + +describe('getExchange()', () => { + it('should return the correct exchange rate', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBe('83.33'); + }); + + it('should return the correct exchange rate with custom decimal places', async () => { + axios.get.mockResolvedValue({ + data: { value: 1.2 }, + }); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const decimalPlaces = 3; + const result = await getExchange(amount, currencyFk, dated, decimalPlaces); + + expect(result).toBe('83.333'); + }); + + it('should return null if the API call fails', async () => { + axios.get.mockRejectedValue(new Error('Network error')); + + const amount = 100; + const currencyFk = 1; + const dated = '2023-01-01'; + const result = await getExchange(amount, currencyFk, dated); + + expect(result).toBeNull(); + }); +}); diff --git a/src/composables/specs/getTotal.spec.js b/src/composables/specs/getTotal.spec.js new file mode 100644 index 00000000000..789e3fbcfe0 --- /dev/null +++ b/src/composables/specs/getTotal.spec.js @@ -0,0 +1,55 @@ +import { vi, describe, expect, it } from 'vitest'; +import { getTotal } from 'src/composables/getTotal'; + +vi.mock('src/filters', () => ({ + toCurrency: vi.fn((value, currency) => `${currency} ${value.toFixed(2)}`), +})); + +describe('getTotal()', () => { + const rows = [ + { amount: 10.5, tax: 2.1 }, + { amount: 20.75, tax: 3.25 }, + { amount: 30.25, tax: 4.75 }, + ]; + + it('should calculate the total for a given key', () => { + const total = getTotal(rows, 'amount'); + expect(total).toBe('61.50'); + }); + + it('should calculate the total with a callback function', () => { + const total = getTotal(rows, null, { cb: (row) => row.amount + row.tax }); + expect(total).toBe('71.60'); + }); + + it('should format the total as currency', () => { + const total = getTotal(rows, 'amount', { currency: 'USD' }); + expect(total).toBe('USD 61.50'); + }); + + it('should format the total as currency with default currency', () => { + const total = getTotal(rows, 'amount', { currency: 'default' }); + expect(total).toBe('undefined 61.50'); + }); + + it('should calculate the total with integer formatting', () => { + const total = getTotal(rows, 'amount', { decimalPlaces: 0 }); + expect(total).toBe('62'); + }); + + it('should calculate the total with custom decimal places', () => { + const total = getTotal(rows, 'amount', { decimalPlaces: 1 }); + expect(total).toBe('61.5'); + }); + + it('should handle rows with missing keys', () => { + const rowsWithMissingKeys = [{ amount: 10.5 }, { amount: 20.75 }, {}]; + const total = getTotal(rowsWithMissingKeys, 'amount'); + expect(total).toBe('31.25'); + }); + + it('should handle empty rows', () => { + const total = getTotal([], 'amount'); + expect(total).toBe('0.00'); + }); +}); diff --git a/src/composables/specs/useAccountShortToStandard.spec.js b/src/composables/specs/useAccountShortToStandard.spec.js new file mode 100644 index 00000000000..d2458581210 --- /dev/null +++ b/src/composables/specs/useAccountShortToStandard.spec.js @@ -0,0 +1,9 @@ +import { describe, expect, it } from 'vitest'; +import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; + +describe('useAccountShortToStandard()', () => { + it('should pad the decimal part with zeros for short numbers', () => { + expect(useAccountShortToStandard('123.45')).toBe('1230000045'); + expect(useAccountShortToStandard('123.')).toBe('1230000000'); + }); +}); diff --git a/src/composables/specs/useAcl.spec.js b/src/composables/specs/useAcl.spec.js new file mode 100644 index 00000000000..6cb29984c48 --- /dev/null +++ b/src/composables/specs/useAcl.spec.js @@ -0,0 +1,110 @@ +import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useAcl } from 'src/composables/useAcl'; + +describe('useAcl', () => { + const acl = useAcl(); + const mockAcls = [ + { + model: 'Address', + property: '*', + accessType: '*', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: 'employee', + }, + { + model: 'Worker', + property: 'holidays', + accessType: 'READ', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: 'employee', + }, + { + model: 'Url', + property: 'getByUser', + accessType: 'READ', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: '$everyone', + }, + { + model: 'TpvTransaction', + property: 'start', + accessType: 'WRITE', + permission: 'ALLOW', + principalType: 'ROLE', + principalId: '$authenticated', + }, + ]; + + beforeAll(async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: mockAcls }); + await acl.fetch(); + }); + + afterAll(async () => await flushPromises()); + + describe('hasAny', () => { + it('should return false if no roles matched', async () => { + expect( + acl.hasAny([ + { model: 'Worker', props: 'updateAttributes', accessType: 'WRITE' }, + ]) + ).toBeFalsy(); + }); + + it('should return false if no roles matched', async () => { + expect( + acl.hasAny([{ model: 'Worker', props: 'holidays', accessType: 'READ' }]) + ).toBeTruthy(); + }); + + describe('*', () => { + it('should return true if an acl matched', async () => { + expect( + acl.hasAny([{ model: 'Address', props: '*', accessType: 'WRITE' }]) + ).toBeTruthy(); + }); + + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([{ model: 'Worker', props: '*', accessType: 'READ' }]) + ).toBeFalsy(); + }); + }); + + describe('$authenticated', () => { + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: '*' }]) + ).toBeFalsy(); + }); + + it('should return true if an acl matched', async () => { + expect( + acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: 'READ' }]) + ).toBeTruthy(); + }); + }); + + describe('$everyone', () => { + it('should return false if no acls matched', async () => { + expect( + acl.hasAny([ + { model: 'TpvTransaction', props: 'start', accessType: 'READ' }, + ]) + ).toBeFalsy(); + }); + + it('should return false if an acl matched', async () => { + expect( + acl.hasAny([ + { model: 'TpvTransaction', props: 'start', accessType: 'WRITE' }, + ]) + ).toBeTruthy(); + }); + }); + }); +}); diff --git a/src/composables/specs/useArrayData.spec.js b/src/composables/specs/useArrayData.spec.js new file mode 100644 index 00000000000..d4c5d094954 --- /dev/null +++ b/src/composables/specs/useArrayData.spec.js @@ -0,0 +1,98 @@ +import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useArrayData } from 'composables/useArrayData'; +import { useRouter } from 'vue-router'; +import * as vueRouter from 'vue-router'; + +describe('useArrayData', () => { + const filter = '{"limit":20,"skip":0}'; + const params = { supplierFk: 2 }; + beforeEach(() => { + vi.spyOn(useRouter(), 'replace'); + vi.spyOn(useRouter(), 'push'); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch and repalce url with new params', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerReplace = useRouter().replace.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerReplace.path).toEqual('mockSection/list'); + expect(JSON.parse(routerReplace.query.params)).toEqual( + expect.objectContaining(params) + ); + }); + + it('Should get data and send new URL without keeping parameters, if there is only one record', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerPush = useRouter().push.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerPush.path).toEqual('mockName/1'); + expect(routerPush.query).toBeUndefined(); + }); + + it('Should get data and send new URL keeping parameters, if you have more than one record', async () => { + vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] }); + + vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ + matched: [], + query: {}, + params: {}, + meta: { moduleName: 'mockName' }, + path: 'mockName/1', + }); + vi.spyOn(vueRouter, 'useRouter').mockReturnValue({ + push: vi.fn(), + replace: vi.fn(), + currentRoute: { + value: { + params: { + id: 1, + }, + meta: { moduleName: 'mockName' }, + matched: [{ path: 'mockName/:id' }], + }, + }, + }); + + const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); + + arrayData.store.userParams = params; + arrayData.fetch({}); + + await flushPromises(); + const routerPush = useRouter().push.mock.calls[0][0]; + + expect(axios.get.mock.calls[0][1].params).toEqual({ + filter, + supplierFk: 2, + }); + expect(routerPush.path).toEqual('mockName/'); + expect(routerPush.query.params).toBeDefined(); + }); +}); diff --git a/src/composables/specs/useRole.spec.js b/src/composables/specs/useRole.spec.js new file mode 100644 index 00000000000..d0bca5342f8 --- /dev/null +++ b/src/composables/specs/useRole.spec.js @@ -0,0 +1,73 @@ +import { vi, describe, expect, it } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useRole } from 'composables/useRole'; +const role = useRole(); + +describe('useRole', () => { + describe('fetch', () => { + it('should call setUser and setRoles of the state with the expected data', async () => { + const rolesData = [ + { + role: { + name: 'salesPerson', + }, + }, + { + role: { + name: 'admin', + }, + }, + ]; + const fetchedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + }; + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + }; + const expectedRoles = ['salesPerson', 'admin']; + vi.spyOn(axios, 'get') + .mockResolvedValueOnce({ + data: { roles: rolesData, user: fetchedUser }, + }) + + vi.spyOn(role.state, 'setUser'); + vi.spyOn(role.state, 'setRoles'); + + role.fetch(); + + await flushPromises(); + + expect(role.state.setUser).toHaveBeenCalledWith(expectedUser); + expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles); + + role.state.setRoles([]); + }); + }); + + describe('hasAny', () => { + it('should return true if a role matched', async () => { + role.state.setRoles(['admin']); + const hasRole = role.hasAny(['admin']); + + await flushPromises(); + + expect(hasRole).toBe(true); + + role.state.setRoles([]); + }); + + it('should return false if no roles matched', async () => { + const hasRole = role.hasAny(['admin']); + + await flushPromises(); + + expect(hasRole).toBe(false); + }); + }); +}); diff --git a/src/composables/specs/useSession.spec.js b/src/composables/specs/useSession.spec.js new file mode 100644 index 00000000000..789b149ec7e --- /dev/null +++ b/src/composables/specs/useSession.spec.js @@ -0,0 +1,211 @@ +import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import { useSession } from 'composables/useSession'; +import { useState } from 'composables/useState'; + +const session = useSession(); +const state = useState(); + +describe('session', () => { + describe('getToken / setToken', () => { + it('should return an empty string if no token is found in local or session storage', async () => { + const expectedToken = ''; + + const token = session.getToken(); + + expect(token).toEqual(expectedToken); + }); + + it('should return the token stored in local or session storage', async () => { + const expectedToken = 'myToken'; + const data = { + token: expectedToken, + keepLogin: false, + }; + session.setToken(data); + + const token = session.getToken(); + + expect(token).toEqual(expectedToken); + }); + }); + + describe('destroy', () => { + it('should remove the token from the local storage and set a blank user', async () => { + const previousUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + darkMode: false, + }; + const expectedUser = { + id: 0, + name: '', + nickname: '', + lang: '', + darkMode: null, + }; + let user = state.getUser(); + + localStorage.setItem('token', 'tokenToBeGone'); + state.setUser(previousUser); + + expect(localStorage.getItem('token')).toEqual('tokenToBeGone'); + expect(user.value).toEqual(previousUser); + + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(axios, 'get').mockResolvedValue({ data: true }); + await session.destroy(); + + user = state.getUser(); + expect(localStorage.getItem('token')).toBeNull(); + expect(user.value).toEqual(expectedUser); + }); + }); + + describe( + 'login', + () => { + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + userConfig: { + darkMode: false, + }, + }; + const rolesData = [ + { + role: { + name: 'salesPerson', + }, + }, + { + role: { + name: 'admin', + }, + }, + ]; + beforeEach(() => { + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); + return Promise.resolve({ + data: { roles: rolesData, user: expectedUser }, + }); + }); + }); + + it('should fetch the user roles and then set token in the sessionStorage', async () => { + const expectedRoles = ['salesPerson', 'admin']; + const expectedToken = 'mySessionToken'; + const expectedTokenMultimedia = 'mySessionTokenMultimedia'; + const keepLogin = false; + + await session.login({ + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin, + }); + + const roles = state.getRoles(); + const localToken = localStorage.getItem('token'); + const sessionToken = sessionStorage.getItem('token'); + + expect(roles.value).toEqual(expectedRoles); + expect(localToken).toBeNull(); + expect(sessionToken).toEqual(expectedToken); + + await session.destroy(); // this clears token and user for any other test + }); + + it('should fetch the user roles and then set token in the localStorage', async () => { + const expectedRoles = ['salesPerson', 'admin']; + const expectedToken = 'myLocalToken'; + const expectedTokenMultimedia = 'myLocalTokenMultimedia'; + const keepLogin = true; + + await session.login({ + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin, + }); + + const roles = state.getRoles(); + const localToken = localStorage.getItem('token'); + const sessionToken = sessionStorage.getItem('token'); + + expect(roles.value).toEqual(expectedRoles); + expect(localToken).toEqual(expectedToken); + expect(sessionToken).toBeNull(); + + await session.destroy(); // this clears token and user for any other test + }); + }, + {} + ); + + describe('RenewToken', () => { + const expectedToken = 'myToken'; + const expectedTokenMultimedia = 'myTokenMultimedia'; + const currentDate = new Date(); + beforeAll(() => { + const tokenConfig = { + id: 1, + renewPeriod: 21600, + courtesyTime: 60, + renewInterval: 300, + }; + state.setTokenConfig(tokenConfig); + sessionStorage.setItem('renewPeriod', 1); + }); + it('NOT Should renewToken', async () => { + const data = { + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin: false, + ttl: 1, + created: Date.now(), + }; + session.setSession(data); + expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); + expect(sessionStorage.getItem('created')).toBeDefined(); + expect(sessionStorage.getItem('ttl')).toEqual(1); + await session.checkValidity(); + expect(sessionStorage.getItem('token')).toEqual(expectedToken); + expect(sessionStorage.getItem('tokenMultimedia')).toEqual( + expectedTokenMultimedia + ); + }); + it('Should renewToken', async () => { + currentDate.setMinutes(currentDate.getMinutes() - 100); + const data = { + token: expectedToken, + tokenMultimedia: expectedTokenMultimedia, + keepLogin: false, + ttl: 1, + created: currentDate, + }; + session.setSession(data); + + vi.spyOn(axios, 'post') + .mockResolvedValueOnce({ + data: { id: '' }, + }) + .mockResolvedValueOnce({ + data: { + id: '', + }, + }); + expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); + expect(sessionStorage.getItem('created')).toBeDefined(); + expect(sessionStorage.getItem('ttl')).toEqual(1); + await session.checkValidity(); + expect(sessionStorage.getItem('token')).not.toEqual(expectedToken); + expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual( + expectedTokenMultimedia + ); + }); + }); +}); diff --git a/src/composables/specs/useTokenConfig.spec.js b/src/composables/specs/useTokenConfig.spec.js new file mode 100644 index 00000000000..a25a4abb1e4 --- /dev/null +++ b/src/composables/specs/useTokenConfig.spec.js @@ -0,0 +1,31 @@ +import { vi, describe, expect, it } from 'vitest'; +import { axios, flushPromises } from 'app/test/vitest/helper'; +import { useTokenConfig } from 'composables/useTokenConfig'; +const tokenConfig = useTokenConfig(); + +describe('useTokenConfig', () => { + describe('fetch', () => { + it('should call setTokenConfig of the state with the expected data', async () => { + const data = { + id: 1, + renewPeriod: 21600, + courtesyTime: 60, + renewInterval: 300, + }; + vi.spyOn(axios, 'get').mockResolvedValueOnce({ + data, + }); + + vi.spyOn(tokenConfig.state, 'setTokenConfig'); + + tokenConfig.fetch(); + + await flushPromises(); + + expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data); + + const renewPeriod = sessionStorage.getItem('renewPeriod'); + expect(renewPeriod).toEqual(data.renewPeriod); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js b/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js new file mode 100644 index 00000000000..b208f1704ac --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js @@ -0,0 +1,33 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; + +describe('ClaimDescriptorMenu', () => { + let vm; + beforeAll(() => { + vm = createWrapper(ClaimDescriptorMenu, { + propsData: { + claim: { + id: 1, + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('remove()', () => { + it('should delete the claim', async () => { + vi.spyOn(axios, 'delete').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.remove(); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimLines.spec.js b/src/pages/Claim/Card/specs/ClaimLines.spec.js new file mode 100644 index 00000000000..2f2c0e2989d --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimLines.spec.js @@ -0,0 +1,75 @@ +import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimLines from '/src/pages/Claim/Card/ClaimLines.vue'; + +describe('ClaimLines', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(ClaimLines, { + global: { + stubs: ['FetchData', 'VnPaginate'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + beforeEach(() => { + vm.claim = { + id: 1, + ticketFk: 1, + }; + vm.store.data = [ + { + id: 1, + quantity: 10, + sale: { + id: 1, + discount: 0, + }, + }, + ]; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('updateDiscount()', () => { + it('should make a POST request to endpoint "updateDiscount"', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + const canceller = new AbortController(); + await vm.updateDiscount({ saleFk: 1, discount: 5, canceller }); + + const expectedData = { salesIds: [1], newDiscount: 5 }; + expect(axios.post).toHaveBeenCalledWith( + 'Tickets/1/updateDiscount', + expectedData, + { + signal: canceller.signal, + } + ); + }); + }); + + describe('onUpdateDiscount()', () => { + it('should make a POST request and then set the discount on the original row', async () => { + vi.spyOn(vm.quasar, 'notify'); + + vm.onUpdateDiscount({ discount: 5, rowIndex: 0 }); + const firstRow = vm.store.data[0]; + + expect(firstRow.sale.discount).toEqual(5); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Discount updated', + type: 'positive', + }) + ); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js b/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js new file mode 100644 index 00000000000..d93c9613238 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js @@ -0,0 +1,47 @@ +import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimLinesImport from 'pages/Claim/Card/ClaimLinesImport.vue'; + +describe('ClaimLinesImport', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(ClaimLinesImport, { + global: { + stubs: ['FetchData'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('importLines()', () => { + it('should make a POST request and then call to the quasar notify() method', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + vm.selected = [{ id: 1, saleFk: 1, claimFk: 1 }]; + + vm.route.params.id = 1; + + await vm.importLines(); + const expectedData = [{ saleFk: 1, claimFk: 1 }]; + + expect(axios.post).toHaveBeenCalledWith('ClaimBeginnings', expectedData, { + signal: expect.any(Object), + }); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Lines added to claim', + type: 'positive', + }) + ); + expect(vm.canceller).toEqual(null); + }); + }); +}); diff --git a/src/pages/Claim/Card/specs/ClaimPhoto.spec.js b/src/pages/Claim/Card/specs/ClaimPhoto.spec.js new file mode 100644 index 00000000000..c38852af154 --- /dev/null +++ b/src/pages/Claim/Card/specs/ClaimPhoto.spec.js @@ -0,0 +1,114 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import ClaimPhoto from 'pages/Claim/Card/ClaimPhoto.vue'; + +describe('ClaimPhoto', () => { + let vm; + + const claimMock = { + claimDms: [ + { + dmsFk: 1, + dms: { + contentType: 'contentType', + }, + }, + ], + client: { + id: '1', + }, + }; + beforeAll(() => { + vm = createWrapper(ClaimPhoto, { + global: { + stubs: ['FetchData', 'vue-i18n'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('deleteDms()', () => { + it('should delete dms and call quasar notify', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.deleteDms({ index: 0 }); + + expect(axios.post).toHaveBeenCalledWith( + `ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile` + ); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + }); + }); + + describe('viewDeleteDms()', () => { + it('should call quasar dialog', async () => { + vi.spyOn(vm.quasar, 'dialog'); + + await vm.viewDeleteDms(1); + + expect(vm.quasar.dialog).toHaveBeenCalledWith( + expect.objectContaining({ + componentProps: { + title: 'This file will be deleted', + icon: 'delete', + data: { index: 1 }, + promise: vm.deleteDms + }, + }) + ); + }); + }); + + describe('setClaimDms()', () => { + it('should assign claimDms and client from data', async () => { + await vm.setClaimDms(claimMock); + + expect(vm.claimDms).toEqual([ + { + dmsFk: 1, + dms: { + contentType: 'contentType', + }, + isVideo: false, + url: '/api/Claims/1/downloadFile?access_token=', + }, + ]); + + expect(vm.client).toEqual(claimMock.client); + }); + }); + + describe('create()', () => { + it('should upload file and call quasar notify', async () => { + const files = [{ name: 'firstFile' }]; + + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + vi.spyOn(vm.claimDmsRef, 'fetch'); + + await vm.create(files); + + expect(axios.post).toHaveBeenCalledWith( + 'claims/1/uploadFile', + new FormData(), + expect.objectContaining({ + params: expect.objectContaining({ hasFile: false }), + }) + ); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ type: 'positive' }) + ); + + expect(vm.claimDmsRef.fetch).toHaveBeenCalledOnce(); + }); + }); +}); diff --git a/src/pages/Customer/Payments/specs/CustomerPayments.spec.js b/src/pages/Customer/Payments/specs/CustomerPayments.spec.js new file mode 100644 index 00000000000..466a544b4c2 --- /dev/null +++ b/src/pages/Customer/Payments/specs/CustomerPayments.spec.js @@ -0,0 +1,38 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import CustomerPayments from 'src/pages/Customer/Payments/CustomerPayments.vue'; + +describe('CustomerPayments', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(CustomerPayments, { + global: { + stubs: ['VnPaginate'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('confirmTransaction()', () => { + it('should make a POST request and then call to quasar notify method', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.confirmTransaction({ id: 1 }); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Payment confirmed', + type: 'positive', + }) + ); + }); + }); +}); diff --git a/src/pages/Login/specs/Login.spec.js b/src/pages/Login/specs/Login.spec.js new file mode 100644 index 00000000000..e90a8ee535b --- /dev/null +++ b/src/pages/Login/specs/Login.spec.js @@ -0,0 +1,49 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import Login from 'pages/Login/LoginMain.vue'; + +describe('Login', () => { + let vm; + beforeAll(() => { + vm = createWrapper(Login).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should successfully set the token into session', async () => { + const expectedUser = { + id: 999, + name: `T'Challa`, + nickname: 'Black Panther', + lang: 'en', + userConfig: { + darkMode: false, + }, + }; + vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } }); + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); + return Promise.resolve({ + data: { + roles: [], + user: expectedUser, + multimediaToken: { id: 'multimediaToken' }, + }, + }); + }); + + expect(vm.session.getToken()).toEqual(''); + + await vm.onSubmit(); + + expect(vm.session.getToken()).toEqual('token'); + await vm.session.destroy(); + }); + + it('should not set the token into session if any error occurred', async () => { + vi.spyOn(axios, 'post').mockReturnValue({ data: null }); + await vm.onSubmit(); + }); +}); diff --git a/src/pages/Ticket/Card/specs/TicketBoxing.spec.js b/src/pages/Ticket/Card/specs/TicketBoxing.spec.js new file mode 100644 index 00000000000..8fd62d8c230 --- /dev/null +++ b/src/pages/Ticket/Card/specs/TicketBoxing.spec.js @@ -0,0 +1,50 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import TicketBoxing from 'pages/Ticket/Card/TicketBoxing.vue'; + +// #4836 - Investigate how to test q-drawer outside +// q-layout or how to teleport q-drawer inside +describe('TicketBoxing', () => { + let vm; + beforeAll(() => { + vm = createWrapper(TicketBoxing).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('getVideoList()', () => { + it('should when response videoList use to list', async () => { + const expeditionId = 1; + const timed = { + min: 1, + max: 2, + }; + const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4']; + + vi.spyOn(axios, 'get').mockResolvedValue({ data: videoList }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.getVideoList(expeditionId, timed); + + expect(vm.videoList.length).toEqual(videoList.length); + expect(vm.slide).toEqual(videoList.reverse()[0]); + }); + + it('should if not have video show notify', async () => { + const expeditionId = 1; + const timed = { + min: 1, + max: 2, + }; + + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.getVideoList(expeditionId, timed); + + expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' })); + }); + }); +}); diff --git a/src/pages/Ticket/specs/TicketAdvance.spec.js b/src/pages/Ticket/specs/TicketAdvance.spec.js new file mode 100644 index 00000000000..ab1a47544ff --- /dev/null +++ b/src/pages/Ticket/specs/TicketAdvance.spec.js @@ -0,0 +1,120 @@ +import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import TicketAdvance from 'pages/Ticket/TicketAdvance.vue'; +import { Notify } from 'quasar'; +import { nextTick } from 'vue'; + +describe('TicketAdvance', () => { + let wrapper; + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockImplementation(() => ({ data: [] })); + wrapper = createWrapper(TicketAdvance); + vm = wrapper.vm; + vi.spyOn(vm.vnTableRef, 'reload').mockImplementation(() => vi.fn()); + vm.vnTableRef.value = { params: {} }; + }); + beforeEach(() => { + Notify.create = vi.fn(); + }); + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('requestComponentUpdate()', () => { + const mockTicket = { + futureId: 1, + futureClientFk: 1, + nickname: 'test', + futureAddressFk: 1, + futureAgencyModeFk: 1, + futureWarehouseFk: 1, + futureCompanyFk: 1, + landed: '2023-01-02', + zoneFk: 1, + }; + const mockParams = { + clientFk: 1, + nickname: 'test', + agencyModeFk: 1, + addressFk: 1, + zoneFk: 1, + warehouseFk: 1, + companyFk: 1, + landed: '2023-01-02', + shipped: '2023-01-01', + isDeleted: false, + isWithoutNegatives: false, + newTicket: undefined, + keepPrice: true, + }; + const queryResult = 'tickets/1/componentUpdate'; + + it('should return query and params when ticket has no landed', async () => { + vm.vnTableRef.params.dateToAdvance = '2023-01-01'; + await nextTick(); + + const mockLanded = { landed: '2023-01-02', zoneFk: 1 }; + vi.spyOn(vm, 'getLanded').mockResolvedValue(mockLanded); + + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + + it('should return query and params when ticket has landed', async () => { + const { query, params } = await vm.requestComponentUpdate(mockTicket, false); + + expect(query).toBe(queryResult); + expect(params).toEqual(mockParams); + }); + }); + + describe('moveTicketsAdvance()', () => { + it('should move tickets and notify success', async () => { + const tickets = [ + { + id: 1, + futureId: 2, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + { + id: 2, + futureId: 3, + futureShipped: '2023-01-01', + shipped: '2023-01-02', + workerFk: 1, + }, + ]; + vm.selectedTickets = tickets; + vi.spyOn(axios, 'post').mockResolvedValue({}); + await vm.moveTicketsAdvance(); + + expect(axios.post).toHaveBeenCalledOnce('Tickets/merge', { + tickets: [ + { + originId: 2, + destinationId: 1, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + { + originId: 3, + destinationId: 2, + originShipped: '2023-01-01', + destinationShipped: '2023-01-02', + workerFk: 1, + }, + ], + }); + expect(vm.vnTableRef.reload).toHaveBeenCalled(); + expect(Notify.create).toHaveBeenCalled(); + expect(vm.selectedTickets).toEqual([]); + }); + }); +}); diff --git a/src/pages/Wagon/specs/WagonCreate.spec.js b/src/pages/Wagon/specs/WagonCreate.spec.js new file mode 100644 index 00000000000..f195c183f17 --- /dev/null +++ b/src/pages/Wagon/specs/WagonCreate.spec.js @@ -0,0 +1,97 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import WagonCreate from 'pages/Wagon/WagonCreate.vue'; + +describe('WagonCreate', () => { + let vmEdit, vmCreate; + const entityId = 1; + + beforeAll(() => { + vmEdit = createWrapper(WagonCreate, { + propsData: { + id: entityId, + }, + }).vm; + vmCreate = createWrapper(WagonCreate).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('onSubmit()', () => { + it('should create a wagon', async () => { + vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); + vmCreate.wagon = { + label: 1234, + plate: 'MOCK PLATE', + volume: 50, + typeFk: 1, + }; + + await vmCreate.onSubmit(); + + expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmCreate.wagon); + }); + + it('should update a wagon', async () => { + vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); + vmEdit.wagon = { + id: entityId, + label: 1234, + plate: 'MOCK PLATE', + volume: 50, + typeFk: 1, + }; + + await vmEdit.onSubmit(); + + expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmEdit.wagon); + }); + }); + + describe('onReset()', () => { + it('should reset wagon if have id', async () => { + vmEdit.originalData = { + label: 1234, + plate: 'Original', + volume: 200, + typeFk: 1, + }; + vmEdit.wagon = { + label: 4321, + plate: 'Edited', + volume: 50, + typeFk: 2, + }; + + await vmEdit.onReset(); + + expect(vmEdit.wagon).toEqual(vmEdit.originalData); + }); + + it('should reset wagon if not have id', async () => { + vmCreate.wagon = { + label: 4321, + plate: 'Edited', + volume: 50, + typeFk: 2, + }; + + await vmCreate.onReset(); + + expect(vmCreate.wagon).toEqual({}); + }); + }); + + describe('fetch()', () => { + it('should fetch data', async () => { + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + + await vmEdit.fetch(); + + expect(axios.get).toHaveBeenCalledWith(`WagonTypes`); + expect(axios.get).toHaveBeenCalledWith(`Wagons/${entityId}`); + }); + }); +}); diff --git a/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js b/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js new file mode 100644 index 00000000000..35ce91e618c --- /dev/null +++ b/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js @@ -0,0 +1,33 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue'; +import { ref } from 'vue'; + +describe('WorkerNotificationsManager', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(WorkerNotificationsManager, { + global: { + stubs: ['CrudModel'], + }, + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('swapEntry()', () => { + it('should swap notification', async () => { + const from = ref(new Map()); + const to = ref(new Map()); + from.value.set(1, { notificationFk: 1 }); + to.value.set(2, { notificationFk: 2 }); + + await vm.swapEntry(from.value, to.value, 1); + expect(to.value.size).toBe(2); + expect(from.value.size).toBe(0); + }); + }); +}); diff --git a/src/stores/specs/useStateQueryStore.spec.js b/src/stores/specs/useStateQueryStore.spec.js new file mode 100644 index 00000000000..ab3afb007d8 --- /dev/null +++ b/src/stores/specs/useStateQueryStore.spec.js @@ -0,0 +1,58 @@ +import { describe, expect, it, beforeEach, beforeAll } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; + +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; + +describe('useStateQueryStore', () => { + beforeAll(() => { + createWrapper({}, {}); + }); + + const stateQueryStore = useStateQueryStore(); + const { add, isLoading, remove, reset } = useStateQueryStore(); + const firstQuery = { url: 'myQuery' }; + + function getQueries() { + return stateQueryStore.queries; + } + + beforeEach(() => { + reset(); + expect(getQueries().size).toBeFalsy(); + }); + + it('should add two queries', async () => { + expect(getQueries().size).toBeFalsy(); + add(firstQuery); + + expect(getQueries().size).toBeTruthy(); + expect(getQueries().has(firstQuery)).toBeTruthy(); + + add(); + expect(getQueries().size).toBe(2); + }); + + it('should add and remove loading state', async () => { + expect(isLoading().value).toBeFalsy(); + add(firstQuery); + expect(isLoading().value).toBeTruthy(); + remove(firstQuery); + expect(isLoading().value).toBeFalsy(); + }); + + it('should add and remove query', async () => { + const secondQuery = { ...firstQuery }; + const thirdQuery = { ...firstQuery }; + + add(firstQuery); + add(secondQuery); + + const beforeCount = getQueries().size; + add(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeTruthy(); + + remove(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeFalsy(); + expect(getQueries().size).toBe(beforeCount); + }); +}); From a09d0004959346fcdfa9325ce57b74fa897def8f Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Thu, 19 Dec 2024 12:32:29 +0100 Subject: [PATCH 122/142] refactor: refs #8320 moved front tests to their respective sections --- test/vitest/__tests__/boot/axios.spec.js | 65 ------ .../__tests__/components/Leftmenu.spec.js | 94 -------- .../__tests__/components/Paginate.spec.js | 118 ---------- .../__tests__/components/VnTable.spec.js | 47 ---- .../components/common/CrudModel.spec.js | 120 ---------- .../common/VnChangePassword.spec.js | 70 ------ .../components/common/VnDiscount.spec.js | 28 --- .../components/common/VnLinkPhone.spec.js | 50 ----- .../__tests__/components/common/VnLog.spec.js | 134 ----------- .../__tests__/components/common/VnSms.spec.js | 25 --- .../components/common/VnSmsDialog.spec.js | 58 ----- .../composables/downloadFile.spec.js | 36 --- .../__tests__/composables/getExchange.spec.js | 45 ---- .../__tests__/composables/getTotal.spec.js | 55 ----- .../useAccountShortToStandard.spec.js | 9 - .../__tests__/composables/useAcl.spec.js | 110 --------- .../composables/useArrayData.spec.js | 98 -------- .../__tests__/composables/useRole.spec.js | 73 ------ .../__tests__/composables/useSession.spec.js | 211 ------------------ .../composables/useTokenConfig.spec.js | 31 --- .../pages/Claims/ClaimDescriptorMenu.spec.js | 33 --- .../__tests__/pages/Claims/ClaimLines.spec.js | 75 ------- .../pages/Claims/ClaimLinesImport.spec.js | 47 ---- .../__tests__/pages/Claims/ClaimPhoto.spec.js | 114 ---------- .../pages/Customer/CustomerPayments.spec.js | 38 ---- .../__tests__/pages/Login/Login.spec.js | 49 ---- .../pages/Tickets/TicketAdvance.spec.js | 120 ---------- .../pages/Tickets/TicketBoxing.spec.js | 50 ----- .../pages/Wagons/WagonCreate.spec.js | 97 -------- .../Worker/WorkerNotificationsManager.spec.js | 33 --- .../stores/useStateQueryStore.spec.js | 58 ----- vitest.config.js | 2 +- 32 files changed, 1 insertion(+), 2192 deletions(-) delete mode 100644 test/vitest/__tests__/boot/axios.spec.js delete mode 100644 test/vitest/__tests__/components/Leftmenu.spec.js delete mode 100644 test/vitest/__tests__/components/Paginate.spec.js delete mode 100644 test/vitest/__tests__/components/VnTable.spec.js delete mode 100644 test/vitest/__tests__/components/common/CrudModel.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnChangePassword.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnDiscount.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnLinkPhone.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnLog.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnSms.spec.js delete mode 100644 test/vitest/__tests__/components/common/VnSmsDialog.spec.js delete mode 100644 test/vitest/__tests__/composables/downloadFile.spec.js delete mode 100644 test/vitest/__tests__/composables/getExchange.spec.js delete mode 100644 test/vitest/__tests__/composables/getTotal.spec.js delete mode 100644 test/vitest/__tests__/composables/useAccountShortToStandard.spec.js delete mode 100644 test/vitest/__tests__/composables/useAcl.spec.js delete mode 100644 test/vitest/__tests__/composables/useArrayData.spec.js delete mode 100644 test/vitest/__tests__/composables/useRole.spec.js delete mode 100644 test/vitest/__tests__/composables/useSession.spec.js delete mode 100644 test/vitest/__tests__/composables/useTokenConfig.spec.js delete mode 100644 test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js delete mode 100644 test/vitest/__tests__/pages/Claims/ClaimLines.spec.js delete mode 100644 test/vitest/__tests__/pages/Claims/ClaimLinesImport.spec.js delete mode 100644 test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js delete mode 100644 test/vitest/__tests__/pages/Customer/CustomerPayments.spec.js delete mode 100644 test/vitest/__tests__/pages/Login/Login.spec.js delete mode 100644 test/vitest/__tests__/pages/Tickets/TicketAdvance.spec.js delete mode 100644 test/vitest/__tests__/pages/Tickets/TicketBoxing.spec.js delete mode 100644 test/vitest/__tests__/pages/Wagons/WagonCreate.spec.js delete mode 100644 test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js delete mode 100644 test/vitest/__tests__/stores/useStateQueryStore.spec.js diff --git a/test/vitest/__tests__/boot/axios.spec.js b/test/vitest/__tests__/boot/axios.spec.js deleted file mode 100644 index b3b6f98c66e..00000000000 --- a/test/vitest/__tests__/boot/axios.spec.js +++ /dev/null @@ -1,65 +0,0 @@ -import { onRequest, onResponseError } from 'src/boot/axios'; -import { describe, expect, it, vi } from 'vitest'; - -vi.mock('src/composables/useSession', () => ({ - useSession: () => ({ - getToken: () => 'DEFAULT_TOKEN', - isLoggedIn: () => vi.fn(), - destroy: () => vi.fn(), - }), -})); - -vi.mock('src/stores/useStateQueryStore', () => ({ - useStateQueryStore: () => ({ - add: () => vi.fn(), - remove: () => vi.fn(), - }), -})); - -describe('Axios boot', () => { - describe('onRequest()', async () => { - it('should set the "Authorization" property on the headers', async () => { - const config = { headers: {} }; - - const resultConfig = onRequest(config); - - expect(resultConfig).toEqual( - expect.objectContaining({ - headers: { - 'Accept-Language': 'en-US', - Authorization: 'DEFAULT_TOKEN', - }, - }) - ); - }); - }); - - describe('onResponseError()', async () => { - it('should call to the Notify plugin with a message error for an status code "500"', async () => { - const error = { - response: { - status: 500, - }, - }; - - const result = onResponseError(error); - expect(result).rejects.toEqual(expect.objectContaining(error)); - }); - - it('should call to the Notify plugin with a message from the response property', async () => { - const error = { - response: { - status: 401, - data: { - error: { - message: 'Invalid user or password', - }, - }, - }, - }; - - const result = onResponseError(error); - expect(result).rejects.toEqual(expect.objectContaining(error)); - }); - }); -}); diff --git a/test/vitest/__tests__/components/Leftmenu.spec.js b/test/vitest/__tests__/components/Leftmenu.spec.js deleted file mode 100644 index 10d9d66fbfb..00000000000 --- a/test/vitest/__tests__/components/Leftmenu.spec.js +++ /dev/null @@ -1,94 +0,0 @@ -import { vi, describe, expect, it, beforeAll } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import Leftmenu from 'components/LeftMenu.vue'; - -import { useNavigationStore } from 'src/stores/useNavigationStore'; - -vi.mock('src/router/modules', () => ({ - default: [ - { - path: '/customer', - name: 'Customer', - meta: { - title: 'customers', - icon: 'vn:client', - }, - menus: { - main: ['CustomerList', 'CustomerCreate'], - card: ['CustomerBasicData'], - }, - children: [ - { - path: '', - name: 'CustomerMain', - children: [ - { - path: 'list', - name: 'CustomerList', - meta: { - title: 'list', - icon: 'view_list', - }, - }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'createCustomer', - icon: 'vn:addperson', - }, - }, - ], - }, - ], - }, - ], -})); - -describe('Leftmenu', () => { - let vm; - let navigation; - beforeAll(() => { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [], - }); - - vm = createWrapper(Leftmenu, { - propsData: { - source: 'main', - }, - }).vm; - - navigation = useNavigationStore(); - navigation.fetchPinned = vi.fn().mockReturnValue(Promise.resolve(true)); - navigation.getModules = vi.fn().mockReturnValue({ - value: [ - { - name: 'customer', - title: 'customer.pageTitles.customers', - icon: 'vn:customer', - module: 'customer', - }, - ], - }); - }); - - it('should return a proper formated object with two child items', async () => { - const expectedMenuItem = [ - { - children: null, - name: 'CustomerList', - title: 'globals.pageTitles.list', - icon: 'view_list', - }, - { - children: null, - name: 'CustomerCreate', - title: 'globals.pageTitles.createCustomer', - icon: 'vn:addperson', - }, - ]; - const firstMenuItem = vm.items[0]; - expect(firstMenuItem.children).toEqual(expect.arrayContaining(expectedMenuItem)); - }); -}); diff --git a/test/vitest/__tests__/components/Paginate.spec.js b/test/vitest/__tests__/components/Paginate.spec.js deleted file mode 100644 index a67dfcdc638..00000000000 --- a/test/vitest/__tests__/components/Paginate.spec.js +++ /dev/null @@ -1,118 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; - -describe('VnPaginate', () => { - const expectedUrl = '/api/customers'; - const defaultData = [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; - let vm; - beforeAll(() => { - const options = { - attrs: { - url: expectedUrl, - dataKey: 'CustomerList', - order: 'id DESC', - limit: 3, - }, - }; - vm = createWrapper(VnPaginate, options).vm; - }); - - afterEach(() => { - vm.store.data = []; - vm.store.skip = 0; - vm.pagination.page = 1; - vm.hasMoreData = true; - }); - - describe('paginate()', () => { - it('should call to the paginate() method and set the data on the rows property', async () => { - vi.spyOn(vm.arrayData, 'loadMore'); - vm.store.data = defaultData; - - await vm.paginate(); - - expect(vm.arrayData.loadMore).toHaveBeenCalledWith(); - expect(vm.store.data.length).toEqual(3); - }); - - it('should call to the paginate() method and then call it again to paginate', async () => { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: defaultData, - }); - vm.store.hasMoreData = true; - await vm.$nextTick(); - - vm.store.data = defaultData; - - await vm.paginate(); - - expect(vm.store.skip).toEqual(3); - expect(vm.store.data.length).toEqual(6); - - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [ - { id: 4, name: 'Peter Parker' }, - { id: 5, name: 'Clark Kent' }, - { id: 6, name: 'Barry Allen' }, - ], - }); - await vm.paginate(); - - expect(vm.store.skip).toEqual(6); - expect(vm.store.data.length).toEqual(9); - }); - }); - - describe('onLoad()', () => { - it('should call to the done() callback and not increment the pagination', async () => { - const index = 1; - const done = vi.fn(); - - await vm.onLoad(index, done); - - expect(vm.pagination.page).toEqual(1); - expect(done).toHaveBeenCalledWith(false); - }); - - it('should increment the pagination and then call to the done() callback', async () => { - expect(vm.pagination.page).toEqual(1); - - const index = 1; - const done = vi.fn(); - vm.store.data = defaultData; - - await vm.onLoad(index, done); - - expect(vm.pagination.page).toEqual(2); - expect(done).toHaveBeenCalledWith(false); - }); - - it('should call to the done() callback with true as argument to finish pagination', async () => { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [ - { id: 1, name: 'Tony Stark' }, - { id: 2, name: 'Jessica Jones' }, - ], - }); - - vm.store.data = defaultData; - - expect(vm.pagination.page).toEqual(1); - - const index = 1; - const done = vi.fn(); - - vm.hasMoreData = false; - - await vm.onLoad(index, done); - - expect(vm.pagination.page).toEqual(2); - expect(done).toHaveBeenCalledWith(false); - }); - }); -}); diff --git a/test/vitest/__tests__/components/VnTable.spec.js b/test/vitest/__tests__/components/VnTable.spec.js deleted file mode 100644 index 162df727dd6..00000000000 --- a/test/vitest/__tests__/components/VnTable.spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, expect, it, beforeAll, beforeEach } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; -import VnTable from 'src/components/VnTable/VnTable.vue'; - -describe('VnTable', () => { - let wrapper; - let vm; - - beforeAll(() => { - wrapper = createWrapper(VnTable, { - propsData: { - columns: [], - }, - }); - vm = wrapper.vm; - }); - - beforeEach(() => (vm.selected = [])); - - describe('handleSelection()', () => { - const rows = [{ $index: 0 }, { $index: 1 }, { $index: 2 }]; - const selectedRows = [{ $index: 1 }]; - it('should add rows to selected when shift key is pressed and rows are added except last one', () => { - vm.handleSelection( - { evt: { shiftKey: true }, added: true, rows: selectedRows }, - rows - ); - expect(vm.selected).toEqual([{ $index: 0 }]); - }); - - it('should not add rows to selected when shift key is not pressed', () => { - vm.handleSelection( - { evt: { shiftKey: false }, added: true, rows: selectedRows }, - rows - ); - expect(vm.selected).toEqual([]); - }); - - it('should not add rows to selected when rows are not added', () => { - vm.handleSelection( - { evt: { shiftKey: true }, added: false, rows: selectedRows }, - rows - ); - expect(vm.selected).toEqual([]); - }); - }); -}); diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js deleted file mode 100644 index 6ce93e59c68..00000000000 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ /dev/null @@ -1,120 +0,0 @@ -import { createWrapper } from 'app/test/vitest/helper'; -import CrudModel from 'components/CrudModel.vue'; -import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; - -describe('CrudModel', () => { - let vm; - beforeAll(() => { - vm = createWrapper(CrudModel, { - global: { - stubs: [ - 'vnPaginate', - 'useState', - 'arrayData', - 'useStateStore', - 'vue-i18n', - ], - mocks: { - validate: vi.fn(), - }, - }, - propsData: { - dataRequired: { - fk: 1, - }, - dataKey: 'crudModelKey', - model: 'crudModel', - url: 'crudModelUrl', - }, - }).vm; - }); - - beforeEach(() => { - vm.fetch([]); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('insert()', () => { - it('should new element in list with index 0 if formData not has data', () => { - vm.insert(); - - expect(vm.formData.length).toEqual(1); - expect(vm.formData[0].fk).toEqual(1); - expect(vm.formData[0].$index).toEqual(0); - }); - }); - - describe('getChanges()', () => { - it('should return correct updates and creates', async () => { - vm.fetch([ - { id: 1, name: 'New name one' }, - { id: 2, name: 'New name two' }, - { id: 3, name: 'Bruce Wayne' }, - ]); - - vm.originalData = [ - { id: 1, name: 'Tony Starks' }, - { id: 2, name: 'Jessica Jones' }, - { id: 3, name: 'Bruce Wayne' }, - ]; - - vm.insert(); - const result = vm.getChanges(); - - const expected = { - creates: [ - { - $index: 3, - fk: 1, - }, - ], - updates: [ - { - data: { - name: 'New name one', - }, - where: { - id: 1, - }, - }, - { - data: { - name: 'New name two', - }, - where: { - id: 2, - }, - }, - ], - }; - - expect(result).toEqual(expected); - }); - }); - - describe('getDifferences()', () => { - it('should return the differences between two objects', async () => { - const obj1 = { - a: 1, - b: 2, - c: 3, - }; - const obj2 = { - a: null, - b: 4, - d: 5, - }; - - const result = vm.getDifferences(obj1, obj2); - - expect(result).toEqual({ - a: null, - b: 4, - d: 5, - }); - }); - }); -}); diff --git a/test/vitest/__tests__/components/common/VnChangePassword.spec.js b/test/vitest/__tests__/components/common/VnChangePassword.spec.js deleted file mode 100644 index f5a967bb50b..00000000000 --- a/test/vitest/__tests__/components/common/VnChangePassword.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { createWrapper, axios } from 'app/test/vitest/helper'; -import VnChangePassword from 'src/components/common/VnChangePassword.vue'; -import { vi, beforeEach, afterEach, beforeAll, describe, expect, it } from 'vitest'; -import { Notify } from 'quasar'; - -describe('VnSmsDialog', () => { - let vm; - - beforeAll(() => { - vi.spyOn(axios, 'get').mockResolvedValue({ - data: [], - }); - vm = createWrapper(VnChangePassword, { - propsData: { - submitFn: vi.fn(), - }, - }).vm; - }); - - beforeEach(() => { - Notify.create = vi.fn(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should notify when new password is empty', async () => { - vm.passwords.newPassword = ''; - vm.passwords.repeatPassword = 'password'; - - await vm.validate(); - expect(Notify.create).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'You must enter a new password', - type: 'negative', - }) - ); - }); - - it("should notify when passwords don't match", async () => { - vm.passwords.newPassword = 'password1'; - vm.passwords.repeatPassword = 'password2'; - await vm.validate(); - expect(Notify.create).toHaveBeenCalledWith( - expect.objectContaining({ - message: `Passwords don't match`, - type: 'negative', - }) - ); - }); - - describe('if passwords match', () => { - it('should call submitFn and emit password', async () => { - vm.passwords.newPassword = 'password'; - vm.passwords.repeatPassword = 'password'; - await vm.validate(); - expect(vm.props.submitFn).toHaveBeenCalledWith('password', undefined); - }); - - it('should call submitFn and emit password and old password', async () => { - vm.passwords.newPassword = 'password'; - vm.passwords.repeatPassword = 'password'; - vm.passwords.oldPassword = 'oldPassword'; - - await vm.validate(); - expect(vm.props.submitFn).toHaveBeenCalledWith('password', 'oldPassword'); - }); - }); -}); diff --git a/test/vitest/__tests__/components/common/VnDiscount.spec.js b/test/vitest/__tests__/components/common/VnDiscount.spec.js deleted file mode 100644 index 5d5be61ac7e..00000000000 --- a/test/vitest/__tests__/components/common/VnDiscount.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; -import VnDiscount from 'components/common/vnDiscount.vue'; - -describe('VnDiscount', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(VnDiscount, { - props: { - data: {}, - price: 100, - quantity: 2, - discount: 10, - } - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('total', () => { - it('should calculate total correctly', () => { - expect(vm.total).toBe(180); - }); - }); -}); \ No newline at end of file diff --git a/test/vitest/__tests__/components/common/VnLinkPhone.spec.js b/test/vitest/__tests__/components/common/VnLinkPhone.spec.js deleted file mode 100644 index a34ef90a514..00000000000 --- a/test/vitest/__tests__/components/common/VnLinkPhone.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, it, expect, beforeAll, vi } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; -import parsePhone from 'src/filters/parsePhone'; - -describe('parsePhone filter', () => { - beforeAll(async () => { - vi.spyOn(axios, 'get').mockReturnValue({ data: { prefix: '34' } }); - }); - - it('no phone', async () => { - const phone = await parsePhone(null, '34'); - expect(phone).toBe(undefined); - }); - - it("adds prefix +34 if it doesn't have one", async () => { - const phone = await parsePhone('123456789', '34'); - expect(phone).toBe('34123456789'); - }); - - it('maintains prefix +34 if it is already correct', async () => { - const phone = await parsePhone('+34123456789', '34'); - expect(phone).toBe('34123456789'); - }); - - it('converts prefix 0034 to +34', async () => { - const phone = await parsePhone('0034123456789', '34'); - expect(phone).toBe('34123456789'); - }); - - it('converts prefix 34 without symbol to +34', async () => { - const phone = await parsePhone('34123456789', '34'); - expect(phone).toBe('34123456789'); - }); - - it('replaces incorrect prefix with the correct one', async () => { - const phone = await parsePhone('+44123456789', '34'); - expect(phone).toBe('44123456789'); - }); - - it('adds default prefix on error', async () => { - vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url.includes('Prefixes')) - return Promise.reject(new Error('Network error')); - else if (url.includes('PbxConfigs')) - return Promise.resolve({ data: { defaultPrefix: '39' } }); - }); - const phone = await parsePhone('123456789', '34'); - expect(phone).toBe('39123456789'); - }); -}); diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js deleted file mode 100644 index 53d2732a074..00000000000 --- a/test/vitest/__tests__/components/common/VnLog.spec.js +++ /dev/null @@ -1,134 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import VnLog from 'src/components/common/VnLog.vue'; - -describe('VnLog', () => { - let vm; - const fakeLogTreeData = [ - { - id: 2, - originFk: 1, - userFk: 18, - action: 'update', - changedModel: 'ClaimObservation', - oldInstance: {}, - newInstance: { - claimFk: 1, - text: 'Waiting for customer', - }, - creationDate: '2023-09-18T12:25:34.000Z', - changedModelId: '1', - changedModelValue: null, - description: null, - user: { - id: 18, - name: 'salesPerson', - nickname: 'salesPersonNick', - image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', - worker: { - id: 18, - userFk: 18, - }, - }, - }, - { - id: 1, - originFk: 1, - userFk: 18, - action: 'update', - changedModel: 'Claim', - oldInstance: { - pickup: null, - }, - newInstance: { - pickup: 'agency', - }, - creationDate: '2023-09-18T12:25:34.000Z', - changedModelId: '1', - changedModelValue: null, - description: null, - user: { - id: 18, - name: 'salesPerson', - nickname: 'salesPersonNick', - image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', - worker: { - id: 18, - userFk: 18, - }, - }, - }, - ]; - const mockValidations = { - Claim: { - locale: { - name: 'reclamación', - }, - }, - ClaimObservation: { - locale: { - name: 'observación', - }, - }, - ClaimDms: { - locale: { - name: 'documento', - }, - }, - ClaimBeginning: { - locale: { - name: 'comienzo', - }, - }, - }; - - beforeAll(async () => { - axios.get.mockImplementation(() => { - return { data: fakeLogTreeData }; - }); - - vm = createWrapper(VnLog, { - global: { - stubs: [], - mocks: {}, - }, - propsData: { - model: 'Claim', - }, - }).vm; - vm.validations = mockValidations; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should correctly set logTree', async () => { - vm.logTree = vm.getLogTree(fakeLogTreeData); - expect(vm.logTree[0].originFk).toEqual(1); - expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); - }); - - it('should correctly set the selectedFilters when filtering', () => { - vm.searchInput = '1'; - vm.userSelect = '21'; - vm.checkboxOptions.insert.selected = true; - vm.checkboxOptions.update.selected = true; - - vm.selectFilter('search'); - vm.selectFilter('userSelect'); - - expect(vm.selectedFilters.changedModelId).toEqual('1'); - expect(vm.selectedFilters.userFk).toEqual('21'); - expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); - }); - - it('should correctly set the date from', () => { - vm.dateFrom = '18-09-2023'; - vm.selectFilter('date', 'from'); - expect(vm.selectedFilters.creationDate.between).toEqual([ - new Date('2023-09-18T00:00:00.000Z'), - new Date('2023-09-18T21:59:59.999Z'), - ]); - }); -}); diff --git a/test/vitest/__tests__/components/common/VnSms.spec.js b/test/vitest/__tests__/components/common/VnSms.spec.js deleted file mode 100644 index e0f8c1868bd..00000000000 --- a/test/vitest/__tests__/components/common/VnSms.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import VnSms from 'src/components/ui/VnSms.vue'; - -describe('VnSms', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(VnSms, { - global: { - stubs: ['VnPaginate'], - mocks: {}, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should format number correctly', () => { - const formattedNumber = vm.formatNumber('123456789012'); - expect(formattedNumber).toBe('1234 56789012'); - }); -}); diff --git a/test/vitest/__tests__/components/common/VnSmsDialog.spec.js b/test/vitest/__tests__/components/common/VnSmsDialog.spec.js deleted file mode 100644 index 0b34739826a..00000000000 --- a/test/vitest/__tests__/components/common/VnSmsDialog.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -import { createWrapper } from 'app/test/vitest/helper'; -import VnSmsDialog from 'components/common/VnSmsDialog.vue'; -import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest'; - - -describe('VnSmsDialog', () => { - let vm; - const orderId = 1; - const shipped = new Date(); - const phone = '012345678'; - const promise = (response) => {return response;}; - const template = 'minAmount'; - const locale = 'en'; - - beforeAll(() => { - vm = createWrapper(VnSmsDialog, { - propsData: { - data: { - orderId, - shipped - }, - template, - locale, - phone, - promise - } - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('updateMessage()', () => { - it('should update the message value with the correct template and parameters', () => { - vm.updateMessage(); - - expect(vm.message).toEqual(`A minimum amount of 50€ (VAT excluded) is required for your order ${orderId} of ${shipped} to receive it without additional shipping costs.`); - }); - }); - - describe('send()', async () => { - it('should send the message', async () => { - vi.spyOn(vm.props, 'promise'); - vm.message = 'Example message'; - const response = { - orderId, - shipped, - destination: phone, - message: vm.message - }; - await vm.send(); - - expect(vm.isLoading).toEqual(false); - expect(vm.props.promise).toHaveBeenCalledWith(response); - }); - }); -}); diff --git a/test/vitest/__tests__/composables/downloadFile.spec.js b/test/vitest/__tests__/composables/downloadFile.spec.js deleted file mode 100644 index f53b56b3e3d..00000000000 --- a/test/vitest/__tests__/composables/downloadFile.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; -import { downloadFile } from 'src/composables/downloadFile'; -import { useSession } from 'src/composables/useSession'; -const session = useSession(); -const token = session.getToken(); - -describe('downloadFile', () => { - const baseUrl = 'http://localhost:9000'; - let defaulCreateObjectURL; - - beforeAll(() => { - defaulCreateObjectURL = window.URL.createObjectURL; - window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); - }); - - afterAll(() => (window.URL.createObjectURL = defaulCreateObjectURL)); - - it('should open a new window to download the file', async () => { - const res = { - data: new Blob(['file content'], { type: 'application/octet-stream' }), - headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, - }; - vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); - else if (url.includes('downloadFile')) return Promise.resolve(res); - }); - - await downloadFile(1); - - expect(axios.get).toHaveBeenCalledWith( - `${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, - { responseType: 'blob' } - ); - }); -}); diff --git a/test/vitest/__tests__/composables/getExchange.spec.js b/test/vitest/__tests__/composables/getExchange.spec.js deleted file mode 100644 index dba31458ee1..00000000000 --- a/test/vitest/__tests__/composables/getExchange.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import axios from 'axios'; -import { getExchange } from 'src/composables/getExchange'; - -vi.mock('axios'); - -describe('getExchange()', () => { - it('should return the correct exchange rate', async () => { - axios.get.mockResolvedValue({ - data: { value: 1.2 }, - }); - - const amount = 100; - const currencyFk = 1; - const dated = '2023-01-01'; - const result = await getExchange(amount, currencyFk, dated); - - expect(result).toBe('83.33'); - }); - - it('should return the correct exchange rate with custom decimal places', async () => { - axios.get.mockResolvedValue({ - data: { value: 1.2 }, - }); - - const amount = 100; - const currencyFk = 1; - const dated = '2023-01-01'; - const decimalPlaces = 3; - const result = await getExchange(amount, currencyFk, dated, decimalPlaces); - - expect(result).toBe('83.333'); - }); - - it('should return null if the API call fails', async () => { - axios.get.mockRejectedValue(new Error('Network error')); - - const amount = 100; - const currencyFk = 1; - const dated = '2023-01-01'; - const result = await getExchange(amount, currencyFk, dated); - - expect(result).toBeNull(); - }); -}); diff --git a/test/vitest/__tests__/composables/getTotal.spec.js b/test/vitest/__tests__/composables/getTotal.spec.js deleted file mode 100644 index 789e3fbcfe0..00000000000 --- a/test/vitest/__tests__/composables/getTotal.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import { vi, describe, expect, it } from 'vitest'; -import { getTotal } from 'src/composables/getTotal'; - -vi.mock('src/filters', () => ({ - toCurrency: vi.fn((value, currency) => `${currency} ${value.toFixed(2)}`), -})); - -describe('getTotal()', () => { - const rows = [ - { amount: 10.5, tax: 2.1 }, - { amount: 20.75, tax: 3.25 }, - { amount: 30.25, tax: 4.75 }, - ]; - - it('should calculate the total for a given key', () => { - const total = getTotal(rows, 'amount'); - expect(total).toBe('61.50'); - }); - - it('should calculate the total with a callback function', () => { - const total = getTotal(rows, null, { cb: (row) => row.amount + row.tax }); - expect(total).toBe('71.60'); - }); - - it('should format the total as currency', () => { - const total = getTotal(rows, 'amount', { currency: 'USD' }); - expect(total).toBe('USD 61.50'); - }); - - it('should format the total as currency with default currency', () => { - const total = getTotal(rows, 'amount', { currency: 'default' }); - expect(total).toBe('undefined 61.50'); - }); - - it('should calculate the total with integer formatting', () => { - const total = getTotal(rows, 'amount', { decimalPlaces: 0 }); - expect(total).toBe('62'); - }); - - it('should calculate the total with custom decimal places', () => { - const total = getTotal(rows, 'amount', { decimalPlaces: 1 }); - expect(total).toBe('61.5'); - }); - - it('should handle rows with missing keys', () => { - const rowsWithMissingKeys = [{ amount: 10.5 }, { amount: 20.75 }, {}]; - const total = getTotal(rowsWithMissingKeys, 'amount'); - expect(total).toBe('31.25'); - }); - - it('should handle empty rows', () => { - const total = getTotal([], 'amount'); - expect(total).toBe('0.00'); - }); -}); diff --git a/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js b/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js deleted file mode 100644 index d2458581210..00000000000 --- a/test/vitest/__tests__/composables/useAccountShortToStandard.spec.js +++ /dev/null @@ -1,9 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { useAccountShortToStandard } from 'src/composables/useAccountShortToStandard'; - -describe('useAccountShortToStandard()', () => { - it('should pad the decimal part with zeros for short numbers', () => { - expect(useAccountShortToStandard('123.45')).toBe('1230000045'); - expect(useAccountShortToStandard('123.')).toBe('1230000000'); - }); -}); diff --git a/test/vitest/__tests__/composables/useAcl.spec.js b/test/vitest/__tests__/composables/useAcl.spec.js deleted file mode 100644 index 6cb29984c48..00000000000 --- a/test/vitest/__tests__/composables/useAcl.spec.js +++ /dev/null @@ -1,110 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterAll } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; -import { useAcl } from 'src/composables/useAcl'; - -describe('useAcl', () => { - const acl = useAcl(); - const mockAcls = [ - { - model: 'Address', - property: '*', - accessType: '*', - permission: 'ALLOW', - principalType: 'ROLE', - principalId: 'employee', - }, - { - model: 'Worker', - property: 'holidays', - accessType: 'READ', - permission: 'ALLOW', - principalType: 'ROLE', - principalId: 'employee', - }, - { - model: 'Url', - property: 'getByUser', - accessType: 'READ', - permission: 'ALLOW', - principalType: 'ROLE', - principalId: '$everyone', - }, - { - model: 'TpvTransaction', - property: 'start', - accessType: 'WRITE', - permission: 'ALLOW', - principalType: 'ROLE', - principalId: '$authenticated', - }, - ]; - - beforeAll(async () => { - vi.spyOn(axios, 'get').mockResolvedValue({ data: mockAcls }); - await acl.fetch(); - }); - - afterAll(async () => await flushPromises()); - - describe('hasAny', () => { - it('should return false if no roles matched', async () => { - expect( - acl.hasAny([ - { model: 'Worker', props: 'updateAttributes', accessType: 'WRITE' }, - ]) - ).toBeFalsy(); - }); - - it('should return false if no roles matched', async () => { - expect( - acl.hasAny([{ model: 'Worker', props: 'holidays', accessType: 'READ' }]) - ).toBeTruthy(); - }); - - describe('*', () => { - it('should return true if an acl matched', async () => { - expect( - acl.hasAny([{ model: 'Address', props: '*', accessType: 'WRITE' }]) - ).toBeTruthy(); - }); - - it('should return false if no acls matched', async () => { - expect( - acl.hasAny([{ model: 'Worker', props: '*', accessType: 'READ' }]) - ).toBeFalsy(); - }); - }); - - describe('$authenticated', () => { - it('should return false if no acls matched', async () => { - expect( - acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: '*' }]) - ).toBeFalsy(); - }); - - it('should return true if an acl matched', async () => { - expect( - acl.hasAny([{ model: 'Url', props: 'getByUser', accessType: 'READ' }]) - ).toBeTruthy(); - }); - }); - - describe('$everyone', () => { - it('should return false if no acls matched', async () => { - expect( - acl.hasAny([ - { model: 'TpvTransaction', props: 'start', accessType: 'READ' }, - ]) - ).toBeFalsy(); - }); - - it('should return false if an acl matched', async () => { - expect( - acl.hasAny([ - { model: 'TpvTransaction', props: 'start', accessType: 'WRITE' }, - ]) - ).toBeTruthy(); - }); - }); - }); -}); diff --git a/test/vitest/__tests__/composables/useArrayData.spec.js b/test/vitest/__tests__/composables/useArrayData.spec.js deleted file mode 100644 index d4c5d094954..00000000000 --- a/test/vitest/__tests__/composables/useArrayData.spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; -import { useArrayData } from 'composables/useArrayData'; -import { useRouter } from 'vue-router'; -import * as vueRouter from 'vue-router'; - -describe('useArrayData', () => { - const filter = '{"limit":20,"skip":0}'; - const params = { supplierFk: 2 }; - beforeEach(() => { - vi.spyOn(useRouter(), 'replace'); - vi.spyOn(useRouter(), 'push'); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should fetch and repalce url with new params', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [] }); - - const arrayData = useArrayData('ArrayData', { url: 'mockUrl' }); - - arrayData.store.userParams = params; - arrayData.fetch({}); - - await flushPromises(); - const routerReplace = useRouter().replace.mock.calls[0][0]; - - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, - }); - expect(routerReplace.path).toEqual('mockSection/list'); - expect(JSON.parse(routerReplace.query.params)).toEqual( - expect.objectContaining(params) - ); - }); - - it('Should get data and send new URL without keeping parameters, if there is only one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }] }); - - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); - - arrayData.store.userParams = params; - arrayData.fetch({}); - - await flushPromises(); - const routerPush = useRouter().push.mock.calls[0][0]; - - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, - }); - expect(routerPush.path).toEqual('mockName/1'); - expect(routerPush.query).toBeUndefined(); - }); - - it('Should get data and send new URL keeping parameters, if you have more than one record', async () => { - vi.spyOn(axios, 'get').mockReturnValueOnce({ data: [{ id: 1 }, { id: 2 }] }); - - vi.spyOn(vueRouter, 'useRoute').mockReturnValue({ - matched: [], - query: {}, - params: {}, - meta: { moduleName: 'mockName' }, - path: 'mockName/1', - }); - vi.spyOn(vueRouter, 'useRouter').mockReturnValue({ - push: vi.fn(), - replace: vi.fn(), - currentRoute: { - value: { - params: { - id: 1, - }, - meta: { moduleName: 'mockName' }, - matched: [{ path: 'mockName/:id' }], - }, - }, - }); - - const arrayData = useArrayData('ArrayData', { url: 'mockUrl', navigate: {} }); - - arrayData.store.userParams = params; - arrayData.fetch({}); - - await flushPromises(); - const routerPush = useRouter().push.mock.calls[0][0]; - - expect(axios.get.mock.calls[0][1].params).toEqual({ - filter, - supplierFk: 2, - }); - expect(routerPush.path).toEqual('mockName/'); - expect(routerPush.query.params).toBeDefined(); - }); -}); diff --git a/test/vitest/__tests__/composables/useRole.spec.js b/test/vitest/__tests__/composables/useRole.spec.js deleted file mode 100644 index d0bca5342f8..00000000000 --- a/test/vitest/__tests__/composables/useRole.spec.js +++ /dev/null @@ -1,73 +0,0 @@ -import { vi, describe, expect, it } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; -import { useRole } from 'composables/useRole'; -const role = useRole(); - -describe('useRole', () => { - describe('fetch', () => { - it('should call setUser and setRoles of the state with the expected data', async () => { - const rolesData = [ - { - role: { - name: 'salesPerson', - }, - }, - { - role: { - name: 'admin', - }, - }, - ]; - const fetchedUser = { - id: 999, - name: `T'Challa`, - nickname: 'Black Panther', - lang: 'en', - }; - const expectedUser = { - id: 999, - name: `T'Challa`, - nickname: 'Black Panther', - lang: 'en', - }; - const expectedRoles = ['salesPerson', 'admin']; - vi.spyOn(axios, 'get') - .mockResolvedValueOnce({ - data: { roles: rolesData, user: fetchedUser }, - }) - - vi.spyOn(role.state, 'setUser'); - vi.spyOn(role.state, 'setRoles'); - - role.fetch(); - - await flushPromises(); - - expect(role.state.setUser).toHaveBeenCalledWith(expectedUser); - expect(role.state.setRoles).toHaveBeenCalledWith(expectedRoles); - - role.state.setRoles([]); - }); - }); - - describe('hasAny', () => { - it('should return true if a role matched', async () => { - role.state.setRoles(['admin']); - const hasRole = role.hasAny(['admin']); - - await flushPromises(); - - expect(hasRole).toBe(true); - - role.state.setRoles([]); - }); - - it('should return false if no roles matched', async () => { - const hasRole = role.hasAny(['admin']); - - await flushPromises(); - - expect(hasRole).toBe(false); - }); - }); -}); diff --git a/test/vitest/__tests__/composables/useSession.spec.js b/test/vitest/__tests__/composables/useSession.spec.js deleted file mode 100644 index 789b149ec7e..00000000000 --- a/test/vitest/__tests__/composables/useSession.spec.js +++ /dev/null @@ -1,211 +0,0 @@ -import { vi, describe, expect, it, beforeAll, beforeEach } from 'vitest'; -import { axios } from 'app/test/vitest/helper'; -import { useSession } from 'composables/useSession'; -import { useState } from 'composables/useState'; - -const session = useSession(); -const state = useState(); - -describe('session', () => { - describe('getToken / setToken', () => { - it('should return an empty string if no token is found in local or session storage', async () => { - const expectedToken = ''; - - const token = session.getToken(); - - expect(token).toEqual(expectedToken); - }); - - it('should return the token stored in local or session storage', async () => { - const expectedToken = 'myToken'; - const data = { - token: expectedToken, - keepLogin: false, - }; - session.setToken(data); - - const token = session.getToken(); - - expect(token).toEqual(expectedToken); - }); - }); - - describe('destroy', () => { - it('should remove the token from the local storage and set a blank user', async () => { - const previousUser = { - id: 999, - name: `T'Challa`, - nickname: 'Black Panther', - lang: 'en', - darkMode: false, - }; - const expectedUser = { - id: 0, - name: '', - nickname: '', - lang: '', - darkMode: null, - }; - let user = state.getUser(); - - localStorage.setItem('token', 'tokenToBeGone'); - state.setUser(previousUser); - - expect(localStorage.getItem('token')).toEqual('tokenToBeGone'); - expect(user.value).toEqual(previousUser); - - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(axios, 'get').mockResolvedValue({ data: true }); - await session.destroy(); - - user = state.getUser(); - expect(localStorage.getItem('token')).toBeNull(); - expect(user.value).toEqual(expectedUser); - }); - }); - - describe( - 'login', - () => { - const expectedUser = { - id: 999, - name: `T'Challa`, - nickname: 'Black Panther', - lang: 'en', - userConfig: { - darkMode: false, - }, - }; - const rolesData = [ - { - role: { - name: 'salesPerson', - }, - }, - { - role: { - name: 'admin', - }, - }, - ]; - beforeEach(() => { - vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); - return Promise.resolve({ - data: { roles: rolesData, user: expectedUser }, - }); - }); - }); - - it('should fetch the user roles and then set token in the sessionStorage', async () => { - const expectedRoles = ['salesPerson', 'admin']; - const expectedToken = 'mySessionToken'; - const expectedTokenMultimedia = 'mySessionTokenMultimedia'; - const keepLogin = false; - - await session.login({ - token: expectedToken, - tokenMultimedia: expectedTokenMultimedia, - keepLogin, - }); - - const roles = state.getRoles(); - const localToken = localStorage.getItem('token'); - const sessionToken = sessionStorage.getItem('token'); - - expect(roles.value).toEqual(expectedRoles); - expect(localToken).toBeNull(); - expect(sessionToken).toEqual(expectedToken); - - await session.destroy(); // this clears token and user for any other test - }); - - it('should fetch the user roles and then set token in the localStorage', async () => { - const expectedRoles = ['salesPerson', 'admin']; - const expectedToken = 'myLocalToken'; - const expectedTokenMultimedia = 'myLocalTokenMultimedia'; - const keepLogin = true; - - await session.login({ - token: expectedToken, - tokenMultimedia: expectedTokenMultimedia, - keepLogin, - }); - - const roles = state.getRoles(); - const localToken = localStorage.getItem('token'); - const sessionToken = sessionStorage.getItem('token'); - - expect(roles.value).toEqual(expectedRoles); - expect(localToken).toEqual(expectedToken); - expect(sessionToken).toBeNull(); - - await session.destroy(); // this clears token and user for any other test - }); - }, - {} - ); - - describe('RenewToken', () => { - const expectedToken = 'myToken'; - const expectedTokenMultimedia = 'myTokenMultimedia'; - const currentDate = new Date(); - beforeAll(() => { - const tokenConfig = { - id: 1, - renewPeriod: 21600, - courtesyTime: 60, - renewInterval: 300, - }; - state.setTokenConfig(tokenConfig); - sessionStorage.setItem('renewPeriod', 1); - }); - it('NOT Should renewToken', async () => { - const data = { - token: expectedToken, - tokenMultimedia: expectedTokenMultimedia, - keepLogin: false, - ttl: 1, - created: Date.now(), - }; - session.setSession(data); - expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); - expect(sessionStorage.getItem('created')).toBeDefined(); - expect(sessionStorage.getItem('ttl')).toEqual(1); - await session.checkValidity(); - expect(sessionStorage.getItem('token')).toEqual(expectedToken); - expect(sessionStorage.getItem('tokenMultimedia')).toEqual( - expectedTokenMultimedia - ); - }); - it('Should renewToken', async () => { - currentDate.setMinutes(currentDate.getMinutes() - 100); - const data = { - token: expectedToken, - tokenMultimedia: expectedTokenMultimedia, - keepLogin: false, - ttl: 1, - created: currentDate, - }; - session.setSession(data); - - vi.spyOn(axios, 'post') - .mockResolvedValueOnce({ - data: { id: '' }, - }) - .mockResolvedValueOnce({ - data: { - id: '', - }, - }); - expect(sessionStorage.getItem('keepLogin')).toBeFalsy(); - expect(sessionStorage.getItem('created')).toBeDefined(); - expect(sessionStorage.getItem('ttl')).toEqual(1); - await session.checkValidity(); - expect(sessionStorage.getItem('token')).not.toEqual(expectedToken); - expect(sessionStorage.getItem('tokenMultimedia')).not.toEqual( - expectedTokenMultimedia - ); - }); - }); -}); diff --git a/test/vitest/__tests__/composables/useTokenConfig.spec.js b/test/vitest/__tests__/composables/useTokenConfig.spec.js deleted file mode 100644 index a25a4abb1e4..00000000000 --- a/test/vitest/__tests__/composables/useTokenConfig.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { vi, describe, expect, it } from 'vitest'; -import { axios, flushPromises } from 'app/test/vitest/helper'; -import { useTokenConfig } from 'composables/useTokenConfig'; -const tokenConfig = useTokenConfig(); - -describe('useTokenConfig', () => { - describe('fetch', () => { - it('should call setTokenConfig of the state with the expected data', async () => { - const data = { - id: 1, - renewPeriod: 21600, - courtesyTime: 60, - renewInterval: 300, - }; - vi.spyOn(axios, 'get').mockResolvedValueOnce({ - data, - }); - - vi.spyOn(tokenConfig.state, 'setTokenConfig'); - - tokenConfig.fetch(); - - await flushPromises(); - - expect(tokenConfig.state.setTokenConfig).toHaveBeenCalledWith(data); - - const renewPeriod = sessionStorage.getItem('renewPeriod'); - expect(renewPeriod).toEqual(data.renewPeriod); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js b/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js deleted file mode 100644 index b208f1704ac..00000000000 --- a/test/vitest/__tests__/pages/Claims/ClaimDescriptorMenu.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; - -describe('ClaimDescriptorMenu', () => { - let vm; - beforeAll(() => { - vm = createWrapper(ClaimDescriptorMenu, { - propsData: { - claim: { - id: 1, - }, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('remove()', () => { - it('should delete the claim', async () => { - vi.spyOn(axios, 'delete').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - - await vm.remove(); - - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'positive' }) - ); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Claims/ClaimLines.spec.js b/test/vitest/__tests__/pages/Claims/ClaimLines.spec.js deleted file mode 100644 index 2f2c0e2989d..00000000000 --- a/test/vitest/__tests__/pages/Claims/ClaimLines.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import ClaimLines from '/src/pages/Claim/Card/ClaimLines.vue'; - -describe('ClaimLines', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(ClaimLines, { - global: { - stubs: ['FetchData', 'VnPaginate'], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - }); - - beforeEach(() => { - vm.claim = { - id: 1, - ticketFk: 1, - }; - vm.store.data = [ - { - id: 1, - quantity: 10, - sale: { - id: 1, - discount: 0, - }, - }, - ]; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('updateDiscount()', () => { - it('should make a POST request to endpoint "updateDiscount"', async () => { - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - - const canceller = new AbortController(); - await vm.updateDiscount({ saleFk: 1, discount: 5, canceller }); - - const expectedData = { salesIds: [1], newDiscount: 5 }; - expect(axios.post).toHaveBeenCalledWith( - 'Tickets/1/updateDiscount', - expectedData, - { - signal: canceller.signal, - } - ); - }); - }); - - describe('onUpdateDiscount()', () => { - it('should make a POST request and then set the discount on the original row', async () => { - vi.spyOn(vm.quasar, 'notify'); - - vm.onUpdateDiscount({ discount: 5, rowIndex: 0 }); - const firstRow = vm.store.data[0]; - - expect(firstRow.sale.discount).toEqual(5); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Discount updated', - type: 'positive', - }) - ); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Claims/ClaimLinesImport.spec.js b/test/vitest/__tests__/pages/Claims/ClaimLinesImport.spec.js deleted file mode 100644 index d93c9613238..00000000000 --- a/test/vitest/__tests__/pages/Claims/ClaimLinesImport.spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import { vi, describe, expect, it, beforeAll, beforeEach, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import ClaimLinesImport from 'pages/Claim/Card/ClaimLinesImport.vue'; - -describe('ClaimLinesImport', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(ClaimLinesImport, { - global: { - stubs: ['FetchData'], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('importLines()', () => { - it('should make a POST request and then call to the quasar notify() method', async () => { - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - - vm.selected = [{ id: 1, saleFk: 1, claimFk: 1 }]; - - vm.route.params.id = 1; - - await vm.importLines(); - const expectedData = [{ saleFk: 1, claimFk: 1 }]; - - expect(axios.post).toHaveBeenCalledWith('ClaimBeginnings', expectedData, { - signal: expect.any(Object), - }); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Lines added to claim', - type: 'positive', - }) - ); - expect(vm.canceller).toEqual(null); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js b/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js deleted file mode 100644 index c38852af154..00000000000 --- a/test/vitest/__tests__/pages/Claims/ClaimPhoto.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import ClaimPhoto from 'pages/Claim/Card/ClaimPhoto.vue'; - -describe('ClaimPhoto', () => { - let vm; - - const claimMock = { - claimDms: [ - { - dmsFk: 1, - dms: { - contentType: 'contentType', - }, - }, - ], - client: { - id: '1', - }, - }; - beforeAll(() => { - vm = createWrapper(ClaimPhoto, { - global: { - stubs: ['FetchData', 'vue-i18n'], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('deleteDms()', () => { - it('should delete dms and call quasar notify', async () => { - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - - await vm.deleteDms({ index: 0 }); - - expect(axios.post).toHaveBeenCalledWith( - `ClaimDms/${claimMock.claimDms[0].dmsFk}/removeFile` - ); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'positive' }) - ); - }); - }); - - describe('viewDeleteDms()', () => { - it('should call quasar dialog', async () => { - vi.spyOn(vm.quasar, 'dialog'); - - await vm.viewDeleteDms(1); - - expect(vm.quasar.dialog).toHaveBeenCalledWith( - expect.objectContaining({ - componentProps: { - title: 'This file will be deleted', - icon: 'delete', - data: { index: 1 }, - promise: vm.deleteDms - }, - }) - ); - }); - }); - - describe('setClaimDms()', () => { - it('should assign claimDms and client from data', async () => { - await vm.setClaimDms(claimMock); - - expect(vm.claimDms).toEqual([ - { - dmsFk: 1, - dms: { - contentType: 'contentType', - }, - isVideo: false, - url: '/api/Claims/1/downloadFile?access_token=', - }, - ]); - - expect(vm.client).toEqual(claimMock.client); - }); - }); - - describe('create()', () => { - it('should upload file and call quasar notify', async () => { - const files = [{ name: 'firstFile' }]; - - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - vi.spyOn(vm.claimDmsRef, 'fetch'); - - await vm.create(files); - - expect(axios.post).toHaveBeenCalledWith( - 'claims/1/uploadFile', - new FormData(), - expect.objectContaining({ - params: expect.objectContaining({ hasFile: false }), - }) - ); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'positive' }) - ); - - expect(vm.claimDmsRef.fetch).toHaveBeenCalledOnce(); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Customer/CustomerPayments.spec.js b/test/vitest/__tests__/pages/Customer/CustomerPayments.spec.js deleted file mode 100644 index 466a544b4c2..00000000000 --- a/test/vitest/__tests__/pages/Customer/CustomerPayments.spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import CustomerPayments from 'src/pages/Customer/Payments/CustomerPayments.vue'; - -describe('CustomerPayments', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(CustomerPayments, { - global: { - stubs: ['VnPaginate'], - mocks: { - fetch: vi.fn(), - }, - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('confirmTransaction()', () => { - it('should make a POST request and then call to quasar notify method', async () => { - vi.spyOn(axios, 'post').mockResolvedValue({ data: true }); - vi.spyOn(vm.quasar, 'notify'); - - await vm.confirmTransaction({ id: 1 }); - - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Payment confirmed', - type: 'positive', - }) - ); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Login/Login.spec.js b/test/vitest/__tests__/pages/Login/Login.spec.js deleted file mode 100644 index e90a8ee535b..00000000000 --- a/test/vitest/__tests__/pages/Login/Login.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import Login from 'pages/Login/LoginMain.vue'; - -describe('Login', () => { - let vm; - beforeAll(() => { - vm = createWrapper(Login).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should successfully set the token into session', async () => { - const expectedUser = { - id: 999, - name: `T'Challa`, - nickname: 'Black Panther', - lang: 'en', - userConfig: { - darkMode: false, - }, - }; - vi.spyOn(axios, 'post').mockResolvedValueOnce({ data: { token: 'token' } }); - vi.spyOn(axios, 'get').mockImplementation((url) => { - if (url === 'VnUsers/acls') return Promise.resolve({ data: [] }); - return Promise.resolve({ - data: { - roles: [], - user: expectedUser, - multimediaToken: { id: 'multimediaToken' }, - }, - }); - }); - - expect(vm.session.getToken()).toEqual(''); - - await vm.onSubmit(); - - expect(vm.session.getToken()).toEqual('token'); - await vm.session.destroy(); - }); - - it('should not set the token into session if any error occurred', async () => { - vi.spyOn(axios, 'post').mockReturnValue({ data: null }); - await vm.onSubmit(); - }); -}); diff --git a/test/vitest/__tests__/pages/Tickets/TicketAdvance.spec.js b/test/vitest/__tests__/pages/Tickets/TicketAdvance.spec.js deleted file mode 100644 index ab1a47544ff..00000000000 --- a/test/vitest/__tests__/pages/Tickets/TicketAdvance.spec.js +++ /dev/null @@ -1,120 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach, beforeEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import TicketAdvance from 'pages/Ticket/TicketAdvance.vue'; -import { Notify } from 'quasar'; -import { nextTick } from 'vue'; - -describe('TicketAdvance', () => { - let wrapper; - let vm; - - beforeAll(() => { - vi.spyOn(axios, 'get').mockImplementation(() => ({ data: [] })); - wrapper = createWrapper(TicketAdvance); - vm = wrapper.vm; - vi.spyOn(vm.vnTableRef, 'reload').mockImplementation(() => vi.fn()); - vm.vnTableRef.value = { params: {} }; - }); - beforeEach(() => { - Notify.create = vi.fn(); - }); - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('requestComponentUpdate()', () => { - const mockTicket = { - futureId: 1, - futureClientFk: 1, - nickname: 'test', - futureAddressFk: 1, - futureAgencyModeFk: 1, - futureWarehouseFk: 1, - futureCompanyFk: 1, - landed: '2023-01-02', - zoneFk: 1, - }; - const mockParams = { - clientFk: 1, - nickname: 'test', - agencyModeFk: 1, - addressFk: 1, - zoneFk: 1, - warehouseFk: 1, - companyFk: 1, - landed: '2023-01-02', - shipped: '2023-01-01', - isDeleted: false, - isWithoutNegatives: false, - newTicket: undefined, - keepPrice: true, - }; - const queryResult = 'tickets/1/componentUpdate'; - - it('should return query and params when ticket has no landed', async () => { - vm.vnTableRef.params.dateToAdvance = '2023-01-01'; - await nextTick(); - - const mockLanded = { landed: '2023-01-02', zoneFk: 1 }; - vi.spyOn(vm, 'getLanded').mockResolvedValue(mockLanded); - - const { query, params } = await vm.requestComponentUpdate(mockTicket, false); - - expect(query).toBe(queryResult); - expect(params).toEqual(mockParams); - }); - - it('should return query and params when ticket has landed', async () => { - const { query, params } = await vm.requestComponentUpdate(mockTicket, false); - - expect(query).toBe(queryResult); - expect(params).toEqual(mockParams); - }); - }); - - describe('moveTicketsAdvance()', () => { - it('should move tickets and notify success', async () => { - const tickets = [ - { - id: 1, - futureId: 2, - futureShipped: '2023-01-01', - shipped: '2023-01-02', - workerFk: 1, - }, - { - id: 2, - futureId: 3, - futureShipped: '2023-01-01', - shipped: '2023-01-02', - workerFk: 1, - }, - ]; - vm.selectedTickets = tickets; - vi.spyOn(axios, 'post').mockResolvedValue({}); - await vm.moveTicketsAdvance(); - - expect(axios.post).toHaveBeenCalledOnce('Tickets/merge', { - tickets: [ - { - originId: 2, - destinationId: 1, - originShipped: '2023-01-01', - destinationShipped: '2023-01-02', - workerFk: 1, - }, - { - originId: 3, - destinationId: 2, - originShipped: '2023-01-01', - destinationShipped: '2023-01-02', - workerFk: 1, - }, - ], - }); - expect(vm.vnTableRef.reload).toHaveBeenCalled(); - expect(Notify.create).toHaveBeenCalled(); - expect(vm.selectedTickets).toEqual([]); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Tickets/TicketBoxing.spec.js b/test/vitest/__tests__/pages/Tickets/TicketBoxing.spec.js deleted file mode 100644 index 8fd62d8c230..00000000000 --- a/test/vitest/__tests__/pages/Tickets/TicketBoxing.spec.js +++ /dev/null @@ -1,50 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import TicketBoxing from 'pages/Ticket/Card/TicketBoxing.vue'; - -// #4836 - Investigate how to test q-drawer outside -// q-layout or how to teleport q-drawer inside -describe('TicketBoxing', () => { - let vm; - beforeAll(() => { - vm = createWrapper(TicketBoxing).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('getVideoList()', () => { - it('should when response videoList use to list', async () => { - const expeditionId = 1; - const timed = { - min: 1, - max: 2, - }; - const videoList = ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4']; - - vi.spyOn(axios, 'get').mockResolvedValue({ data: videoList }); - vi.spyOn(vm.quasar, 'notify'); - - await vm.getVideoList(expeditionId, timed); - - expect(vm.videoList.length).toEqual(videoList.length); - expect(vm.slide).toEqual(videoList.reverse()[0]); - }); - - it('should if not have video show notify', async () => { - const expeditionId = 1; - const timed = { - min: 1, - max: 2, - }; - - vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); - vi.spyOn(vm.quasar, 'notify'); - - await vm.getVideoList(expeditionId, timed); - - expect(vm.quasar.notify).toHaveBeenCalledWith(expect.objectContaining({ type: 'negative' })); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Wagons/WagonCreate.spec.js b/test/vitest/__tests__/pages/Wagons/WagonCreate.spec.js deleted file mode 100644 index f195c183f17..00000000000 --- a/test/vitest/__tests__/pages/Wagons/WagonCreate.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; -import WagonCreate from 'pages/Wagon/WagonCreate.vue'; - -describe('WagonCreate', () => { - let vmEdit, vmCreate; - const entityId = 1; - - beforeAll(() => { - vmEdit = createWrapper(WagonCreate, { - propsData: { - id: entityId, - }, - }).vm; - vmCreate = createWrapper(WagonCreate).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('onSubmit()', () => { - it('should create a wagon', async () => { - vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); - vmCreate.wagon = { - label: 1234, - plate: 'MOCK PLATE', - volume: 50, - typeFk: 1, - }; - - await vmCreate.onSubmit(); - - expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmCreate.wagon); - }); - - it('should update a wagon', async () => { - vi.spyOn(axios, 'patch').mockResolvedValue({ data: true }); - vmEdit.wagon = { - id: entityId, - label: 1234, - plate: 'MOCK PLATE', - volume: 50, - typeFk: 1, - }; - - await vmEdit.onSubmit(); - - expect(axios.patch).toHaveBeenCalledWith(`Wagons`, vmEdit.wagon); - }); - }); - - describe('onReset()', () => { - it('should reset wagon if have id', async () => { - vmEdit.originalData = { - label: 1234, - plate: 'Original', - volume: 200, - typeFk: 1, - }; - vmEdit.wagon = { - label: 4321, - plate: 'Edited', - volume: 50, - typeFk: 2, - }; - - await vmEdit.onReset(); - - expect(vmEdit.wagon).toEqual(vmEdit.originalData); - }); - - it('should reset wagon if not have id', async () => { - vmCreate.wagon = { - label: 4321, - plate: 'Edited', - volume: 50, - typeFk: 2, - }; - - await vmCreate.onReset(); - - expect(vmCreate.wagon).toEqual({}); - }); - }); - - describe('fetch()', () => { - it('should fetch data', async () => { - vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); - - await vmEdit.fetch(); - - expect(axios.get).toHaveBeenCalledWith(`WagonTypes`); - expect(axios.get).toHaveBeenCalledWith(`Wagons/${entityId}`); - }); - }); -}); diff --git a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js b/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js deleted file mode 100644 index 35ce91e618c..00000000000 --- a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; -import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue'; -import { ref } from 'vue'; - -describe('WorkerNotificationsManager', () => { - let vm; - - beforeAll(() => { - vm = createWrapper(WorkerNotificationsManager, { - global: { - stubs: ['CrudModel'], - }, - }).vm; - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe('swapEntry()', () => { - it('should swap notification', async () => { - const from = ref(new Map()); - const to = ref(new Map()); - from.value.set(1, { notificationFk: 1 }); - to.value.set(2, { notificationFk: 2 }); - - await vm.swapEntry(from.value, to.value, 1); - expect(to.value.size).toBe(2); - expect(from.value.size).toBe(0); - }); - }); -}); diff --git a/test/vitest/__tests__/stores/useStateQueryStore.spec.js b/test/vitest/__tests__/stores/useStateQueryStore.spec.js deleted file mode 100644 index ab3afb007d8..00000000000 --- a/test/vitest/__tests__/stores/useStateQueryStore.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, expect, it, beforeEach, beforeAll } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; - -import { useStateQueryStore } from 'src/stores/useStateQueryStore'; - -describe('useStateQueryStore', () => { - beforeAll(() => { - createWrapper({}, {}); - }); - - const stateQueryStore = useStateQueryStore(); - const { add, isLoading, remove, reset } = useStateQueryStore(); - const firstQuery = { url: 'myQuery' }; - - function getQueries() { - return stateQueryStore.queries; - } - - beforeEach(() => { - reset(); - expect(getQueries().size).toBeFalsy(); - }); - - it('should add two queries', async () => { - expect(getQueries().size).toBeFalsy(); - add(firstQuery); - - expect(getQueries().size).toBeTruthy(); - expect(getQueries().has(firstQuery)).toBeTruthy(); - - add(); - expect(getQueries().size).toBe(2); - }); - - it('should add and remove loading state', async () => { - expect(isLoading().value).toBeFalsy(); - add(firstQuery); - expect(isLoading().value).toBeTruthy(); - remove(firstQuery); - expect(isLoading().value).toBeFalsy(); - }); - - it('should add and remove query', async () => { - const secondQuery = { ...firstQuery }; - const thirdQuery = { ...firstQuery }; - - add(firstQuery); - add(secondQuery); - - const beforeCount = getQueries().size; - add(thirdQuery); - expect(getQueries().has(thirdQuery)).toBeTruthy(); - - remove(thirdQuery); - expect(getQueries().has(thirdQuery)).toBeFalsy(); - expect(getQueries().size).toBe(beforeCount); - }); -}); diff --git a/vitest.config.js b/vitest.config.js index ca9f6c1fea1..a465f0e2d7f 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -13,7 +13,7 @@ export default defineConfig({ include: [ // Matches vitest tests in any subfolder of 'src' or into 'test/vitest/__tests__' // Matches all files with extension 'js', 'jsx', 'ts' and 'tsx' - 'test/vitest/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', + 'src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', ], }, plugins: [ From 77d7c2261ecd263fc13f034a0003512f920f15b9 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Fri, 20 Dec 2024 09:19:49 +0100 Subject: [PATCH 123/142] refactor: refs #8320 changed folder names from "specs" to "__tests__" --- src/boot/{specs => __tests__}/axios.spec.js | 0 src/components/VnTable/{specs => __tests__}/VnTable.spec.js | 0 src/components/{specs => __tests__}/CrudModel.spec.js | 0 src/components/{specs => __tests__}/Leftmenu.spec.js | 0 .../common/{specs => __tests__}/VnChangePassword.spec.js | 0 src/components/common/{specs => __tests__}/VnDiscount.spec.js | 0 src/components/common/{specs => __tests__}/VnLog.spec.js | 0 src/components/common/{specs => __tests__}/VnSmsDialog.spec.js | 0 src/components/ui/{specs => __tests__}/Paginate.spec.js | 0 src/components/ui/{specs => __tests__}/VnLinkPhone.spec.js | 0 src/components/ui/{specs => __tests__}/VnSms.spec.js | 0 src/composables/{specs => __tests__}/downloadFile.spec.js | 0 src/composables/{specs => __tests__}/getExchange.spec.js | 0 src/composables/{specs => __tests__}/getTotal.spec.js | 0 .../{specs => __tests__}/useAccountShortToStandard.spec.js | 0 src/composables/{specs => __tests__}/useAcl.spec.js | 0 src/composables/{specs => __tests__}/useArrayData.spec.js | 0 src/composables/{specs => __tests__}/useRole.spec.js | 0 src/composables/{specs => __tests__}/useSession.spec.js | 0 src/composables/{specs => __tests__}/useTokenConfig.spec.js | 0 .../Claim/Card/{specs => __tests__}/ClaimDescriptorMenu.spec.js | 0 src/pages/Claim/Card/{specs => __tests__}/ClaimLines.spec.js | 0 .../Claim/Card/{specs => __tests__}/ClaimLinesImport.spec.js | 0 src/pages/Claim/Card/{specs => __tests__}/ClaimPhoto.spec.js | 0 .../Payments/{specs => __tests__}/CustomerPayments.spec.js | 0 src/pages/Login/{specs => __tests__}/Login.spec.js | 0 src/pages/Ticket/Card/{specs => __tests__}/TicketBoxing.spec.js | 0 src/pages/Ticket/{specs => __tests__}/TicketAdvance.spec.js | 0 src/pages/Wagon/{specs => __tests__}/WagonCreate.spec.js | 0 .../Card/{specs => __tests__}/WorkerNotificationsManager.spec.js | 0 src/stores/{specs => __tests__}/useStateQueryStore.spec.js | 0 31 files changed, 0 insertions(+), 0 deletions(-) rename src/boot/{specs => __tests__}/axios.spec.js (100%) rename src/components/VnTable/{specs => __tests__}/VnTable.spec.js (100%) rename src/components/{specs => __tests__}/CrudModel.spec.js (100%) rename src/components/{specs => __tests__}/Leftmenu.spec.js (100%) rename src/components/common/{specs => __tests__}/VnChangePassword.spec.js (100%) rename src/components/common/{specs => __tests__}/VnDiscount.spec.js (100%) rename src/components/common/{specs => __tests__}/VnLog.spec.js (100%) rename src/components/common/{specs => __tests__}/VnSmsDialog.spec.js (100%) rename src/components/ui/{specs => __tests__}/Paginate.spec.js (100%) rename src/components/ui/{specs => __tests__}/VnLinkPhone.spec.js (100%) rename src/components/ui/{specs => __tests__}/VnSms.spec.js (100%) rename src/composables/{specs => __tests__}/downloadFile.spec.js (100%) rename src/composables/{specs => __tests__}/getExchange.spec.js (100%) rename src/composables/{specs => __tests__}/getTotal.spec.js (100%) rename src/composables/{specs => __tests__}/useAccountShortToStandard.spec.js (100%) rename src/composables/{specs => __tests__}/useAcl.spec.js (100%) rename src/composables/{specs => __tests__}/useArrayData.spec.js (100%) rename src/composables/{specs => __tests__}/useRole.spec.js (100%) rename src/composables/{specs => __tests__}/useSession.spec.js (100%) rename src/composables/{specs => __tests__}/useTokenConfig.spec.js (100%) rename src/pages/Claim/Card/{specs => __tests__}/ClaimDescriptorMenu.spec.js (100%) rename src/pages/Claim/Card/{specs => __tests__}/ClaimLines.spec.js (100%) rename src/pages/Claim/Card/{specs => __tests__}/ClaimLinesImport.spec.js (100%) rename src/pages/Claim/Card/{specs => __tests__}/ClaimPhoto.spec.js (100%) rename src/pages/Customer/Payments/{specs => __tests__}/CustomerPayments.spec.js (100%) rename src/pages/Login/{specs => __tests__}/Login.spec.js (100%) rename src/pages/Ticket/Card/{specs => __tests__}/TicketBoxing.spec.js (100%) rename src/pages/Ticket/{specs => __tests__}/TicketAdvance.spec.js (100%) rename src/pages/Wagon/{specs => __tests__}/WagonCreate.spec.js (100%) rename src/pages/Worker/Card/{specs => __tests__}/WorkerNotificationsManager.spec.js (100%) rename src/stores/{specs => __tests__}/useStateQueryStore.spec.js (100%) diff --git a/src/boot/specs/axios.spec.js b/src/boot/__tests__/axios.spec.js similarity index 100% rename from src/boot/specs/axios.spec.js rename to src/boot/__tests__/axios.spec.js diff --git a/src/components/VnTable/specs/VnTable.spec.js b/src/components/VnTable/__tests__/VnTable.spec.js similarity index 100% rename from src/components/VnTable/specs/VnTable.spec.js rename to src/components/VnTable/__tests__/VnTable.spec.js diff --git a/src/components/specs/CrudModel.spec.js b/src/components/__tests__/CrudModel.spec.js similarity index 100% rename from src/components/specs/CrudModel.spec.js rename to src/components/__tests__/CrudModel.spec.js diff --git a/src/components/specs/Leftmenu.spec.js b/src/components/__tests__/Leftmenu.spec.js similarity index 100% rename from src/components/specs/Leftmenu.spec.js rename to src/components/__tests__/Leftmenu.spec.js diff --git a/src/components/common/specs/VnChangePassword.spec.js b/src/components/common/__tests__/VnChangePassword.spec.js similarity index 100% rename from src/components/common/specs/VnChangePassword.spec.js rename to src/components/common/__tests__/VnChangePassword.spec.js diff --git a/src/components/common/specs/VnDiscount.spec.js b/src/components/common/__tests__/VnDiscount.spec.js similarity index 100% rename from src/components/common/specs/VnDiscount.spec.js rename to src/components/common/__tests__/VnDiscount.spec.js diff --git a/src/components/common/specs/VnLog.spec.js b/src/components/common/__tests__/VnLog.spec.js similarity index 100% rename from src/components/common/specs/VnLog.spec.js rename to src/components/common/__tests__/VnLog.spec.js diff --git a/src/components/common/specs/VnSmsDialog.spec.js b/src/components/common/__tests__/VnSmsDialog.spec.js similarity index 100% rename from src/components/common/specs/VnSmsDialog.spec.js rename to src/components/common/__tests__/VnSmsDialog.spec.js diff --git a/src/components/ui/specs/Paginate.spec.js b/src/components/ui/__tests__/Paginate.spec.js similarity index 100% rename from src/components/ui/specs/Paginate.spec.js rename to src/components/ui/__tests__/Paginate.spec.js diff --git a/src/components/ui/specs/VnLinkPhone.spec.js b/src/components/ui/__tests__/VnLinkPhone.spec.js similarity index 100% rename from src/components/ui/specs/VnLinkPhone.spec.js rename to src/components/ui/__tests__/VnLinkPhone.spec.js diff --git a/src/components/ui/specs/VnSms.spec.js b/src/components/ui/__tests__/VnSms.spec.js similarity index 100% rename from src/components/ui/specs/VnSms.spec.js rename to src/components/ui/__tests__/VnSms.spec.js diff --git a/src/composables/specs/downloadFile.spec.js b/src/composables/__tests__/downloadFile.spec.js similarity index 100% rename from src/composables/specs/downloadFile.spec.js rename to src/composables/__tests__/downloadFile.spec.js diff --git a/src/composables/specs/getExchange.spec.js b/src/composables/__tests__/getExchange.spec.js similarity index 100% rename from src/composables/specs/getExchange.spec.js rename to src/composables/__tests__/getExchange.spec.js diff --git a/src/composables/specs/getTotal.spec.js b/src/composables/__tests__/getTotal.spec.js similarity index 100% rename from src/composables/specs/getTotal.spec.js rename to src/composables/__tests__/getTotal.spec.js diff --git a/src/composables/specs/useAccountShortToStandard.spec.js b/src/composables/__tests__/useAccountShortToStandard.spec.js similarity index 100% rename from src/composables/specs/useAccountShortToStandard.spec.js rename to src/composables/__tests__/useAccountShortToStandard.spec.js diff --git a/src/composables/specs/useAcl.spec.js b/src/composables/__tests__/useAcl.spec.js similarity index 100% rename from src/composables/specs/useAcl.spec.js rename to src/composables/__tests__/useAcl.spec.js diff --git a/src/composables/specs/useArrayData.spec.js b/src/composables/__tests__/useArrayData.spec.js similarity index 100% rename from src/composables/specs/useArrayData.spec.js rename to src/composables/__tests__/useArrayData.spec.js diff --git a/src/composables/specs/useRole.spec.js b/src/composables/__tests__/useRole.spec.js similarity index 100% rename from src/composables/specs/useRole.spec.js rename to src/composables/__tests__/useRole.spec.js diff --git a/src/composables/specs/useSession.spec.js b/src/composables/__tests__/useSession.spec.js similarity index 100% rename from src/composables/specs/useSession.spec.js rename to src/composables/__tests__/useSession.spec.js diff --git a/src/composables/specs/useTokenConfig.spec.js b/src/composables/__tests__/useTokenConfig.spec.js similarity index 100% rename from src/composables/specs/useTokenConfig.spec.js rename to src/composables/__tests__/useTokenConfig.spec.js diff --git a/src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js b/src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js similarity index 100% rename from src/pages/Claim/Card/specs/ClaimDescriptorMenu.spec.js rename to src/pages/Claim/Card/__tests__/ClaimDescriptorMenu.spec.js diff --git a/src/pages/Claim/Card/specs/ClaimLines.spec.js b/src/pages/Claim/Card/__tests__/ClaimLines.spec.js similarity index 100% rename from src/pages/Claim/Card/specs/ClaimLines.spec.js rename to src/pages/Claim/Card/__tests__/ClaimLines.spec.js diff --git a/src/pages/Claim/Card/specs/ClaimLinesImport.spec.js b/src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js similarity index 100% rename from src/pages/Claim/Card/specs/ClaimLinesImport.spec.js rename to src/pages/Claim/Card/__tests__/ClaimLinesImport.spec.js diff --git a/src/pages/Claim/Card/specs/ClaimPhoto.spec.js b/src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js similarity index 100% rename from src/pages/Claim/Card/specs/ClaimPhoto.spec.js rename to src/pages/Claim/Card/__tests__/ClaimPhoto.spec.js diff --git a/src/pages/Customer/Payments/specs/CustomerPayments.spec.js b/src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js similarity index 100% rename from src/pages/Customer/Payments/specs/CustomerPayments.spec.js rename to src/pages/Customer/Payments/__tests__/CustomerPayments.spec.js diff --git a/src/pages/Login/specs/Login.spec.js b/src/pages/Login/__tests__/Login.spec.js similarity index 100% rename from src/pages/Login/specs/Login.spec.js rename to src/pages/Login/__tests__/Login.spec.js diff --git a/src/pages/Ticket/Card/specs/TicketBoxing.spec.js b/src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js similarity index 100% rename from src/pages/Ticket/Card/specs/TicketBoxing.spec.js rename to src/pages/Ticket/Card/__tests__/TicketBoxing.spec.js diff --git a/src/pages/Ticket/specs/TicketAdvance.spec.js b/src/pages/Ticket/__tests__/TicketAdvance.spec.js similarity index 100% rename from src/pages/Ticket/specs/TicketAdvance.spec.js rename to src/pages/Ticket/__tests__/TicketAdvance.spec.js diff --git a/src/pages/Wagon/specs/WagonCreate.spec.js b/src/pages/Wagon/__tests__/WagonCreate.spec.js similarity index 100% rename from src/pages/Wagon/specs/WagonCreate.spec.js rename to src/pages/Wagon/__tests__/WagonCreate.spec.js diff --git a/src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js b/src/pages/Worker/Card/__tests__/WorkerNotificationsManager.spec.js similarity index 100% rename from src/pages/Worker/Card/specs/WorkerNotificationsManager.spec.js rename to src/pages/Worker/Card/__tests__/WorkerNotificationsManager.spec.js diff --git a/src/stores/specs/useStateQueryStore.spec.js b/src/stores/__tests__/useStateQueryStore.spec.js similarity index 100% rename from src/stores/specs/useStateQueryStore.spec.js rename to src/stores/__tests__/useStateQueryStore.spec.js From 7a53282122322c2fb94824375922062a2b447622 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Fri, 20 Dec 2024 12:00:32 +0100 Subject: [PATCH 124/142] feat: refs #7056 update route meta information and add FormModel tests --- .../__tests__/components/FormModel.spec.js | 145 ++++++++++++++++++ test/vitest/helper.js | 4 +- 2 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 test/vitest/__tests__/components/FormModel.spec.js diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/test/vitest/__tests__/components/FormModel.spec.js new file mode 100644 index 00000000000..a608d26d675 --- /dev/null +++ b/test/vitest/__tests__/components/FormModel.spec.js @@ -0,0 +1,145 @@ +import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import FormModel from 'src/components/FormModel.vue'; + +describe('FormModel', () => { + const model = 'mockModel'; + const url = 'mockUrl'; + const formInitialData = { mockKey: 'mockVal' }; + + describe('modelValue', () => { + it('should use the provided model', () => { + const { vm } = mount({ propsData: { model: 'mockModel' } }); + expect(vm.modelValue).toBe('mockModel'); + }); + + it('should use the route meta title when model is not provided', () => { + const { vm } = mount({}); + expect(vm.modelValue).toBe('formModel_mockTitle'); + }); + }); + + describe('onMounted()', () => { + let mockGet; + + beforeAll(() => { + mockGet = vi.spyOn(axios, 'get').mockResolvedValue({ data: {} }); + }); + + afterAll(() => { + mockGet.mockRestore(); + }); + + it('should not fetch when has formInitialData', () => { + mount({ propsData: { url, model, autoLoad: true, formInitialData } }); + expect(mockGet).not.toHaveBeenCalled(); + }); + + it('should fetch when there is url and auto-load', () => { + mount({ propsData: { url, model, autoLoad: true } }); + expect(mockGet).toHaveBeenCalled(); + }); + + it('should not observe changes', () => { + const { vm } = mount({ + propsData: { url, model, observeFormChanges: false, formInitialData }, + }); + + expect(vm.hasChanges).toBe(true); + vm.reset(); + expect(vm.hasChanges).toBe(true); + }); + + it('should observe changes', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData }, + }); + vm.state.set(model, { mockKey: 'mockVal' }); + expect(vm.hasChanges).toBe(false); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + expect(vm.hasChanges).toBe(true); + }); + }); + + describe.skip('watch()', () => { + let wrapper; + let vm; + + beforeAll(() => { + wrapper = mount({ + propsData: { url, model, formInitialData }, + }); + vm = wrapper.vm; + }); + + it('should call updateAndEmit when arrayData.store.data changes', async () => { + const updateAndEmitSpy = vi.spyOn(vm, 'updateAndEmit'); + await vm.$nextTick(); + console.log('vm.arrayData.store.data', vm.arrayData.store.data); + vm.arrayData.store.data = { newData: 'newValue' }; + await vm.$nextTick(); + vm.arrayData.store.data = { newData: 'anotherVal' }; + await vm.$nextTick(); + + expect(updateAndEmitSpy).toHaveBeenCalled(); + }); + + it('should call reset and fetch when $props.url or $props.filter changes', async () => { + const resetSpy = vi.spyOn(vm, 'reset'); + const fetchSpy = vi.spyOn(vm, 'fetch'); + + wrapper.setProps({ url: 'newMockUrl' }); + await wrapper.vm.$nextTick(); + expect(resetSpy).toHaveBeenCalled(); + expect(fetchSpy).toHaveBeenCalled(); + }); + }); + + describe('trimData()', () => { + let vm; + beforeAll(() => { + vm = mount({}).vm; + }); + + it('should trim whitespace from string values', () => { + const data = { key1: ' value1 ', key2: ' value2 ' }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual({ key1: 'value1', key2: 'value2' }); + }); + + it('should not modify non-string values', () => { + const data = { key1: 123, key2: true, key3: null, key4: undefined }; + const trimmedData = vm.trimData(data); + expect(trimmedData).toEqual(data); + }); + }); + + describe('onUnmounted()', () => { + it('should restore original data in the store if changes were made but not saved', async () => { + const wrapper = mount({ propsData: { model, formInitialData } }); + const vm = wrapper.vm; + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await wrapper.unmount(); + expect(vm.state.get(model)).toEqual(formInitialData); + }); + + it('should clear the store on unmount if clearStoreOnUnmount is true', async () => { + const wrapper = mount({ + propsData: { model, formInitialData, clearStoreOnUnmount: true }, + }); + const vm = wrapper.vm; + vm.hasChanges = false; + await wrapper.unmount(); + expect(vm.state.get(model)).toBeUndefined(); + }); + }); +}); + +function mount({ propsData = {} }) { + return createWrapper(FormModel, { + propsData, + }); +} diff --git a/test/vitest/helper.js b/test/vitest/helper.js index ce057c7c361..1e693ab6316 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -26,7 +26,7 @@ vi.mock('vue-router', () => ({ params: { id: 1, }, - meta: { moduleName: 'mockName' }, + meta: { moduleName: 'mockModuleName' }, matched: [{ path: 'mockName/list' }], }, }, @@ -35,7 +35,7 @@ vi.mock('vue-router', () => ({ matched: [], query: {}, params: {}, - meta: { moduleName: 'mockName' }, + meta: { moduleName: 'mockModuleName', title: 'mockTitle', name: 'mockName' }, path: 'mockSection/list', }), onBeforeRouteLeave: () => {}, From 4fc6b9b8ba6a0162e503df20110e6298f719b668 Mon Sep 17 00:00:00 2001 From: carlossa <carlossa@verdnatura.es> Date: Mon, 23 Dec 2024 09:05:18 +0100 Subject: [PATCH 125/142] fix: fix translations --- src/i18n/locale/en.yml | 1 + src/i18n/locale/es.yml | 1 + src/pages/Item/ItemFixedPriceFilter.vue | 34 +++++++++++++++---- .../Monitor/Ticket/MonitorTicketFilter.vue | 2 ++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 67d7a2beeac..4a78811e69b 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -861,6 +861,7 @@ components: ended: To mine: For me hasMinPrice: Minimum price + warehouseFk: Warehouse # LatestBuysFilter salesPersonFk: Buyer from: From diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 4fa8699c981..2bfe7ec4beb 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -853,6 +853,7 @@ components: ended: Hasta mine: Para mi hasMinPrice: Precio mínimo + wareHouseFk: Almacén # LatestBuysFilter salesPersonFk: Comprador active: Activo diff --git a/src/pages/Item/ItemFixedPriceFilter.vue b/src/pages/Item/ItemFixedPriceFilter.vue index a8f7d0c5f7b..531c7e09e94 100644 --- a/src/pages/Item/ItemFixedPriceFilter.vue +++ b/src/pages/Item/ItemFixedPriceFilter.vue @@ -32,7 +32,7 @@ const itemTypeWorkersOptions = ref([]); <QItem class="q-my-md"> <QItemSection> <VnSelect - :label="t('components.itemsFilterPanel.buyerFk')" + :label="t('params.buyerFk')" v-model="params.buyerFk" :options="itemTypeWorkersOptions" option-value="id" @@ -51,7 +51,7 @@ const itemTypeWorkersOptions = ref([]); url="Warehouses" auto-load :filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }" - :label="t('globals.warehouse')" + :label="t('params.warehouseFk')" v-model="params.warehouseFk" option-label="name" option-value="id" @@ -66,7 +66,7 @@ const itemTypeWorkersOptions = ref([]); <QItem class="q-my-md"> <QItemSection> <VnInputDate - :label="t('components.itemsFilterPanel.started')" + :label="t('params.started')" v-model="params.started" is-outlined @update:model-value="searchFn()" @@ -76,7 +76,7 @@ const itemTypeWorkersOptions = ref([]); <QItem class="q-my-md"> <QItemSection> <VnInputDate - :label="t('components.itemsFilterPanel.ended')" + :label="t('params.ended')" v-model="params.ended" is-outlined @update:model-value="searchFn()" @@ -86,7 +86,7 @@ const itemTypeWorkersOptions = ref([]); <QItem> <QItemSection> <QCheckbox - :label="t('components.itemsFilterPanel.mine')" + :label="t('params.mine')" v-model="params.mine" toggle-indeterminate @update:model-value="searchFn()" @@ -94,14 +94,14 @@ const itemTypeWorkersOptions = ref([]); <QCheckbox v-model="params.showBadDates" - :label="t(`components.itemsFilterPanel.showBadDates`)" + :label="t(`params.showBadDates`)" toggle-indeterminate @update:model-value="searchFn()" > </QCheckbox> <QCheckbox - :label="t('components.itemsFilterPanel.hasMinPrice')" + :label="t('params.hasMinPrice')" v-model="params.hasMinPrice" toggle-indeterminate @update:model-value="searchFn()" @@ -111,3 +111,23 @@ const itemTypeWorkersOptions = ref([]); </template> </ItemsFilterPanel> </template> +<i18n> +en: + params: + buyerFk: Buyer + warehouseFk: Warehouse + started: Started + ended: Ended + mine: Mine + showBadDates: Show future items + hasMinPrice: Has Min Price +es: + params: + buyerFk: Comprador + warehouseFk: Almacén + started: Desde + ended: Hasta + mine: Para mi + showBadDates: Ver items a futuro + hasMinPrice: Precio mínimo + </i18n> diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue index 82578a61fbe..48710d6961c 100644 --- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue +++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue @@ -281,6 +281,7 @@ en: problems: With problems pending: Pending alertLevel: Grouped State + department: Department FREE: Free DELIVERED: Delivered ON_PREPARATION: On preparation @@ -300,6 +301,7 @@ es: problems: Con problemas pending: Pendiente alertLevel: Estado agrupado + department: Departamento FREE: Libre DELIVERED: Servido ON_PREPARATION: En preparación From 48742289fef97cf9d4a2e9d4b11e114c848d8ca7 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 23 Dec 2024 09:45:15 +0100 Subject: [PATCH 126/142] feat: refs #7056 add tests in FormModel --- .../__tests__/components/FormModel.spec.js | 77 +++++++------------ 1 file changed, 27 insertions(+), 50 deletions(-) diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/test/vitest/__tests__/components/FormModel.spec.js index a608d26d675..469e5277093 100644 --- a/test/vitest/__tests__/components/FormModel.spec.js +++ b/test/vitest/__tests__/components/FormModel.spec.js @@ -54,46 +54,13 @@ describe('FormModel', () => { const { vm } = mount({ propsData: { url, model, formInitialData }, }); - vm.state.set(model, { mockKey: 'mockVal' }); + vm.state.set(model, formInitialData); expect(vm.hasChanges).toBe(false); vm.formData.mockKey = 'newVal'; await vm.$nextTick(); expect(vm.hasChanges).toBe(true); - }); - }); - - describe.skip('watch()', () => { - let wrapper; - let vm; - - beforeAll(() => { - wrapper = mount({ - propsData: { url, model, formInitialData }, - }); - vm = wrapper.vm; - }); - - it('should call updateAndEmit when arrayData.store.data changes', async () => { - const updateAndEmitSpy = vi.spyOn(vm, 'updateAndEmit'); - await vm.$nextTick(); - console.log('vm.arrayData.store.data', vm.arrayData.store.data); - vm.arrayData.store.data = { newData: 'newValue' }; - await vm.$nextTick(); - vm.arrayData.store.data = { newData: 'anotherVal' }; - await vm.$nextTick(); - - expect(updateAndEmitSpy).toHaveBeenCalled(); - }); - - it('should call reset and fetch when $props.url or $props.filter changes', async () => { - const resetSpy = vi.spyOn(vm, 'reset'); - const fetchSpy = vi.spyOn(vm, 'fetch'); - - wrapper.setProps({ url: 'newMockUrl' }); - await wrapper.vm.$nextTick(); - expect(resetSpy).toHaveBeenCalled(); - expect(fetchSpy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; }); }); @@ -116,24 +83,34 @@ describe('FormModel', () => { }); }); - describe('onUnmounted()', () => { - it('should restore original data in the store if changes were made but not saved', async () => { - const wrapper = mount({ propsData: { model, formInitialData } }); - const vm = wrapper.vm; - vm.formData.mockKey = 'newVal'; - await vm.$nextTick(); - await wrapper.unmount(); - expect(vm.state.get(model)).toEqual(formInitialData); + describe('save()', async () => { + it('should not call if there are not changes', async () => { + const { vm } = mount({ propsData: { url, model } }); + + await vm.save(); + expect(vm.hasChanges).toBe(false); }); - it('should clear the store on unmount if clearStoreOnUnmount is true', async () => { - const wrapper = mount({ - propsData: { model, formInitialData, clearStoreOnUnmount: true }, + it('should call axios.patch with the right data', async () => { + const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const { vm } = mount({ propsData: { url, model, formInitialData } }); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should call axios.post with the right data', async () => { + const spy = vi.spyOn(axios, 'post').mockResolvedValue({ data: {} }); + const { vm } = mount({ + propsData: { url, model, formInitialData, urlCreate: 'mockUrlCreate' }, }); - const vm = wrapper.vm; - vm.hasChanges = false; - await wrapper.unmount(); - expect(vm.state.get(model)).toBeUndefined(); + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spy).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; }); }); }); From d29ecd0a2b570cc38d02afa52f3ff5bf64485f13 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Mon, 23 Dec 2024 09:47:00 +0000 Subject: [PATCH 127/142] fix: use value intead computedRef --- src/pages/Customer/components/CustomerSamplesCreate.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Customer/components/CustomerSamplesCreate.vue b/src/pages/Customer/components/CustomerSamplesCreate.vue index a75dfa1b2a7..665e136e41c 100644 --- a/src/pages/Customer/components/CustomerSamplesCreate.vue +++ b/src/pages/Customer/components/CustomerSamplesCreate.vue @@ -107,7 +107,7 @@ const setParams = (params) => { const getPreview = async () => { const params = { - recipientId: entityId, + recipientId: entityId.value, }; const validationMessage = validateMessage(); if (validationMessage) return notify(t(validationMessage), 'negative'); From bf41f338b70158f40045a0caf976589d8e5424e8 Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Mon, 23 Dec 2024 12:13:58 +0100 Subject: [PATCH 128/142] feat: refs #7079 created VnLocation front test --- src/components/common/VnLocation.vue | 2 +- .../common/__tests__/VnLocation.spec.js | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/components/common/__tests__/VnLocation.spec.js diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index a8840f24327..f5822218727 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -26,7 +26,7 @@ const locationProperties = [ (obj) => obj.country?.name, ]; -const formatLocation = (obj, properties) => { +const formatLocation = (obj, properties = locationProperties) => { const parts = properties.map((prop) => { if (typeof prop === 'string') { return obj[prop]; diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js new file mode 100644 index 00000000000..920afced861 --- /dev/null +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -0,0 +1,100 @@ +import { createWrapper } from 'app/test/vitest/helper'; +import VnLocation from 'components/common/VnLocation.vue'; +import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; + +function buildComponent(data) { + return createWrapper(VnLocation, { + global: { + stubs: [''], + props: { + location: data + } + }, + }).vm; +} + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe('formatLocation', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + postcode: '46680', + city: 'Algemesi', + province: { name: 'Valencia' }, + country: { name: 'Spain' } + }; + }); + + it('should return the postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should return the postcode and country', () => { + const location = { ...locationBase, city: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('46680, Spain'); + }); + + it('should return the city, province and country', () => { + const location = { ...locationBase, postcode: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should return the country', () => { + const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; + const vm = buildComponent(location); + const parts = vm.formatLocation(location); + expect(parts).toEqual('Spain'); + }); +}); + +describe('showLabel', () => { + let locationBase; + + beforeEach(() => { + locationBase = { + code: '46680', + town: 'Algemesi', + province: 'Valencia', + country: 'Spain' + }; + }); + + it('should show the label with postcode, city, province and country', () => { + const location = { ...locationBase }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + }); + + it('should show the label with postcode and country', () => { + const location = { ...locationBase, town: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('46680, Spain'); + }); + + it('should show the label with city, province and country', () => { + const location = { ...locationBase, code: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Algemesi(Valencia), Spain'); + }); + + it('should show the label with country', () => { + const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; + const vm = buildComponent(location); + const label = vm.showLabel(location); + expect(label).toEqual('Spain'); + }); +}); \ No newline at end of file From 093034c523e561622549decdacc8e8678b6a22dc Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 23 Dec 2024 13:01:59 +0100 Subject: [PATCH 129/142] chore: refs #7056 move test --- .../components => src/components/__tests__}/FormModel.spec.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {test/vitest/__tests__/components => src/components/__tests__}/FormModel.spec.js (100%) diff --git a/test/vitest/__tests__/components/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js similarity index 100% rename from test/vitest/__tests__/components/FormModel.spec.js rename to src/components/__tests__/FormModel.spec.js From 7cb4dfe16a51e56f86a4f19ceb20e0f0ec6d43f3 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 23 Dec 2024 13:34:34 +0100 Subject: [PATCH 130/142] test: refs #7056 add save function and reload data tests for FormModel component --- src/components/__tests__/FormModel.spec.js | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 469e5277093..66157d56789 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -112,6 +112,33 @@ describe('FormModel', () => { expect(spy).toHaveBeenCalled(); vm.formData.mockKey = 'mockVal'; }); + + it('should use the saveFn', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, saveFn: () => {} }, + }); + const spyPatch = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + const spySaveFn = vi.spyOn(vm.$props, 'saveFn'); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + expect(spyPatch).not.toHaveBeenCalled(); + expect(spySaveFn).toHaveBeenCalled(); + vm.formData.mockKey = 'mockVal'; + }); + + it('should reload the data after save', async () => { + const { vm } = mount({ + propsData: { url, model, formInitialData, reload: true }, + }); + vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} }); + + vm.formData.mockKey = 'newVal'; + await vm.$nextTick(); + await vm.save(); + vm.formData.mockKey = 'mockVal'; + }); }); }); From f4aee047ff7f77e2dfbbc3f5dc10a049a2e8cd58 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Mon, 23 Dec 2024 14:21:36 +0100 Subject: [PATCH 131/142] test: refs #7056 update FormModel.spec.js to use dynamic model value in tests --- src/components/__tests__/FormModel.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/FormModel.spec.js b/src/components/__tests__/FormModel.spec.js index 66157d56789..e35684bc338 100644 --- a/src/components/__tests__/FormModel.spec.js +++ b/src/components/__tests__/FormModel.spec.js @@ -9,8 +9,8 @@ describe('FormModel', () => { describe('modelValue', () => { it('should use the provided model', () => { - const { vm } = mount({ propsData: { model: 'mockModel' } }); - expect(vm.modelValue).toBe('mockModel'); + const { vm } = mount({ propsData: { model } }); + expect(vm.modelValue).toBe(model); }); it('should use the route meta title when model is not provided', () => { From 1cf817be173892a8b3a9e8780aef00c8943f8b13 Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Mon, 23 Dec 2024 15:09:14 +0100 Subject: [PATCH 132/142] refactor: refs #7050 refactorize --- src/components/CrudModel.vue | 4 +- .../components/common/CrudModel.spec.js | 65 ++++++++----------- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index cff18f2dea9..940b72ff0fa 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -127,7 +127,7 @@ function resetData(data) { originalData.value = JSON.parse(JSON.stringify(data)); formData.value = JSON.parse(JSON.stringify(data)); - if (watchChanges.value) watchChanges.value(); //destoy watcher + if (watchChanges.value) watchChanges.value(); //destroy watcher watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true }); } @@ -271,7 +271,7 @@ function getChanges() { function isEmpty(obj) { if (obj == null) return true; if (Array.isArray(obj)) return !obj.length; - return Object.keys(obj).length === 0 ; + return !Object.keys(obj).length; } async function reload(params) { diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index b3cdbede7d9..e28b6003e9d 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -3,10 +3,11 @@ import CrudModel from 'components/CrudModel.vue'; import { vi, afterEach, beforeEach, beforeAll, describe, expect, it } from 'vitest'; describe('CrudModel', () => { + let wrapper; let vm; let data; beforeAll(() => { - vm = createWrapper(CrudModel, { + wrapper = createWrapper(CrudModel, { global: { stubs: [ 'vnPaginate', @@ -26,8 +27,11 @@ describe('CrudModel', () => { dataKey: 'crudModelKey', model: 'crudModel', url: 'crudModelUrl', + saveFn: '', }, - }).vm; + }); + wrapper=wrapper.wrapper; + vm=wrapper.vm; }); beforeEach(() => { @@ -143,11 +147,6 @@ describe('CrudModel', () => { result = vm.isEmpty(dummyObj); expect(result).toBe(true); - - dummyArray = []; - result = vm.isEmpty(dummyArray); - - expect(result).toBe(true); }); it('should return false if object is not empty', async () => { @@ -155,6 +154,17 @@ describe('CrudModel', () => { result = vm.isEmpty(dummyObj); expect(result).toBe(false); + }); + + it('should return true if array is empty', async () => { + + dummyArray = []; + result = vm.isEmpty(dummyArray); + + expect(result).toBe(true); + }); + + it('should return false if array is not empty', async () => { dummyArray = [1,2,3]; result = vm.isEmpty(dummyArray); @@ -193,7 +203,7 @@ describe('CrudModel', () => { lastName: 'Stark', age: 42, }; - + vm.resetData(data); expect(vm.originalData).toEqual(data); @@ -210,39 +220,18 @@ describe('CrudModel', () => { }]; it('should call saveFn if exists', async () => { - const saveFnMock = vi.fn(); - - const localVm = createWrapper(CrudModel, { - global: { - stubs: [ - 'vnPaginate', - 'useState', - 'arrayData', - 'useStateStore', - 'vue-i18n', - ], - mocks: { - validate: vi.fn(), - }, - }, - propsData: { - dataRequired: { - fk: 1, - }, - dataKey: 'crudModelKey', - model: 'crudModel', - url: 'crudModelUrl', - saveFn: saveFnMock, - }, - }); + await wrapper.setProps({ saveFn: vi.fn() }); - localVm.vm.saveChanges(data); - expect(saveFnMock).toHaveBeenCalledOnce(); - expect(localVm.vm.isLoading).toBe(false); - expect(localVm.vm.hasChanges).toBe(false); + vm.saveChanges(data); + + expect(vm.saveFn).toHaveBeenCalledOnce(); + expect(vm.isLoading).toBe(false); + expect(vm.hasChanges).toBe(false); + + await wrapper.setProps({ saveFn: '' }); }); - it('should not call saveFn if not exists', async () => { + it("should use default url if there's not saveFn", async () => { const postMock =vi.spyOn(axios, 'post'); vm.formData = [{ From dd36b35bf7f6f8e64b95323efe7c71bd9b487a89 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 23 Dec 2024 17:56:09 +0100 Subject: [PATCH 133/142] fix: refs #8197 staticParams and redirect --- src/components/NavBar.vue | 1 + src/components/common/VnSection.vue | 11 ++++++++--- src/components/ui/VnPaginate.vue | 5 ++--- src/components/ui/VnSearchbar.vue | 6 ++++-- src/composables/useArrayData.js | 1 + src/pages/Account/AccountList.vue | 7 +++++-- src/pages/Customer/CustomerList.vue | 1 - .../integration/vnComponent/VnSearchBar.spec.js | 5 ++--- 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 9b0393489cb..08c2410f1e0 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -59,6 +59,7 @@ const pinnedModulesRef = ref(); 'no-visible': !stateQuery.isLoading().value, }" size="xs" + data-cy="loading-spinner" /> <QSpace /> <div id="searchbar" class="searchbar"></div> diff --git a/src/components/common/VnSection.vue b/src/components/common/VnSection.vue index e6afea4b6a3..e69e586b5df 100644 --- a/src/components/common/VnSection.vue +++ b/src/components/common/VnSection.vue @@ -34,15 +34,20 @@ const $props = defineProps({ type: Object, default: null, }, + redirect: { + type: Boolean, + default: true, + }, }); const sectionValue = computed(() => $props.section ?? $props.dataKey); - +let arrayData; onBeforeMount(() => { if ($props.dataKey) - useArrayData($props.dataKey, { + arrayData = useArrayData($props.dataKey, { searchUrl: 'table', ...$props.arrayDataProps, + navigate: $props.redirect, }); }); </script> @@ -63,12 +68,12 @@ onBeforeMount(() => { <VnTableFilter v-if="rightFilter && columns" :data-key="dataKey" + :array-data="arrayData" :columns="columns" /> </slot> </template> </RightMenu> - <slot name="body" v-if="sectionValue == $route.name" /> <RouterView v-else /> </template> diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 42f558f89b8..a2ccd5d92d1 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -112,7 +112,6 @@ onMounted(async () => { onBeforeUnmount(() => { arrayData.resetPagination(); - arrayData.reset(['currentFilter', 'userParams', 'userFilter']); }); watch( @@ -142,7 +141,7 @@ const addFilter = async (filter, params) => { async function fetch(params) { useArrayData(props.dataKey, params); arrayData.resetPagination(); - await arrayData.fetch({ append: false, updateRouter: mounted.value }); + await arrayData.fetch({ append: false }); return emitStoreData(); } @@ -217,7 +216,7 @@ defineExpose({ <template> <div class="full-width"> <div - v-if="!props.autoLoad && !store.data && !isLoading" + v-if="!store.data && !store.data?.length && !isLoading" class="info-row q-pa-md text-center" > <h5> diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 6a9de44cba7..4e284d8e45b 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -100,7 +100,9 @@ onMounted(() => { }); async function search() { - const staticParams = Object.entries(store.userParams); + const staticParams = Object.keys(store.userParams ?? {}).length + ? store.userParams + : store.defaultParams; arrayData.resetPagination(); const filter = { @@ -112,7 +114,7 @@ async function search() { if (!props.searchRemoveParams || !searchText.value) { filter.params = { - ...Object.fromEntries(staticParams), + ...staticParams, search: searchText.value, }; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index f4b30438af4..1a91cc50b50 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -64,6 +64,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { store[option] = userOptions.keepOpts?.includes(option) ? Object.assign(defaultOpts, store[option]) : defaultOpts; + if (option === 'userParams') store.defaultParams = store[option]; } } } diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 997e3104142..7004abcf161 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -1,10 +1,11 @@ <script setup> import { useI18n } from 'vue-i18n'; -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import VnTable from 'components/VnTable/VnTable.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import VnSection from 'src/components/common/VnSection.vue'; +import FetchData from 'src/components/FetchData.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); @@ -12,6 +13,7 @@ const filter = { include: { relation: 'role', scope: { fields: ['id', 'name'] } }, }; const dataKey = 'AccountList'; +const roles = ref([]); const columns = computed(() => [ { align: 'left', @@ -29,7 +31,7 @@ const columns = computed(() => [ component: 'select', name: 'roleFk', attrs: { - url: 'VnRoles', + options: roles, optionValue: 'id', optionLabel: 'name', }, @@ -103,6 +105,7 @@ function exprBuilder(param, value) { </script> <template> + <FetchData url="VnRoles" @on-fetch="(data) => (roles = data)" auto-load /> <VnSection :data-key="dataKey" :columns="columns" diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 865287aeb38..b9b63208523 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -419,7 +419,6 @@ function handleLocation(data, location) { :columns="columns" redirect="customer" :right-search="false" - auto-load > <template #more-create-dialog="{ data }"> <VnSelect diff --git a/test/cypress/integration/vnComponent/VnSearchBar.spec.js b/test/cypress/integration/vnComponent/VnSearchBar.spec.js index b8621118cfc..885e5d6b3b0 100644 --- a/test/cypress/integration/vnComponent/VnSearchBar.spec.js +++ b/test/cypress/integration/vnComponent/VnSearchBar.spec.js @@ -7,10 +7,10 @@ describe('VnSearchBar', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/list'); + cy.visit('#/account/list'); }); - it('should redirect to customer summary page', () => { + it('should redirect to account summary page', () => { searchAndCheck('1', employeeId); searchAndCheck('salesPerson', salesPersonId); }); @@ -20,7 +20,6 @@ describe('VnSearchBar', () => { checkTableLength(2); cy.clearSearchbar(); - cy.writeSearchbar('0{enter}'); checkTableLength(0); }); From cfb35d23b436b37b0e71275d5fc25a435b40d19a Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Mon, 23 Dec 2024 18:16:50 +0100 Subject: [PATCH 134/142] perf: refs #8197 perf --- src/pages/Account/AccountAcls.vue | 2 +- src/stores/useArrayDataStore.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue index b4eeb0648d4..6d357166121 100644 --- a/src/pages/Account/AccountAcls.vue +++ b/src/pages/Account/AccountAcls.vue @@ -151,7 +151,7 @@ const deleteAcl = async ({ id }) => { <template #body> <VnTable ref="tableRef" - data-key="AccountAcls" + :data-key="dataKey" :create="{ urlCreate: 'ACLs', title: 'Create ACL', diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index 47bc06fd6fe..e0d8b792932 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -56,7 +56,6 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { } return { - state, get, set, clear, From 17b178d9f13aa6ea2c588438d137a325e58a8ab9 Mon Sep 17 00:00:00 2001 From: Javier Segarra <jsegarra@verdnatura.es> Date: Tue, 24 Dec 2024 09:06:17 +0000 Subject: [PATCH 135/142] fix: orderBy priority --- src/pages/Claim/ClaimList.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue index d561a69f7f6..6b9fa77a094 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -131,7 +131,7 @@ const STATE_COLOR = { <VnTable data-key="ClaimList" url="Claims/filter" - :order="['priority ASC', 'created ASC']" + :order="['t.priority ASC', 'created ASC']" :columns="columns" redirect="claim" :right-search="false" From 9dabe11cd005711818adeaa0185100ca20d7af83 Mon Sep 17 00:00:00 2001 From: alexm <alexm@verdnatura.es> Date: Tue, 24 Dec 2024 12:02:58 +0100 Subject: [PATCH 136/142] fix(AccountList): use $refs --- src/pages/Account/AccountList.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index 0a4d6df4ed8..c1c75fcee6a 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -152,12 +152,13 @@ function exprBuilder(param, value) { > <template #body> <VnTable + ref="tableRef" :data-key="dataKey" :columns="columns" :create="{ urlCreate: 'VnUsers', title: t('Create user'), - onDataSaved: ({ id }) => tableRef.redirect(id), + onDataSaved: ({ id }) => $refs.tableRef.redirect(id), formInitialData: {}, }" default-mode="table" From 89acb338a9ebbaf521e93ca39828c65af53cba1a Mon Sep 17 00:00:00 2001 From: provira <provira@verdnatura.es> Date: Tue, 24 Dec 2024 12:18:36 +0100 Subject: [PATCH 137/142] refactor: refs #7079 removed useless code --- .../common/__tests__/VnLocation.spec.js | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/components/common/__tests__/VnLocation.spec.js b/src/components/common/__tests__/VnLocation.spec.js index 920afced861..65fdae96099 100644 --- a/src/components/common/__tests__/VnLocation.spec.js +++ b/src/components/common/__tests__/VnLocation.spec.js @@ -5,7 +5,6 @@ import { vi, afterEach, expect, it, beforeEach, describe } from 'vitest'; function buildComponent(data) { return createWrapper(VnLocation, { global: { - stubs: [''], props: { location: data } @@ -32,29 +31,25 @@ describe('formatLocation', () => { it('should return the postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should return the postcode and country', () => { const location = { ...locationBase, city: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('46680, Spain'); + expect(vm.formatLocation(location)).toEqual('46680, Spain'); }); it('should return the city, province and country', () => { const location = { ...locationBase, postcode: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Algemesi(Valencia), Spain'); + expect(vm.formatLocation(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should return the country', () => { const location = { ...locationBase, postcode: undefined, city: undefined, province: undefined }; const vm = buildComponent(location); - const parts = vm.formatLocation(location); - expect(parts).toEqual('Spain'); + expect(vm.formatLocation(location)).toEqual('Spain'); }); }); @@ -73,28 +68,24 @@ describe('showLabel', () => { it('should show the label with postcode, city, province and country', () => { const location = { ...locationBase }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('46680, Algemesi(Valencia), Spain'); }); it('should show the label with postcode and country', () => { const location = { ...locationBase, town: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('46680, Spain'); + expect(vm.showLabel(location)).toEqual('46680, Spain'); }); it('should show the label with city, province and country', () => { const location = { ...locationBase, code: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Algemesi(Valencia), Spain'); + expect(vm.showLabel(location)).toEqual('Algemesi(Valencia), Spain'); }); it('should show the label with country', () => { const location = { ...locationBase, code: undefined, town: undefined, province: undefined }; const vm = buildComponent(location); - const label = vm.showLabel(location); - expect(label).toEqual('Spain'); + expect(vm.showLabel(location)).toEqual('Spain'); }); }); \ No newline at end of file From 638bd1dc260b1c2de2e3517eed18bb5227aed926 Mon Sep 17 00:00:00 2001 From: jtubau <jtubau@verdnatura.es> Date: Tue, 24 Dec 2024 12:59:40 +0100 Subject: [PATCH 138/142] refactor: refs #7050 removed blank spaces --- test/vitest/__tests__/components/common/CrudModel.spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/vitest/__tests__/components/common/CrudModel.spec.js b/test/vitest/__tests__/components/common/CrudModel.spec.js index e28b6003e9d..e0afd30adfd 100644 --- a/test/vitest/__tests__/components/common/CrudModel.spec.js +++ b/test/vitest/__tests__/components/common/CrudModel.spec.js @@ -157,7 +157,6 @@ describe('CrudModel', () => { }); it('should return true if array is empty', async () => { - dummyArray = []; result = vm.isEmpty(dummyArray); @@ -165,7 +164,6 @@ describe('CrudModel', () => { }); it('should return false if array is not empty', async () => { - dummyArray = [1,2,3]; result = vm.isEmpty(dummyArray); @@ -174,7 +172,6 @@ describe('CrudModel', () => { }); describe('resetData()', () => { - it('should add $index to elements in data[] and sets originalData and formData with data', async () => { data = [{ name: 'Tony', From be71e66075b5e90a8430d0a17eab1eca1e62d6e0 Mon Sep 17 00:00:00 2001 From: jorgep <jorgep@verdnatura.es> Date: Thu, 26 Dec 2024 17:59:42 +0100 Subject: [PATCH 139/142] fix: refs #7935 remove unused 'companyFk' column from InvoiceInList component --- src/pages/InvoiceIn/InvoiceInList.vue | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue index db6e7d21418..43b9f2c1153 100644 --- a/src/pages/InvoiceIn/InvoiceInList.vue +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -108,19 +108,6 @@ const cols = computed(() => [ }, format: (row) => row.code, }, - { - name: 'companyFk', - label: t('globals.company'), - columnFilter: { - component: 'select', - attrs: { - url: 'Companies', - fields: ['id', 'code'], - optionLabel: 'code', - }, - }, - format: (row) => row.code, - }, { align: 'right', name: 'tableActions', From 1ff54d7423c9bd621249d96ac41a831c8c3f102a Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 27 Dec 2024 09:18:29 +0100 Subject: [PATCH 140/142] refactor: refs #8201 deleted condition --- src/pages/Account/Card/AccountDescriptor.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index ed7adc45536..826c285b306 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -75,14 +75,14 @@ const hasAccount = ref(false); <VnLv :label="t('account.card.role')" :value="entity.role.name" /> </template> <template #icons="{ entity }"> - <QCardActions v-if="accountData" class="q-gutter-x-md"> + <QCardActions class="q-gutter-x-md"> <QIcon v-if="!entity.active" color="primary" name="vn:disabled" flat round - size="sm" + size="xs" class="fill-icon" > <QTooltip>{{ t('account.card.deactivated') }}</QTooltip> @@ -93,7 +93,7 @@ const hasAccount = ref(false); v-if="hasAccount" flat round - size="sm" + size="xs" class="fill-icon" > <QTooltip>{{ t('account.card.enabled') }}</QTooltip> From 9d89f6c5d1c40a86b3a115a74997ab69ef5e0b18 Mon Sep 17 00:00:00 2001 From: PAU ROVIRA ROSALENY <provira@verdnatura.es> Date: Fri, 27 Dec 2024 09:24:39 +0000 Subject: [PATCH 141/142] fix: discount class --- src/pages/Claim/Card/ClaimLines.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index 90a68beaeb0..7c545b15b1e 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -230,7 +230,7 @@ async function saveWhenHasChanges() { </QTd> </template> <template #body-cell-discount="{ row, value, rowIndex }"> - <QTd auto-width align="right" class="text-primary shrink"> + <QTd auto-width align="right" class="link shrink"> {{ value }} <VnDiscount :quantity="row.quantity" From de307253225f992d032267e29df7ac185af41578 Mon Sep 17 00:00:00 2001 From: Jon <jon@verdnatura.es> Date: Fri, 27 Dec 2024 10:36:03 +0100 Subject: [PATCH 142/142] perf: refs #8201 onDataSaved fetch --- src/pages/Supplier/Card/SupplierBasicData.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pages/Supplier/Card/SupplierBasicData.vue b/src/pages/Supplier/Card/SupplierBasicData.vue index 4b4988789ef..22a6deaabb9 100644 --- a/src/pages/Supplier/Card/SupplierBasicData.vue +++ b/src/pages/Supplier/Card/SupplierBasicData.vue @@ -16,10 +16,6 @@ const companySizes = [ { id: 'medium', name: t('globals.medium'), size: '6-50' }, { id: 'big', name: t('globals.big'), size: '>50' }, ]; - -const onSave = () => { - arrayData.fetch({}); -}; </script> <template> <FormModel @@ -28,7 +24,7 @@ const onSave = () => { model="supplier" auto-load :clear-store-on-unmount="false" - @on-data-saved="onSave" + @on-data-saved="arrayData.fetch({})" > <template #form="{ data, validate }"> <VnRow>