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>