From 7f376c8ea4cfa0b1c7863017e33c5a9457f9491a Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Fri, 19 Jul 2024 14:53:44 +0200
Subject: [PATCH 01/60] fix: refs #7717 fix catalog filter, searchbar redirect
 and search

---
 src/pages/Order/{ => Card}/OrderCatalog.vue | 12 ++++++++++++
 src/pages/Order/Card/OrderCatalogFilter.vue | 20 +++++++++++++++-----
 src/pages/Order/{ => Card}/OrderLines.vue   |  0
 src/pages/Order/{ => Card}/OrderVolume.vue  |  0
 src/router/modules/order.js                 |  6 +++---
 5 files changed, 30 insertions(+), 8 deletions(-)
 rename src/pages/Order/{ => Card}/OrderCatalog.vue (87%)
 rename src/pages/Order/{ => Card}/OrderLines.vue (100%)
 rename src/pages/Order/{ => Card}/OrderVolume.vue (100%)

diff --git a/src/pages/Order/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue
similarity index 87%
rename from src/pages/Order/OrderCatalog.vue
rename to src/pages/Order/Card/OrderCatalog.vue
index 2cf6e1c29..071827a8b 100644
--- a/src/pages/Order/OrderCatalog.vue
+++ b/src/pages/Order/Card/OrderCatalog.vue
@@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n';
 import VnPaginate from 'components/ui/VnPaginate.vue';
 import CatalogItem from 'components/ui/CatalogItem.vue';
 import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
+import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
 
 const route = useRoute();
 const stateStore = useStateStore();
@@ -52,6 +53,17 @@ function extractValueTags(items) {
 </script>
 
 <template>
+    <Teleport to="#searchbar">
+        <VnSearchbar
+            data-key="OrderCatalogList"
+            :user-params="catalogParams"
+            :static-params="['orderFk', 'orderBy']"
+            :redirect="false"
+            url="Orders/CatalogFilter"
+            :label="t('Search items')"
+            :info="t('You can search orders by reference')"
+        />
+    </Teleport>
     <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
         <QScrollArea class="fit text-grey-8">
             <OrderCatalogFilter
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index c354ec94b..992fe4b3f 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -27,7 +27,6 @@ const props = defineProps({
         required: true,
     },
 });
-
 const categoryList = ref(null);
 const selectedCategoryFk = ref(null);
 const typeList = ref(null);
@@ -84,7 +83,14 @@ const selectedCategory = computed(() =>
         (category) => category?.id === selectedCategoryFk.value
     )
 );
-
+function filterFn(val, update) {
+    update(() => {
+        const needle = val.toLowerCase();
+        tagOptions.value = props.tagValue.filter(
+            (v) => v.toLowerCase().indexOf(needle) > -1
+        );
+    });
+}
 const selectedType = computed(() => {
     return (typeList.value || []).find((type) => type?.id === selectedTypeFk.value);
 });
@@ -352,7 +358,7 @@ const useLang = (values) => {
                     v-if="!selectedTag"
                     :label="t('params.value')"
                     v-model="value.value"
-                    :options="tagValue || []"
+                    :options="tagOptions || []"
                     option-value="value"
                     option-label="value"
                     dense
@@ -362,6 +368,8 @@ const useLang = (values) => {
                     use-input
                     class="filter-input"
                     @new-value="createValue"
+                    @filter="filterFn"
+                    @update:model-value="applyTagFilter(params, searchFn)"
                 />
                 <VnSelect
                     v-else-if="selectedTag === 1"
@@ -377,6 +385,7 @@ const useLang = (values) => {
                     use-input
                     class="filter-input"
                     @new-value="createValue"
+                    @update:model-value="applyTagFilter(params, searchFn)"
                 />
                 <VnInput
                     v-else
@@ -386,6 +395,7 @@ const useLang = (values) => {
                     outlined
                     rounded
                     class="filter-input"
+                    @keyup.enter="applyTagFilter(params, searchFn)"
                 />
                 <QIcon
                     name="delete"
@@ -400,7 +410,7 @@ const useLang = (values) => {
                     @click="tagValues.push({})"
                 />
             </QItem>
-            <QItem>
+            <!-- <QItem>
                 <QItemSection class="q-py-sm">
                     <QBtn
                         :label="t('Search')"
@@ -414,7 +424,7 @@ const useLang = (values) => {
                         @click.stop="applyTagFilter(params, searchFn)"
                     />
                 </QItemSection>
-            </QItem>
+            </QItem> -->
             <QSeparator />
         </template>
     </VnFilterPanel>
diff --git a/src/pages/Order/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue
similarity index 100%
rename from src/pages/Order/OrderLines.vue
rename to src/pages/Order/Card/OrderLines.vue
diff --git a/src/pages/Order/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue
similarity index 100%
rename from src/pages/Order/OrderVolume.vue
rename to src/pages/Order/Card/OrderVolume.vue
diff --git a/src/router/modules/order.js b/src/router/modules/order.js
index aa1ca774d..8d1a6e5fe 100644
--- a/src/router/modules/order.js
+++ b/src/router/modules/order.js
@@ -72,7 +72,7 @@ export default {
                         title: 'catalog',
                         icon: 'vn:basket',
                     },
-                    component: () => import('src/pages/Order/OrderCatalog.vue'),
+                    component: () => import('src/pages/Order/Card/OrderCatalog.vue'),
                 },
                 {
                     name: 'OrderVolume',
@@ -81,7 +81,7 @@ export default {
                         title: 'volume',
                         icon: 'vn:volume',
                     },
-                    component: () => import('src/pages/Order/OrderVolume.vue'),
+                    component: () => import('src/pages/Order/Card/OrderVolume.vue'),
                 },
                 {
                     name: 'OrderLines',
@@ -90,7 +90,7 @@ export default {
                         title: 'lines',
                         icon: 'vn:lines',
                     },
-                    component: () => import('src/pages/Order/OrderLines.vue'),
+                    component: () => import('src/pages/Order/Card/OrderLines.vue'),
                 },
             ],
         },

From 02e1de083a934e1178bef41c67375f844b2e7f7e Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Fri, 19 Jul 2024 15:00:05 +0200
Subject: [PATCH 02/60] fix: duplicate key

---
 src/i18n/locale/en.yml | 1 -
 src/i18n/locale/es.yml | 1 -
 2 files changed, 2 deletions(-)

diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 2f1209a3a..6943252c9 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -246,7 +246,6 @@ globals:
         mailForwarding: Mail forwarding
         mailAlias: Mail alias
         privileges: Privileges
-        labeler: Labeler
     created: Created
     worker: Worker
     now: Now
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index c5c4fab66..f32562313 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -248,7 +248,6 @@ globals:
         components: Componentes
         pictures: Fotos
         packages: Bultos
-        labeler: Etiquetas
     created: Fecha creación
     worker: Trabajador
     now: Ahora

From def3ca33a26619cb0a38b9d050283c9fc7227125 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Mon, 22 Jul 2024 12:52:58 +0200
Subject: [PATCH 03/60] fix: refs #7717 fixed searchbar filter with rightmenu
 filters' applied

---
 src/components/ui/VnSearchbar.vue     | 8 +++++---
 src/pages/Order/Card/OrderCatalog.vue | 1 +
 src/pages/Order/OrderList.vue         | 8 ++------
 3 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue
index 0c7a8a3f6..6fb204afe 100644
--- a/src/components/ui/VnSearchbar.vue
+++ b/src/components/ui/VnSearchbar.vue
@@ -100,9 +100,11 @@ onMounted(() => {
 });
 
 async function search() {
-    const staticParams = Object.entries(store.userParams).filter(
-        ([key, value]) => value && (props.staticParams || []).includes(key)
-    );
+    const staticParams = Object.entries(store.userParams);
+    console.log('staticParams: ', staticParams);
+    // .filter(
+    //     ([key, value]) => value && (props.staticParams || []).includes(key)
+    // );
     arrayData.reset(['skip', 'page']);
 
     if (props.makeFetch)
diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue
index 071827a8b..d1479e3c3 100644
--- a/src/pages/Order/Card/OrderCatalog.vue
+++ b/src/pages/Order/Card/OrderCatalog.vue
@@ -53,6 +53,7 @@ function extractValueTags(items) {
 </script>
 
 <template>
+    <!-- TODO Sobreescribir la barra de búsqueda -->
     <Teleport to="#searchbar">
         <VnSearchbar
             data-key="OrderCatalogList"
diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue
index 945f61f3b..fa82540e6 100644
--- a/src/pages/Order/OrderList.vue
+++ b/src/pages/Order/OrderList.vue
@@ -3,12 +3,12 @@ import axios from 'axios';
 import { useI18n } from 'vue-i18n';
 import { computed, ref } from 'vue';
 import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
-import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
 import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
 import { useSummaryDialog } from 'src/composables/useSummaryDialog';
 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';
 
 const { t } = useI18n();
 const { viewSummary } = useSummaryDialog();
@@ -132,11 +132,7 @@ async function fetchClientAddress(id, data) {
 }
 </script>
 <template>
-    <VnSearchbar
-        data-key="OrderList"
-        :label="t('Search order')"
-        :info="t('You can search orders by reference')"
-    />
+    <OrderSearchbar />
     <VnTable
         ref="tableRef"
         data-key="OrderList"

From 368b8404fa60f9ddca22fa3b8463406d2e5fd004 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Mon, 22 Jul 2024 15:13:03 +0200
Subject: [PATCH 04/60] updates: OrderCatlago

---
 src/components/common/VnCard.vue            |  8 ++++--
 src/pages/Order/Card/OrderCard.vue          | 32 +++++++++++++++++++--
 src/pages/Order/Card/OrderCatalogFilter.vue |  2 +-
 3 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue
index 17fa74317..4b7a6ba63 100644
--- a/src/components/common/VnCard.vue
+++ b/src/components/common/VnCard.vue
@@ -31,7 +31,10 @@ const url = computed(() => {
     if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`;
     return props.customUrl;
 });
-
+const searchRightDataKey = computed(() => {
+    if (!props.searchDataKey) return route.name;
+    else return props.searchDataKey;
+});
 const arrayData = useArrayData(props.dataKey, {
     url: url.value,
     filter: props.filter,
@@ -74,10 +77,9 @@ if (props.baseUrl) {
             :redirect="searchRedirect"
         />
     </slot>
-    <slot v-else name="searchbar" />
     <RightMenu>
         <template #right-panel v-if="props.filterPanel">
-            <component :is="props.filterPanel" :data-key="props.searchDataKey" />
+            <component :is="props.filterPanel" :data-key="searchRightDataKey" />
         </template>
     </RightMenu>
     <QPageContainer>
diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue
index 5b6896656..ed4bea2ff 100644
--- a/src/pages/Order/Card/OrderCard.vue
+++ b/src/pages/Order/Card/OrderCard.vue
@@ -1,16 +1,44 @@
 <script setup>
+import { useI18n } from 'vue-i18n';
+import { computed } from 'vue';
+import { useRoute } from 'vue-router';
 import VnCard from 'components/common/VnCard.vue';
 import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
 import OrderFilter from './OrderFilter.vue';
 import OrderSearchbar from './OrderSearchbar.vue';
+import OrderCatalogFilter from './OrderCatalogFilter.vue';
+const config = {
+    OrderCatalog: OrderCatalogFilter,
+};
+const route = useRoute();
+
+const routeName = computed(() => route.name);
+const customRouteRedirectName = computed(() => {
+    const route = config[routeName.value];
+    if (route) return null;
+    return 'OrderList';
+    //Jon, asi es como lo dejamos
+    // if (routeName.value === 'OrderCatalog') return null;
+    // return 'OrderList';
+});
+const customFilterPanel = computed(() => {
+    const filterPanel = config[routeName.value] ?? OrderFilter;
+    return filterPanel;
+    //Jon, asi es como lo dejamos
+    // if (routeName.value === 'OrderCatalog') return null;
+    // return 'OrderList';
+    // if (routeName.value === 'OrderCatalog') return OrderCatalogFilter;
+    // return OrderFilter;
+});
 </script>
+
 <template>
     <VnCard
         data-key="Order"
         base-url="Orders"
         :descriptor="OrderDescriptor"
-        :filter-panel="OrderFilter"
-        search-data-key="OrderList"
+        :filter-panel="customFilterPanel"
+        :search-data-key="customRouteRedirectName"
     >
         <template #searchbar>
             <OrderSearchbar />
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index 992fe4b3f..8d96fe93a 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -205,7 +205,7 @@ const useLang = (values) => {
     <VnFilterPanel
         :data-key="props.dataKey"
         :hidden-tags="['orderFk', 'orderBy']"
-        :unremovable-params="['orderFk', 'orderBy']"
+        :un-removable-params="['orderFk', 'orderBy']"
         :expr-builder="exprBuilder"
         :custom-tags="['tagGroups']"
         @remove="clearFilter"

From ce6a94bab8a13341adac336a4e1e7d80e5324e16 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Tue, 23 Jul 2024 11:46:21 +0200
Subject: [PATCH 05/60] fix: refs #7717 fix catalog searchbar and worker
 tests(refs #7323)

---
 src/components/ui/VnFilterPanel.vue           | 72 ++++++++++---------
 src/components/ui/VnSearchbar.vue             |  4 --
 .../InvoiceOutNegativeBasesFilter.vue         |  2 +-
 src/pages/Order/Card/OrderCard.vue            |  9 ---
 .../integration/worker/workerList.spec.js     | 10 ++-
 .../integration/worker/workerLocker.spec.js   |  7 +-
 6 files changed, 49 insertions(+), 55 deletions(-)

diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index 27b6e7b34..f79852a9d 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -106,21 +106,23 @@ watch(
 
 const isLoading = ref(false);
 async function search(evt) {
-    if (evt && $props.disableSubmitEvent) return;
+    try {
+        if (evt && $props.disableSubmitEvent) return;
 
-    store.filter.where = {};
-    isLoading.value = true;
-    const filter = { ...userParams.value, ...$props.modelValue };
-    store.userParamsChanged = true;
-    const { params: newParams } = await arrayData.addFilter({
-        params: filter,
-    });
-    userParams.value = newParams;
+        store.filter.where = {};
+        isLoading.value = true;
+        const filter = { ...userParams.value, ...$props.modelValue };
+        store.userParamsChanged = true;
+        const { params: newParams } = await arrayData.addFilter({
+            params: filter,
+        });
+        userParams.value = newParams;
 
-    if (!$props.showAll && !Object.values(filter).length) store.data = [];
-
-    isLoading.value = false;
-    emit('search');
+        if (!$props.showAll && !Object.values(filter).length) store.data = [];
+        emit('search');
+    } finally {
+        isLoading.value = false;
+    }
 }
 
 async function reload() {
@@ -135,29 +137,31 @@ async function reload() {
 }
 
 async function clearFilters() {
-    isLoading.value = true;
-    store.userParamsChanged = true;
-    arrayData.reset(['skip', 'filter.skip', 'page']);
-    // Filtrar los params no removibles
-    const removableFilters = Object.keys(userParams.value).filter((param) =>
-        $props.unRemovableParams.includes(param)
-    );
-    const newParams = {};
-    // Conservar solo los params que no son removibles
-    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 });
+    try {
+        isLoading.value = true;
+        store.userParamsChanged = true;
+        arrayData.reset(['skip', 'filter.skip', 'page']);
+        // Filtrar los params no removibles
+        const removableFilters = Object.keys(userParams.value).filter((param) =>
+            $props.unRemovableParams.includes(param)
+        );
+        const newParams = {};
+        // Conservar solo los params que no son removibles
+        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 });
 
-    if (!$props.showAll) {
-        store.data = [];
+        if (!$props.showAll) {
+            store.data = [];
+        }
+        emit('clear');
+        emit('update:modelValue', userParams.value);
+    } finally {
+        isLoading.value = false;
     }
-
-    isLoading.value = false;
-    emit('clear');
-    emit('update:modelValue', userParams.value);
 }
 
 const tagsList = computed(() => {
diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue
index 6fb204afe..694ce294e 100644
--- a/src/components/ui/VnSearchbar.vue
+++ b/src/components/ui/VnSearchbar.vue
@@ -101,10 +101,6 @@ onMounted(() => {
 
 async function search() {
     const staticParams = Object.entries(store.userParams);
-    console.log('staticParams: ', staticParams);
-    // .filter(
-    //     ([key, value]) => value && (props.staticParams || []).includes(key)
-    // );
     arrayData.reset(['skip', 'page']);
 
     if (props.makeFetch)
diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue
index 9eeac8355..4865a614a 100644
--- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue
+++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue
@@ -19,7 +19,7 @@ const props = defineProps({
     <VnFilterPanel
         :data-key="props.dataKey"
         :search-button="true"
-        :unremovable-params="['from', 'to']"
+        :un-removable-params="['from', 'to']"
         :hidden-tags="['from', 'to']"
     >
         <template #tags="{ tag, formatFn }">
diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue
index ed4bea2ff..67c0f1de5 100644
--- a/src/pages/Order/Card/OrderCard.vue
+++ b/src/pages/Order/Card/OrderCard.vue
@@ -1,5 +1,4 @@
 <script setup>
-import { useI18n } from 'vue-i18n';
 import { computed } from 'vue';
 import { useRoute } from 'vue-router';
 import VnCard from 'components/common/VnCard.vue';
@@ -17,18 +16,10 @@ const customRouteRedirectName = computed(() => {
     const route = config[routeName.value];
     if (route) return null;
     return 'OrderList';
-    //Jon, asi es como lo dejamos
-    // if (routeName.value === 'OrderCatalog') return null;
-    // return 'OrderList';
 });
 const customFilterPanel = computed(() => {
     const filterPanel = config[routeName.value] ?? OrderFilter;
     return filterPanel;
-    //Jon, asi es como lo dejamos
-    // if (routeName.value === 'OrderCatalog') return null;
-    // return 'OrderList';
-    // if (routeName.value === 'OrderCatalog') return OrderCatalogFilter;
-    // return OrderFilter;
 });
 </script>
 
diff --git a/test/cypress/integration/worker/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js
index 29df01f3b..16f5430de 100644
--- a/test/cypress/integration/worker/workerList.spec.js
+++ b/test/cypress/integration/worker/workerList.spec.js
@@ -5,8 +5,14 @@ describe('WorkerList', () => {
         cy.visit('/#/worker/list');
     });
 
-    it('should open the worker summary', () => {
-        cy.get('.bg-header > :nth-child(2) > .full-width > :nth-child(1) >').type(
+    it('should opern the worker summary', () => {
+        cy.get(
+            ':nth-child(1) > .text-right > .q-btn > .q-btn__content > .q-icon'
+        ).click();
+    });
+
+    it('should go to the worker summary', () => {
+        cy.get('#searchbar > .q-field > .q-field__inner > .q-field__control').type(
             'jessica jones{enter}'
         );
     });
diff --git a/test/cypress/integration/worker/workerLocker.spec.js b/test/cypress/integration/worker/workerLocker.spec.js
index ef2f88300..75539fcd1 100644
--- a/test/cypress/integration/worker/workerLocker.spec.js
+++ b/test/cypress/integration/worker/workerLocker.spec.js
@@ -2,7 +2,6 @@ describe('WorkerLocker', () => {
     const workerId = 1109;
     const lockerCode = '2F';
     const input = '.q-card input';
-    const firstOpt = '[role="listbox"] .q-item:nth-child(1)';
     beforeEach(() => {
         cy.viewport(1280, 720);
         cy.login('productionBoss');
@@ -11,9 +10,7 @@ describe('WorkerLocker', () => {
 
     it('should allocates a locker', () => {
         cy.get(input).click();
-        cy.get(input).type(lockerCode);
-        cy.get(firstOpt).click();
-        cy.saveCard();
-        cy.get(input).invoke('val').should('eq', lockerCode);
+        cy.get(input).type(`${lockerCode}{enter}`);
+        cy.get(input).should('have.value', lockerCode);
     });
 });

From 1ac0a8596916c920f842516015a7baba5742f6d8 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Tue, 23 Jul 2024 15:09:42 +0200
Subject: [PATCH 06/60] updates

---
 src/components/ui/VnFilterPanel.vue         |  4 ++--
 src/pages/Order/Card/OrderCatalog.vue       | 22 ++++++++++-----------
 src/pages/Order/Card/OrderCatalogFilter.vue | 12 +++++++++--
 3 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index f79852a9d..2dce3228c 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -95,8 +95,8 @@ watch(
 );
 
 watch(
-    () => arrayData.store.userParams,
-    (val) => setUserParams(val)
+    () => [store.userParams, store.userFilter],
+    ([userParams, userFilter]) => setUserParams({ ...userParams, filter: userFilter })
 );
 
 watch(
diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue
index d1479e3c3..cb3e9a8eb 100644
--- a/src/pages/Order/Card/OrderCatalog.vue
+++ b/src/pages/Order/Card/OrderCatalog.vue
@@ -54,17 +54,17 @@ function extractValueTags(items) {
 
 <template>
     <!-- TODO Sobreescribir la barra de búsqueda -->
-    <Teleport to="#searchbar">
-        <VnSearchbar
-            data-key="OrderCatalogList"
-            :user-params="catalogParams"
-            :static-params="['orderFk', 'orderBy']"
-            :redirect="false"
-            url="Orders/CatalogFilter"
-            :label="t('Search items')"
-            :info="t('You can search orders by reference')"
-        />
-    </Teleport>
+    <!-- <Teleport to="#searchbar"> -->
+    <VnSearchbar
+        data-key="OrderCatalogList"
+        :user-params="catalogParams"
+        :static-params="['orderFk', 'orderBy']"
+        :redirect="false"
+        url="Orders/CatalogFilter"
+        :label="t('Search items')"
+        :info="t('You can search orders by reference')"
+    />
+    <!-- </Teleport> -->
     <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
         <QScrollArea class="fit text-grey-8">
             <OrderCatalogFilter
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index 8d96fe93a..d9447e530 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -29,7 +29,7 @@ const props = defineProps({
 });
 const categoryList = ref(null);
 const selectedCategoryFk = ref(null);
-const typeList = ref(null);
+const typeList = ref([]);
 const selectedTypeFk = ref(null);
 const validationsStore = useValidator();
 const selectedOrder = ref(null);
@@ -165,7 +165,8 @@ const onOrderFieldChange = (value, params) => {
             break;
     }
 };
-
+const getCategory = (fk) =>
+    (categoryList.value || []).find((category) => category?.id === fk);
 const _moreFields = ['ASC', 'DESC'];
 const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
 const setCategoryList = (data) => {
@@ -177,6 +178,13 @@ const setCategoryList = (data) => {
         }));
     moreFields.value = useLang(_moreFields);
     moreFieldsOrder.value = useLang(_moreFieldsTypes);
+    const { categoryFk } = JSON.parse(JSON.parse(route.query.params).filter).where;
+
+    if (!selectedCategoryFk.value) {
+        selectedCategoryFk.value = categoryFk;
+        selectedCategory.value = getCategory(categoryFk);
+        loadTypes(categoryFk);
+    }
 };
 
 const getCategoryClass = (category, params) => {

From e013e5571dfad469af9bbd657d229a9ee9579989 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Tue, 23 Jul 2024 20:18:53 +0200
Subject: [PATCH 07/60] perf: use ref at component start

---
 src/pages/Order/Card/OrderCatalogFilter.vue | 41 +++++++++++++++++----
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index d9447e530..62095bf1a 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -28,7 +28,34 @@ const props = defineProps({
     },
 });
 const categoryList = ref(null);
-const selectedCategoryFk = ref(null);
+const params = JSON.parse(route?.query?.params ?? '{}');
+const filter = JSON.parse(params?.filter ?? '{}');
+const selectedCategoryFk = ref(filter?.where?.categoryFk ?? null);
+// CHATGPT
+/**
+ // Función para parsear JSON de manera segura
+const parseJSON = (str, fallback) => {
+  try {
+    return JSON.parse(str);
+  } catch (e) {
+    console.error("Error parsing JSON:", e);
+    return fallback;
+  }
+};
+
+// Obtener los parámetros de la ruta
+const params = parseJSON(route?.query?.params, {});
+
+// Extraer y parsear el filtro de los parámetros
+const { filter: filterStr = '{}' } = params;
+const filter = parseJSON(filterStr, {});
+
+// Obtener el categoryFk del filtro, si existe
+const selectedCategoryFk = ref(filter?.where?.categoryFk ?? null);
+
+console.log(selectedCategoryFk.value);
+
+ */
 const typeList = ref([]);
 const selectedTypeFk = ref(null);
 const validationsStore = useValidator();
@@ -178,13 +205,13 @@ const setCategoryList = (data) => {
         }));
     moreFields.value = useLang(_moreFields);
     moreFieldsOrder.value = useLang(_moreFieldsTypes);
-    const { categoryFk } = JSON.parse(JSON.parse(route.query.params).filter).where;
+    // const { categoryFk } = JSON.parse(JSON.parse(route.query.params).filter).where;
 
-    if (!selectedCategoryFk.value) {
-        selectedCategoryFk.value = categoryFk;
-        selectedCategory.value = getCategory(categoryFk);
-        loadTypes(categoryFk);
-    }
+    // if (!selectedCategoryFk.value) {
+    //     selectedCategoryFk.value = categoryFk;
+    //     selectedCategory.value = getCategory(categoryFk);
+    //     loadTypes(categoryFk);
+    // }
 };
 
 const getCategoryClass = (category, params) => {

From cc518d5a80d4734a2a7c750f07032796e74b5f33 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Wed, 24 Jul 2024 10:33:22 +0200
Subject: [PATCH 08/60] perf: refs #7717 right menu filter

---
 src/filters/getParamWhere.js                | 21 +++++++++++
 src/filters/index.js                        |  2 +
 src/pages/Order/Card/OrderCatalog.vue       |  3 --
 src/pages/Order/Card/OrderCatalogFilter.vue | 41 ++-------------------
 4 files changed, 27 insertions(+), 40 deletions(-)
 create mode 100644 src/filters/getParamWhere.js

diff --git a/src/filters/getParamWhere.js b/src/filters/getParamWhere.js
new file mode 100644
index 000000000..48cd9c479
--- /dev/null
+++ b/src/filters/getParamWhere.js
@@ -0,0 +1,21 @@
+// parsing JSON safely
+function parseJSON(str, fallback) {
+    try {
+        return JSON.parse(str ?? '{}');
+    } catch (e) {
+        console.error('Error parsing JSON:', e);
+        return fallback;
+    }
+}
+export default function (route, param) {
+    // catch route query params
+    const params = parseJSON(route?.query?.params, {});
+
+    // extract and parse filter from params
+    const { filter: filterStr = '{}' } = params;
+    const where = parseJSON(filterStr, {})?.where;
+    if (where && where[param] !== undefined) {
+        return where[param];
+    }
+    return null;
+}
diff --git a/src/filters/index.js b/src/filters/index.js
index 940788ed1..5f08f19c7 100644
--- a/src/filters/index.js
+++ b/src/filters/index.js
@@ -11,6 +11,7 @@ import dashIfEmpty from './dashIfEmpty';
 import dateRange from './dateRange';
 import toHour from './toHour';
 import dashOrCurrency from './dashOrCurrency';
+import getParamWhere from './getParamWhere';
 
 export {
     toLowerCase,
@@ -26,4 +27,5 @@ export {
     toPercentage,
     dashIfEmpty,
     dateRange,
+    getParamWhere,
 };
diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue
index cb3e9a8eb..dc7115422 100644
--- a/src/pages/Order/Card/OrderCatalog.vue
+++ b/src/pages/Order/Card/OrderCatalog.vue
@@ -53,8 +53,6 @@ function extractValueTags(items) {
 </script>
 
 <template>
-    <!-- TODO Sobreescribir la barra de búsqueda -->
-    <!-- <Teleport to="#searchbar"> -->
     <VnSearchbar
         data-key="OrderCatalogList"
         :user-params="catalogParams"
@@ -64,7 +62,6 @@ function extractValueTags(items) {
         :label="t('Search items')"
         :info="t('You can search orders by reference')"
     />
-    <!-- </Teleport> -->
     <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
         <QScrollArea class="fit text-grey-8">
             <OrderCatalogFilter
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index 62095bf1a..85476b438 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -9,6 +9,7 @@ import VnSelect from 'components/common/VnSelect.vue';
 import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
 import { useValidator } from 'src/composables/useValidator';
 import VnInput from 'src/components/common/VnInput.vue';
+import getParamWhere from 'src/filters/getParamWhere';
 
 const { t } = useI18n();
 
@@ -28,34 +29,7 @@ const props = defineProps({
     },
 });
 const categoryList = ref(null);
-const params = JSON.parse(route?.query?.params ?? '{}');
-const filter = JSON.parse(params?.filter ?? '{}');
-const selectedCategoryFk = ref(filter?.where?.categoryFk ?? null);
-// CHATGPT
-/**
- // Función para parsear JSON de manera segura
-const parseJSON = (str, fallback) => {
-  try {
-    return JSON.parse(str);
-  } catch (e) {
-    console.error("Error parsing JSON:", e);
-    return fallback;
-  }
-};
-
-// Obtener los parámetros de la ruta
-const params = parseJSON(route?.query?.params, {});
-
-// Extraer y parsear el filtro de los parámetros
-const { filter: filterStr = '{}' } = params;
-const filter = parseJSON(filterStr, {});
-
-// Obtener el categoryFk del filtro, si existe
-const selectedCategoryFk = ref(filter?.where?.categoryFk ?? null);
-
-console.log(selectedCategoryFk.value);
-
- */
+const selectedCategoryFk = ref(getParamWhere(route, 'categoryFk'));
 const typeList = ref([]);
 const selectedTypeFk = ref(null);
 const validationsStore = useValidator();
@@ -98,7 +72,7 @@ const selectCategory = (params, category, search) => {
     search();
 };
 
-const loadTypes = async (categoryFk) => {
+const loadTypes = async (categoryFk = selectedCategoryFk.value) => {
     const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
         params: { itemCategoryId: categoryFk },
     });
@@ -192,8 +166,6 @@ const onOrderFieldChange = (value, params) => {
             break;
     }
 };
-const getCategory = (fk) =>
-    (categoryList.value || []).find((category) => category?.id === fk);
 const _moreFields = ['ASC', 'DESC'];
 const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
 const setCategoryList = (data) => {
@@ -205,13 +177,8 @@ const setCategoryList = (data) => {
         }));
     moreFields.value = useLang(_moreFields);
     moreFieldsOrder.value = useLang(_moreFieldsTypes);
-    // const { categoryFk } = JSON.parse(JSON.parse(route.query.params).filter).where;
 
-    // if (!selectedCategoryFk.value) {
-    //     selectedCategoryFk.value = categoryFk;
-    //     selectedCategory.value = getCategory(categoryFk);
-    //     loadTypes(categoryFk);
-    // }
+    selectedCategoryFk.value && loadTypes();
 };
 
 const getCategoryClass = (category, params) => {

From d588db05ac9b5e6adb97ddc01a5d98f91086f434 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Fri, 26 Jul 2024 14:09:09 +0200
Subject: [PATCH 09/60] fix: refs #7717 fix OrderList table filters' and
 summary table style

---
 src/pages/Order/Card/OrderSummary.vue | 37 ++++++++++----------------
 src/pages/Order/OrderList.vue         | 38 ++++++++++++++++++---------
 2 files changed, 40 insertions(+), 35 deletions(-)

diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue
index cb708fb1d..09c47d4a7 100644
--- a/src/pages/Order/Card/OrderSummary.vue
+++ b/src/pages/Order/Card/OrderSummary.vue
@@ -180,15 +180,17 @@ const detailsColumns = ref([
                                         <ItemDescriptorProxy :id="props.row.item?.id" />
                                     </span>
                                 </QTd>
-                                <QTd key="description" :props="props" class="description">
-                                    <div class="name">
-                                        <span>{{ props.row.item.name }}</span>
-                                        <span
-                                            v-if="props.row.item.subName"
-                                            class="subName"
-                                        >
-                                            {{ props.row.item.subName }}
-                                        </span>
+                                <QTd key="description" :props="props">
+                                    <div class="description">
+                                        <div class="name">
+                                            {{ props.row.item.name }}
+                                            <span
+                                                v-if="props.row.item.subName"
+                                                class="subName"
+                                            >
+                                                {{ props.row.item.subName }}
+                                            </span>
+                                        </div>
                                     </div>
                                     <FetchedTags :item="props.row.item" :max-length="5" />
                                 </QTd>
@@ -228,24 +230,13 @@ const detailsColumns = ref([
 }
 
 .description {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
     text-align: left;
-    height: auto;
-    padding-top: 12px;
-    padding-bottom: 12px;
+    padding-top: 15px;
+    padding-bottom: 15px;
 
     .name {
-        display: flex;
-        align-items: center;
-        padding-bottom: 8px;
-
-        & > * {
-            flex: 1;
-        }
-
         .subName {
+            margin-left: 5%;
             text-transform: uppercase;
             color: var(--vn-label-color);
         }
diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue
index fa82540e6..e0261fc19 100644
--- a/src/pages/Order/OrderList.vue
+++ b/src/pages/Order/OrderList.vue
@@ -29,7 +29,7 @@ const columns = computed(() => [
     },
     {
         align: 'left',
-        name: 'clientName',
+        name: 'clientFk',
         label: t('module.customer'),
         isTitle: true,
         cardVisible: true,
@@ -41,20 +41,26 @@ const columns = computed(() => [
         columnField: {
             component: null,
         },
+        format: (row) => row?.clientName,
     },
     {
         align: 'left',
-        name: 'name',
+        name: 'salesPersonFk',
         label: t('module.salesPerson'),
-        component: 'select',
-        attrs: {
-            url: 'Workers/activeWithInheritedRole',
-            fields: ['id', 'name'],
-            where: { role: 'salesPerson' },
-        },
-        columnField: {
-            component: null,
+        columnFilter: {
+            component: 'select',
+            inWhere: true,
+            attrs: {
+                url: 'Workers/activeWithInheritedRole',
+                fields: ['id', 'name'],
+                where: { role: 'salesPerson' },
+                useLike: false,
+                optionValue: 'id',
+                optionLabel: 'name',
+                optionFilter: 'firstName',
+            },
         },
+        format: (row) => row?.name,
     },
     {
         align: 'left',
@@ -92,22 +98,30 @@ const columns = computed(() => [
     },
     {
         align: 'left',
-        name: 'agencyName',
+        name: 'agencyModeFk',
         label: t('module.agency'),
         component: 'select',
         cardVisible: true,
         attrs: {
-            url: 'Agencies',
+            url: 'agencyModes',
             fields: ['id', 'name'],
+            find: {
+                value: 'agencyModeFk',
+                label: 'agencyName',
+            },
         },
         columnField: {
             component: null,
         },
+        format: (row) => row?.agencyName,
     },
     {
         align: 'left',
         name: 'total',
         label: t('module.total'),
+        columnFilter: {
+            inWhere: true,
+        },
         format: ({ total }) => toCurrency(total),
         cardVisible: true,
     },

From 4d7fe0fd864f816e733aea6ea7c9aaa779d0225c Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Fri, 26 Jul 2024 14:56:26 +0200
Subject: [PATCH 10/60] fix: refs #7717 fix volume and lines redirect

---
 src/pages/Order/Card/OrderLines.vue  | 13 ++++++++++-
 src/pages/Order/Card/OrderVolume.vue | 32 +++++++++++++++++++++-------
 2 files changed, 36 insertions(+), 9 deletions(-)

diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue
index f361d9898..391a4e374 100644
--- a/src/pages/Order/Card/OrderLines.vue
+++ b/src/pages/Order/Card/OrderLines.vue
@@ -1,8 +1,9 @@
 <script setup>
 import { useRoute } from 'vue-router';
 import { useI18n } from 'vue-i18n';
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import { useQuasar } from 'quasar';
+import { useRouter } from 'vue-router';
 
 import VnConfirm from 'components/ui/VnConfirm.vue';
 import { toCurrency, toDate } from 'src/filters';
@@ -14,6 +15,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
 import FetchedTags from 'src/components/ui/FetchedTags.vue';
 import { useStateStore } from 'stores/useStateStore';
 
+const router = useRouter();
 const stateStore = useStateStore();
 const route = useRoute();
 const { t } = useI18n();
@@ -194,6 +196,15 @@ async function confirmOrder() {
         type: 'positive',
     });
 }
+
+watch(
+    () => router.currentRoute.value.params.id,
+    () => {
+        lineFilter.value.where.orderFk = router.currentRoute.value.params.id;
+
+        tableLinesRef.value.reload();
+    }
+);
 </script>
 
 <template>
diff --git a/src/pages/Order/Card/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue
index 35d68de16..62aa08bc3 100644
--- a/src/pages/Order/Card/OrderVolume.vue
+++ b/src/pages/Order/Card/OrderVolume.vue
@@ -1,7 +1,9 @@
 <script setup>
+import axios from 'axios';
 import { useRoute } from 'vue-router';
 import { useI18n } from 'vue-i18n';
-import { ref } from 'vue';
+import { ref, watch } from 'vue';
+import { useRouter } from 'vue-router';
 
 import VnPaginate from 'components/ui/VnPaginate.vue';
 import FetchData from 'components/FetchData.vue';
@@ -10,11 +12,20 @@ import CardList from 'components/ui/CardList.vue';
 import FetchedTags from 'components/ui/FetchedTags.vue';
 
 import { dashIfEmpty } from 'src/filters';
-import axios from 'axios';
 
+const router = useRouter();
 const route = useRoute();
 const { t } = useI18n();
 const volumeSummary = ref(null);
+const volumeRef = ref();
+const volumeFilter = ref({
+    include: [
+        {
+            relation: 'item',
+        },
+    ],
+    where: { orderFk: route.params.id },
+});
 
 const loadVolumes = async (rows) => {
     const { data } = await axios.get(`Orders/${route.params.id}/getVolumes`);
@@ -26,6 +37,15 @@ const loadVolumes = async (rows) => {
         });
     });
 };
+
+watch(
+    () => router.currentRoute.value.params.id,
+    () => {
+        volumeFilter.value.where.orderFk = router.currentRoute.value.params.id;
+
+        volumeRef.value.fetch();
+    }
+);
 </script>
 
 <template>
@@ -54,15 +74,11 @@ const loadVolumes = async (rows) => {
             </QCard>
             <VnPaginate
                 data-key="OrderCatalogVolume"
+                ref="volumeRef"
                 url="OrderRows"
                 :limit="20"
                 auto-load
-                :filter="{
-                    include: {
-                        relation: 'item',
-                    },
-                    where: { orderFk: route.params.id },
-                }"
+                :user-filter="volumeFilter"
                 order="itemFk"
                 @on-fetch="(data) => loadVolumes(data)"
             >

From 7e3bfce7320beb2bdba80d8cefbd37c71aa5c4d5 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Tue, 30 Jul 2024 13:31:12 +0200
Subject: [PATCH 11/60] fix: refs #7717 fix basic data form & minor errors

---
 src/pages/Order/Card/OrderForm.vue   | 9 ++-------
 src/pages/Order/Card/OrderLines.vue  | 5 ++++-
 src/pages/Order/Card/OrderVolume.vue | 2 +-
 3 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/pages/Order/Card/OrderForm.vue b/src/pages/Order/Card/OrderForm.vue
index f447950d2..91640101c 100644
--- a/src/pages/Order/Card/OrderForm.vue
+++ b/src/pages/Order/Card/OrderForm.vue
@@ -1,5 +1,5 @@
 <script setup>
-import { useRoute, useRouter } from 'vue-router';
+import { useRoute } from 'vue-router';
 import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import axios from 'axios';
@@ -15,7 +15,6 @@ const route = useRoute();
 const state = useState();
 const ORDER_MODEL = 'order';
 
-const router = useRouter();
 const isNew = Boolean(!route.params.id);
 const clientList = ref([]);
 const agencyList = ref([]);
@@ -106,10 +105,6 @@ const onClientChange = async (clientId) => {
         console.error('Error al cambiar el cliente:', error);
     }
 };
-
-async function onDataSaved({ id }) {
-    await router.push({ path: `/order/${id}/catalog` });
-}
 </script>
 
 <template>
@@ -117,7 +112,7 @@ async function onDataSaved({ id }) {
     <div class="q-pa-md">
         <FormModel
             :url="`Orders/${route.params.id}`"
-            @on-data-saved="onDataSaved"
+            :url-update="`Orders/${route.params.id}/updateBasicData`"
             :model="ORDER_MODEL"
             :mapper="orderMapper"
             :filter="orderFilter"
diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue
index 391a4e374..9165cfd67 100644
--- a/src/pages/Order/Card/OrderLines.vue
+++ b/src/pages/Order/Card/OrderLines.vue
@@ -106,6 +106,7 @@ const columns = computed(() => [
             component: null,
         },
         format: (row) => row?.item?.name,
+        columnClass: 'expand',
     },
     {
         align: 'left',
@@ -149,7 +150,6 @@ const columns = computed(() => [
         align: 'left',
         name: 'amount',
         label: t('lines.amount'),
-        format: (row) => toCurrency(row.amount),
     },
     {
         align: 'right',
@@ -278,6 +278,9 @@ watch(
                     </div>
                     <FetchedTags :item="row?.item" :max-length="6" />
                 </template>
+                <template #column-amount="{ row }">
+                    {{ toCurrency(row.quantity * row.price) }}
+                </template>
             </VnTable>
         </div>
         <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2">
diff --git a/src/pages/Order/Card/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue
index 62aa08bc3..69f0a9c6b 100644
--- a/src/pages/Order/Card/OrderVolume.vue
+++ b/src/pages/Order/Card/OrderVolume.vue
@@ -166,7 +166,7 @@ es:
     total: Total
     boxes: Cajas
     item: Artículo
-    subName: Subname
+    subName: Subnombre
     quantity: Cantidad
     volume: m³ por cantidad
 </i18n>

From 68ce5880dd7f1a82aa206ca44bbf1938fe5563e0 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Wed, 31 Jul 2024 07:35:59 +0200
Subject: [PATCH 12/60] refactor: refs #7717 deleted useless code

---
 src/components/common/VnCard.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue
index 4b7a6ba63..8470fdff0 100644
--- a/src/components/common/VnCard.vue
+++ b/src/components/common/VnCard.vue
@@ -33,7 +33,7 @@ const url = computed(() => {
 });
 const searchRightDataKey = computed(() => {
     if (!props.searchDataKey) return route.name;
-    else return props.searchDataKey;
+    return props.searchDataKey;
 });
 const arrayData = useArrayData(props.dataKey, {
     url: url.value,

From 6b69a1612222cf94b65e99724a4943e2af991489 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Wed, 7 Aug 2024 12:22:52 +0200
Subject: [PATCH 13/60] refs #7355 changes BasicData

---
 src/i18n/locale/es.yml                      | 2 +-
 src/pages/Account/AccountList.vue           | 9 ++++++++-
 src/pages/Account/Card/AccountBasicData.vue | 4 +++-
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 0266bd208..708084d45 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -873,7 +873,7 @@ worker:
     card:
         workerId: ID Trabajador
         name: Nombre
-        email: Email
+        email: Correo personal
         phone: Teléfono
         mobile: Móvil
         active: Activo
diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue
index ecb027e49..d789fdf84 100644
--- a/src/pages/Account/AccountList.vue
+++ b/src/pages/Account/AccountList.vue
@@ -48,6 +48,14 @@ const columns = computed(() => [
         cardVisible: true,
         create: true,
     },
+    {
+        align: 'left',
+        name: 'email',
+        label: t('email'),
+        component: 'input',
+        create: true,
+        visible: false,
+    },
     {
         align: 'right',
         label: '',
@@ -96,7 +104,6 @@ const exprBuilder = (param, value) => {
         order="id DESC"
         :columns="columns"
         default-mode="table"
-        auto-load
         redirect="account"
         :use-model="true"
     />
diff --git a/src/pages/Account/Card/AccountBasicData.vue b/src/pages/Account/Card/AccountBasicData.vue
index 42b77419f..f38299f9e 100644
--- a/src/pages/Account/Card/AccountBasicData.vue
+++ b/src/pages/Account/Card/AccountBasicData.vue
@@ -37,9 +37,11 @@ watch(
                 <VnInput v-model="data.nickname" :label="t('account.card.alias')" />
                 <VnInput v-model="data.email" :label="t('account.card.email')" />
                 <VnSelect
+                    url="Languages"
                     v-model="data.lang"
-                    :options="['es', 'en']"
                     :label="t('account.card.lang')"
+                    option-value="code"
+                    option-label="code"
                 />
             </div>
         </template>

From 3eaa5c864d45c28ada6322ef5d333b8588260a95 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Thu, 8 Aug 2024 13:57:01 +0200
Subject: [PATCH 14/60] refs #7355 account fixes

---
 src/components/ui/VnFilterPanel.vue           |  8 ++--
 src/pages/Account/AccountAcls.vue             | 26 ++++++++--
 src/pages/Account/Card/AccountCard.vue        |  5 +-
 .../Account/Card/AccountDescriptorMenu.vue    | 48 +++++++++++++++++++
 src/pages/Account/Card/AccountPrivileges.vue  |  9 +---
 src/pages/Account/Role/AccountRoles.vue       | 17 ++++++-
 src/pages/Account/Role/Card/RoleBasicData.vue |  5 --
 src/router/modules/account.js                 | 18 +++----
 8 files changed, 102 insertions(+), 34 deletions(-)

diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index 430383e40..ebae673a2 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -37,7 +37,7 @@ const $props = defineProps({
     },
     hiddenTags: {
         type: Array,
-        default: () => ['filter'],
+        default: () => ['filter', 'search', 'or', 'and'],
     },
     customTags: {
         type: Array,
@@ -195,8 +195,10 @@ function formatValue(value) {
 
 function sanitizer(params) {
     for (const [key, value] of Object.entries(params)) {
-        if (typeof value == 'object')
-            params[key] = Object.values(value)[0].replaceAll('%', '');
+        if (typeof value == 'object') {
+            const param = Object.values(value)[0];
+            if (typeof param == 'string') params[key] = param.replaceAll('%', '');
+        }
     }
     return params;
 }
diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue
index ad8600786..3a6679956 100644
--- a/src/pages/Account/AccountAcls.vue
+++ b/src/pages/Account/AccountAcls.vue
@@ -41,14 +41,12 @@ const columns = computed(() => [
         name: 'id',
         label: t('id'),
         isId: true,
-        field: 'id',
         cardVisible: true,
     },
     {
         align: 'left',
         name: 'model',
         label: t('model'),
-        field: 'model',
         cardVisible: true,
         create: true,
     },
@@ -56,15 +54,19 @@ const columns = computed(() => [
         align: 'left',
         name: 'principalId',
         label: t('principalId'),
-        field: 'principalId',
         cardVisible: true,
+        component: 'select',
+        attrs: {
+            url: 'VnRoles',
+            optionLabel: 'name',
+            optionValue: 'name',
+        },
         create: true,
     },
     {
         align: 'left',
         name: 'property',
         label: t('property'),
-        field: 'property',
         cardVisible: true,
         create: true,
     },
@@ -72,7 +74,10 @@ const columns = computed(() => [
         align: 'left',
         name: 'accessType',
         label: t('accessType'),
-        field: 'accessType',
+        component: 'select',
+        attrs: {
+            options: ['READ', 'WRITE', '*'],
+        },
         cardVisible: true,
         create: true,
     },
@@ -162,4 +167,15 @@ es:
     Are you sure you want to continue?: ¿Seguro que quieres continuar?
     Remove ACL: Eliminar Acl
     Do you want to remove this ACL?: ¿Quieres eliminar este ACL?
+    principalId: Rol
+    model: Modelo
+en:
+    New ACL: New ACL
+    ACL removed: ACL removed
+    ACL will be removed: ACL will be removed
+    Are you sure you want to continue?: Are you sure you want to continue?
+    Remove ACL: Remove ACL
+    Do you want to remove this ACL?: Do you want to remove this ACL?
+    principalId: Rol
+    model: Models
 </i18n>
diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue
index e4db3ee2b..555ce0866 100644
--- a/src/pages/Account/Card/AccountCard.vue
+++ b/src/pages/Account/Card/AccountCard.vue
@@ -25,9 +25,8 @@ const searchBarDataKeys = {
     <VnCard
         data-key="Account"
         :descriptor="AccountDescriptor"
-        :search-data-key="searchBarDataKeys[routeName]"
-        :search-custom-route-redirect="customRouteRedirectName"
-        :search-redirect="!!customRouteRedirectName"
+        search-data-key="AccountUsers"
+        search-url="VnUsers/preview"
         :searchbar-label="t('account.search')"
         :searchbar-info="t('account.searchInfo')"
     />
diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue
index 05576b145..f67cc0c6b 100644
--- a/src/pages/Account/Card/AccountDescriptorMenu.vue
+++ b/src/pages/Account/Card/AccountDescriptorMenu.vue
@@ -82,6 +82,54 @@ const removeAccount = async () => {
 };
 </script>
 <template>
+    <VnConfirm
+        v-model="showSyncDialog"
+        :message="t('account.card.actions.sync.message')"
+        :title="t('account.card.actions.sync.title')"
+        :promise="sync"
+    >
+        <template #customHTML>
+            {{ shouldSyncPassword }}
+            <QCheckbox
+                :label="t('account.card.actions.sync.checkbox')"
+                v-model="shouldSyncPassword"
+                class="full-width"
+                clearable
+                clear-icon="close"
+            >
+                <QIcon style="padding-left: 10px" color="primary" name="info" size="sm">
+                    <QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
+                </QIcon></QCheckbox
+            >
+            <QInput
+                v-if="shouldSyncPassword"
+                :label="t('login.password')"
+                v-model="syncPassword"
+                class="full-width"
+                clearable
+                clear-icon="close"
+                type="password"
+            />
+        </template>
+    </VnConfirm>
+    <!-- <QItem v-ripple clickable @click="setPassword">
+        <QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
+    </QItem>
+    <QItem
+        v-if="!account.hasAccount"
+        v-ripple
+        clickable
+        @click="
+            openConfirmationModal(
+                t('account.card.actions.enableAccount.title'),
+                t('account.card.actions.enableAccount.subtitle'),
+                () => updateStatusAccount(true)
+            )
+        "
+    >
+        <QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection>
+    </QItem> -->
+
     <QItem
         v-if="account.hasAccount"
         v-ripple
diff --git a/src/pages/Account/Card/AccountPrivileges.vue b/src/pages/Account/Card/AccountPrivileges.vue
index f1f24f19b..1300f5018 100644
--- a/src/pages/Account/Card/AccountPrivileges.vue
+++ b/src/pages/Account/Card/AccountPrivileges.vue
@@ -14,16 +14,11 @@ const rolesOptions = ref([]);
 const formModelRef = ref();
 </script>
 <template>
-    <FetchData
-        url="VnRoles"
-        :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
-        auto-load
-        @on-fetch="(data) => (rolesOptions = data)"
-    />
+    <FetchData url="VnRoles" auto-load @on-fetch="(data) => (rolesOptions = data)" />
     <FormModel
         ref="formModelRef"
         model="AccountPrivileges"
-        :url="`VnUsers/${route.params.id}`"
+        :url="`VnUsers/${route.params.id}/privileges`"
         :url-create="`VnUsers/${route.params.id}/privileges`"
         auto-load
         @on-data-saved="formModelRef.fetch()"
diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue
index c11bb340a..07b205993 100644
--- a/src/pages/Account/Role/AccountRoles.vue
+++ b/src/pages/Account/Role/AccountRoles.vue
@@ -5,6 +5,8 @@ import VnTable from 'components/VnTable/VnTable.vue';
 import { useRoute } from 'vue-router';
 import VnSearchbar from 'components/ui/VnSearchbar.vue';
 import { useStateStore } from 'stores/useStateStore';
+import { useSummaryDialog } from 'src/composables/useSummaryDialog';
+import RoleSummary from './Card/RoleSummary.vue';
 const route = useRoute();
 const stateStore = useStateStore();
 const { t } = useI18n();
@@ -16,7 +18,7 @@ const $props = defineProps({
 });
 const tableRef = ref();
 const entityId = computed(() => $props.id || route.params.id);
-
+const { viewSummary } = useSummaryDialog();
 const columns = computed(() => [
     {
         align: 'left',
@@ -42,6 +44,18 @@ const columns = computed(() => [
         cardVisible: true,
         create: true,
     },
+    {
+        align: 'right',
+        label: '',
+        name: 'tableActions',
+        actions: [
+            {
+                title: t('View Summary'),
+                icon: 'preview',
+                action: (row) => viewSummary(row.id, RoleSummary),
+            },
+        ],
+    },
 ]);
 const exprBuilder = (param, value) => {
     switch (param) {
@@ -89,6 +103,5 @@ const exprBuilder = (param, value) => {
         default-mode="table"
         auto-load
         redirect="account/role"
-        :is-editable="true"
     />
 </template>
diff --git a/src/pages/Account/Role/Card/RoleBasicData.vue b/src/pages/Account/Role/Card/RoleBasicData.vue
index cddf755f0..56f4f0c27 100644
--- a/src/pages/Account/Role/Card/RoleBasicData.vue
+++ b/src/pages/Account/Role/Card/RoleBasicData.vue
@@ -23,11 +23,6 @@ const { t } = useI18n();
                     />
                 </div>
             </VnRow>
-            <VnRow>
-                <div class="col">
-                    <QCheckbox :label="t('mailAlias.isPublic')" v-model="data.isPublic" />
-                </div>
-            </VnRow>
         </template>
     </FormModel>
 </template>
diff --git a/src/router/modules/account.js b/src/router/modules/account.js
index 3faa00fbc..cfec2b95d 100644
--- a/src/router/modules/account.js
+++ b/src/router/modules/account.js
@@ -65,13 +65,13 @@ export default {
                     component: () => import('src/pages/Account/AccountAliasList.vue'),
                 },
                 {
-                    path: 'connections',
-                    name: 'AccountConnections',
+                    path: 'acls',
+                    name: 'AccountAcls',
                     meta: {
-                        title: 'connections',
+                        title: 'acls',
                         icon: 'check',
                     },
-                    component: () => import('src/pages/Account/AccountConnections.vue'),
+                    component: () => import('src/pages/Account/AccountAcls.vue'),
                 },
                 {
                     path: 'accounts',
@@ -104,13 +104,13 @@ export default {
                     component: () => import('src/pages/Account/AccountSamba.vue'),
                 },
                 {
-                    path: 'acls',
-                    name: 'AccountAcls',
+                    path: 'connections',
+                    name: 'AccountConnections',
                     meta: {
-                        title: 'acls',
-                        icon: 'check',
+                        title: 'connections',
+                        icon: 'share',
                     },
-                    component: () => import('src/pages/Account/AccountAcls.vue'),
+                    component: () => import('src/pages/Account/AccountConnections.vue'),
                 },
                 {
                     path: 'acl-form',

From 40c3ba15e6e0e02756526535af42462df54d3f11 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Thu, 8 Aug 2024 14:55:53 +0200
Subject: [PATCH 15/60] refs #7355 account acls roles cars

---
 src/pages/Account/AccountAcls.vue        |  7 -------
 src/pages/Account/Role/AccountRoles.vue  | 16 ++++++----------
 src/pages/Account/Role/Card/RoleCard.vue |  5 ++---
 3 files changed, 8 insertions(+), 20 deletions(-)

diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue
index 3a6679956..080956d46 100644
--- a/src/pages/Account/AccountAcls.vue
+++ b/src/pages/Account/AccountAcls.vue
@@ -123,13 +123,6 @@ const deleteAcl = async ({ id }) => {
 </script>
 
 <template>
-    <FetchData
-        url="VnRoles"
-        :filter="{ fields: ['name'], order: 'name ASC' }"
-        @on-fetch="(data) => (rolesOptions = data)"
-        auto-load
-    />
-
     <VnSearchbar
         data-key="AccountAcls"
         url="ACLs"
diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue
index 07b205993..0604b71af 100644
--- a/src/pages/Account/Role/AccountRoles.vue
+++ b/src/pages/Account/Role/AccountRoles.vue
@@ -76,16 +76,12 @@ const exprBuilder = (param, value) => {
 </script>
 
 <template>
-    <template v-if="stateStore.isHeaderMounted()">
-        <Teleport to="#searchbar">
-            <VnSearchbar
-                data-key="Roles"
-                :expr-builder="exprBuilder"
-                :label="t('role.searchRoles')"
-                :info="t('role.searchInfo')"
-            />
-        </Teleport>
-    </template>
+    <VnSearchbar
+        data-key="Roles"
+        :expr-builder="exprBuilder"
+        :label="t('role.searchRoles')"
+        :info="t('role.searchInfo')"
+    />
     <VnTable
         ref="tableRef"
         data-key="Roles"
diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue
index 8a65d37d7..e5c8c3ebb 100644
--- a/src/pages/Account/Role/Card/RoleCard.vue
+++ b/src/pages/Account/Role/Card/RoleCard.vue
@@ -22,9 +22,8 @@ const searchBarDataKeys = {
     <VnCard
         data-key="Role"
         :descriptor="RoleDescriptor"
-        :search-data-key="searchBarDataKeys[routeName]"
-        :search-custom-route-redirect="customRouteRedirectName"
-        :search-redirect="!!customRouteRedirectName"
+        search-data-key="AccountRoles"
+        search-url="VnRoles"
         :searchbar-label="t('role.searchRoles')"
         :searchbar-info="t('role.searchInfo')"
     />

From 3f76b5496a2c30c278bd1b7c06e1f16b472bd090 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Fri, 9 Aug 2024 07:46:32 +0200
Subject: [PATCH 16/60] fix: refs #7717 fix order sections

---
 src/composables/useArrayData.js               |  2 +-
 src/pages/Order/Card/OrderCatalog.vue         | 25 +++++++++++++++----
 src/pages/Order/Card/OrderCatalogFilter.vue   |  3 ++-
 .../Order/Card/OrderCatalogItemDialog.vue     | 23 ++++++++++-------
 src/pages/Order/Card/OrderDescriptor.vue      | 19 +++++++++-----
 src/pages/Order/Card/OrderForm.vue            | 21 +++++++++++++---
 src/pages/Order/Card/OrderLines.vue           | 19 +++++++++++++-
 src/pages/Order/Card/OrderVolume.vue          |  8 +++---
 8 files changed, 90 insertions(+), 30 deletions(-)

diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js
index 50d620a34..651bcefb0 100644
--- a/src/composables/useArrayData.js
+++ b/src/composables/useArrayData.js
@@ -28,7 +28,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
             delete params.filter;
             store.userParams = { ...params, ...store.userParams };
             store.userFilter = { ...filter, ...store.userFilter };
-            if (filter.order) store.order = filter.order;
+            if (filter?.order) store.order = filter.order;
         }
     });
 
diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue
index dc7115422..68bf9511f 100644
--- a/src/pages/Order/Card/OrderCatalog.vue
+++ b/src/pages/Order/Card/OrderCatalog.vue
@@ -1,7 +1,8 @@
 <script setup>
 import { useStateStore } from 'stores/useStateStore';
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { onMounted, onUnmounted, ref } from 'vue';
+import axios from 'axios';
 import { useI18n } from 'vue-i18n';
 import VnPaginate from 'components/ui/VnPaginate.vue';
 import CatalogItem from 'components/ui/CatalogItem.vue';
@@ -9,10 +10,15 @@ import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
 import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
 
 const route = useRoute();
+const router = useRouter();
 const stateStore = useStateStore();
 const { t } = useI18n();
+const tags = ref([]);
 
-onMounted(() => (stateStore.rightDrawer = true));
+onMounted(() => {
+    stateStore.rightDrawer = true;
+    checkOrderConfirmation();
+});
 onUnmounted(() => (stateStore.rightDrawer = false));
 
 const catalogParams = {
@@ -20,7 +26,12 @@ const catalogParams = {
     orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }),
 };
 
-const tags = ref([]);
+async function checkOrderConfirmation() {
+    const response = await axios.get(`Orders/${route.params.id}`);
+    if (response.data.isConfirmed === 1) {
+        router.push(`/order/${route.params.id}/line`);
+    }
+}
 
 function extractTags(items) {
     const resultTags = [];
@@ -60,7 +71,7 @@ function extractValueTags(items) {
         :redirect="false"
         url="Orders/CatalogFilter"
         :label="t('Search items')"
-        :info="t('You can search orders by reference')"
+        :info="t('You can search items by name or id')"
     />
     <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
         <QScrollArea class="fit text-grey-8">
@@ -78,7 +89,6 @@ function extractValueTags(items) {
                 url="Orders/CatalogFilter"
                 :limit="50"
                 :user-params="catalogParams"
-                auto-load
                 @on-fetch="extractTags"
                 :update-router="false"
             >
@@ -116,3 +126,8 @@ function extractValueTags(items) {
     text-align: center;
 }
 </style>
+
+<i18n>
+es:
+    You can search items by name or id: Puedes buscar items por nombre o id
+</i18n>
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index 85476b438..887cb6740 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -102,7 +102,8 @@ function exprBuilder(param, value) {
         case 'typeFk':
             return { [param]: value };
         case 'search':
-            return { 'i.name': { like: `%${value}%` } };
+            if (/^\d+$/.test(value)) return { 'i.id': value };
+            else return { 'i.name': { like: `%${value}%` } };
     }
 }
 
diff --git a/src/pages/Order/Card/OrderCatalogItemDialog.vue b/src/pages/Order/Card/OrderCatalogItemDialog.vue
index 047816127..3f97443ca 100644
--- a/src/pages/Order/Card/OrderCatalogItemDialog.vue
+++ b/src/pages/Order/Card/OrderCatalogItemDialog.vue
@@ -5,10 +5,12 @@ import { useI18n } from 'vue-i18n';
 import axios from 'axios';
 import { useRoute } from 'vue-router';
 import useNotify from 'composables/useNotify';
+import { useArrayData } from 'composables/useArrayData';
 
 const { t } = useI18n();
-const route = useRoute();
 const { notify } = useNotify();
+const emit = defineEmits(['added']);
+const route = useRoute();
 const props = defineProps({
     prices: {
         type: Array,
@@ -16,9 +18,8 @@ const props = defineProps({
     },
 });
 
-const emit = defineEmits(['added']);
-
 const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
+const descriptorData = useArrayData('orderData');
 
 const addToOrder = async () => {
     const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
@@ -28,19 +29,20 @@ const addToOrder = async () => {
     });
     notify(t('globals.dataSaved'), 'positive');
     emit('added');
+    descriptorData.fetch({});
 };
 </script>
 
 <template>
-    <div class="container order-catalog-item q-pb-md">
+    <div class="container order-catalog-item q-pa-md">
         <QForm @submit="addToOrder">
             <QMarkupTable class="shadow-0">
                 <tbody>
                     <tr v-for="item in fields" :key="item.warehouse">
-                        <td class="text-bold q-py-lg">
+                        <td class="text-bold q-pr-md td" style="width: 35%">
                             {{ item.warehouse }}
                         </td>
-                        <td class="text-right">
+                        <td class="text-right" style="width: 35%">
                             <span
                                 class="link"
                                 @click="
@@ -75,8 +77,11 @@ const addToOrder = async () => {
 </template>
 
 <style lang="scss" scoped>
-.container {
-    max-width: 448px;
-    width: 100%;
+// .container {
+//     max-width: 768px;
+//     width: 100%;
+// }
+.td {
+    width: 200px;
 }
 </style>
diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue
index 4d84a32fc..8d2b5309e 100644
--- a/src/pages/Order/Card/OrderDescriptor.vue
+++ b/src/pages/Order/Card/OrderDescriptor.vue
@@ -4,13 +4,13 @@ import { useRoute } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { toCurrency, toDate } from 'src/filters';
 import { useState } from 'src/composables/useState';
-
 import useCardDescription from 'src/composables/useCardDescription';
-import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
+
 import CardDescriptor from 'components/ui/CardDescriptor.vue';
 import VnLv from 'src/components/ui/VnLv.vue';
-import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue';
 import FetchData from 'components/FetchData.vue';
+import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue';
+import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
 
 const DEFAULT_ITEMS = 0;
 
@@ -25,6 +25,8 @@ const $props = defineProps({
 const route = useRoute();
 const state = useState();
 const { t } = useI18n();
+const data = ref(useCardDescription());
+const getTotalRef = ref();
 
 const entityId = computed(() => {
     return $props.id || route.params.id;
@@ -57,11 +59,11 @@ const filter = {
     ],
 };
 
-const data = ref(useCardDescription());
 const setData = (entity) => {
     if (!entity) return;
+    getTotalRef.value && getTotalRef.value.fetch();
     data.value = useCardDescription(entity?.client?.name, entity?.id);
-    state.set('OrderDescriptor', entity);
+    state.set('orderData', entity);
 };
 
 const getConfirmationValue = (isConfirmed) => {
@@ -69,10 +71,15 @@ const getConfirmationValue = (isConfirmed) => {
 };
 
 const total = ref(null);
+
+function ticketFilter(order) {
+    return JSON.stringify({ id: order.id });
+}
 </script>
 
 <template>
     <FetchData
+        ref="getTotalRef"
         :url="`Orders/${entityId}/getTotal`"
         @on-fetch="(response) => (total = response)"
         auto-load
@@ -120,7 +127,7 @@ const total = ref(null);
                     color="primary"
                     :to="{
                         name: 'TicketList',
-                        query: { params: JSON.stringify({ orderFk: entity.id }) },
+                        query: { table: ticketFilter(entity) },
                     }"
                 >
                     <QTooltip>{{ t('order.summary.orderTicketList') }}</QTooltip>
diff --git a/src/pages/Order/Card/OrderForm.vue b/src/pages/Order/Card/OrderForm.vue
index 91640101c..74d0a8b75 100644
--- a/src/pages/Order/Card/OrderForm.vue
+++ b/src/pages/Order/Card/OrderForm.vue
@@ -64,11 +64,14 @@ const fetchOrderDetails = (order) => {
 };
 
 const orderMapper = (order) => {
-    return {
+    const mappedOrder = {
         addressId: order.addressFk,
-        agencyModeId: order.agencyModeFk,
         landed: new Date(order.landed).toISOString(),
     };
+    if (order.agencyModeFk !== null && order.agencyModeFk !== undefined) {
+        mappedOrder.agencyModeId = order.agencyModeFk;
+    }
+    return mappedOrder;
 };
 const orderFilter = {
     include: [
@@ -184,7 +187,19 @@ const onClientChange = async (clientId) => {
                         option-value="agencyModeFk"
                         option-label="agencyMode"
                         hide-selected
-                        :disable="!agencyList?.length"
+                        :disable="!agencyList?.length && data.isConfirmed === 1"
+                        clearable
+                        emit-value
+                        map-options
+                        :model-value="
+                            !data.isConfirmed &&
+                            agencyList?.length &&
+                            agencyList.some(
+                                (agency) => agency.agencyModeFk === data.agency_id
+                            )
+                                ? data.agencyModeFk
+                                : null
+                        "
                     >
                     </VnSelect>
                 </VnRow>
diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue
index 21ea3b300..d4a5c4b1d 100644
--- a/src/pages/Order/Card/OrderLines.vue
+++ b/src/pages/Order/Card/OrderLines.vue
@@ -61,6 +61,13 @@ const lineFilter = ref({
                 fields: ['id', 'name'],
             },
         },
+        {
+            relation: 'order',
+            scope: {
+                fields: ['id', 'isConfirmed'],
+                where: { id: route.params.id },
+            },
+        },
     ],
     where: { orderFk: route.params.id },
 });
@@ -157,8 +164,9 @@ const columns = computed(() => [
         name: 'tableActions',
         actions: [
             {
-                title: t('delete'),
+                title: t('Delete'),
                 icon: 'delete',
+                show: (row) => !row.order.isConfirmed,
                 action: (row) => confirmRemove(row),
                 isPrimary: true,
             },
@@ -281,6 +289,15 @@ watch(
                 <template #column-amount="{ row }">
                     {{ toCurrency(row.quantity * row.price) }}
                 </template>
+                <template #column-tableActions="{ row }">
+                    <QIcon
+                        v-if="row.order?.isConfirmed === 0"
+                        name="delete"
+                        icon="delete"
+                        @click="confirmRemove(row)"
+                        class="cursor-pointer"
+                    />
+                </template>
             </VnTable>
         </div>
         <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2">
diff --git a/src/pages/Order/Card/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue
index 69f0a9c6b..8622a1fa5 100644
--- a/src/pages/Order/Card/OrderVolume.vue
+++ b/src/pages/Order/Card/OrderVolume.vue
@@ -10,14 +10,16 @@ import FetchData from 'components/FetchData.vue';
 import VnLv from 'components/ui/VnLv.vue';
 import CardList from 'components/ui/CardList.vue';
 import FetchedTags from 'components/ui/FetchedTags.vue';
-
+import { useStateStore } from 'stores/useStateStore';
 import { dashIfEmpty } from 'src/filters';
+import { onMounted } from 'vue';
 
 const router = useRouter();
 const route = useRoute();
 const { t } = useI18n();
 const volumeSummary = ref(null);
 const volumeRef = ref();
+const stateStore = useStateStore();
 const volumeFilter = ref({
     include: [
         {
@@ -46,6 +48,7 @@ watch(
         volumeRef.value.fetch();
     }
 );
+onMounted(() => (stateStore.rightDrawer = false));
 </script>
 
 <template>
@@ -63,9 +66,6 @@ watch(
                 {{ t('globals.noResults') }}
             </div>
             <QCard v-else class="order-volume-summary q-pa-lg">
-                <p class="header text-right block">
-                    {{ t('summary') }}
-                </p>
                 <VnLv :label="t('total')" :value="`${volumeSummary?.totalVolume} m³`" />
                 <VnLv
                     :label="t('boxes')"

From ee71ae9d24ccb018b7017f09016bff4985dc16c7 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 9 Aug 2024 13:07:37 +0200
Subject: [PATCH 17/60] feat(orderList): use orderFilter and fixed this

---
 src/pages/Order/Card/OrderFilter.vue | 232 ++++++++++-----------------
 src/pages/Order/OrderList.vue        |  14 +-
 2 files changed, 94 insertions(+), 152 deletions(-)

diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue
index 347affb04..be47eed54 100644
--- a/src/pages/Order/Card/OrderFilter.vue
+++ b/src/pages/Order/Card/OrderFilter.vue
@@ -21,15 +21,13 @@ const salesPersonFilter = {
     fields: ['id', 'nickname'],
 };
 const salesPersonList = ref(null);
-const sourceFilter = { fields: ['value'] };
-const sourceList = ref(null);
+const sourceList = ref([]);
 </script>
 
 <template>
     <FetchData
         url="AgencyModes/isActive"
         :filter="agencyFilter"
-        limit="30"
         sort-by="name ASC"
         auto-load
         @on-fetch="(data) => (agencyList = data)"
@@ -37,7 +35,6 @@ const sourceList = ref(null);
     <FetchData
         url="Workers/search"
         :filter="salesPersonFilter"
-        limit="30"
         sort-by="nickname ASC"
         @on-fetch="(data) => (salesPersonList = data)"
         :params="{ departmentCodes: ['VT'] }"
@@ -45,8 +42,7 @@ const sourceList = ref(null);
     />
     <FetchData
         url="Orders/getSourceValues"
-        :filter="sourceFilter"
-        limit="30"
+        :filter="{ fields: ['value'] }"
         sort-by="value ASC"
         @on-fetch="(data) => (sourceList = data)"
         auto-load
@@ -59,148 +55,88 @@ const sourceList = ref(null);
             </div>
         </template>
         <template #body="{ params }">
-            <QItem>
-                <QItemSection>
-                    <VnInput
-                        is-outlined
-                        :label="t('customerId')"
-                        v-model="params.clientFk"
-                        lazy-rules
-                    >
-                        <template #prepend>
-                            <QIcon name="badge" size="sm"></QIcon>
-                        </template>
-                    </VnInput>
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection v-if="agencyList">
-                    <VnSelect
-                        :label="t('agency')"
-                        v-model="params.agencyModeFk"
-                        :options="agencyList"
-                        option-value="id"
-                        option-label="name"
-                        dense
-                        outlined
-                        rounded
-                        emit-value
-                        map-options
-                        use-input
-                        :input-debounce="0"
-                    />
-                </QItemSection>
-                <QItemSection v-else>
-                    <QSkeleton type="QInput" class="full-width" />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection v-if="salesPersonList">
-                    <VnSelect
-                        :label="t('salesPerson')"
-                        v-model="params.workerFk"
-                        :options="salesPersonList"
-                        option-value="id"
-                        option-label="name"
-                        dense
-                        outlined
-                        rounded
-                        emit-value
-                        map-options
-                        use-input
-                        :input-debounce="0"
-                    >
-                        <template #option="{ itemProps, opt }">
-                            <QItem v-bind="itemProps">
-                                <QItemSection>
-                                    <QItemLabel>{{ opt.name }}</QItemLabel>
-                                    <QItemLabel caption>
-                                        {{ opt.nickname }},{{ opt.code }}
-                                    </QItemLabel>
-                                </QItemSection>
-                            </QItem>
-                        </template>
-                    </VnSelect>
-                </QItemSection>
-                <QItemSection v-else>
-                    <QSkeleton type="QInput" class="full-width" />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <VnInputDate
-                        v-model="params.from"
-                        :label="t('fromLanded')"
-                        dense
-                        outlined
-                        rounded
-                    />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <VnInputDate
-                        v-model="params.to"
-                        :label="t('toLanded')"
-                        dense
-                        outlined
-                        rounded
-                    />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <VnInput
-                        :label="t('orderId')"
-                        v-model="params.orderFk"
-                        lazy-rules
-                        is-outlined
-                    />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection v-if="sourceList">
-                    <VnSelect
-                        :label="t('application')"
-                        v-model="params.sourceApp"
-                        :options="sourceList"
-                        option-label="value"
-                        emit-value
-                        map-options
-                        use-input
-                        dense
-                        outlined
-                        rounded
-                        :input-debounce="0"
-                    />
-                </QItemSection>
-                <QItemSection v-else>
-                    <QSkeleton type="QInput" class="full-width" />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <QCheckbox
-                        v-model="params.myTeam"
-                        :label="t('myTeam')"
-                        toggle-indeterminate
-                    />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <QCheckbox
-                        v-model="params.isConfirmed"
-                        :label="t('isConfirmed')"
-                        toggle-indeterminate
-                    />
-                </QItemSection>
-            </QItem>
-            <QItem>
-                <QItemSection>
-                    <QCheckbox v-model="params.showEmpty" :label="t('showEmpty')" />
-                </QItemSection>
-            </QItem>
+            <div class="q-px-md q-gutter-y-sm">
+                <VnInput
+                    :label="t('customerId')"
+                    v-model="params.clientFk"
+                    lazy-rules
+                    dense
+                    outlined
+                    rounded
+                />
+                <VnSelect
+                    :label="t('agency')"
+                    v-model="params.agencyModeFk"
+                    :options="agencyList"
+                    :input-debounce="0"
+                    dense
+                    outlined
+                    rounded
+                />
+                <VnSelect
+                    :label="t('salesPerson')"
+                    v-model="params.workerFk"
+                    url="Workers/search"
+                    :filter="{ departmentCodes: ['VT'] }"
+                    sort-by="nickname ASC"
+                    option-label="nickname"
+                    dense
+                    outlined
+                    rounded
+                >
+                    <template #option="{ itemProps, opt }">
+                        <QItem v-bind="itemProps">
+                            <QItemSection>
+                                <QItemLabel>{{ opt.name }}</QItemLabel>
+                                <QItemLabel caption>
+                                    {{ opt.nickname }},{{ opt.code }}
+                                </QItemLabel>
+                            </QItemSection>
+                        </QItem>
+                    </template>
+                </VnSelect>
+                <VnInputDate
+                    v-model="params.from"
+                    :label="t('fromLanded')"
+                    dense
+                    outlined
+                    rounded
+                />
+                <VnInputDate
+                    v-model="params.to"
+                    :label="t('toLanded')"
+                    dense
+                    outlined
+                    rounded
+                />
+                <VnInput
+                    :label="t('orderId')"
+                    v-model="params.orderFk"
+                    lazy-rules
+                    is-outlined
+                />
+                <VnSelect
+                    :label="t('application')"
+                    v-model="params.sourceApp"
+                    :options="sourceList"
+                    option-label="value"
+                    dense
+                    outlined
+                    rounded
+                    :input-debounce="0"
+                />
+                <QCheckbox
+                    v-model="params.myTeam"
+                    :label="t('myTeam')"
+                    toggle-indeterminate
+                />
+                <QCheckbox
+                    v-model="params.isConfirmed"
+                    :label="t('isConfirmed')"
+                    toggle-indeterminate
+                />
+                <QCheckbox v-model="params.showEmpty" :label="t('showEmpty')" />
+            </div>
         </template>
     </VnFilterPanel>
 </template>
diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue
index e0261fc19..1622f5ffe 100644
--- a/src/pages/Order/OrderList.vue
+++ b/src/pages/Order/OrderList.vue
@@ -9,6 +9,8 @@ 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';
 
 const { t } = useI18n();
 const { viewSummary } = useSummaryDialog();
@@ -119,9 +121,6 @@ const columns = computed(() => [
         align: 'left',
         name: 'total',
         label: t('module.total'),
-        columnFilter: {
-            inWhere: true,
-        },
         format: ({ total }) => toCurrency(total),
         cardVisible: true,
     },
@@ -147,10 +146,16 @@ async function fetchClientAddress(id, data) {
 </script>
 <template>
     <OrderSearchbar />
+    <RightMenu>
+        <template #right-panel>
+            <OrderFilter data-key="OrderList" />
+        </template>
+    </RightMenu>
     <VnTable
         ref="tableRef"
         data-key="OrderList"
         url="Orders/filter"
+        :order="['landed DESC', 'clientFk ASC', 'id DESC']"
         :create="{
             urlCreate: 'Orders/new',
             title: 'Create Order',
@@ -162,9 +167,10 @@ async function fetchClientAddress(id, data) {
                 addressId: null,
             },
         }"
+        :user-params="{ showEmpty: false }"
+        :right-search="false"
         :columns="columns"
         redirect="order"
-        auto-load
     >
         <template #more-create-dialog="{ data }">
             <VnSelect

From b21ba3dc2c0f06924eb30c266aff0c0a0128dec1 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Fri, 9 Aug 2024 13:55:29 +0200
Subject: [PATCH 18/60] refs #7355 account cards roles cards

---
 src/pages/Account/AccountList.vue        |  2 +-
 src/pages/Account/Card/AccountCard.vue   | 15 ---------------
 src/pages/Account/Role/Card/RoleCard.vue | 13 -------------
 3 files changed, 1 insertion(+), 29 deletions(-)

diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue
index d789fdf84..cdd88551b 100644
--- a/src/pages/Account/AccountList.vue
+++ b/src/pages/Account/AccountList.vue
@@ -91,9 +91,9 @@ const exprBuilder = (param, value) => {
 
 <template>
     <VnSearchbar
-        :label="t('account.search')"
         data-key="AccountUsers"
         :expr-builder="exprBuilder"
+        :label="t('account.search')"
         :info="t('account.searchInfo')"
     />
 
diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue
index 555ce0866..813e7fffd 100644
--- a/src/pages/Account/Card/AccountCard.vue
+++ b/src/pages/Account/Card/AccountCard.vue
@@ -1,24 +1,9 @@
 <script setup>
-import { computed } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { useRoute } from 'vue-router';
-
 import VnCard from 'components/common/VnCard.vue';
 import AccountDescriptor from './AccountDescriptor.vue';
 
 const { t } = useI18n();
-const route = useRoute();
-
-const routeName = computed(() => route.name);
-const customRouteRedirectName = computed(() => routeName.value);
-const searchBarDataKeys = {
-    AccountSummary: 'AccountSummary',
-    AccountInheritedRoles: 'AccountInheritedRoles',
-    AccountMailForwarding: 'AccountMailForwarding',
-    AccountMailAlias: 'AccountMailAlias',
-    AccountPrivileges: 'AccountPrivileges',
-    AccountLog: 'AccountLog',
-};
 </script>
 
 <template>
diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue
index e5c8c3ebb..97c842b8f 100644
--- a/src/pages/Account/Role/Card/RoleCard.vue
+++ b/src/pages/Account/Role/Card/RoleCard.vue
@@ -1,22 +1,9 @@
 <script setup>
-import { computed } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { useRoute } from 'vue-router';
 import VnCard from 'components/common/VnCard.vue';
 import RoleDescriptor from './RoleDescriptor.vue';
 
 const { t } = useI18n();
-const route = useRoute();
-
-const routeName = computed(() => route.name);
-const customRouteRedirectName = computed(() => routeName.value);
-const searchBarDataKeys = {
-    RoleSummary: 'RoleSummary',
-    RoleBasicData: 'RoleBasicData',
-    SubRoles: 'SubRoles',
-    InheritedRoles: 'InheritedRoles',
-    RoleLog: 'RoleLog',
-};
 </script>
 <template>
     <VnCard

From dc7c686508acbaedb756fac6eddf7d2a4f355b63 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 9 Aug 2024 14:19:08 +0200
Subject: [PATCH 19/60] feat(orderList): correct create order

---
 src/pages/Order/OrderList.vue | 85 ++++++++++++++++++++++-------------
 1 file changed, 54 insertions(+), 31 deletions(-)

diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue
index 1622f5ffe..9870be9b3 100644
--- a/src/pages/Order/OrderList.vue
+++ b/src/pages/Order/OrderList.vue
@@ -15,9 +15,9 @@ import OrderFilter from './Card/OrderFilter.vue';
 const { t } = useI18n();
 const { viewSummary } = useSummaryDialog();
 const tableRef = ref();
-const clientList = ref([]);
 const agencyList = ref([]);
-const selectedAddress = ref();
+const addressesList = ref([]);
+const clientId = ref();
 
 const columns = computed(() => [
     {
@@ -102,20 +102,19 @@ const columns = computed(() => [
         align: 'left',
         name: 'agencyModeFk',
         label: t('module.agency'),
-        component: 'select',
-        cardVisible: true,
-        attrs: {
-            url: 'agencyModes',
-            fields: ['id', 'name'],
-            find: {
-                value: 'agencyModeFk',
-                label: 'agencyName',
+        format: (row) => row?.agencyName,
+        columnFilter: {
+            component: 'select',
+            attrs: {
+                url: 'agencyModes',
+                fields: ['id', 'name'],
+                find: {
+                    value: 'agencyModeFk',
+                    label: 'agencyName',
+                },
             },
         },
-        columnField: {
-            component: null,
-        },
-        format: (row) => row?.agencyName,
+        cardVisible: true,
     },
     {
         align: 'left',
@@ -138,10 +137,22 @@ const columns = computed(() => [
     },
 ]);
 
-async function fetchClientAddress(id, data) {
-    const clientData = await axios.get(`Clients/${id}`);
-    selectedAddress.value = clientData.data.defaultAddressFk;
-    data.addressId = selectedAddress.value;
+async function fetchClientAddress(id, formData) {
+    const { data } = await axios.get(`Clients/${id}`, {
+        params: { filter: { include: { relation: 'addresses' } } },
+    });
+    addressesList.value = data.addresses;
+    formData.addressId = data.defaultAddressFk;
+    fetchAgencies(formData);
+}
+
+async function fetchAgencies({ landed, addressId }) {
+    if (!landed || !addressId) return (agencyList.value = []);
+
+    const { data } = await axios.get('Agencies/landsThatDay', {
+        params: { addressFk: addressId, landed },
+    });
+    agencyList.value = data;
 }
 </script>
 <template>
@@ -175,29 +186,41 @@ async function fetchClientAddress(id, data) {
         <template #more-create-dialog="{ data }">
             <VnSelect
                 url="Clients"
-                v-model="data.id"
+                :include="{ relation: 'addresses' }"
+                v-model="clientId"
                 :label="t('module.customer')"
-                :options="clientList"
-                option-value="id"
-                option-label="name"
                 @update:model-value="(id) => fetchClientAddress(id, data)"
             />
             <VnSelect
-                url="Clients"
-                v-model="selectedAddress"
+                v-model="data.addressId"
+                :options="addressesList"
                 :label="t('module.address')"
-                :options="selectedAddress"
-                option-value="defaultAddressFk"
-                option-label="street"
+                option-value="id"
+                option-label="nickname"
+                @update:model-value="() => fetchAgencies(data)"
+            >
+                <template #option="scope">
+                    <QItem v-bind="scope.itemProps">
+                        <QItemSection>
+                            <QItemLabel>
+                                {{ scope.opt?.nickname }}: {{ scope.opt?.street }},
+                                {{ scope.opt?.city }}</QItemLabel
+                            >
+                        </QItemSection>
+                    </QItem>
+                </template>
+            </VnSelect>
+            <VnInputDate
+                v-model="data.landed"
+                :label="t('module.landed')"
+                @update:model-value="() => fetchAgencies(data)"
             />
-            <VnInputDate v-model="data.landed" :label="t('module.landed')" />
             <VnSelect
-                url="Agencies"
                 v-model="data.agencyModeId"
                 :label="t('module.agency')"
                 :options="agencyList"
-                option-value="id"
-                option-label="name"
+                option-value="agencyModeFk"
+                option-label="agencyMode"
             />
         </template>
     </VnTable>

From 37244cdbc7a78f9b98fb9875747678b4a52fe790 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 9 Aug 2024 14:55:49 +0200
Subject: [PATCH 20/60] feat(orderBasicData): add notes

---
 src/i18n/locale/en.yml                           |  1 +
 src/i18n/locale/es.yml                           |  1 +
 .../Card/{OrderForm.vue => OrderBasicData.vue}   | 16 +++++++++++-----
 src/router/modules/order.js                      |  2 +-
 4 files changed, 14 insertions(+), 6 deletions(-)
 rename src/pages/Order/Card/{OrderForm.vue => OrderBasicData.vue} (95%)

diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 12680d0cb..e639b9fda 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -93,6 +93,7 @@ globals:
     since: Since
     from: From
     to: To
+    notes: Notes
     pageTitles:
         logIn: Login
         summary: Summary
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 747a10d51..2d7358d5e 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -93,6 +93,7 @@ globals:
     since: Desde
     from: Desde
     to: Hasta
+    notes: Notas
     pageTitles:
         logIn: Inicio de sesión
         summary: Resumen
diff --git a/src/pages/Order/Card/OrderForm.vue b/src/pages/Order/Card/OrderBasicData.vue
similarity index 95%
rename from src/pages/Order/Card/OrderForm.vue
rename to src/pages/Order/Card/OrderBasicData.vue
index 74d0a8b75..dfdb5ae48 100644
--- a/src/pages/Order/Card/OrderForm.vue
+++ b/src/pages/Order/Card/OrderBasicData.vue
@@ -7,6 +7,7 @@ import { useState } from 'composables/useState';
 import FormModel from 'components/FormModel.vue';
 import VnRow from 'components/ui/VnRow.vue';
 import VnSelect from 'components/common/VnSelect.vue';
+import VnInput from 'components/common/VnInput.vue';
 import VnInputDate from 'components/common/VnInputDate.vue';
 import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
 
@@ -117,7 +118,6 @@ const onClientChange = async (clientId) => {
             :url="`Orders/${route.params.id}`"
             :url-update="`Orders/${route.params.id}/updateBasicData`"
             :model="ORDER_MODEL"
-            :mapper="orderMapper"
             :filter="orderFilter"
             @on-fetch="fetchOrderDetails"
             auto-load
@@ -178,8 +178,6 @@ const onClientChange = async (clientId) => {
                             () => fetchAgencyList(data.landed, data.addressFk)
                         "
                     />
-                </VnRow>
-                <VnRow>
                     <VnSelect
                         :label="t('order.form.agencyModeFk')"
                         v-model="data.agencyModeFk"
@@ -200,8 +198,16 @@ const onClientChange = async (clientId) => {
                                 ? data.agencyModeFk
                                 : null
                         "
-                    >
-                    </VnSelect>
+                    />
+                </VnRow>
+                <VnRow>
+                    <VnInput
+                        :label="t('globals.notes')"
+                        type="textarea"
+                        v-model="data.note"
+                        fill-input
+                        autogrow
+                    />
                 </VnRow>
             </template>
         </FormModel>
diff --git a/src/router/modules/order.js b/src/router/modules/order.js
index 9cac802c2..9ccdb820b 100644
--- a/src/router/modules/order.js
+++ b/src/router/modules/order.js
@@ -63,7 +63,7 @@ export default {
                         title: 'basicData',
                         icon: 'vn:settings',
                     },
-                    component: () => import('src/pages/Order/Card/OrderForm.vue'),
+                    component: () => import('src/pages/Order/Card/OrderBasicData.vue'),
                 },
                 {
                     name: 'OrderCatalog',

From e11e51f432d2d80214876f6ad488880ca7d928d8 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Mon, 12 Aug 2024 09:17:51 +0200
Subject: [PATCH 21/60] fix: order description to vnTable

---
 src/pages/Order/Card/OrderVolume.vue | 166 +++++++++++----------------
 1 file changed, 67 insertions(+), 99 deletions(-)

diff --git a/src/pages/Order/Card/OrderVolume.vue b/src/pages/Order/Card/OrderVolume.vue
index 8622a1fa5..b16022c2f 100644
--- a/src/pages/Order/Card/OrderVolume.vue
+++ b/src/pages/Order/Card/OrderVolume.vue
@@ -2,24 +2,20 @@
 import axios from 'axios';
 import { useRoute } from 'vue-router';
 import { useI18n } from 'vue-i18n';
-import { ref, watch } from 'vue';
-import { useRouter } from 'vue-router';
-
-import VnPaginate from 'components/ui/VnPaginate.vue';
-import FetchData from 'components/FetchData.vue';
-import VnLv from 'components/ui/VnLv.vue';
-import CardList from 'components/ui/CardList.vue';
-import FetchedTags from 'components/ui/FetchedTags.vue';
-import { useStateStore } from 'stores/useStateStore';
+import { ref } from 'vue';
 import { dashIfEmpty } from 'src/filters';
-import { onMounted } from 'vue';
 
-const router = useRouter();
+import FetchData from 'components/FetchData.vue';
+import FetchedTags from 'components/ui/FetchedTags.vue';
+import ItemDescriptorProxy from 'pages/Item/Card/ItemDescriptorProxy.vue';
+import VnLv from 'components/ui/VnLv.vue';
+import VnTable from 'components/VnTable/VnTable.vue';
+
 const route = useRoute();
 const { t } = useI18n();
 const volumeSummary = ref(null);
 const volumeRef = ref();
-const stateStore = useStateStore();
+const volumes = ref([]);
 const volumeFilter = ref({
     include: [
         {
@@ -29,26 +25,39 @@ const volumeFilter = ref({
     where: { orderFk: route.params.id },
 });
 
+const columns = [
+    {
+        name: 'itemFk',
+        label: t('item'),
+        align: 'left',
+    },
+    {
+        name: 'description',
+        label: t('globals.description'),
+        align: 'left',
+    },
+    {
+        name: 'quantity',
+        label: t('quantity'),
+        align: 'left',
+    },
+    {
+        name: 'volume',
+        label: t('volume'),
+        align: 'left',
+    },
+];
+
 const loadVolumes = async (rows) => {
+    if (!rows) return;
     const { data } = await axios.get(`Orders/${route.params.id}/getVolumes`);
-    (rows || []).forEach((order) => {
+    rows.forEach((order) => {
         (data.volumes || []).forEach((volume) => {
-            if (order.itemFk === volume.itemFk) {
-                order.volume = volume.volume;
-            }
+            if (order.itemFk === volume.itemFk) order.volume = volume.volume;
         });
     });
+    volumes.value = rows;
 };
-
-watch(
-    () => router.currentRoute.value.params.id,
-    () => {
-        volumeFilter.value.where.orderFk = router.currentRoute.value.params.id;
-
-        volumeRef.value.fetch();
-    }
-);
-onMounted(() => (stateStore.rightDrawer = false));
 </script>
 
 <template>
@@ -57,61 +66,38 @@ onMounted(() => (stateStore.rightDrawer = false));
         @on-fetch="(data) => (volumeSummary = data)"
         auto-load
     />
-    <QPage class="column items-center q-pa-md">
-        <div class="vn-card-list">
-            <div
-                v-if="!volumeSummary?.totalVolume && !volumeSummary?.totalBoxes"
-                class="no-result"
-            >
-                {{ t('globals.noResults') }}
-            </div>
-            <QCard v-else class="order-volume-summary q-pa-lg">
-                <VnLv :label="t('total')" :value="`${volumeSummary?.totalVolume} m³`" />
-                <VnLv
-                    :label="t('boxes')"
-                    :value="`${dashIfEmpty(volumeSummary?.totalBoxes)} U`"
-                />
-            </QCard>
-            <VnPaginate
-                data-key="OrderCatalogVolume"
-                ref="volumeRef"
-                url="OrderRows"
-                :limit="20"
-                auto-load
-                :user-filter="volumeFilter"
-                order="itemFk"
-                @on-fetch="(data) => loadVolumes(data)"
-            >
-                <template #body="{ rows }">
-                    <div class="catalog-list q-mt-xl">
-                        <CardList
-                            v-for="row in rows"
-                            :key="row.id"
-                            :id="row.id"
-                            :title="row?.item?.name"
-                            class="cursor-inherit"
-                        >
-                            <template #list-items>
-                                <div class="q-mb-sm">
-                                    <FetchedTags :item="row.item" :max-length="5" />
-                                </div>
-                                <VnLv :label="t('item')" :value="row.item.id" />
-                                <VnLv :label="t('subName')">
-                                    <template #value>
-                                        <span class="text-uppercase">
-                                            {{ row.item.subName }}
-                                        </span>
-                                    </template>
-                                </VnLv>
-                                <VnLv :label="t('quantity')" :value="row.quantity" />
-                                <VnLv :label="t('volume')" :value="row.volume" />
-                            </template>
-                        </CardList>
-                    </div>
-                </template>
-            </VnPaginate>
-        </div>
-    </QPage>
+    <QCard v-if="volumeSummary" class="order-volume-summary q-pa-lg">
+        <VnLv :label="t('total')" :value="`${volumeSummary?.totalVolume} m³`" />
+        <VnLv
+            :label="t('boxes')"
+            :value="`${dashIfEmpty(volumeSummary?.totalBoxes)} U`"
+        />
+    </QCard>
+    <VnTable
+        ref="volumeRef"
+        data-key="OrderCatalogVolume"
+        url="OrderRows"
+        :columns="columns"
+        auto-load
+        :user-filter="volumeFilter"
+        order="itemFk"
+        @on-fetch="(data) => loadVolumes(data)"
+        :right-search="false"
+        :column-search="false"
+    >
+        <template #column-itemFk="{ row }">
+            <span class="link">
+                {{ row.itemFk }}
+                <ItemDescriptorProxy :id="row.itemFk" />
+            </span>
+        </template>
+        <template #column-description="{ row }">
+            <FetchedTags :item="row.item" :max-length="5" />
+        </template>
+        <template #column-volume="{ rowIndex }">
+            {{ volumes?.[rowIndex]?.volume }}
+        </template>
+    </VnTable>
 </template>
 
 <style lang="scss">
@@ -136,29 +122,12 @@ onMounted(() => (stateStore.rightDrawer = false));
     }
 }
 </style>
-<style lang="scss" scoped>
-.header {
-    color: $primary;
-    font-weight: bold;
-    margin-bottom: 25px;
-    font-size: 20px;
-    display: inline-block;
-}
-
-.no-result {
-    font-size: 24px;
-    font-weight: bold;
-    color: var(--vn-label-color);
-    text-align: center;
-}
-</style>
 <i18n>
 en:
     summary: Summary
     total: Total
     boxes: Boxes
     item: Item
-    subName: Subname
     quantity: Quantity
     volume: m³ per quantity
 es:
@@ -166,7 +135,6 @@ es:
     total: Total
     boxes: Cajas
     item: Artículo
-    subName: Subnombre
     quantity: Cantidad
     volume: m³ por cantidad
 </i18n>

From 190579e7fd8661f4883576753d38b1c91daa5af9 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Mon, 12 Aug 2024 10:22:52 +0200
Subject: [PATCH 22/60] fix: orderCatalogFilter order

---
 src/pages/Order/Card/OrderCatalogFilter.vue | 122 ++++++++------------
 1 file changed, 45 insertions(+), 77 deletions(-)

diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index 887cb6740..e9987d363 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -32,14 +32,22 @@ const categoryList = ref(null);
 const selectedCategoryFk = ref(getParamWhere(route, 'categoryFk'));
 const typeList = ref([]);
 const selectedTypeFk = ref(null);
-const validationsStore = useValidator();
-const selectedOrder = ref(null);
-const selectedOrderField = ref(null);
-const moreFields = ref([]);
-const moreFieldsOrder = ref([]);
 const selectedTag = ref(null);
 const tagValues = ref([{}]);
 const tagOptions = ref([]);
+const vnFilterPanelRef = ref();
+const orderByList = ref([
+    { id: 'relevancy DESC, name', name: t('params.relevancy'), priority: 999 },
+    { id: 'showOrder, price', name: t('params.colorAndPrice'), priority: 999 },
+    { id: 'name', name: t('params.name'), priority: 999 },
+    { id: 'price', name: t('params.price'), priority: 999 },
+]);
+const orderWayList = ref([
+    { id: 'ASC', name: t('params.ASC') },
+    { id: 'DESC', name: t('params.DESC') },
+]);
+const orderBySelected = ref('relevancy DESC, name');
+const orderWaySelected = ref('ASC');
 
 const createValue = (val, done) => {
     if (val.length > 2) {
@@ -140,35 +148,6 @@ const removeTagChip = (selection, params, search) => {
     search();
 };
 
-const onOrderChange = (value, params) => {
-    const tagObj = JSON.parse(params.orderBy);
-    tagObj.way = value.name;
-    params.orderBy = JSON.stringify(tagObj);
-};
-
-const onOrderFieldChange = (value, params) => {
-    const tagObj = JSON.parse(params.orderBy);
-    switch (value) {
-        case 'Relevancy':
-            tagObj.name = value + ' DESC, name';
-            params.orderBy = JSON.stringify(tagObj);
-            break;
-        case 'ColorAndPrice':
-            tagObj.name = 'showOrder, price';
-            params.orderBy = JSON.stringify(tagObj);
-            break;
-        case 'Name':
-            tagObj.name = 'name';
-            params.orderBy = JSON.stringify(tagObj);
-            break;
-        case 'Price':
-            tagObj.name = 'price';
-            params.orderBy = JSON.stringify(tagObj);
-            break;
-    }
-};
-const _moreFields = ['ASC', 'DESC'];
-const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
 const setCategoryList = (data) => {
     categoryList.value = (data || [])
         .filter((category) => category.display)
@@ -176,8 +155,6 @@ const setCategoryList = (data) => {
             ...category,
             icon: `vn:${(category.icon || '').split('-')[1]}`,
         }));
-    moreFields.value = useLang(_moreFields);
-    moreFieldsOrder.value = useLang(_moreFieldsTypes);
 
     selectedCategoryFk.value && loadTypes();
 };
@@ -188,24 +165,19 @@ const getCategoryClass = (category, params) => {
     }
 };
 
-const useLang = (values) => {
-    const { models } = validationsStore;
-    const properties = models.Item?.properties || {};
-    return values.map((name) => {
-        let prop = properties[name];
-        const label = t(`params.${name}`);
-        return {
-            name,
-            label,
-            type: prop ? prop.type : null,
-        };
-    });
-};
+function addOrder(value, field, params) {
+    let { orderBy } = params;
+    orderBy = JSON.parse(orderBy);
+    orderBy[field] = value;
+    params.orderBy = JSON.stringify(orderBy);
+    vnFilterPanelRef.value.search();
+}
 </script>
 
 <template>
     <FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
     <VnFilterPanel
+        ref="vnFilterPanelRef"
         :data-key="props.dataKey"
         :hidden-tags="['orderFk', 'orderBy']"
         :un-removable-params="['orderFk', 'orderBy']"
@@ -298,33 +270,29 @@ const useLang = (values) => {
                 </QItemSection>
             </QItem>
             <QSeparator />
-            <QItem class="q-my-md">
-                <QItemSection>
-                    <VnSelect
-                        :label="t('Order')"
-                        v-model="selectedOrder"
-                        :options="moreFields"
-                        option-label="label"
-                        option-value="way"
-                        dense
-                        outlined
-                        rounded
-                        @update:model-value="(value) => onOrderChange(value, params)"
-                    />
-                </QItemSection>
-            </QItem>
             <QItem class="q-mb-md">
                 <QItemSection>
                     <VnSelect
                         :label="t('Order by')"
-                        v-model="selectedOrderField"
-                        :options="moreFieldsOrder"
-                        option-label="label"
-                        option-value="name"
+                        v-model="orderBySelected"
+                        :options="orderByList"
                         dense
                         outlined
                         rounded
-                        @update:model-value="(value) => onOrderFieldChange(value, params)"
+                        @update:model-value="(value) => addOrder(value, 'field', params)"
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem class="q-my-md">
+                <QItemSection>
+                    <VnSelect
+                        :label="t('Order')"
+                        v-model="orderWaySelected"
+                        :options="orderWayList"
+                        dense
+                        outlined
+                        rounded
+                        @update:model-value="(value) => addOrder(value, 'way', params)"
                     />
                 </QItemSection>
             </QItem>
@@ -490,10 +458,10 @@ en:
         order: Order
         ASC: Ascendant
         DESC: Descendant
-        Relevancy: Relevancy
-        ColorAndPrice: Color and price
-        Name: Name
-        Price: Price
+        relevancy: Relevancy
+        colorAndPrice: Color and price
+        name: Name
+        price: Price
 es:
     params:
         type: Tipo
@@ -503,10 +471,10 @@ es:
         order: Orden
         ASC: Ascendiente
         DESC: Descendiente
-        Relevancy: Relevancia
-        ColorAndPrice: Color y precio
-        Name: Nombre
-        Price: Precio
+        relevancy: Relevancia
+        colorAndPrice: Color y precio
+        name: Nombre
+        price: Precio
     Order: Orden
     Order by: Ordenar por
     Plant: Planta

From 3222a7a42cc40effd9291964e0ebf6ddc08f2fdd Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Mon, 12 Aug 2024 11:51:21 +0200
Subject: [PATCH 23/60] fix(orderLines): reload when delete and redirect when
 confirm

---
 src/pages/Order/Card/OrderDescriptor.vue |   1 -
 src/pages/Order/Card/OrderLines.vue      | 136 ++++++++++++-----------
 2 files changed, 69 insertions(+), 68 deletions(-)

diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue
index 8d2b5309e..a035971b0 100644
--- a/src/pages/Order/Card/OrderDescriptor.vue
+++ b/src/pages/Order/Card/OrderDescriptor.vue
@@ -82,7 +82,6 @@ function ticketFilter(order) {
         ref="getTotalRef"
         :url="`Orders/${entityId}/getTotal`"
         @on-fetch="(response) => (total = response)"
-        auto-load
     />
     <CardDescriptor
         ref="descriptor"
diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue
index d4a5c4b1d..dab4a959c 100644
--- a/src/pages/Order/Card/OrderLines.vue
+++ b/src/pages/Order/Card/OrderLines.vue
@@ -1,25 +1,26 @@
 <script setup>
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { useI18n } from 'vue-i18n';
 import { ref, computed, watch } from 'vue';
 import { useQuasar } from 'quasar';
-import { useRouter } from 'vue-router';
+import axios from 'axios';
+import { useStateStore } from 'stores/useStateStore';
+import { useArrayData } from 'composables/useArrayData';
+import { toCurrency, toDate } from 'src/filters';
 
 import VnConfirm from 'components/ui/VnConfirm.vue';
-import { toCurrency, toDate } from 'src/filters';
-import axios from 'axios';
 import VnTable from 'src/components/VnTable/VnTable.vue';
 import FetchData from 'src/components/FetchData.vue';
 import VnImg from 'src/components/ui/VnImg.vue';
 import VnLv from 'src/components/ui/VnLv.vue';
 import FetchedTags from 'src/components/ui/FetchedTags.vue';
-import { useStateStore } from 'stores/useStateStore';
 
 const router = useRouter();
 const stateStore = useStateStore();
 const route = useRoute();
 const { t } = useI18n();
 const quasar = useQuasar();
+const descriptorData = useArrayData('orderData');
 const componentKey = ref(0);
 const tableLinesRef = ref();
 const order = ref();
@@ -27,6 +28,8 @@ const orderSummary = ref({
     total: null,
     vat: null,
 });
+const getTotalRef = ref();
+const getVATRef = ref();
 
 const lineFilter = ref({
     include: [
@@ -195,6 +198,9 @@ async function remove(item) {
         type: 'positive',
     });
     tableLinesRef.value.reload();
+    descriptorData.fetch({});
+    getTotalRef.value.fetch();
+    getVATRef.value.fetch();
 }
 
 async function confirmOrder() {
@@ -203,6 +209,12 @@ async function confirmOrder() {
         message: t('globals.confirm'),
         type: 'positive',
     });
+    router.push({
+        name: 'TicketList',
+        query: {
+            table: JSON.stringify({ clientFk: descriptorData.store.data.clientFk }),
+        },
+    });
 }
 
 watch(
@@ -223,90 +235,80 @@ watch(
         auto-load
     />
     <FetchData
+        ref="getTotalRef"
         :key="componentKey"
         :url="`Orders/${route.params.id}/getTotal`"
         @on-fetch="(data) => (orderSummary.total = data)"
         auto-load
     />
     <FetchData
+        ref="getVATRef"
         :key="componentKey"
         :url="`Orders/${route.params.id}/getVAT`"
         @on-fetch="(data) => (orderSummary.vat = data)"
         auto-load
     />
     <QDrawer side="right" :width="270" v-model="stateStore.rightDrawer">
-        <QCard class="order-lines-summary q-pa-lg">
+        <QCard
+            class="order-lines-summary q-pa-lg"
+            v-if="orderSummary.vat && orderSummary.total"
+        >
             <p class="header text-right block">
                 {{ t('summary') }}
             </p>
             <VnLv
-                v-if="orderSummary.vat && orderSummary.total"
                 :label="t('subtotal') + ': '"
                 :value="toCurrency(orderSummary.total - orderSummary.vat)"
             />
-            <VnLv
-                v-if="orderSummary.vat"
-                :label="t('VAT') + ': '"
-                :value="toCurrency(orderSummary?.vat)"
-            />
-            <VnLv
-                v-if="orderSummary.total"
-                :label="t('total') + ': '"
-                :value="toCurrency(orderSummary?.total)"
-            />
+            <VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" />
+            <VnLv :label="t('total') + ': '" :value="toCurrency(orderSummary?.total)" />
         </QCard>
     </QDrawer>
-    <QPage :key="componentKey" class="column items-center">
-        <div class="order-list full-width">
-            <div v-if="!orderSummary.total" class="no-result">
-                {{ t('globals.noResults') }}
-            </div>
-            <VnTable
-                ref="tableLinesRef"
-                data-key="OrderLines"
-                url="OrderRows"
-                :columns="columns"
-                :right-search="false"
-                :use-model="true"
-                auto-load
-                :user-filter="lineFilter"
-            >
-                <template #column-image="{ row }">
-                    <div class="image-wrapper">
-                        <VnImg :id="parseInt(row?.item?.image)" class="rounded" />
-                    </div>
-                </template>
 
-                <template #column-itemFk="{ row }">
-                    <div class="row column full-width justify-between items-start">
-                        {{ row?.item?.name }}
-                        <div v-if="row?.item?.subName" class="subName">
-                            {{ row?.item?.subName.toUpperCase() }}
-                        </div>
-                    </div>
-                    <FetchedTags :item="row?.item" :max-length="6" />
-                </template>
-                <template #column-amount="{ row }">
-                    {{ toCurrency(row.quantity * row.price) }}
-                </template>
-                <template #column-tableActions="{ row }">
-                    <QIcon
-                        v-if="row.order?.isConfirmed === 0"
-                        name="delete"
-                        icon="delete"
-                        @click="confirmRemove(row)"
-                        class="cursor-pointer"
-                    />
-                </template>
-            </VnTable>
-        </div>
-        <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2">
-            <QBtn fab icon="check" color="primary" @click="confirmOrder()" />
-            <QTooltip>
-                {{ t('confirm') }}
-            </QTooltip>
-        </QPageSticky>
-    </QPage>
+    <VnTable
+        ref="tableLinesRef"
+        data-key="OrderLines"
+        url="OrderRows"
+        :columns="columns"
+        :right-search="false"
+        :use-model="true"
+        auto-load
+        :user-filter="lineFilter"
+    >
+        <template #column-image="{ row }">
+            <div class="image-wrapper">
+                <VnImg :id="parseInt(row?.item?.image)" class="rounded" />
+            </div>
+        </template>
+
+        <template #column-itemFk="{ row }">
+            <div class="row column full-width justify-between items-start">
+                {{ row?.item?.name }}
+                <div v-if="row?.item?.subName" class="subName">
+                    {{ row?.item?.subName.toUpperCase() }}
+                </div>
+            </div>
+            <FetchedTags :item="row?.item" :max-length="6" />
+        </template>
+        <template #column-amount="{ row }">
+            {{ toCurrency(row.quantity * row.price) }}
+        </template>
+        <template #column-tableActions="{ row }">
+            <QIcon
+                v-if="row.order?.isConfirmed === 0"
+                name="delete"
+                icon="delete"
+                @click="confirmRemove(row)"
+                class="cursor-pointer"
+            />
+        </template>
+    </VnTable>
+    <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2">
+        <QBtn fab icon="check" color="primary" @click="confirmOrder()" />
+        <QTooltip>
+            {{ t('confirm') }}
+        </QTooltip>
+    </QPageSticky>
 </template>
 
 <style lang="scss" scoped>

From 94878b700b0ead02425718e75d0dd8d995daace6 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Mon, 12 Aug 2024 12:39:06 +0200
Subject: [PATCH 24/60] refs #7355 account cards loads

---
 src/pages/Account/AccountAcls.vue       | 1 -
 src/pages/Account/AccountAliasList.vue  | 1 -
 src/pages/Account/Role/AccountRoles.vue | 1 -
 3 files changed, 3 deletions(-)

diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue
index 080956d46..fdb0ecee1 100644
--- a/src/pages/Account/AccountAcls.vue
+++ b/src/pages/Account/AccountAcls.vue
@@ -145,7 +145,6 @@ const deleteAcl = async ({ id }) => {
         order="id DESC"
         :columns="columns"
         default-mode="table"
-        auto-load
         :right-search="true"
         :is-editable="true"
         :use-model="true"
diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue
index b86580017..b8d594d6d 100644
--- a/src/pages/Account/AccountAliasList.vue
+++ b/src/pages/Account/AccountAliasList.vue
@@ -70,7 +70,6 @@ const columns = computed(() => [
         order="id DESC"
         :columns="columns"
         default-mode="table"
-        auto-load
         redirect="account/alias"
         :is-editable="true"
         :use-model="true"
diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue
index 0604b71af..2f80606b4 100644
--- a/src/pages/Account/Role/AccountRoles.vue
+++ b/src/pages/Account/Role/AccountRoles.vue
@@ -97,7 +97,6 @@ const exprBuilder = (param, value) => {
         order="id ASC"
         :columns="columns"
         default-mode="table"
-        auto-load
         redirect="account/role"
     />
 </template>

From a46198e2d0170cd7ae3c41619d54f34b88967c12 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Tue, 13 Aug 2024 12:25:08 +0200
Subject: [PATCH 25/60] feat: add recover password and reset password

---
 src/components/ui/VnOutForm.vue     | 32 ++++++++++
 src/i18n/locale/en.yml              | 16 +++--
 src/i18n/locale/es.yml              | 16 +++--
 src/pages/Login/LoginMain.vue       | 15 ++++-
 src/pages/Login/RecoverPassword.vue | 59 +++++++++++++++++
 src/pages/Login/ResetPassword.vue   | 99 +++++++++++++++++++++++++++++
 src/pages/Login/TwoFactor.vue       | 51 ++++++---------
 src/router/index.js                 |  2 +-
 src/router/routes.js                | 12 ++++
 9 files changed, 256 insertions(+), 46 deletions(-)
 create mode 100644 src/components/ui/VnOutForm.vue
 create mode 100644 src/pages/Login/RecoverPassword.vue
 create mode 100644 src/pages/Login/ResetPassword.vue

diff --git a/src/components/ui/VnOutForm.vue b/src/components/ui/VnOutForm.vue
new file mode 100644
index 000000000..b44445682
--- /dev/null
+++ b/src/components/ui/VnOutForm.vue
@@ -0,0 +1,32 @@
+<script setup>
+const emit = defineEmits(['submit']);
+defineProps({
+    icon: { type: String, required: false, default: 'phonelink_lock' },
+    title: { type: String, required: true },
+});
+</script>
+<template>
+    <QForm @submit="emit('submit')" class="q-gutter-y-md q-pa-lg formCard">
+        <div class="column items-center">
+            <QIcon v-if="icon != false" :name="icon" size="xl" color="primary" />
+            <h5 class="text-center q-my-md">
+                {{ title }}
+            </h5>
+        </div>
+        <slot name="default"></slot>
+        <div class="q-mt-lg">
+            <slot name="buttons"></slot>
+        </div>
+    </QForm>
+</template>
+<style lang="scss" scoped>
+.formCard {
+    max-width: 350px;
+    min-width: 300px;
+}
+@media (max-width: $breakpoint-xs-max) {
+    .formCard {
+        min-width: 100%;
+    }
+}
+</style>
diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 12680d0cb..ee53da80c 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -251,6 +251,9 @@ globals:
         privileges: Privileges
         ldap: LDAP
         samba: Samba
+        twoFactor: Two factor
+        recoverPassword: Recover password
+        resetPassword: Reset password
     created: Created
     worker: Worker
     now: Now
@@ -288,14 +291,17 @@ twoFactor:
     explanation: >-
         Please, enter the verification code that we have sent to your email in the
         next 5 minutes
-    pageTitles:
-        twoFactor: Two-Factor
 verifyEmail:
     pageTitles:
         verifyEmail: Email verification
-dashboard:
-    pageTitles:
-
+recoverPassword:
+    userOrEmail: User or recovery email
+    explanation: >-
+        We will sent you an email to recover your password
+resetPassword:
+    repeatPassword: Repeat password
+    passwordNotMatch: Passwords don't match
+    passwordChanged: Password changed
 customer:
     list:
         phone: Phone
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 747a10d51..71e227fbd 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -253,6 +253,9 @@ globals:
         packages: Bultos
         ldap: LDAP
         samba: Samba
+        twoFactor: Doble factor
+        recoverPassword: Recuperar contraseña
+        resetPassword: Restablecer contraseña
     created: Fecha creación
     worker: Trabajador
     now: Ahora
@@ -289,14 +292,17 @@ twoFactor:
     validate: Validar
     insert: Introduce el código de verificación
     explanation: Por favor introduce el código de verificación que te hemos enviado a tu email en los próximos 5 minutos
-    pageTitles:
-        twoFactor: Doble factor
 verifyEmail:
     pageTitles:
         verifyEmail: Verificación de correo
-dashboard:
-    pageTitles:
-
+recoverPassword:
+    userOrEmail: Usuario o correo de recuperación
+    explanation: >-
+        Te enviaremos un correo para restablecer tu contraseña
+resetPassword:
+    repeatPassword: Repetir contraseña
+    passwordNotMatch: Las contraseñas no coinciden
+    passwordChanged: Contraseña cambiada
 customer:
     list:
         phone: Teléfono
diff --git a/src/pages/Login/LoginMain.vue b/src/pages/Login/LoginMain.vue
index 4eb21f573..c17201969 100644
--- a/src/pages/Login/LoginMain.vue
+++ b/src/pages/Login/LoginMain.vue
@@ -72,7 +72,8 @@ async function onSubmit() {
             :rules="[(val) => (val && val.length > 0) || t('login.fieldRequired')]"
             class="red"
         />
-        <div>
+        <QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
+        <div class="column flex-center q-mt-lg">
             <QBtn
                 :label="t('login.submit')"
                 type="submit"
@@ -81,11 +82,15 @@ async function onSubmit() {
                 rounded
                 unelevated
             />
+            <RouterLink
+                class="q-mt-md text-primary"
+                :to="`/recoverPassword?user=${username}`"
+            >
+                {{ t('I do not remember my password') }}
+            </RouterLink>
         </div>
-        <QToggle v-model="keepLogin" :label="t('login.keepLogin')" />
     </QForm>
 </template>
-
 <style lang="scss" scoped>
 .formCard {
     max-width: 350px;
@@ -101,3 +106,7 @@ async function onSubmit() {
     }
 }
 </style>
+<i18n>
+es:
+    I do not remember my password: No recuerdo mi contraseña
+</i18n>
diff --git a/src/pages/Login/RecoverPassword.vue b/src/pages/Login/RecoverPassword.vue
new file mode 100644
index 000000000..5e0fd367a
--- /dev/null
+++ b/src/pages/Login/RecoverPassword.vue
@@ -0,0 +1,59 @@
+<script setup>
+import { ref } from 'vue';
+import { useQuasar } from 'quasar';
+import { useI18n } from 'vue-i18n';
+import { useRouter, useRoute } from 'vue-router';
+import axios from 'axios';
+import VnInput from 'src/components/common/VnInput.vue';
+import VnOutForm from 'src/components/ui/VnOutForm.vue';
+
+const quasar = useQuasar();
+const router = useRouter();
+const route = useRoute();
+const { t } = useI18n();
+
+const user = ref(route.query.user);
+
+async function onSubmit() {
+    try {
+        await axios.post('VnUsers/recoverPassword', { user: user.value, app: 'lilium' });
+        router.push('Login');
+        quasar.notify({
+            message: t('globals.notificationSent'),
+            type: 'positive',
+        });
+    } catch (e) {
+        quasar.notify({
+            message: e.response?.data?.error.message,
+            type: 'negative',
+        });
+    }
+}
+</script>
+<template>
+    <VnOutForm @submit="onSubmit" :title="t('globals.pageTitles.recoverPassword')">
+        <template #default>
+            <VnInput
+                v-model="user"
+                :label="t('recoverPassword.userOrEmail')"
+                :hint="t('recoverPassword.explanation')"
+                autofocus
+                required
+            >
+                <template #prepend>
+                    <QIcon name="contact_mail" />
+                </template>
+            </VnInput>
+        </template>
+        <template #buttons>
+            <QBtn
+                :label="t('globals.pageTitles.recoverPassword')"
+                type="submit"
+                color="primary"
+                class="full-width q-mt-md"
+                rounded
+                unelevated
+            />
+        </template>
+    </VnOutForm>
+</template>
diff --git a/src/pages/Login/ResetPassword.vue b/src/pages/Login/ResetPassword.vue
new file mode 100644
index 000000000..eff718e97
--- /dev/null
+++ b/src/pages/Login/ResetPassword.vue
@@ -0,0 +1,99 @@
+<script setup>
+import { ref, onMounted } from 'vue';
+import { useQuasar } from 'quasar';
+import { useI18n } from 'vue-i18n';
+import { useRouter, useRoute } from 'vue-router';
+import axios from 'axios';
+
+import VnInput from 'components/common/VnInput.vue';
+import VnOutForm from 'components/ui/VnOutForm.vue';
+
+const quasar = useQuasar();
+const router = useRouter();
+const route = useRoute();
+const { t } = useI18n();
+
+const newPassword = ref();
+const repeatPassword = ref();
+const passRequirements = ref({});
+
+onMounted(async () => {
+    passRequirements.value = (await axios('UserPasswords/findOne')).data;
+});
+
+async function onSubmit() {
+    if (newPassword.value != repeatPassword.value)
+        return quasar.notify({
+            message: t('resetPassword.passwordNotMatch'),
+            type: 'negative',
+        });
+
+    const headers = {
+        Authorization: route.query.access_token,
+    };
+
+    try {
+        console.log('newPassword: ', newPassword);
+        await axios.post(
+            'VnUsers/reset-password',
+            { newPassword: newPassword.value },
+            { headers }
+        );
+        router.push('Login');
+        quasar.notify({
+            message: t('resetPassword.passwordChanged'),
+            type: 'positive',
+        });
+    } catch (e) {
+        quasar.notify({
+            message: e.response?.data?.error.message,
+            type: 'negative',
+        });
+    }
+}
+</script>
+<template>
+    <VnOutForm @submit="onSubmit" :title="t('globals.pageTitles.resetPassword')">
+        <template #default>
+            <VnInput
+                type="password"
+                :label="t('login.password')"
+                v-model="newPassword"
+                :info="
+                    t('passwordRequirements', {
+                        length: passRequirements.length,
+                        nAlpha: passRequirements.nAlpha,
+                        nUpper: passRequirements.nUpper,
+                        nDigits: passRequirements.nDigits,
+                        nPunct: passRequirements.nPunct,
+                    })
+                "
+                required
+            >
+                <template #prepend>
+                    <QIcon name="password" />
+                </template>
+            </VnInput>
+            <VnInput
+                type="password"
+                :label="t('resetPassword.repeatPassword')"
+                v-model="repeatPassword"
+                required
+            >
+                <template #prepend>
+                    <QIcon name="password" />
+                </template>
+            </VnInput>
+        </template>
+        <template #buttons>
+            <QBtn
+                :label="t('globals.pageTitles.resetPassword')"
+                type="submit"
+                color="primary"
+                class="full-width q-mt-md"
+                rounded
+                unelevated
+            />
+        </template>
+    </VnOutForm>
+</template>
diff --git a/src/pages/Login/TwoFactor.vue b/src/pages/Login/TwoFactor.vue
index 31b4ccc79..dd404094f 100644
--- a/src/pages/Login/TwoFactor.vue
+++ b/src/pages/Login/TwoFactor.vue
@@ -8,6 +8,7 @@ import axios from 'axios';
 import { useSession } from 'src/composables/useSession';
 import { useLogin } from 'src/composables/useLogin';
 import VnInput from 'src/components/common/VnInput.vue';
+import VnOutForm from 'src/components/ui/VnOutForm.vue';
 
 const quasar = useQuasar();
 const session = useSession();
@@ -38,24 +39,22 @@ async function onSubmit() {
 }
 </script>
 <template>
-    <QForm @submit="onSubmit" class="q-gutter-y-md q-pa-lg formCard">
-        <div class="column items-center">
-            <QIcon name="phonelink_lock" size="xl" color="primary" />
-            <h5 class="text-center q-my-md">{{ t('twoFactor.insert') }}</h5>
-        </div>
-        <VnInput
-            v-model="code"
-            :hint="t('twoFactor.explanation')"
-            mask="# # # # # #"
-            fill-mask
-            unmasked-value
-            autofocus
-        >
-            <template #prepend>
-                <QIcon name="lock" />
-            </template>
-        </VnInput>
-        <div class="q-mt-xl">
+    <VnOutForm @submit="onSubmit" :title="t('twoFactor.insert')">
+        <template #default>
+            <VnInput
+                v-model="code"
+                :hint="t('twoFactor.explanation')"
+                mask="# # # # # #"
+                fill-mask
+                unmasked-value
+                autofocus
+            >
+                <template #prepend>
+                    <QIcon name="lock" />
+                </template>
+            </VnInput>
+        </template>
+        <template #buttons>
             <QBtn
                 :label="t('twoFactor.validate')"
                 type="submit"
@@ -64,18 +63,6 @@ async function onSubmit() {
                 rounded
                 unelevated
             />
-        </div>
-    </QForm>
+        </template>
+    </VnOutForm>
 </template>
-<style lang="scss" scoped>
-.formCard {
-    max-width: 350px;
-    min-width: 300px;
-}
-
-@media (max-width: $breakpoint-xs-max) {
-    .formCard {
-        min-width: 100%;
-    }
-}
-</style>
diff --git a/src/router/index.js b/src/router/index.js
index faa3ab5d4..686da2dde 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -46,7 +46,7 @@ export { Router };
 export default route(function (/* { store, ssrContext } */) {
     Router.beforeEach(async (to, from, next) => {
         const { isLoggedIn } = session;
-        const outLayout = ['Login', 'TwoFactor', 'VerifyEmail'];
+        const outLayout = Router.options.routes[0].children.map((r) => r.name);
         if (!isLoggedIn() && !outLayout.includes(to.name)) {
             return next({ name: 'Login', query: { redirect: to.fullPath } });
         }
diff --git a/src/router/routes.js b/src/router/routes.js
index 805eefb8c..cced308b5 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -46,6 +46,18 @@ const routes = [
                 meta: { title: 'verifyEmail' },
                 component: () => import('../pages/Login/VerifyEmail.vue'),
             },
+            {
+                path: '/recoverPassword',
+                name: 'RecoverPassword',
+                meta: { title: 'recoverPassword' },
+                component: () => import('../pages/Login/RecoverPassword.vue'),
+            },
+            {
+                path: '/resetPassword',
+                name: 'ResetPassword',
+                meta: { title: 'resetPassword' },
+                component: () => import('../pages/Login/ResetPassword.vue'),
+            },
         ],
     },
     {

From dc1accff7ab684c9babbd52c94d5085e592b5b3b Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Tue, 13 Aug 2024 12:25:37 +0200
Subject: [PATCH 26/60] test: add recover and reset password e2e

---
 .../integration/{ => outLogin}/login.spec.js  |  0
 .../integration/{ => outLogin}/logout.spec.js |  0
 .../outLogin/recoverPassword.spec.js          | 56 +++++++++++++++++++
 3 files changed, 56 insertions(+)
 rename test/cypress/integration/{ => outLogin}/login.spec.js (100%)
 rename test/cypress/integration/{ => outLogin}/logout.spec.js (100%)
 create mode 100755 test/cypress/integration/outLogin/recoverPassword.spec.js

diff --git a/test/cypress/integration/login.spec.js b/test/cypress/integration/outLogin/login.spec.js
similarity index 100%
rename from test/cypress/integration/login.spec.js
rename to test/cypress/integration/outLogin/login.spec.js
diff --git a/test/cypress/integration/logout.spec.js b/test/cypress/integration/outLogin/logout.spec.js
similarity index 100%
rename from test/cypress/integration/logout.spec.js
rename to test/cypress/integration/outLogin/logout.spec.js
diff --git a/test/cypress/integration/outLogin/recoverPassword.spec.js b/test/cypress/integration/outLogin/recoverPassword.spec.js
new file mode 100755
index 000000000..09111f0d7
--- /dev/null
+++ b/test/cypress/integration/outLogin/recoverPassword.spec.js
@@ -0,0 +1,56 @@
+/// <reference types="cypress" />
+describe('Recover Password', () => {
+    const username = 'trainee';
+    beforeEach(() => {
+        cy.visit('/#/login');
+        cy.get('#switchLanguage').click();
+        cy.get('.q-menu > :nth-child(1) > .q-item').click();
+    });
+
+    it('should go to recover password section and send notification', () => {
+        cy.get('input[aria-label="Username"]').type(username);
+        cy.get(`a[href="#/recoverPassword?user=${username}"]`).click();
+
+        cy.waitForElement('input[aria-label="User or recovery email"]');
+        cy.get('input[aria-label="User or recovery email"]').should(
+            'have.value',
+            username
+        );
+
+        cy.get('button[type="submit"]').click();
+        cy.get('.q-notification__message').should('have.text', 'Notification sent');
+    });
+
+    it('should change password to user', () => {
+        // Get token from mail
+        cy.request(
+            `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+        ).then((response) => {
+            cy.log('response: ', response.body[0].body);
+            const regex = /access_token=([a-zA-Z0-9]+)/;
+            const [match] = response.body[0].body.match(regex);
+            cy.log('response: ', match);
+
+            const resetUrl = '/#/resetPassword?' + match;
+            cy.visit(resetUrl);
+        });
+
+        // Change password
+        const newPassword = 'test.1234';
+        cy.waitForElement('input[aria-label="Password"]');
+        cy.waitForElement('input[aria-label="Repeat password"]');
+        cy.get('input[aria-label="Password"]').type(newPassword);
+        cy.get('input[aria-label="Repeat password"]').type(newPassword);
+
+        cy.get('button[type="submit"]').click();
+        cy.get('.q-notification__message').should('have.text', 'Password changed');
+
+        // Try to login successfully
+        cy.get('input[aria-label="Username"]').type(username);
+        cy.get('input[aria-label="Password"]').type(newPassword);
+        cy.get('button[type="submit"]').click();
+        cy.url().should('contain', '/dashboard');
+
+        // ❗The password cannot be returned because "nightmare" does not meet the requirements
+    });
+});

From b828fd35b7b60b88475972ada0974986f1cb5d16 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Tue, 13 Aug 2024 12:25:46 +0200
Subject: [PATCH 27/60] test: add two factor e2e

---
 .../integration/outLogin/twoFactor.spec.js    | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100755 test/cypress/integration/outLogin/twoFactor.spec.js

diff --git a/test/cypress/integration/outLogin/twoFactor.spec.js b/test/cypress/integration/outLogin/twoFactor.spec.js
new file mode 100755
index 000000000..f998dda39
--- /dev/null
+++ b/test/cypress/integration/outLogin/twoFactor.spec.js
@@ -0,0 +1,57 @@
+/// <reference types="cypress" />
+describe('Two Factor', () => {
+    const username = 'sysadmin';
+    const userId = 66;
+    beforeEach(() => {
+        cy.visit('/#/login');
+        cy.get('#switchLanguage').click();
+        cy.get('.q-menu > :nth-child(1) > .q-item').click();
+    });
+
+    it('should enable two factor to sysadmin', () => {
+        cy.request(
+            'PATCH',
+            `http://localhost:3000/api/VnUsers/${userId}/update-user?access_token=DEFAULT_TOKEN`,
+            { twoFactor: 'email' }
+        );
+    });
+
+    it('should fail when login with incorrect two factor', () => {
+        cy.get('input[aria-label="Username"]').type(username);
+        cy.get('input[aria-label="Password"]').type('nightmare');
+        cy.get('button[type="submit"]').click();
+        cy.get('.q-notification__message').should(
+            'have.text',
+            'Two-factor verification required'
+        );
+
+        cy.get('input[type="text"]').type('123456');
+        cy.get('button[type="submit"]').click();
+        cy.url().should('contain', '/twoFactor');
+    });
+
+    it('should login with correct two factor', () => {
+        cy.get('input[aria-label="Username"]').type(username);
+        cy.get('input[aria-label="Password"]').type('nightmare');
+        cy.get('button[type="submit"]').click();
+        cy.get('.q-notification__message').should(
+            'have.text',
+            'Two-factor verification required'
+        );
+
+        // Get code from mail
+        cy.request(
+            `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
+        ).then((response) => {
+            const tempDiv = document.createElement('div');
+            tempDiv.innerHTML = response.body[0].body;
+            const codeElement = tempDiv.querySelector('.code');
+            const code = codeElement ? codeElement.textContent.trim() : null;
+            cy.log('response: ', code);
+
+            cy.get('input[type="text"]').type(code);
+            cy.get('button[type="submit"]').click();
+            cy.url().should('contain', '/dashboard');
+        });
+    });
+});

From aae881c24a1fb7ac372981f07aae7b29976f2b25 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Tue, 13 Aug 2024 12:59:22 +0200
Subject: [PATCH 28/60] fix(account_card): redirection

---
 src/pages/Account/Card/AccountCard.vue   | 3 +--
 src/pages/Account/Role/Card/RoleCard.vue | 2 --
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue
index a76d549a5..a9857b283 100644
--- a/src/pages/Account/Card/AccountCard.vue
+++ b/src/pages/Account/Card/AccountCard.vue
@@ -13,10 +13,9 @@ const { t } = useI18n();
         search-data-key="AccountUsers"
         :searchbar-props="{
             url: 'VnUsers/preview',
-            redirect: !!customRouteRedirectName,
-            customRouteRedirectName,
             label: t('account.search'),
             info: t('account.searchInfo'),
+            searchUrl: 'table',
         }"
     />
 </template>
diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue
index 0c6e3aca0..ba98b9c19 100644
--- a/src/pages/Account/Role/Card/RoleCard.vue
+++ b/src/pages/Account/Role/Card/RoleCard.vue
@@ -12,8 +12,6 @@ const { t } = useI18n();
         search-data-key="AccountRoles"
         :searchbar-props="{
             url: 'VnRoles',
-            redirect: !!customRouteRedirectName,
-            customRouteRedirectName,
             label: t('role.searchRoles'),
             info: t('role.searchInfo'),
         }"

From 405092f66d2cb145530e34797f0abfca2c1f45f8 Mon Sep 17 00:00:00 2001
From: carlossa <carlossa@verdnatura.es>
Date: Tue, 13 Aug 2024 13:11:23 +0200
Subject: [PATCH 29/60] fix: account subsections cards

---
 src/pages/Account/AccountAliasList.vue     |  2 +-
 src/pages/Account/Alias/Card/AliasCard.vue | 20 +++-----------------
 src/pages/Account/Role/Card/RoleCard.vue   |  1 +
 3 files changed, 5 insertions(+), 18 deletions(-)

diff --git a/src/pages/Account/AccountAliasList.vue b/src/pages/Account/AccountAliasList.vue
index b8d594d6d..b6f7b219c 100644
--- a/src/pages/Account/AccountAliasList.vue
+++ b/src/pages/Account/AccountAliasList.vue
@@ -60,7 +60,7 @@ const columns = computed(() => [
     <VnTable
         ref="tableRef"
         data-key="AccountAliasList"
-        :url="`MailAliases`"
+        url="MailAliases"
         :create="{
             urlCreate: 'MailAliases',
             title: 'Create MailAlias',
diff --git a/src/pages/Account/Alias/Card/AliasCard.vue b/src/pages/Account/Alias/Card/AliasCard.vue
index feb9b6240..65951b3bf 100644
--- a/src/pages/Account/Alias/Card/AliasCard.vue
+++ b/src/pages/Account/Alias/Card/AliasCard.vue
@@ -1,22 +1,8 @@
 <script setup>
 import { useI18n } from 'vue-i18n';
-import { useRoute } from 'vue-router';
-import { computed } from 'vue';
-
 import VnCard from 'components/common/VnCard.vue';
 import AliasDescriptor from './AliasDescriptor.vue';
-
 const { t } = useI18n();
-const route = useRoute();
-
-const routeName = computed(() => route.name);
-const customRouteRedirectName = computed(() => {
-    return routeName.value;
-});
-const searchBarDataKeys = {
-    AliasBasicData: 'AliasBasicData',
-    AliasUsers: 'AliasUsers',
-};
 </script>
 
 <template>
@@ -24,12 +10,12 @@ const searchBarDataKeys = {
         data-key="Alias"
         base-url="MailAliases"
         :descriptor="AliasDescriptor"
-        :search-data-key="searchBarDataKeys[routeName]"
+        search-data-key="AccountAliasList"
         :searchbar-props="{
-            redirect: !!customRouteRedirectName,
-            customRouteRedirectName,
+            url: 'MailAliases',
             info: t('mailAlias.searchInfo'),
             label: t('mailAlias.search'),
+            searchUrl: 'table',
         }"
     />
 </template>
diff --git a/src/pages/Account/Role/Card/RoleCard.vue b/src/pages/Account/Role/Card/RoleCard.vue
index ba98b9c19..35f9a1f27 100644
--- a/src/pages/Account/Role/Card/RoleCard.vue
+++ b/src/pages/Account/Role/Card/RoleCard.vue
@@ -14,6 +14,7 @@ const { t } = useI18n();
             url: 'VnRoles',
             label: t('role.searchRoles'),
             info: t('role.searchInfo'),
+            searchUrl: 'table',
         }"
     />
 </template>

From fdb508de97570d64d0a9752e82acd7eb02882613 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Tue, 13 Aug 2024 16:31:34 +0200
Subject: [PATCH 30/60] delete cy.logs

---
 test/cypress/integration/outLogin/recoverPassword.spec.js | 2 --
 test/cypress/integration/outLogin/twoFactor.spec.js       | 1 -
 2 files changed, 3 deletions(-)

diff --git a/test/cypress/integration/outLogin/recoverPassword.spec.js b/test/cypress/integration/outLogin/recoverPassword.spec.js
index 09111f0d7..eec81b661 100755
--- a/test/cypress/integration/outLogin/recoverPassword.spec.js
+++ b/test/cypress/integration/outLogin/recoverPassword.spec.js
@@ -26,10 +26,8 @@ describe('Recover Password', () => {
         cy.request(
             `http://localhost:3000/api/Mails?filter=%7B%22where%22%3A%20%7B%22receiver%22%3A%20%22${username}%40mydomain.com%22%7D%2C%20%22order%22%3A%20%5B%22id%20DESC%22%5D%7D&access_token=DEFAULT_TOKEN`
         ).then((response) => {
-            cy.log('response: ', response.body[0].body);
             const regex = /access_token=([a-zA-Z0-9]+)/;
             const [match] = response.body[0].body.match(regex);
-            cy.log('response: ', match);
 
             const resetUrl = '/#/resetPassword?' + match;
             cy.visit(resetUrl);
diff --git a/test/cypress/integration/outLogin/twoFactor.spec.js b/test/cypress/integration/outLogin/twoFactor.spec.js
index f998dda39..4d8561f0f 100755
--- a/test/cypress/integration/outLogin/twoFactor.spec.js
+++ b/test/cypress/integration/outLogin/twoFactor.spec.js
@@ -47,7 +47,6 @@ describe('Two Factor', () => {
             tempDiv.innerHTML = response.body[0].body;
             const codeElement = tempDiv.querySelector('.code');
             const code = codeElement ? codeElement.textContent.trim() : null;
-            cy.log('response: ', code);
 
             cy.get('input[type="text"]').type(code);
             cy.get('button[type="submit"]').click();

From 3cbdf25410c0846df4a8223cee988d5a88088f15 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Tue, 13 Aug 2024 16:40:02 +0200
Subject: [PATCH 31/60] delete name default

---
 src/components/ui/VnOutForm.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/ui/VnOutForm.vue b/src/components/ui/VnOutForm.vue
index b44445682..e7ad441d0 100644
--- a/src/components/ui/VnOutForm.vue
+++ b/src/components/ui/VnOutForm.vue
@@ -13,7 +13,7 @@ defineProps({
                 {{ title }}
             </h5>
         </div>
-        <slot name="default"></slot>
+        <slot></slot>
         <div class="q-mt-lg">
             <slot name="buttons"></slot>
         </div>

From 06c0f491281df78a661a71a0a9bb21c4e25411b1 Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Fri, 16 Aug 2024 10:10:40 +0200
Subject: [PATCH 32/60] feat: refs #7346 add seriaType option

---
 src/pages/InvoiceOut/InvoiceOutGlobalForm.vue | 38 ++++++++++++++++---
 src/pages/InvoiceOut/InvoiceOutList.vue       |  4 +-
 src/stores/invoiceOutGlobal.js                | 11 +++++-
 3 files changed, 45 insertions(+), 8 deletions(-)

diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index 23c63ee6a..5fc519061 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -20,20 +20,18 @@ const { initialDataLoading, formInitialData, invoicing, status } =
 const { makeInvoice, setStatusValue } = invoiceOutGlobalStore;
 
 const clientsToInvoice = ref('all');
-
 const companiesOptions = ref([]);
-
 const printersOptions = ref([]);
-
 const clientsOptions = ref([]);
-
+const serialTypesOptions = ref([]);
 const formData = ref({});
 
 const optionsInitialData = computed(() => {
     return (
         companiesOptions.value.length > 0 &&
         printersOptions.value.length > 0 &&
-        clientsOptions.value.length > 0
+        clientsOptions.value.length > 0 &&
+        serialTypesOptions.value.length > 0
     );
 });
 
@@ -60,7 +58,22 @@ onMounted(async () => {
     />
     <FetchData url="Printers" @on-fetch="(data) => (printersOptions = data)" auto-load />
     <FetchData url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
-
+    <FetchData
+        url="invoiceOutSerials"
+        @on-fetch="
+            (data) => {
+                const uniqueTypes = new Set();
+                serialTypesOptions = data.filter((item) => {
+                    if (item.type === null || uniqueTypes.has(item.type)) {
+                        return false;
+                    }
+                    uniqueTypes.add(item.type);
+                    return true;
+                });
+            }
+        "
+        auto-load
+    />
     <QForm
         v-if="!initialDataLoading && optionsInitialData"
         @submit="makeInvoice(formData, clientsToInvoice)"
@@ -95,6 +108,17 @@ onMounted(async () => {
                 outlined
                 rounded
             />
+            <VnSelect
+                :label="t('invoiceOutSerialType')"
+                v-model="formData.invoiceType"
+                :options="serialTypesOptions"
+                option-value="type"
+                option-label="type"
+                hide-selected
+                dense
+                outlined
+                rounded
+            />
             <VnInputDate
                 v-model="formData.invoiceDate"
                 :label="t('invoiceDate')"
@@ -168,6 +192,7 @@ en:
     printer: Printer
     invoiceOut: Invoice out
     client: Client
+    invoiceOutSerialType: Serial Type
     stop: Stop
 
 es:
@@ -179,5 +204,6 @@ es:
     printer: Impresora
     invoiceOut: Facturar
     client: Cliente
+    invoiceOutSerialType: Tipo de Serie
     stop: Parar
 </i18n>
diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue
index 822f6bb33..b08e12b3e 100644
--- a/src/pages/InvoiceOut/InvoiceOutList.vue
+++ b/src/pages/InvoiceOut/InvoiceOutList.vue
@@ -198,7 +198,7 @@ watchEffect(selectedRows);
         :url="`${MODEL}/filter`"
         :create="{
             urlCreate: 'InvoiceOuts/createManualInvoice',
-            title: t('Create Manual Invoice'),
+            title: t('Create manual invoice'),
             onDataSaved: ({ id }) => tableRef.redirect(id),
             formInitialData: {
                 active: true,
@@ -275,10 +275,12 @@ en:
     fileAllowed: Successful download of CSV file
     youCanSearchByInvoiceReference: You can search by invoice reference
     createInvoice: Make invoice
+    Create manual invoice: Create manual invoice
 es:
     searchInvoice: Buscar factura emitida
     fileDenied: El navegador denegó la descarga de archivos...
     fileAllowed: Descarga exitosa de archivo CSV
     youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura
     createInvoice: Crear factura
+    Create manual invoice: Crear factura manual
 </i18n>
diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js
index bb9a3d376..ca123d0b3 100644
--- a/src/stores/invoiceOutGlobal.js
+++ b/src/stores/invoiceOutGlobal.js
@@ -19,6 +19,7 @@ export const useInvoiceOutGlobalStore = defineStore({
             maxShipped: null,
             clientId: null,
             printer: null,
+            invoiceType: null,
         },
         addresses: [],
         minInvoicingDate: null,
@@ -100,6 +101,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                     maxShipped: new Date(formData.maxShipped),
                     clientId: formData.clientId ? formData.clientId : null,
                     companyFk: formData.companyFk,
+                    invoiceType: formData.invoiceType,
                 };
 
                 this.validateMakeInvoceParams(params, clientsToInvoice);
@@ -152,7 +154,13 @@ export const useInvoiceOutGlobalStore = defineStore({
                 );
                 throw new Error('Invoice date in the future');
             }
-
+            if (!params.invoiceType) {
+                notify(
+                    'invoiceOut.globalInvoices.errors.chooseValidinvoiceType',
+                    'negative'
+                );
+                throw new Error('Invalid Serial Type');
+            }
             if (!params.companyFk) {
                 notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative');
                 throw new Error('Invalid company');
@@ -180,6 +188,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                     invoiceDate: new Date(formData.invoiceDate),
                     maxShipped: new Date(formData.maxShipped),
                     companyFk: formData.companyFk,
+                    invoiceType: formData.invoiceType,
                 };
 
                 this.status = 'invoicing';

From ed29088d766d8362c97cdf55e01f384f4c99a4d7 Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 16 Aug 2024 10:49:52 +0200
Subject: [PATCH 33/60] typo

---
 src/pages/Login/LoginMain.vue | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/pages/Login/LoginMain.vue b/src/pages/Login/LoginMain.vue
index c17201969..44b868ebd 100644
--- a/src/pages/Login/LoginMain.vue
+++ b/src/pages/Login/LoginMain.vue
@@ -1,6 +1,6 @@
 <script setup>
 import { ref } from 'vue';
-import { Notify, useQuasar } from 'quasar';
+import { Notify } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import { useRouter } from 'vue-router';
 
@@ -11,7 +11,6 @@ import VnLogo from 'components/ui/VnLogo.vue';
 import VnInput from 'src/components/common/VnInput.vue';
 import axios from 'axios';
 
-const quasar = useQuasar();
 const session = useSession();
 const loginCache = useLogin();
 const router = useRouter();

From 2bea46b601cb98851c1166f9b4a8ade08516d6bf Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 16 Aug 2024 11:47:32 +0200
Subject: [PATCH 34/60] fix: test

---
 test/vitest/__tests__/pages/Login/Login.spec.js | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/test/vitest/__tests__/pages/Login/Login.spec.js b/test/vitest/__tests__/pages/Login/Login.spec.js
index 5ab4cee9e..e90a8ee53 100644
--- a/test/vitest/__tests__/pages/Login/Login.spec.js
+++ b/test/vitest/__tests__/pages/Login/Login.spec.js
@@ -44,10 +44,6 @@ describe('Login', () => {
 
     it('should not set the token into session if any error occurred', async () => {
         vi.spyOn(axios, 'post').mockReturnValue({ data: null });
-        vi.spyOn(vm.quasar, 'notify');
-
         await vm.onSubmit();
-
-        expect(vm.quasar.notify).not.toHaveBeenCalled();
     });
 });

From 4b2f4ffac181fd42c7f5ececc2b56c3daed4035e Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 16 Aug 2024 12:20:08 +0200
Subject: [PATCH 35/60] fix(VnTable): orderBy v-model

---
 src/components/VnTable/VnTable.vue | 4 ++--
 src/pages/Claim/ClaimList.vue      | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue
index d6b35d4da..6c77d44df 100644
--- a/src/components/VnTable/VnTable.vue
+++ b/src/components/VnTable/VnTable.vue
@@ -315,7 +315,7 @@ defineExpose({
                                 col?.columnFilter !== false &&
                                 col?.name !== 'tableActions'
                             "
-                            v-model="orders[col.name]"
+                            v-model="orders[col.orderBy ?? col.name]"
                             :name="col.orderBy ?? col.name"
                             :data-key="$attrs['data-key']"
                             :search-url="searchUrl"
@@ -409,7 +409,7 @@ defineExpose({
                                     style="height: 30px"
                                 >
                                     <VnTableOrder
-                                        v-model="orders[col.name]"
+                                        v-model="orders[col.orderBy ?? col.name]"
                                         :name="col.orderBy ?? col.name"
                                         :label="col?.label"
                                         :data-key="$attrs['data-key']"
diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue
index 6fd607da0..d1a08b9c6 100644
--- a/src/pages/Claim/ClaimList.vue
+++ b/src/pages/Claim/ClaimList.vue
@@ -50,7 +50,7 @@ const columns = computed(() => [
         align: 'left',
         label: t('claim.attendedBy'),
         name: 'attendedBy',
-        cardVisible: true,
+        orderBy: 'workerFk',
         columnFilter: {
             component: 'select',
             attrs: {
@@ -63,6 +63,7 @@ const columns = computed(() => [
                 optionFilter: 'firstName',
             },
         },
+        cardVisible: true,
     },
     {
         align: 'left',

From fa0ca732fc7e7b9ec3ab4b108719b66e64ea795e Mon Sep 17 00:00:00 2001
From: alexm <alexm@verdnatura.es>
Date: Fri, 16 Aug 2024 12:29:35 +0200
Subject: [PATCH 36/60] feat(FormModel): trim data by default

---
 src/components/FormModel.vue | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue
index a0f6bf479..22ef1622c 100644
--- a/src/components/FormModel.vue
+++ b/src/components/FormModel.vue
@@ -87,6 +87,10 @@ const $props = defineProps({
         type: Boolean,
         default: false,
     },
+    defaultTrim: {
+        type: Boolean,
+        default: true,
+    },
 });
 const emit = defineEmits(['onFetch', 'onDataSaved']);
 const modelValue = computed(
@@ -195,6 +199,7 @@ async function save() {
 
     isLoading.value = true;
     try {
+        formData.value = trimData(formData.value);
         const body = $props.mapper ? $props.mapper(formData.value) : formData.value;
         const method = $props.urlCreate ? 'post' : 'patch';
         const url =
@@ -253,6 +258,14 @@ function updateAndEmit(evt, val, res) {
     emit(evt, state.get(modelValue), res);
 }
 
+function trimData(data) {
+    if (!$props.defaultTrim) return data;
+    for (const key in data) {
+        if (typeof data[key] == 'string') data[key] = data[key].trim();
+    }
+    return data;
+}
+
 defineExpose({
     save,
     isLoading,

From 74f335b22e3e0c7a0336b3cac017978aaa260077 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Mon, 19 Aug 2024 06:51:52 +0000
Subject: [PATCH 37/60] fix: #6336 ClaimListStates

---
 src/pages/Claim/ClaimList.vue | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/pages/Claim/ClaimList.vue b/src/pages/Claim/ClaimList.vue
index d1a08b9c6..b03dfb226 100644
--- a/src/pages/Claim/ClaimList.vue
+++ b/src/pages/Claim/ClaimList.vue
@@ -78,6 +78,9 @@ const columns = computed(() => [
     {
         align: 'left',
         label: t('claim.state'),
+        format: ({ stateCode }) =>
+            claimFilterRef.value?.states.find(({code}) => code === stateCode)
+                ?.description,
         name: 'stateCode',
         chip: {
             condition: () => true,

From f3d7590edfc191c4e60b26aecd0d1c8c6c8c9bc6 Mon Sep 17 00:00:00 2001
From: Jon <jon@verdnatura.es>
Date: Mon, 19 Aug 2024 10:49:04 +0200
Subject: [PATCH 38/60] refactor: refs #7717 delete useless function and import

---
 src/pages/Order/Card/OrderBasicData.vue     | 10 ----------
 src/pages/Order/Card/OrderCatalogFilter.vue |  1 -
 2 files changed, 11 deletions(-)

diff --git a/src/pages/Order/Card/OrderBasicData.vue b/src/pages/Order/Card/OrderBasicData.vue
index dfdb5ae48..4bc9e2e43 100644
--- a/src/pages/Order/Card/OrderBasicData.vue
+++ b/src/pages/Order/Card/OrderBasicData.vue
@@ -64,16 +64,6 @@ const fetchOrderDetails = (order) => {
     fetchAgencyList(order?.landed, order?.addressFk);
 };
 
-const orderMapper = (order) => {
-    const mappedOrder = {
-        addressId: order.addressFk,
-        landed: new Date(order.landed).toISOString(),
-    };
-    if (order.agencyModeFk !== null && order.agencyModeFk !== undefined) {
-        mappedOrder.agencyModeId = order.agencyModeFk;
-    }
-    return mappedOrder;
-};
 const orderFilter = {
     include: [
         { relation: 'agencyMode', scope: { fields: ['name'] } },
diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue
index e9987d363..850abb755 100644
--- a/src/pages/Order/Card/OrderCatalogFilter.vue
+++ b/src/pages/Order/Card/OrderCatalogFilter.vue
@@ -7,7 +7,6 @@ import FetchData from 'components/FetchData.vue';
 import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
 import VnSelect from 'components/common/VnSelect.vue';
 import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
-import { useValidator } from 'src/composables/useValidator';
 import VnInput from 'src/components/common/VnInput.vue';
 import getParamWhere from 'src/filters/getParamWhere';
 

From c19585fb1b5ef85ffeeafbdbb73587ab1e6f1acd Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Mon, 19 Aug 2024 11:36:12 +0200
Subject: [PATCH 39/60] fix: quasar build warnings

---
 src/components/ui/VnSms.vue                   |  2 +-
 src/pages/Account/AccountAcls.vue             |  2 -
 .../Account/Card/AccountDescriptorMenu.vue    | 50 +---------------
 src/pages/Account/Role/AccountRoles.vue       |  2 -
 .../Customer/Card/CustomerConsumption.vue     |  2 -
 src/pages/Item/Card/ItemBasicData.vue         |  1 -
 src/pages/Monitor/SalesTicketsTable.vue       | 57 +------------------
 src/pages/Ticket/Card/TicketSale.vue          |  1 -
 src/pages/Zone/Card/ZoneCard.vue              |  5 --
 9 files changed, 6 insertions(+), 116 deletions(-)

diff --git a/src/components/ui/VnSms.vue b/src/components/ui/VnSms.vue
index 81058a6cb..bf6e0695e 100644
--- a/src/components/ui/VnSms.vue
+++ b/src/components/ui/VnSms.vue
@@ -1,5 +1,5 @@
 <script setup>
-import { watch, computed } from 'vue';
+import { computed } from 'vue';
 import { date } from 'quasar';
 import VnPaginate from 'src/components/ui/VnPaginate.vue';
 import VnAvatar from '../ui/VnAvatar.vue';
diff --git a/src/pages/Account/AccountAcls.vue b/src/pages/Account/AccountAcls.vue
index fdb0ecee1..110358e7f 100644
--- a/src/pages/Account/AccountAcls.vue
+++ b/src/pages/Account/AccountAcls.vue
@@ -6,7 +6,6 @@ import axios from 'axios';
 import useNotify from 'src/composables/useNotify.js';
 import { useQuasar } from 'quasar';
 
-import FetchData from 'components/FetchData.vue';
 import VnTable from 'components/VnTable/VnTable.vue';
 import VnSearchbar from 'components/ui/VnSearchbar.vue';
 import VnConfirm from 'components/ui/VnConfirm.vue';
@@ -24,7 +23,6 @@ const stateStore = useStateStore();
 const quasar = useQuasar();
 
 const tableRef = ref();
-const rolesOptions = ref([]);
 
 const exprBuilder = (param, value) => {
     switch (param) {
diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue
index f67cc0c6b..0e35d25f3 100644
--- a/src/pages/Account/Card/AccountDescriptorMenu.vue
+++ b/src/pages/Account/Card/AccountDescriptorMenu.vue
@@ -1,15 +1,12 @@
 <script setup>
 import axios from 'axios';
 import { computed, ref, toRefs } from 'vue';
-import { useQuasar } from 'quasar';
 import { useI18n } from 'vue-i18n';
 import { useVnConfirm } from 'composables/useVnConfirm';
 import { useRoute } from 'vue-router';
 import { useArrayData } from 'src/composables/useArrayData';
-import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
 import VnConfirm from 'src/components/ui/VnConfirm.vue';
 import useNotify from 'src/composables/useNotify.js';
-const quasar = useQuasar();
 const $props = defineProps({
     hasAccount: {
         type: Boolean,
@@ -35,7 +32,7 @@ async function updateStatusAccount(active) {
 
     account.value.hasAccount = active;
     const status = active ? 'enable' : 'disable';
-    quasar.notify({
+    notify({
         message: t(`account.card.${status}Account.success`),
         type: 'positive',
     });
@@ -44,19 +41,11 @@ async function updateStatusUser(active) {
     await axios.patch(`VnUsers/${entityId.value}`, { active });
     account.value.active = active;
     const status = active ? 'activate' : 'deactivate';
-    quasar.notify({
+    notify({
         message: t(`account.card.actions.${status}User.success`),
         type: 'positive',
     });
 }
-function setPassword() {
-    quasar.dialog({
-        component: CustomerChangePassword,
-        componentProps: {
-            id: entityId.value,
-        },
-    });
-}
 const showSyncDialog = ref(false);
 const syncPassword = ref(null);
 const shouldSyncPassword = ref(false);
@@ -66,20 +55,11 @@ async function sync() {
     await axios.patch(`Accounts/${account.value.name}/sync`, {
         params,
     });
-    quasar.notify({
+    notify({
         message: t('account.card.actions.sync.success'),
         type: 'positive',
     });
 }
-
-const removeAccount = async () => {
-    try {
-        await axios.delete(`VnUsers/${account.value.id}`);
-        notify(t('Account removed'), 'positive');
-    } catch (error) {
-        console.error('Error deleting user', error);
-    }
-};
 </script>
 <template>
     <VnConfirm
@@ -112,24 +92,6 @@ const removeAccount = async () => {
             />
         </template>
     </VnConfirm>
-    <!-- <QItem v-ripple clickable @click="setPassword">
-        <QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
-    </QItem>
-    <QItem
-        v-if="!account.hasAccount"
-        v-ripple
-        clickable
-        @click="
-            openConfirmationModal(
-                t('account.card.actions.enableAccount.title'),
-                t('account.card.actions.enableAccount.subtitle'),
-                () => updateStatusAccount(true)
-            )
-        "
-    >
-        <QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection>
-    </QItem> -->
-
     <QItem
         v-if="account.hasAccount"
         v-ripple
@@ -178,10 +140,4 @@ const removeAccount = async () => {
     </QItem>
 
     <QSeparator />
-    <!-- <QItem @click="removeAccount(id)" v-ripple clickable>
-        <QItemSection avatar>
-            <QIcon name="delete" />
-        </QItemSection>
-        <QItemSection>{{ t('account.card.actions.delete.name') }}</QItemSection>
-    </QItem> -->
 </template>
diff --git a/src/pages/Account/Role/AccountRoles.vue b/src/pages/Account/Role/AccountRoles.vue
index 2f80606b4..8f3372a6d 100644
--- a/src/pages/Account/Role/AccountRoles.vue
+++ b/src/pages/Account/Role/AccountRoles.vue
@@ -4,11 +4,9 @@ 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 { useStateStore } from 'stores/useStateStore';
 import { useSummaryDialog } from 'src/composables/useSummaryDialog';
 import RoleSummary from './Card/RoleSummary.vue';
 const route = useRoute();
-const stateStore = useStateStore();
 const { t } = useI18n();
 const $props = defineProps({
     id: {
diff --git a/src/pages/Customer/Card/CustomerConsumption.vue b/src/pages/Customer/Card/CustomerConsumption.vue
index 98a3115da..4d3da1116 100644
--- a/src/pages/Customer/Card/CustomerConsumption.vue
+++ b/src/pages/Customer/Card/CustomerConsumption.vue
@@ -1,8 +1,6 @@
 <script setup>
-import { useI18n } from 'vue-i18n';
 import CustomerConsumptionFilter from './CustomerConsumptionFilter.vue';
 import { useStateStore } from 'src/stores/useStateStore';
-const { t } = useI18n();
 </script>
 
 <template>
diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue
index 89c883295..ae9e45983 100644
--- a/src/pages/Item/Card/ItemBasicData.vue
+++ b/src/pages/Item/Card/ItemBasicData.vue
@@ -16,7 +16,6 @@ const route = useRoute();
 const { t } = useI18n();
 
 const itemTypesOptions = ref([]);
-const itemsWithNameOptions = ref([]);
 const intrastatsOptions = ref([]);
 const expensesOptions = ref([]);
 
diff --git a/src/pages/Monitor/SalesTicketsTable.vue b/src/pages/Monitor/SalesTicketsTable.vue
index 1ca68240b..b301c1145 100644
--- a/src/pages/Monitor/SalesTicketsTable.vue
+++ b/src/pages/Monitor/SalesTicketsTable.vue
@@ -1,8 +1,6 @@
 <script setup>
-import { ref, computed, onMounted, reactive } from 'vue';
+import { ref, computed, reactive } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { useRouter } from 'vue-router';
-
 import FetchData from 'components/FetchData.vue';
 import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
 import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
@@ -11,18 +9,14 @@ import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.v
 import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
 import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
 import TicketSummary from 'src/pages/Ticket/Card/TicketSummary.vue';
-import VnInput from 'src/components/common/VnInput.vue';
-import VnSelect from 'src/components/common/VnSelect.vue';
-import VnInputDate from 'src/components/common/VnInputDate.vue';
 import VnTable from 'components/VnTable/VnTable.vue';
-
 import { useSummaryDialog } from 'src/composables/useSummaryDialog';
 import { toDateFormat, toTimeFormat } from 'src/filters/date.js';
 import { toCurrency, dateRange } from 'src/filters';
+
 const DEFAULT_AUTO_REFRESH = 1000;
 const { t } = useI18n();
 const autoRefresh = ref(false);
-const router = useRouter();
 const paginateRef = ref(null);
 const workersActiveOptions = ref([]);
 const provincesOptions = ref([]);
@@ -57,46 +51,6 @@ function exprBuilder(param, value) {
 const filter = { order: ['totalProblems DESC'] };
 let params = reactive({});
 
-const applyColumnFilter = async (col) => {
-    try {
-        const paramKey = col.columnFilter?.filterParamKey || col.field;
-        params[paramKey] = col.columnFilter.filterValue;
-        await paginateRef.value.addFilter(null, params);
-    } catch (err) {
-        console.error('Error applying column filter', err);
-    }
-};
-
-const getInputEvents = (col) => {
-    return col.columnFilter.type === 'select' || col.columnFilter.type === 'date'
-        ? { 'update:modelValue': () => applyColumnFilter(col) }
-        : {
-              'keyup.enter': () => applyColumnFilter(col),
-          };
-};
-
-const fetchParams = ($params = {}) => {
-    const excludedParams = ['search', 'clientFk', 'orderFk', 'refFk', 'scopeDays'];
-
-    const hasExcludedParams = excludedParams.some((param) => {
-        return $params && $params[param] != undefined;
-    });
-    const hasParams = Object.entries($params).length;
-    if (!hasParams || !hasExcludedParams) $params.scopeDays = 1;
-
-    if (typeof $params.scopeDays === 'number') {
-        const from = Date.vnNew();
-        from.setHours(0, 0, 0, 0);
-
-        const to = new Date(from.getTime());
-        to.setDate(to.getDate() + $params.scopeDays);
-        to.setHours(23, 59, 59, 999);
-
-        Object.assign($params, { from, to });
-    }
-    return { tableOrder: 'totalProblems DESC', ...$params };
-};
-
 const columns = computed(() => [
     {
         label: t('salesTicketsTable.problems'),
@@ -379,13 +333,6 @@ const redirectToSales = (id) => {
     const url = `#/ticket/${id}/sale`;
     window.open(url, '_blank');
 };
-
-// onMounted(async () => {
-//     const filteredColumns = columns.value.filter((col) => col.name !== 'rowActions');
-//     allColumnNames.value = filteredColumns.map((col) => col.name);
-//     params = fetchParams();
-//     await paginateRef.value.addFilter(null, params);
-// });
 </script>
 
 <template>
diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue
index cbc94b388..5978dbf09 100644
--- a/src/pages/Ticket/Card/TicketSale.vue
+++ b/src/pages/Ticket/Card/TicketSale.vue
@@ -11,7 +11,6 @@ import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
 import TicketEditManaProxy from './TicketEditMana.vue';
 import VnImg from 'src/components/ui/VnImg.vue';
 
-import RightMenu from 'src/components/common/RightMenu.vue';
 import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
 import TicketSaleMoreActions from './TicketSaleMoreActions.vue';
 import TicketTransfer from './TicketTransfer.vue';
diff --git a/src/pages/Zone/Card/ZoneCard.vue b/src/pages/Zone/Card/ZoneCard.vue
index 02ec12fe7..b81ee9039 100644
--- a/src/pages/Zone/Card/ZoneCard.vue
+++ b/src/pages/Zone/Card/ZoneCard.vue
@@ -10,11 +10,6 @@ const { t } = useI18n();
 const route = useRoute();
 
 const routeName = computed(() => route.name);
-const customRouteRedirectName = computed(() => {
-    if (routeName.value === 'ZoneLocations') return null;
-    return routeName.value;
-});
-const searchbarMakeFetch = computed(() => routeName.value !== 'ZoneEvents');
 const searchBarDataKeys = {
     ZoneWarehouses: 'ZoneWarehouses',
     ZoneSummary: 'ZoneSummary',

From 0eb699bcbedd910007abee213e271962e77118d8 Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Tue, 20 Aug 2024 10:10:11 +0200
Subject: [PATCH 40/60] feat: refs #7346 formdata uses serialType

---
 src/i18n/locale/en.yml                                 |  1 +
 src/i18n/locale/es.yml                                 |  1 +
 src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue |  4 ++--
 src/pages/InvoiceOut/InvoiceOutGlobalForm.vue          |  2 +-
 src/pages/Ticket/Card/TicketSaleMoreActions.vue        |  4 ++--
 src/stores/invoiceOutGlobal.js                         | 10 +++++-----
 6 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index 7d3682525..7f15ecf6a 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -689,6 +689,7 @@ invoiceOut:
             chooseValidClient: Choose a valid client
             chooseValidCompany: Choose a valid company
             chooseValidPrinter: Choose a valid printer
+            chooseValidSerialType: Choose a serial type
             fillDates: Invoice date and the max date should be filled
             invoiceDateLessThanMaxDate: Invoice date can not be less than max date
             invoiceWithFutureDate: Exists an invoice with a future date
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index 8157f1803..fb892f015 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -695,6 +695,7 @@ invoiceOut:
             chooseValidClient: Selecciona un cliente válido
             chooseValidCompany: Selecciona una empresa válida
             chooseValidPrinter: Selecciona una impresora válida
+            chooseValidSerialType: Selecciona una tipo de serie válida
             fillDates: La fecha de la factura y la fecha máxima deben estar completas
             invoiceDateLessThanMaxDate: La fecha de la factura no puede ser menor que la fecha máxima
             invoiceWithFutureDate: Existe una factura con una fecha futura
diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
index e524faa24..4c02ccf84 100644
--- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
+++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue
@@ -222,7 +222,7 @@ const showTransferInvoiceForm = async () => {
         <QItemSection>{{ t('Generate PDF invoice') }}</QItemSection>
     </QItem>
     <QItem v-ripple clickable>
-        <QItemSection>{{ t('Refund...') }}</QItemSection>
+        <QItemSection>{{ t('Refund') }}</QItemSection>
         <QItemSection side>
             <QIcon name="keyboard_arrow_right" />
         </QItemSection>
@@ -250,7 +250,7 @@ es:
     Delete invoice: Eliminar factura
     Book invoice: Asentar factura
     Generate PDF invoice: Generar PDF factura
-    Refund...: Abono
+    Refund: Abono
     As PDF: como PDF
     As CSV: como CSV
     Send PDF: Enviar PDF
diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index 5fc519061..88d55b093 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -110,7 +110,7 @@ onMounted(async () => {
             />
             <VnSelect
                 :label="t('invoiceOutSerialType')"
-                v-model="formData.invoiceType"
+                v-model="formData.serialType"
                 :options="serialTypesOptions"
                 option-value="type"
                 option-label="type"
diff --git a/src/pages/Ticket/Card/TicketSaleMoreActions.vue b/src/pages/Ticket/Card/TicketSaleMoreActions.vue
index 9ec6b303a..f6803a9f1 100644
--- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue
+++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue
@@ -244,7 +244,7 @@ const createRefund = async (withWarehouse) => {
             </QItem>
             <QItem clickable v-ripple>
                 <QItemSection>
-                    <QItemLabel>{{ t('Refund...') }}</QItemLabel>
+                    <QItemLabel>{{ t('Refund') }}</QItemLabel>
                 </QItemSection>
                 <QItemSection side>
                     <QIcon name="keyboard_arrow_right" />
@@ -279,7 +279,7 @@ es:
     Add claim: Crear reclamación
     Mark as reserved: Marcar como reservado
     Unmark as reserved: Desmarcar como reservado
-    Refund...: Abono...
+    Refund: Abono
     with warehouse: con almacén
     without warehouse: sin almacén
     Claim out of time: Reclamación fuera de plazo
diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js
index ca123d0b3..669d65728 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,
-            invoiceType: null,
+            serialType: null,
         },
         addresses: [],
         minInvoicingDate: null,
@@ -101,7 +101,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                     maxShipped: new Date(formData.maxShipped),
                     clientId: formData.clientId ? formData.clientId : null,
                     companyFk: formData.companyFk,
-                    invoiceType: formData.invoiceType,
+                    serialType: formData.serialType,
                 };
 
                 this.validateMakeInvoceParams(params, clientsToInvoice);
@@ -154,9 +154,9 @@ export const useInvoiceOutGlobalStore = defineStore({
                 );
                 throw new Error('Invoice date in the future');
             }
-            if (!params.invoiceType) {
+            if (!params.serialType) {
                 notify(
-                    'invoiceOut.globalInvoices.errors.chooseValidinvoiceType',
+                    'invoiceOut.globalInvoices.errors.chooseValidSerialType',
                     'negative'
                 );
                 throw new Error('Invalid Serial Type');
@@ -188,7 +188,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                     invoiceDate: new Date(formData.invoiceDate),
                     maxShipped: new Date(formData.maxShipped),
                     companyFk: formData.companyFk,
-                    invoiceType: formData.invoiceType,
+                    serialType: formData.serialType,
                 };
 
                 this.status = 'invoicing';

From 89eaf02800b9839c58565fa389e40588ee546b64 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Wed, 21 Aug 2024 11:45:26 +0200
Subject: [PATCH 41/60] feat: #7323 handle workerPhoto

---
 src/css/app.scss                            |  7 +++
 src/pages/Item/Card/ItemDescriptorImage.vue |  8 ---
 src/pages/Worker/Card/WorkerDescriptor.vue  | 70 +++++++++++++++------
 3 files changed, 57 insertions(+), 28 deletions(-)

diff --git a/src/css/app.scss b/src/css/app.scss
index c233b14f0..525fb2a95 100644
--- a/src/css/app.scss
+++ b/src/css/app.scss
@@ -268,3 +268,10 @@ input::-webkit-inner-spin-button {
         max-width: 400px;
     }
 }
+.edit-photo-btn {
+    position: absolute;
+    right: 12px;
+    bottom: 12px;
+    z-index: 1;
+    cursor: pointer;
+}
diff --git a/src/pages/Item/Card/ItemDescriptorImage.vue b/src/pages/Item/Card/ItemDescriptorImage.vue
index d923dd28f..d83f534b8 100644
--- a/src/pages/Item/Card/ItemDescriptorImage.vue
+++ b/src/pages/Item/Card/ItemDescriptorImage.vue
@@ -138,14 +138,6 @@ en:
 </i18n>
 
 <style lang="scss" scoped>
-.edit-photo-btn {
-    position: absolute;
-    right: 12px;
-    bottom: 12px;
-    z-index: 1;
-    cursor: pointer;
-}
-
 .separation-borders {
     border-left: 1px solid $white;
     border-right: 1px solid $white;
diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue
index 8fbb23ef8..154db1258 100644
--- a/src/pages/Worker/Card/WorkerDescriptor.vue
+++ b/src/pages/Worker/Card/WorkerDescriptor.vue
@@ -10,6 +10,7 @@ import useCardDescription from 'src/composables/useCardDescription';
 import { useState } from 'src/composables/useState';
 import axios from 'axios';
 import VnImg from 'src/components/ui/VnImg.vue';
+import EditPictureForm from 'components/EditPictureForm.vue';
 
 const $props = defineProps({
     id: {
@@ -18,6 +19,7 @@ const $props = defineProps({
         default: null,
     },
 });
+const image = ref(null);
 
 const route = useRoute();
 const { t } = useI18n();
@@ -25,6 +27,10 @@ const state = useState();
 const user = state.getUser();
 const changePasswordFormDialog = ref(null);
 const cardDescriptorRef = ref(null);
+const showEditPhotoForm = ref(false);
+const toggleEditPictureForm = () => {
+    showEditPhotoForm.value = !showEditPhotoForm.value;
+};
 
 const entityId = computed(() => {
     return $props.id || route.params.id;
@@ -99,7 +105,9 @@ const handleExcluded = async () => {
 
     workerExcluded.value = !workerExcluded.value;
 };
-
+const handlePhotoUpdated = (evt = false) => {
+    image.value.reload(evt);
+};
 const refetch = async () => await cardDescriptorRef.value.getData();
 </script>
 <template>
@@ -144,27 +152,49 @@ const refetch = async () => await cardDescriptorRef.value.getData();
             </QItem>
         </template>
         <template #before>
-            <VnImg
-                :id="parseInt(entityId)"
-                collection="user"
-                resolution="520x520"
-                class="photo"
-            >
-                <template #error>
-                    <div
-                        class="absolute-full picture text-center q-pa-md flex flex-center"
-                    >
-                        <div>
-                            <div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
-                                <QIcon name="vn:claims" />
-                            </div>
-                            <div class="text-grey-5" style="opacity: 0.4">
-                                {{ t('worker.imageNotFound') }}
+            <div class="relative-position">
+                <VnImg
+                    ref="image"
+                    :id="parseInt(entityId)"
+                    collection="user"
+                    resolution="520x520"
+                    class="photo"
+                >
+                    <template #error>
+                        <div
+                            class="absolute-full picture text-center q-pa-md flex flex-center"
+                        >
+                            <div>
+                                <div
+                                    class="text-grey-5"
+                                    style="opacity: 0.4; font-size: 5vh"
+                                >
+                                    <QIcon name="vn:claims" />
+                                </div>
+                                <div class="text-grey-5" style="opacity: 0.4">
+                                    {{ t('worker.imageNotFound') }}
+                                </div>
                             </div>
                         </div>
-                    </div>
-                </template>
-            </VnImg>
+                    </template> </VnImg
+                ><QBtn
+                    color="primary"
+                    size="lg"
+                    round
+                    class="edit-photo-btn"
+                    @click="toggleEditPictureForm()"
+                >
+                    <QIcon name="edit" size="sm" />
+                    <QDialog ref="editPhotoFormDialog" v-model="showEditPhotoForm">
+                        <EditPictureForm
+                            collection="user"
+                            :id="entityId"
+                            @close-form="toggleEditPictureForm()"
+                            @on-photo-uploaded="handlePhotoUpdated"
+                        />
+                    </QDialog>
+                </QBtn>
+            </div>
         </template>
         <template #body="{ entity }">
             <VnLv :label="t('worker.card.name')" :value="entity.user?.nickname" />

From 3d5807463b55065b053d6235452927eb1fe3263e Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Wed, 21 Aug 2024 13:46:19 +0200
Subject: [PATCH 42/60] feat: refs #7346 refactor

---
 src/pages/InvoiceOut/InvoiceOutGlobalForm.vue | 38 ++++++++++---------
 src/pages/Item/Card/ItemBasicData.vue         |  1 -
 2 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index 88d55b093..3dc34dc15 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -24,15 +24,28 @@ const companiesOptions = ref([]);
 const printersOptions = ref([]);
 const clientsOptions = ref([]);
 const serialTypesOptions = ref([]);
+
+const handleInvoiceOutSerialsFetch = (data) => {
+    const uniqueTypes = new Set();
+    serialTypesOptions.value = data.filter((item) => {
+        if (item.type === null || uniqueTypes.has(item.type)) {
+            return false;
+        }
+        uniqueTypes.add(item.type);
+        return true;
+    });
+};
+
 const formData = ref({});
 
 const optionsInitialData = computed(() => {
-    return (
-        companiesOptions.value.length > 0 &&
-        printersOptions.value.length > 0 &&
-        clientsOptions.value.length > 0 &&
-        serialTypesOptions.value.length > 0
-    );
+    const optionsArrays = [
+        companiesOptions.value,
+        printersOptions.value,
+        clientsOptions.value,
+        serialTypesOptions.value,
+    ];
+    return optionsArrays.every((arr) => arr.length > 0);
 });
 
 const getStatus = computed({
@@ -60,18 +73,7 @@ onMounted(async () => {
     <FetchData url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
     <FetchData
         url="invoiceOutSerials"
-        @on-fetch="
-            (data) => {
-                const uniqueTypes = new Set();
-                serialTypesOptions = data.filter((item) => {
-                    if (item.type === null || uniqueTypes.has(item.type)) {
-                        return false;
-                    }
-                    uniqueTypes.add(item.type);
-                    return true;
-                });
-            }
-        "
+        @on-fetch="handleInvoiceOutSerialsFetch"
         auto-load
     />
     <QForm
diff --git a/src/pages/Item/Card/ItemBasicData.vue b/src/pages/Item/Card/ItemBasicData.vue
index 89c883295..ae9e45983 100644
--- a/src/pages/Item/Card/ItemBasicData.vue
+++ b/src/pages/Item/Card/ItemBasicData.vue
@@ -16,7 +16,6 @@ const route = useRoute();
 const { t } = useI18n();
 
 const itemTypesOptions = ref([]);
-const itemsWithNameOptions = ref([]);
 const intrastatsOptions = ref([]);
 const expensesOptions = ref([]);
 

From b71d24cf3d47edcda32e6a7734112796b5510cde Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Wed, 21 Aug 2024 14:13:36 +0200
Subject: [PATCH 43/60] feat: refs #7346 sonarLint warnings

---
 src/css/app.scss                          |  4 ----
 src/pages/InvoiceOut/InvoiceOutGlobal.vue | 12 +++++++-----
 src/stores/invoiceOutGlobal.js            |  9 ++-------
 3 files changed, 9 insertions(+), 16 deletions(-)

diff --git a/src/css/app.scss b/src/css/app.scss
index c233b14f0..b2976b86a 100644
--- a/src/css/app.scss
+++ b/src/css/app.scss
@@ -103,10 +103,6 @@ select:-webkit-autofill {
     border-radius: 8px;
 }
 
-.card-width {
-    width: 770px;
-}
-
 .vn-card-list {
     width: 100%;
     max-width: 60em;
diff --git a/src/pages/InvoiceOut/InvoiceOutGlobal.vue b/src/pages/InvoiceOut/InvoiceOutGlobal.vue
index eecc61bc2..5f2eb3c02 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobal.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobal.vue
@@ -94,11 +94,13 @@ const selectCustomerId = (id) => {
 };
 
 const statusText = computed(() => {
-    return status.value === 'invoicing'
-        ? `${t(`status.${status.value}`)} ${
-              addresses.value[getAddressNumber.value]?.clientId
-          }`
-        : t(`status.${status.value}`);
+    const baseStatus = t(`status.${status.value}`);
+    const clientId =
+        status.value === 'invoicing'
+            ? addresses.value[getAddressNumber.value]?.clientId || ''
+            : '';
+
+    return clientId ? `${baseStatus} ${clientId}`.trim() : baseStatus;
 });
 
 onMounted(() => (stateStore.rightDrawer = true));
diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js
index 669d65728..42acac013 100644
--- a/src/stores/invoiceOutGlobal.js
+++ b/src/stores/invoiceOutGlobal.js
@@ -200,12 +200,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                 this.addressIndex++;
                 this.isInvoicing = false;
             } catch (err) {
-                if (
-                    err &&
-                    err.response &&
-                    err.response.status >= 400 &&
-                    err.response.status < 500
-                ) {
+                if (err?.response?.status >= 400 && err?.response?.status < 500) {
                     this.invoiceClientError(address, err.response?.data?.error?.message);
                     return;
                 } else {
@@ -252,7 +247,7 @@ export const useInvoiceOutGlobalStore = defineStore({
                     params,
                 });
 
-                if (data.data && data.data.error) throw new Error();
+                if (data?.data?.error) throw new Error();
 
                 const status = exportFile('negativeBases.csv', data, {
                     encoding: 'windows-1252',

From 6197d283e4fa2d104644848e3fa98ae7d60cf496 Mon Sep 17 00:00:00 2001
From: wbuezas <wbuezas@verdnatura.es>
Date: Wed, 21 Aug 2024 10:51:38 -0300
Subject: [PATCH 44/60] Create ticket future filter

---
 src/components/ui/VnFilterPanel.vue     |   2 +-
 src/pages/Ticket/TicketFuture.vue       |  19 +-
 src/pages/Ticket/TicketFutureFilter.vue | 244 ++++++++++++++++++++++++
 src/pages/Ticket/locale/en.yml          |   5 +
 src/pages/Ticket/locale/es.yml          |   5 +
 5 files changed, 269 insertions(+), 6 deletions(-)
 create mode 100644 src/pages/Ticket/TicketFutureFilter.vue

diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index 9059605ca..3598f96a5 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -202,7 +202,7 @@ function formatValue(value) {
 
 function sanitizer(params) {
     for (const [key, value] of Object.entries(params)) {
-        if (typeof value == 'object') {
+        if (value && typeof value === 'object') {
             const param = Object.values(value)[0];
             if (typeof param == 'string') params[key] = param.replaceAll('%', '');
         }
diff --git a/src/pages/Ticket/TicketFuture.vue b/src/pages/Ticket/TicketFuture.vue
index 2fec6dc18..4ec430e2e 100644
--- a/src/pages/Ticket/TicketFuture.vue
+++ b/src/pages/Ticket/TicketFuture.vue
@@ -8,6 +8,8 @@ import VnSelect from 'src/components/common/VnSelect.vue';
 import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
 import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
 import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
+import RightMenu from 'src/components/common/RightMenu.vue';
+import TicketFutureFilter from './TicketFutureFilter.vue';
 
 import { dashIfEmpty, toCurrency } from 'src/filters';
 import { useVnConfirm } from 'composables/useVnConfirm';
@@ -37,9 +39,9 @@ const exprBuilder = (param, value) => {
             return { liters: value };
         case 'lines':
             return { lines: value };
-        case 'ipt':
+        case 'iptColFilter':
             return { ipt: { like: `%${value}%` } };
-        case 'futureIpt':
+        case 'futureIptColFilter':
             return { futureIpt: { like: `%${value}%` } };
         case 'totalWithVat':
             return { totalWithVat: value };
@@ -83,6 +85,8 @@ const getInputEvents = (col) => {
           };
 };
 
+const tickets = computed(() => store.data);
+
 const ticketColumns = computed(() => [
     {
         label: t('futureTickets.problems'),
@@ -121,7 +125,7 @@ const ticketColumns = computed(() => [
         sortable: true,
         columnFilter: {
             component: VnSelect,
-            filterParamKey: 'ipt',
+            filterParamKey: 'iptColFilter',
             type: 'select',
             filterValue: null,
             event: getInputEvents,
@@ -214,7 +218,7 @@ const ticketColumns = computed(() => [
         sortable: true,
         columnFilter: {
             component: VnSelect,
-            filterParamKey: 'futureIpt',
+            filterParamKey: 'futureIptColFilter',
             type: 'select',
             filterValue: null,
             event: getInputEvents,
@@ -305,9 +309,14 @@ onMounted(async () => {
             </QBtn>
         </template>
     </VnSubToolbar>
+    <RightMenu>
+        <template #right-panel>
+            <TicketFutureFilter data-key="FutureTickets" />
+        </template>
+    </RightMenu>
     <QPage class="column items-center q-pa-md">
         <QTable
-            :rows="store.data"
+            :rows="tickets"
             :columns="ticketColumns"
             row-key="id"
             selection="multiple"
diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue
new file mode 100644
index 000000000..36a9d154f
--- /dev/null
+++ b/src/pages/Ticket/TicketFutureFilter.vue
@@ -0,0 +1,244 @@
+<script setup>
+import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+
+import FetchData from 'components/FetchData.vue';
+import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
+import VnSelect from 'components/common/VnSelect.vue';
+import VnInputDate from 'src/components/common/VnInputDate.vue';
+import VnInput from 'src/components/common/VnInput.vue';
+
+import axios from 'axios';
+import { onMounted } from 'vue';
+
+const { t } = useI18n();
+const props = defineProps({
+    dataKey: {
+        type: String,
+        required: true,
+    },
+});
+
+const warehousesOptions = ref([]);
+const itemPackingTypes = ref([]);
+const stateOptions = ref([]);
+
+const getItemPackingTypes = async () => {
+    try {
+        const filter = {
+            where: { isActive: true },
+        };
+        const { data } = await axios.get('ItemPackingTypes', {
+            params: { filter: JSON.stringify(filter) },
+        });
+        itemPackingTypes.value = data.map((ipt) => ({
+            description: t(ipt.description),
+            code: ipt.code,
+        }));
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const getGroupedStates = async () => {
+    try {
+        const { data } = await axios.get('AlertLevels');
+        stateOptions.value = data.map((state) => ({
+            id: state.id,
+            name: t(`futureTickets.${state.code}`),
+            code: state.code,
+        }));
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+onMounted(async () => {
+    getItemPackingTypes();
+    getGroupedStates();
+});
+</script>
+
+<template>
+    <FetchData
+        url="Warehouses"
+        @on-fetch="(data) => (warehousesOptions = data)"
+        auto-load
+    />
+    <VnFilterPanel
+        :data-key="props.dataKey"
+        :hidden-tags="['search']"
+        :unremovable-params="['warehouseFk', 'originDated', 'futureDated']"
+    >
+        <template #tags="{ tag, formatFn }">
+            <div class="q-gutter-x-xs">
+                <strong>{{ t(`params.${tag.label}`) }}: </strong>
+                <span>{{ formatFn(tag.value) }}</span>
+            </div>
+        </template>
+        <template #body="{ params, searchFn }">
+            <QItem class="q-my-sm">
+                <QItemSection>
+                    <VnInputDate
+                        v-model="params.originDated"
+                        :label="t('params.originDated')"
+                        is-outlined
+                        @update:model-value="searchFn()"
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem class="q-my-sm">
+                <QItemSection>
+                    <VnInputDate
+                        v-model="params.futureDated"
+                        :label="t('params.futureDated')"
+                        is-outlined
+                        @update:model-value="searchFn()"
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem class="q-my-sm">
+                <QItemSection>
+                    <VnInput
+                        :label="t('params.litersMax')"
+                        v-model="params.litersMax"
+                        is-outlined
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem class="q-my-sm">
+                <QItemSection>
+                    <VnInput
+                        :label="t('params.linesMax')"
+                        v-model="params.linesMax"
+                        is-outlined
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem>
+                <QItemSection>
+                    <VnSelect
+                        :label="t('params.ipt')"
+                        v-model="params.ipt"
+                        :options="itemPackingTypes"
+                        option-value="code"
+                        option-label="description"
+                        :info="t('iptInfo')"
+                        @update:model-value="searchFn()"
+                        dense
+                        outlined
+                        rounded
+                    >
+                    </VnSelect>
+                </QItemSection>
+            </QItem>
+            <QItem>
+                <QItemSection>
+                    <VnSelect
+                        :label="t('params.futureIpt')"
+                        v-model="params.futureIpt"
+                        :options="itemPackingTypes"
+                        option-value="code"
+                        option-label="description"
+                        :info="t('iptInfo')"
+                        @update:model-value="searchFn()"
+                        dense
+                        outlined
+                        rounded
+                    >
+                    </VnSelect>
+                </QItemSection>
+            </QItem>
+            <QItem>
+                <QItemSection>
+                    <VnSelect
+                        :label="t('params.state')"
+                        v-model="params.state"
+                        :options="stateOptions"
+                        option-value="code"
+                        option-label="name"
+                        @update:model-value="searchFn()"
+                        dense
+                        outlined
+                        rounded
+                    >
+                    </VnSelect>
+                </QItemSection>
+            </QItem>
+            <QItem>
+                <QItemSection>
+                    <VnSelect
+                        :label="t('params.futureState')"
+                        v-model="params.futureState"
+                        :options="stateOptions"
+                        option-value="code"
+                        option-label="name"
+                        @update:model-value="searchFn()"
+                        dense
+                        outlined
+                        rounded
+                    >
+                    </VnSelect>
+                </QItemSection>
+            </QItem>
+
+            <QItem>
+                <QItemSection>
+                    <QCheckbox
+                        :label="t('params.problems')"
+                        v-model="params.problems"
+                        :toggle-indeterminate="false"
+                        @update:model-value="searchFn()"
+                    />
+                </QItemSection>
+            </QItem>
+            <QItem>
+                <QItemSection>
+                    <VnSelect
+                        :label="t('params.warehouseFk')"
+                        v-model="params.warehouseFk"
+                        :options="warehousesOptions"
+                        option-value="id"
+                        option-label="name"
+                        @update:model-value="searchFn()"
+                        dense
+                        outlined
+                        rounded
+                    >
+                    </VnSelect>
+                </QItemSection>
+            </QItem>
+        </template>
+    </VnFilterPanel>
+</template>
+
+<i18n>
+en:
+    iptInfo: IPT
+    params:
+        originDated: Origin date
+        futureDated: Destination date
+        futureIpt: Destination IPT
+        ipt: Origin IPT
+        warehouseFk: Warehouse
+        litersMax: Max liters
+        linesMax: Max lines
+        state: Origin grouped state
+        futureState: Destination grouped state
+        problems: With problems
+es:
+    Horizontal: Horizontal
+    Vertical: Vertical
+    iptInfo: Encajado
+    params:
+        originDated: Fecha origen
+        futureDated: Fecha destino
+        futureIpt: IPT destino
+        ipt: IPT Origen
+        warehouseFk: Almacén
+        litersMax: Litros máx.
+        linesMax: Líneas máx.
+        state: Estado agrupado origen
+        futureState: Estado agrupado destino
+        problems: Con problemas
+</i18n>
diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml
index 10a8e1fa4..305228669 100644
--- a/src/pages/Ticket/locale/en.yml
+++ b/src/pages/Ticket/locale/en.yml
@@ -93,6 +93,11 @@ futureTickets:
     moveTicketSuccess: Tickets moved successfully!
     searchInfo: Search future tickets by date
     futureTicket: Future tickets
+    FREE: Free
+    ON_PREVIOUS: ON_PREVIOUS
+    ON_PREPARATION: On preparation
+    PACKED: Packed
+    DELIVERED: Delivered
 expedition:
     id: Expedition
     item: Item
diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml
index a80692bfe..20b4edf16 100644
--- a/src/pages/Ticket/locale/es.yml
+++ b/src/pages/Ticket/locale/es.yml
@@ -140,6 +140,11 @@ futureTickets:
     moveTicketSuccess: Tickets movidos correctamente
     searchInfo: Buscar tickets por fecha
     futureTicket: Tickets a futuro
+    FREE: Libre
+    ON_PREVIOUS: ON_PREVIOUS
+    ON_PREPARATION: En preparación
+    PACKED: Encajado
+    DELIVERED: Servido
 ticketSale:
     id: Id
     visible: Visible

From 54eb567e39e43fc6c87f8de52c9359a487b59a5c Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Thu, 22 Aug 2024 22:13:50 +0200
Subject: [PATCH 45/60] perf: date fields

---
 src/pages/Ticket/TicketAdvance.vue       | 2 +-
 src/pages/Ticket/TicketAdvanceFilter.vue | 4 +---
 src/pages/Ticket/TicketFuture.vue        | 4 ++--
 src/pages/Ticket/TicketFutureFilter.vue  | 4 +---
 4 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/pages/Ticket/TicketAdvance.vue b/src/pages/Ticket/TicketAdvance.vue
index bf4000fdf..45bd8fd39 100644
--- a/src/pages/Ticket/TicketAdvance.vue
+++ b/src/pages/Ticket/TicketAdvance.vue
@@ -448,7 +448,7 @@ const handleCloseProgressDialog = () => {
 const handleCancelProgress = () => (cancelProgress.value = true);
 
 onMounted(async () => {
-    let today = Date.vnNew();
+    let today = Date.vnNew().toISOString();
     const tomorrow = new Date(today);
     tomorrow.setDate(tomorrow.getDate() + 1);
     userParams.dateFuture = tomorrow;
diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue
index c4548763a..ed61d9447 100644
--- a/src/pages/Ticket/TicketAdvanceFilter.vue
+++ b/src/pages/Ticket/TicketAdvanceFilter.vue
@@ -55,7 +55,7 @@ onMounted(async () => await getItemPackingTypes());
         :data-key="props.dataKey"
         :search-button="true"
         :hidden-tags="['search']"
-        :unremovable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
+        :un-removable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']"
     >
         <template #tags="{ tag, formatFn }">
             <div class="q-gutter-x-xs">
@@ -70,7 +70,6 @@ onMounted(async () => await getItemPackingTypes());
                         v-model="params.dateFuture"
                         :label="t('params.dateFuture')"
                         is-outlined
-                        @update:model-value="searchFn()"
                     />
                 </QItemSection>
             </QItem>
@@ -80,7 +79,6 @@ onMounted(async () => await getItemPackingTypes());
                         v-model="params.dateToAdvance"
                         :label="t('params.dateToAdvance')"
                         is-outlined
-                        @update:model-value="searchFn()"
                     />
                 </QItemSection>
             </QItem>
diff --git a/src/pages/Ticket/TicketFuture.vue b/src/pages/Ticket/TicketFuture.vue
index 4ec430e2e..14a4a5ac8 100644
--- a/src/pages/Ticket/TicketFuture.vue
+++ b/src/pages/Ticket/TicketFuture.vue
@@ -49,8 +49,8 @@ const exprBuilder = (param, value) => {
 };
 
 const userParams = reactive({
-    futureDated: Date.vnNew(),
-    originDated: Date.vnNew(),
+    futureDated: Date.vnNew().toISOString(),
+    originDated: Date.vnNew().toISOString(),
     warehouseFk: user.value.warehouseFk,
 });
 
diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue
index 36a9d154f..c5e647d64 100644
--- a/src/pages/Ticket/TicketFutureFilter.vue
+++ b/src/pages/Ticket/TicketFutureFilter.vue
@@ -68,7 +68,7 @@ onMounted(async () => {
     <VnFilterPanel
         :data-key="props.dataKey"
         :hidden-tags="['search']"
-        :unremovable-params="['warehouseFk', 'originDated', 'futureDated']"
+        :un-removable-params="['warehouseFk', 'originDated', 'futureDated']"
     >
         <template #tags="{ tag, formatFn }">
             <div class="q-gutter-x-xs">
@@ -83,7 +83,6 @@ onMounted(async () => {
                         v-model="params.originDated"
                         :label="t('params.originDated')"
                         is-outlined
-                        @update:model-value="searchFn()"
                     />
                 </QItemSection>
             </QItem>
@@ -93,7 +92,6 @@ onMounted(async () => {
                         v-model="params.futureDated"
                         :label="t('params.futureDated')"
                         is-outlined
-                        @update:model-value="searchFn()"
                     />
                 </QItemSection>
             </QItem>

From 1984e1d0d81cc21578b86cd279fe0fab7f9fabe3 Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Fri, 23 Aug 2024 10:25:05 +0200
Subject: [PATCH 46/60] feat: refs #7710 uses cloneAll

---
 src/pages/Ticket/Card/TicketExpedition.vue    |  1 -
 .../Ticket/Card/TicketSaleMoreActions.vue     | 22 +++++++++++++------
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue
index a7eb9e27e..f344e4276 100644
--- a/src/pages/Ticket/Card/TicketExpedition.vue
+++ b/src/pages/Ticket/Card/TicketExpedition.vue
@@ -268,7 +268,6 @@ onMounted(async () => {
     stateStore.rightDrawer = true;
     const filteredColumns = columns.value.filter((col) => col.name !== 'history');
     allColumnNames.value = filteredColumns.map((col) => col.name);
-    // await expeditionsArrayData.fetch({ append: false });
 });
 
 onUnmounted(() => (stateStore.rightDrawer = false));
diff --git a/src/pages/Ticket/Card/TicketSaleMoreActions.vue b/src/pages/Ticket/Card/TicketSaleMoreActions.vue
index 9ec6b303a..ba827d062 100644
--- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue
+++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue
@@ -153,14 +153,22 @@ const setReserved = async (reserved) => {
 };
 
 const createRefund = async (withWarehouse) => {
-    if (!props.sales) return;
+    if (!props.ticket) return;
 
-    const salesIds = props.sales.map((sale) => sale.id);
-    const params = { salesIds: salesIds, withWarehouse: withWarehouse, negative: true };
-    const { data } = await axios.post('Sales/clone', params);
-    const [refundTicket] = data;
-    notify(t('refundTicketCreated', { ticketId: refundTicket.id }), 'positive');
-    router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
+    const params = {
+        ticketsIds: [props.ticket.id],
+        withWarehouse: withWarehouse,
+        negative: true,
+    };
+
+    try {
+        const { data } = await axios.post('Tickets/cloneAll', params);
+        const [refundTicket] = data;
+        notify(t('refundTicketCreated', { ticketId: refundTicket.id }), 'positive');
+        router.push({ name: 'TicketSale', params: { id: refundTicket.id } });
+    } catch (error) {
+        console.error(error);
+    }
 };
 </script>
 

From d0ccce26567cee02c49466d62a78312d45529ee3 Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Fri, 23 Aug 2024 10:32:36 +0200
Subject: [PATCH 47/60] feat: refs #7346 elimino ===

---
 src/pages/InvoiceOut/InvoiceOutGlobalForm.vue | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index 3dc34dc15..362ede4be 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -26,14 +26,9 @@ const clientsOptions = ref([]);
 const serialTypesOptions = ref([]);
 
 const handleInvoiceOutSerialsFetch = (data) => {
-    const uniqueTypes = new Set();
-    serialTypesOptions.value = data.filter((item) => {
-        if (item.type === null || uniqueTypes.has(item.type)) {
-            return false;
-        }
-        uniqueTypes.add(item.type);
-        return true;
-    });
+    serialTypesOptions.value = Array.from(
+        new Set(data.map((item) => item.type).filter((type) => type))
+    );
 };
 
 const formData = ref({});

From efc7df5a60cd4b5ea0c0fa00a8a8b55c20840bca Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Fri, 23 Aug 2024 12:19:54 +0200
Subject: [PATCH 48/60] delivery traducido a Reparto

---
 src/pages/Claim/locale/es.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/Claim/locale/es.yml b/src/pages/Claim/locale/es.yml
index 90bef8e66..052416aa7 100644
--- a/src/pages/Claim/locale/es.yml
+++ b/src/pages/Claim/locale/es.yml
@@ -42,7 +42,7 @@ claim:
     pickup: Recoger
     null: No
     agency: Agencia
-    delivery: Entrega
+    delivery: Reparto
     fileDescription: 'ID de reclamación {claimId} del cliente {clientName} con ID {clientId}'
     noData: 'No hay imágenes/videos, haz clic aquí o arrastra y suelta el archivo'
     dragDrop: Arrastra y suelta aquí

From fb0ed9c4ce8b5ab956d1a74de4031c941a40f2cc Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Fri, 23 Aug 2024 13:52:46 +0200
Subject: [PATCH 49/60] para que no de el fallo de muchos registros

---
 src/pages/InvoiceOut/InvoiceOutGlobalForm.vue | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
index 362ede4be..2070ef09c 100644
--- a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
+++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue
@@ -22,7 +22,6 @@ const { makeInvoice, setStatusValue } = invoiceOutGlobalStore;
 const clientsToInvoice = ref('all');
 const companiesOptions = ref([]);
 const printersOptions = ref([]);
-const clientsOptions = ref([]);
 const serialTypesOptions = ref([]);
 
 const handleInvoiceOutSerialsFetch = (data) => {
@@ -37,7 +36,6 @@ const optionsInitialData = computed(() => {
     const optionsArrays = [
         companiesOptions.value,
         printersOptions.value,
-        clientsOptions.value,
         serialTypesOptions.value,
     ];
     return optionsArrays.every((arr) => arr.length > 0);
@@ -65,7 +63,6 @@ onMounted(async () => {
         auto-load
     />
     <FetchData url="Printers" @on-fetch="(data) => (printersOptions = data)" auto-load />
-    <FetchData url="Clients" @on-fetch="(data) => (clientsOptions = data)" auto-load />
     <FetchData
         url="invoiceOutSerials"
         @on-fetch="handleInvoiceOutSerialsFetch"
@@ -97,7 +94,7 @@ onMounted(async () => {
                 v-if="clientsToInvoice === 'one'"
                 :label="t('client')"
                 v-model="formData.clientId"
-                :options="clientsOptions"
+                url="Clients"
                 option-value="id"
                 option-label="name"
                 hide-selected

From 566a649c208c4a1d85ba7b0438b3abe730f49d61 Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Mon, 26 Aug 2024 16:34:46 +0200
Subject: [PATCH 50/60] fix: refs #7524 use limit item Section

---
 src/components/ui/VnFilterPanel.vue   |  2 +-
 src/pages/Item/Card/ItemBotanical.vue | 28 +++++++++------------------
 src/pages/Item/Card/ItemShelving.vue  | 24 ++++++-----------------
 src/pages/Item/Card/ItemTags.vue      |  4 +++-
 src/pages/Item/ItemListFilter.vue     | 12 ++++--------
 src/pages/Item/ItemRequest.vue        | 12 +++---------
 src/pages/Item/ItemRequestFilter.vue  | 18 ++++-------------
 7 files changed, 30 insertions(+), 70 deletions(-)

diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue
index ab4e2e0da..b12a9f7b8 100644
--- a/src/components/ui/VnFilterPanel.vue
+++ b/src/components/ui/VnFilterPanel.vue
@@ -202,7 +202,7 @@ function formatValue(value) {
 function sanitizer(params) {
     for (const [key, value] of Object.entries(params)) {
         if (typeof value == 'object')
-            params[key] = Object.values(value)[0].replaceAll('%', '');
+            params[key] = Object.values(value)?.[0]?.replaceAll('%', '');
     }
     return params;
 }
diff --git a/src/pages/Item/Card/ItemBotanical.vue b/src/pages/Item/Card/ItemBotanical.vue
index 416c7f78b..599cd2b32 100644
--- a/src/pages/Item/Card/ItemBotanical.vue
+++ b/src/pages/Item/Card/ItemBotanical.vue
@@ -19,15 +19,6 @@ const itemSpeciesOptions = ref([]);
 const itemBotanicals = ref([]);
 let itemBotanicalsForm = reactive({ itemFk: null });
 
-const onGenusCreated = (response, formData) => {
-    itemGenusOptions.value = [...itemGenusOptions.value, response];
-    formData.genusFk = response.id;
-};
-
-const onSpecieCreated = (response, formData) => {
-    itemSpeciesOptions.value = [...itemSpeciesOptions.value, response];
-    formData.specieFk = response.id;
-};
 const entityId = computed(() => {
     return route.params.id;
 });
@@ -69,36 +60,35 @@ onMounted(async () => {
         <template #form="{ data }">
             <VnRow>
                 <VnSelectDialog
+                    ref="genusRef"
                     :label="t('Genus')"
                     v-model="data.genusFk"
-                    :options="itemGenusOptions"
+                    url="Genera"
                     option-label="name"
                     option-value="id"
+                    :fields="['id', 'name']"
+                    sort-by="name ASC"
                     hide-selected
                 >
                     <template #form>
                         <CreateGenusForm
-                            @on-data-saved="
-                                (_, requestResponse) =>
-                                    onGenusCreated(requestResponse, data)
-                            "
+                            @on-data-saved="(_, res) => (data.genusFk = res.id)"
                         />
                     </template>
                 </VnSelectDialog>
                 <VnSelectDialog
                     :label="t('Species')"
                     v-model="data.specieFk"
-                    :options="itemSpeciesOptions"
+                    url="Species"
                     option-label="name"
                     option-value="id"
+                    :fields="['id', 'name']"
+                    sort-by="name ASC"
                     hide-selected
                 >
                     <template #form>
                         <CreateSpecieForm
-                            @on-data-saved="
-                                (_, requestResponse) =>
-                                    onSpecieCreated(requestResponse, data)
-                            "
+                            @on-data-saved="(_, res) => (data.specieFk = res.id)"
                         />
                     </template>
                 </VnSelectDialog>
diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue
index 7e7faab36..41cb34c03 100644
--- a/src/pages/Item/Card/ItemShelving.vue
+++ b/src/pages/Item/Card/ItemShelving.vue
@@ -3,7 +3,6 @@ import { onMounted, ref, computed, reactive } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
-import FetchData from 'components/FetchData.vue';
 import VnInput from 'src/components/common/VnInput.vue';
 import VnSelect from 'src/components/common/VnSelect.vue';
 import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
@@ -24,8 +23,6 @@ const { notify } = useNotify();
 const { openConfirmationModal } = useVnConfirm();
 
 const rowsSelected = ref([]);
-const parkingsOptions = ref([]);
-const shelvingsOptions = ref([]);
 
 const exprBuilder = (param, value) => {
     switch (param) {
@@ -104,7 +101,9 @@ const columns = computed(() => [
             filterValue: null,
             event: getInputEvents,
             attrs: {
-                options: parkingsOptions.value,
+                url: 'parkings',
+                fields: ['code'],
+                'sort-by': 'code ASC',
                 'option-value': 'code',
                 'option-label': 'code',
                 dense: true,
@@ -124,7 +123,9 @@ const columns = computed(() => [
             filterValue: null,
             event: getInputEvents,
             attrs: {
-                options: shelvingsOptions.value,
+                url: 'shelvings',
+                fields: ['code'],
+                'sort-by': 'code ASC',
                 'option-value': 'code',
                 'option-label': 'code',
                 dense: true,
@@ -188,18 +189,6 @@ onMounted(async () => {
 </script>
 
 <template>
-    <FetchData
-        url="parkings"
-        :filter="{ fields: ['code'], order: 'code ASC' }"
-        auto-load
-        @on-fetch="(data) => (parkingsOptions = data)"
-    />
-    <FetchData
-        url="shelvings"
-        :filter="{ fields: ['code'], order: 'code ASC' }"
-        auto-load
-        @on-fetch="(data) => (shelvingsOptions = data)"
-    />
     <template v-if="stateStore.isHeaderMounted()">
         <Teleport to="#st-data">
             <div class="q-pa-md q-mr-lg q-ma-xs" style="border: 2px solid #222">
@@ -237,7 +226,6 @@ onMounted(async () => {
             </QBtn>
         </Teleport>
     </template>
-
     <QPage class="column items-center q-pa-md">
         <QTable
             :rows="rows"
diff --git a/src/pages/Item/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue
index 40c9941e9..39723ae65 100644
--- a/src/pages/Item/Card/ItemTags.vue
+++ b/src/pages/Item/Card/ItemTags.vue
@@ -24,6 +24,7 @@ const getSelectedTagValues = async (tag) => {
         const filter = {
             fields: ['value'],
             order: 'value ASC',
+            limit: 30,
         };
 
         const params = { filter: JSON.stringify(filter) };
@@ -126,7 +127,7 @@ const insertTag = (rows) => {
                                 :key="row.tagFk"
                                 :label="t('Value')"
                                 v-model="row.value"
-                                :options="valueOptionsMap.get(row.tagFk)"
+                                :url="`Tags/${row.tagFk}/filterValue`"
                                 option-label="value"
                                 option-value="value"
                                 emit-value
@@ -135,6 +136,7 @@ const insertTag = (rows) => {
                                 :is-clearable="false"
                                 :required="false"
                                 :rules="validate('itemTag.tagFk')"
+                                :use-like="false"
                             />
                             <VnInput
                                 v-else-if="
diff --git a/src/pages/Item/ItemListFilter.vue b/src/pages/Item/ItemListFilter.vue
index 62c0c56dc..22dce9c64 100644
--- a/src/pages/Item/ItemListFilter.vue
+++ b/src/pages/Item/ItemListFilter.vue
@@ -30,7 +30,7 @@ const itemTypesRef = ref(null);
 const categoriesOptions = ref([]);
 const itemTypesOptions = ref([]);
 const buyersOptions = ref([]);
-const suppliersOptions = ref([]);
+const tagOptions = ref([]);
 const tagValues = ref([]);
 const fieldFiltersValues = ref([]);
 const moreFields = ref([]);
@@ -161,12 +161,6 @@ onMounted(async () => {
         @on-fetch="(data) => (buyersOptions = data)"
         auto-load
     />
-    <FetchData
-        url="Suppliers"
-        :filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC' }"
-        @on-fetch="(data) => (suppliersOptions = data)"
-        auto-load
-    />
     <FetchData
         url="Tags"
         :filter="{ fields: ['id', 'name', 'isFree'] }"
@@ -261,9 +255,11 @@ onMounted(async () => {
                         :label="t('params.supplierFk')"
                         v-model="params.supplierFk"
                         @update:model-value="searchFn()"
-                        :options="suppliersOptions"
+                        url="Suppliers"
                         option-value="id"
                         option-label="name"
+                        :fields="['id', 'name', 'nickname']"
+                        sort-by="name ASC"
                         hide-selected
                         dense
                         outlined
diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue
index ae6638953..c1ee15a7e 100644
--- a/src/pages/Item/ItemRequest.vue
+++ b/src/pages/Item/ItemRequest.vue
@@ -22,7 +22,6 @@ import RightMenu from 'src/components/common/RightMenu.vue';
 const { t } = useI18n();
 const { notify } = useNotify();
 const stateStore = useStateStore();
-const workersOptions = ref([]);
 let filterParams = ref({});
 const denyFormRef = ref(null);
 const denyRequestId = ref(null);
@@ -208,13 +207,6 @@ onBeforeMount(() => {
 </script>
 
 <template>
-    <FetchData
-        url="Workers"
-        :filter="{ where: { role: 'buyer' } }"
-        order="id"
-        @on-fetch="(data) => (workersOptions = data)"
-        auto-load
-    />
     <VnSearchbar
         data-key="ItemRequests"
         url="TicketRequests/filter"
@@ -268,7 +260,9 @@ onBeforeMount(() => {
                 <QTd>
                     <VnSelect
                         v-model="row.attenderFk"
-                        :options="workersOptions"
+                        :where="{ role: 'buyer' }"
+                        sort-by="id"
+                        url="Workers"
                         hide-selected
                         option-label="firstName"
                         option-value="id"
diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue
index c9340dc1f..aa07b8d50 100644
--- a/src/pages/Item/ItemRequestFilter.vue
+++ b/src/pages/Item/ItemRequestFilter.vue
@@ -24,7 +24,6 @@ const stateOptions = [
 
 const itemTypesOptions = ref([]);
 const warehousesOptions = ref([]);
-const workersOptions = ref([]);
 
 const exprBuilder = (param, value) => {
     switch (param) {
@@ -72,18 +71,6 @@ const decrement = (paramsObj, key) => {
         @on-fetch="(data) => (warehousesOptions = data)"
         auto-load
     />
-    <FetchData
-        url="Workers/search"
-        :filter="{
-            fields: ['id', 'name'],
-            order: 'name ASC',
-        }"
-        :params="{
-            departmentCodes: ['VT'],
-        }"
-        @on-fetch="(data) => (workersOptions = data)"
-        auto-load
-    />
     <VnFilterPanel
         :data-key="props.dataKey"
         :search-button="true"
@@ -162,7 +149,10 @@ const decrement = (paramsObj, key) => {
                         :label="t('params.requesterFk')"
                         v-model="params.requesterFk"
                         @update:model-value="searchFn()"
-                        :options="workersOptions"
+                        url="Workers/search"
+                        :fields="['id', 'name']"
+                        order="name ASC"
+                        :params="{ departmentCodes: ['VT'] }"
                         option-value="id"
                         option-label="name"
                         hide-selected

From 7c2a13db0b8b71388713b341d0c2710df2f0e318 Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Mon, 26 Aug 2024 16:39:17 +0200
Subject: [PATCH 51/60] fix: refs #7524 use limit shelving Section

---
 src/pages/Shelving/Card/ShelvingFilter.vue | 55 +++++++---------------
 src/pages/Shelving/Card/ShelvingForm.vue   | 48 +++----------------
 2 files changed, 24 insertions(+), 79 deletions(-)

diff --git a/src/pages/Shelving/Card/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue
index abc91373b..0056ffaec 100644
--- a/src/pages/Shelving/Card/ShelvingFilter.vue
+++ b/src/pages/Shelving/Card/ShelvingFilter.vue
@@ -3,6 +3,7 @@ import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import FetchData from 'components/FetchData.vue';
 import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
+import VnSelect from 'src/components/common/VnSelect.vue';
 
 const { t } = useI18n();
 const props = defineProps({
@@ -15,25 +16,13 @@ const props = defineProps({
 const emit = defineEmits(['search']);
 
 const workers = ref();
-const parkings = ref();
 
 function setWorkers(data) {
     workers.value = data;
 }
-
-function setParkings(data) {
-    parkings.value = data;
-}
 </script>
 
 <template>
-    <FetchData
-        url="Parkings"
-        :filter="{ fields: ['id', 'code'] }"
-        sort-by="code ASC"
-        @on-fetch="setParkings"
-        auto-load
-    />
     <FetchData
         url="Workers/activeWithInheritedRole"
         :filter="{ where: { role: 'salesPerson' } }"
@@ -54,44 +43,36 @@ function setParkings(data) {
         </template>
         <template #body="{ params }">
             <QItem class="q-my-sm">
-                <QItemSection v-if="!parkings">
-                    <QSkeleton type="QInput" class="full-width" />
-                </QItemSection>
-                <QItemSection v-if="parkings">
-                    <QSelect
+                <QItemSection>
+                    <VnSelect
+                        v-model="params.parkingFk"
+                        url="Parkings"
+                        :fields="['id', 'code']"
+                        :label="t('params.parkingFk')"
+                        option-value="id"
+                        option-label="code"
+                        :filter-options="['id', 'code']"
                         dense
                         outlined
                         rounded
-                        :label="t('params.parkingFk')"
-                        v-model="params.parkingFk"
-                        :options="parkings"
-                        option-value="id"
-                        option-label="code"
-                        emit-value
-                        map-options
-                        use-input
-                        :input-debounce="0"
+                        sort-by="code ASC"
                     />
                 </QItemSection>
             </QItem>
             <QItem class="q-mb-sm">
-                <QItemSection v-if="!workers">
-                    <QSkeleton type="QInput" class="full-width" />
-                </QItemSection>
-                <QItemSection v-if="workers">
-                    <QSelect
+                <QItemSection>
+                    <VnSelect
                         dense
                         outlined
                         rounded
                         :label="t('params.userFk')"
                         v-model="params.userFk"
-                        :options="workers"
+                        url="Workers/activeWithInheritedRole"
                         option-value="id"
-                        option-label="name"
-                        emit-value
-                        map-options
-                        use-input
-                        :input-debounce="0"
+                        option-label="firstName"
+                        :where="{ role: 'salesPerson' }"
+                        sort-by="firstName ASC"
+                        :use-like="false"
                     />
                 </QItemSection>
             </QItem>
diff --git a/src/pages/Shelving/Card/ShelvingForm.vue b/src/pages/Shelving/Card/ShelvingForm.vue
index dd1c4e4a2..aee6f7f3a 100644
--- a/src/pages/Shelving/Card/ShelvingForm.vue
+++ b/src/pages/Shelving/Card/ShelvingForm.vue
@@ -1,12 +1,11 @@
 <script setup>
 import { useI18n } from 'vue-i18n';
-import { ref } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import VnRow from 'components/ui/VnRow.vue';
-import FetchData from 'components/FetchData.vue';
 import FormModel from 'components/FormModel.vue';
 import VnInput from 'src/components/common/VnInput.vue';
 import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
+import VnSelect from 'src/components/common/VnSelect.vue';
 
 const { t } = useI18n();
 const route = useRoute();
@@ -20,30 +19,6 @@ const defaultInitialData = {
     isRecyclable: false,
 };
 
-const parkingFilter = { fields: ['id', 'code'] };
-const parkingList = ref([]);
-const parkingListCopy = ref([]);
-
-const setParkingList = (data) => {
-    parkingList.value = data;
-    parkingListCopy.value = data;
-};
-
-const parkingSelectFilter = {
-    options: parkingList,
-    filterFn: (options, value) => {
-        const search = value.trim().toLowerCase();
-
-        if (!search || search === '') {
-            return parkingListCopy.value;
-        }
-
-        return options.value.filter((option) =>
-            option.code.toLowerCase().startsWith(search)
-        );
-    },
-};
-
 const shelvingFilter = {
     include: [
         {
@@ -68,12 +43,6 @@ const onSave = (shelving, newShelving) => {
 </script>
 <template>
     <VnSubToolbar />
-    <FetchData
-        url="Parkings"
-        :filter="parkingFilter"
-        @on-fetch="setParkingList"
-        auto-load
-    />
     <FormModel
         :url="isNew ? null : `Shelvings/${shelvingId}`"
         :url-create="isNew ? 'Shelvings' : null"
@@ -84,27 +53,22 @@ const onSave = (shelving, newShelving) => {
         :form-initial-data="defaultInitialData"
         @on-data-saved="onSave"
     >
-        <template #form="{ data, validate, filter }">
+        <template #form="{ data, validate }">
             <VnRow>
                 <VnInput
                     v-model="data.code"
                     :label="t('shelving.basicData.code')"
                     :rules="validate('Shelving.code')"
                 />
-                <QSelect
+                <VnSelect
                     v-model="data.parkingFk"
-                    :options="parkingList"
+                    url="Parkings"
                     option-value="id"
                     option-label="code"
-                    emit-value
+                    :filter-options="['id', 'code']"
+                    :fields="['id', 'code']"
                     :label="t('shelving.basicData.parking')"
-                    map-options
-                    use-input
-                    @filter="
-                        (value, update) => filter(value, update, parkingSelectFilter)
-                    "
                     :rules="validate('Shelving.parkingFk')"
-                    :input-debounce="0"
                 />
             </VnRow>
             <VnRow>

From 717c52fc02a39edeb554ac9b644daf088c06051c Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Mon, 26 Aug 2024 16:55:16 +0200
Subject: [PATCH 52/60] fix: refs #7524 use id

---
 src/pages/Customer/Card/CustomerBasicData.vue | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue
index 87a3b08f7..a6150bb3f 100644
--- a/src/pages/Customer/Card/CustomerBasicData.vue
+++ b/src/pages/Customer/Card/CustomerBasicData.vue
@@ -97,7 +97,12 @@ const title = ref();
                     :rules="validate('client.salesPersonFk')"
                     :use-like="false"
                     :emit-value="false"
-                    @update:model-value="(val) => (title = val?.nickname)"
+                    @update:model-value="
+                        (val) => {
+                            title = val?.nickname;
+                            data.salesPersonFk = val?.id;
+                        }
+                    "
                 >
                     <template #prepend>
                         <VnAvatar

From 4d7a4be2c044ab18ab4eda266b6666aefe1804d5 Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Tue, 27 Aug 2024 09:54:21 +0200
Subject: [PATCH 53/60] fix: refs #7524 use limit entry Section wip

---
 src/pages/Entry/Card/EntryBasicData.vue | 22 ++++------------------
 src/pages/Entry/EntryFilter.vue         | 11 +----------
 2 files changed, 5 insertions(+), 28 deletions(-)

diff --git a/src/pages/Entry/Card/EntryBasicData.vue b/src/pages/Entry/Card/EntryBasicData.vue
index e5d6f50d9..b81b1db22 100644
--- a/src/pages/Entry/Card/EntryBasicData.vue
+++ b/src/pages/Entry/Card/EntryBasicData.vue
@@ -19,8 +19,6 @@ const { t } = useI18n();
 const { hasAny } = useRole();
 const isAdministrative = () => hasAny(['administrative']);
 
-const suppliersOptions = ref([]);
-const travelsOptions = ref([]);
 const companiesOptions = ref([]);
 const currenciesOptions = ref([]);
 
@@ -29,20 +27,6 @@ const onFilterTravelSelected = (formData, id) => {
 };
 </script>
 <template>
-    <FetchData
-        url="Suppliers"
-        :filter="{ fields: ['id', 'nickname'] }"
-        order="nickname"
-        @on-fetch="(data) => (suppliersOptions = data)"
-        auto-load
-    />
-    <FetchData
-        url="Travels/filter"
-        :filter="{ fields: ['id', 'warehouseInName'] }"
-        order="id"
-        @on-fetch="(data) => (travelsOptions = data)"
-        auto-load
-    />
     <FetchData
         ref="companiesRef"
         url="Companies"
@@ -71,9 +55,10 @@ const onFilterTravelSelected = (formData, id) => {
                 <VnSelect
                     :label="t('entry.basicData.supplier')"
                     v-model="data.supplierFk"
-                    :options="suppliersOptions"
+                    url="Suppliers"
                     option-value="id"
                     option-label="nickname"
+                    :fields="['id', 'nickname']"
                     hide-selected
                     :required="true"
                     map-options
@@ -92,7 +77,8 @@ const onFilterTravelSelected = (formData, id) => {
                 <VnSelectDialog
                     :label="t('entry.basicData.travel')"
                     v-model="data.travelFk"
-                    :options="travelsOptions"
+                    url="Travels/filter"
+                    :fields="['id', 'warehouseInName']"
                     option-value="id"
                     option-label="warehouseInName"
                     map-options
diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue
index 194ce0a28..c4d821842 100644
--- a/src/pages/Entry/EntryFilter.vue
+++ b/src/pages/Entry/EntryFilter.vue
@@ -20,7 +20,6 @@ const props = defineProps({
 
 const currenciesOptions = ref([]);
 const companiesOptions = ref([]);
-const suppliersOptions = ref([]);
 
 const stateStore = useStateStore();
 onMounted(async () => {
@@ -45,14 +44,6 @@ onMounted(async () => {
         @on-fetch="(data) => (currenciesOptions = data)"
         auto-load
     />
-    <FetchData
-        url="Suppliers"
-        :filter="{ fields: ['id', 'nickname', 'name'] }"
-        order="nickname"
-        @on-fetch="(data) => (suppliersOptions = data)"
-        auto-load
-    />
-
     <VnFilterPanel :data-key="props.dataKey" :search-button="true">
         <template #tags="{ tag, formatFn }">
             <div class="q-gutter-x-xs">
@@ -135,7 +126,7 @@ onMounted(async () => {
                         :label="t('params.supplierFk')"
                         v-model="params.supplierFk"
                         @update:model-value="searchFn()"
-                        :options="suppliersOptions"
+                        url="Suppliers"
                         option-value="id"
                         option-label="name"
                         hide-selected

From 4a815988837815dd835650c654af639a23ffedb4 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Tue, 27 Aug 2024 10:27:43 +0200
Subject: [PATCH 54/60] fix: ticketDescriptorMenu

---
 src/pages/Ticket/Card/TicketDescriptorMenu.vue | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue
index c7784bc2a..d5a578ca7 100644
--- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue
+++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue
@@ -30,10 +30,13 @@ const actions = {
         let clonedTicketId;
 
         try {
-            const { data } = await axios.post(`Tickets/${ticketId}/clone`, {
+            const { data } = await axios.post(`Tickets/cloneAll`, {
                 shipped: ticket.value.shipped,
+                ticketsIds: [ticket.value.id],
+                withWarehouse: true,
+                negative: false,
             });
-            clonedTicketId = data;
+            clonedTicketId = data[0].id;
         } catch (e) {
             opts.message = t('It was not able to clone the ticket');
             opts.type = 'negative';

From ceec78da17b6ceec640e6fb6d37d37c49d8d43b3 Mon Sep 17 00:00:00 2001
From: Javier Segarra <jsegarra@verdnatura.es>
Date: Tue, 27 Aug 2024 10:27:51 +0200
Subject: [PATCH 55/60] test: ticketDescriptorMenu

---
 test/cypress/integration/ticket/ticketDescriptor.spec.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/cypress/integration/ticket/ticketDescriptor.spec.js b/test/cypress/integration/ticket/ticketDescriptor.spec.js
index fc920f346..882aab157 100644
--- a/test/cypress/integration/ticket/ticketDescriptor.spec.js
+++ b/test/cypress/integration/ticket/ticketDescriptor.spec.js
@@ -16,7 +16,7 @@ describe('Ticket descriptor', () => {
         cy.openActionsDescriptor();
         cy.get(toCloneOpt).click();
         cy.clickConfirm();
-        cy.get(warehouseValue).contains('-');
+        cy.get(warehouseValue).contains('Warehouse One');
         cy.get(summaryHeader)
             .invoke('text')
             .then((text) => {

From 3051b134781a819dfecb321a9f223017d66456a8 Mon Sep 17 00:00:00 2001
From: jgallego <jgallego@verdnatura.es>
Date: Wed, 28 Aug 2024 17:31:39 +0200
Subject: [PATCH 56/60] warmFix fields with wrong name

---
 src/pages/InvoiceOut/InvoiceOutList.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue
index b08e12b3e..9423c28f6 100644
--- a/src/pages/InvoiceOut/InvoiceOutList.vue
+++ b/src/pages/InvoiceOut/InvoiceOutList.vue
@@ -242,7 +242,7 @@ watchEffect(selectedRows);
             />
             <VnSelect
                 url="InvoiceOutSerials"
-                v-model="data.invoiceOutSerial"
+                v-model="data.serial"
                 :label="t('invoiceOutList.tableVisibleColumns.invoiceOutSerial')"
                 :options="invoiceOutSerialsOptions"
                 option-label="description"
@@ -254,7 +254,7 @@ watchEffect(selectedRows);
             />
             <VnSelect
                 url="TaxAreas"
-                v-model="data.area"
+                v-model="data.taxArea"
                 :label="t('invoiceOutList.tableVisibleColumns.taxArea')"
                 :options="taxAreasOptions"
                 option-label="code"

From 77c93117609a31e0a1f653daf9f42b5766f5e63b Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Wed, 28 Aug 2024 17:39:39 +0200
Subject: [PATCH 57/60] fix: refs #7524 use limit travels section

---
 src/pages/Travel/ExtraCommunityFilter.vue | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue
index c51151451..a8bbde75b 100644
--- a/src/pages/Travel/ExtraCommunityFilter.vue
+++ b/src/pages/Travel/ExtraCommunityFilter.vue
@@ -20,7 +20,6 @@ const props = defineProps({
 const warehousesOptions = ref([]);
 const continentsOptions = ref([]);
 const agenciesOptions = ref([]);
-const suppliersOptions = ref([]);
 const warehousesByContinent = ref({});
 
 const add = (paramsObj, key) => {
@@ -76,12 +75,6 @@ warehouses();
         @on-fetch="(data) => (agenciesOptions = data)"
         auto-load
     />
-    <FetchData
-        url="Suppliers"
-        @on-fetch="(data) => (suppliersOptions = data)"
-        auto-load
-    />
-
     <VnFilterPanel :data-key="props.dataKey" :search-button="true">
         <template #tags="{ tag, formatFn }">
             <div class="q-gutter-x-xs">
@@ -220,7 +213,7 @@ warehouses();
                     <VnSelect
                         :label="t('globals.pageTitles.supplier')"
                         v-model="params.cargoSupplierFk"
-                        :options="suppliersOptions"
+                        url="Suppliers"
                         option-value="id"
                         option-label="name"
                         hide-selected

From 42e7629c4089b04f7867adb231628c8e666855da Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Thu, 29 Aug 2024 13:53:10 +0200
Subject: [PATCH 58/60] fix: refs #7524 use limit ticket section

---
 .../Ticket/Card/BasicData/TicketBasicDataForm.vue  | 14 +++-----------
 src/pages/Ticket/Card/TicketExpedition.vue         | 12 +++---------
 src/pages/Ticket/Card/TicketSale.vue               | 11 +++--------
 src/pages/Ticket/Card/TicketSaleTracking.vue       | 11 ++---------
 4 files changed, 11 insertions(+), 37 deletions(-)

diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue
index 2d937346a..28c6fcf15 100644
--- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue
+++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue
@@ -30,7 +30,6 @@ const { t } = useI18n();
 const agencyFetchRef = ref(null);
 const zonesFetchRef = ref(null);
 
-const clientsOptions = ref([]);
 const warehousesOptions = ref([]);
 const companiesOptions = ref([]);
 const agenciesOptions = ref([]);
@@ -273,15 +272,6 @@ const redirectToCustomerAddress = () => {
 onMounted(() => onFormModelInit());
 </script>
 <template>
-    <FetchData
-        url="Clients"
-        :filter="{
-            fields: ['id', 'name'],
-            order: 'id',
-        }"
-        @on-fetch="(data) => (clientsOptions = data)"
-        auto-load
-    />
     <FetchData
         url="Warehouses"
         @on-fetch="(data) => (warehousesOptions = data)"
@@ -317,7 +307,9 @@ onMounted(() => onFormModelInit());
                 v-model="clientId"
                 option-value="id"
                 option-label="name"
-                :options="clientsOptions"
+                url="Clients"
+                :fields="['id', 'name']"
+                sort-by="id"
                 hide-selected
                 map-options
                 :required="true"
diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue
index f344e4276..c4ab63b39 100644
--- a/src/pages/Ticket/Card/TicketExpedition.vue
+++ b/src/pages/Ticket/Card/TicketExpedition.vue
@@ -3,7 +3,6 @@ import { onMounted, ref, computed, onUnmounted, reactive, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
 
-import FetchData from 'components/FetchData.vue';
 import VnInput from 'src/components/common/VnInput.vue';
 import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
 import TicketEditManaProxy from './TicketEditMana.vue';
@@ -35,7 +34,6 @@ const selectedExpeditions = ref([]);
 const allColumnNames = ref([]);
 const visibleColumns = ref([]);
 const newTicketWithRoute = ref(false);
-const itemsOptions = ref([]);
 
 const exprBuilder = (param, value) => {
     switch (param) {
@@ -139,7 +137,9 @@ const columns = computed(() => [
             filterValue: null,
             event: getInputEvents,
             attrs: {
-                options: itemsOptions.value,
+                url: 'Items',
+                fields: ['id', 'name'],
+                'sort-by': 'name ASC',
                 'option-value': 'id',
                 'option-label': 'name',
                 dense: true,
@@ -274,12 +274,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
 </script>
 
 <template>
-    <FetchData
-        url="Items"
-        auto-load
-        :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
-        @on-fetch="(data) => (itemsOptions = data)"
-    />
     <VnSubToolbar>
         <template #st-data>
             <TableVisibleColumns
diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue
index 5978dbf09..bd7297b56 100644
--- a/src/pages/Ticket/Card/TicketSale.vue
+++ b/src/pages/Ticket/Card/TicketSale.vue
@@ -38,7 +38,6 @@ const ticketConfig = ref(null);
 const isLocked = ref(false);
 const isTicketEditable = ref(false);
 const sales = ref([]);
-const itemsWithNameOptions = ref([]);
 const editableStatesOptions = ref([]);
 const selectedSales = ref([]);
 const mana = ref(null);
@@ -435,12 +434,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
         auto-load
         @on-fetch="(data) => (isLocked = data)"
     />
-    <FetchData
-        url="Items/withName"
-        :filter="{ fields: ['id', 'name'], order: 'id DESC' }"
-        auto-load
-        @on-fetch="(data) => (itemsWithNameOptions = data)"
-    />
     <FetchData
         url="States/editableStates"
         :filter="{ fields: ['code', 'name', 'id', 'alertLevel'], order: 'name ASC' }"
@@ -626,10 +619,12 @@ onUnmounted(() => (stateStore.rightDrawer = false));
                 </div>
                 <VnSelect
                     v-else
-                    :options="itemsWithNameOptions"
                     hide-selected
                     option-label="name"
                     option-value="id"
+                    url="Items/withName"
+                    :fields="['id', 'name']"
+                    sort-by="id DESC"
                     @update:model-value="changeQuantity(row)"
                     v-model="row.itemFk"
                 >
diff --git a/src/pages/Ticket/Card/TicketSaleTracking.vue b/src/pages/Ticket/Card/TicketSaleTracking.vue
index d519bc2c6..b8e166e6a 100644
--- a/src/pages/Ticket/Card/TicketSaleTracking.vue
+++ b/src/pages/Ticket/Card/TicketSaleTracking.vue
@@ -26,7 +26,6 @@ const saleTrackingFetchDataRef = ref(null);
 const sales = ref([]);
 const saleTrackings = ref([]);
 const itemShelvignsSales = ref([]);
-const shelvingsOptions = ref([]);
 const parkingsOptions = ref([]);
 const saleTrackingUrl = computed(() => `SaleTrackings/${route.params.id}/filter`);
 const oldQuantity = ref(null);
@@ -330,12 +329,6 @@ const qCheckBoxController = (sale, action) => {
         auto-load
         @on-fetch="(data) => (sales = data)"
     />
-    <FetchData
-        url="Shelvings"
-        auto-load
-        @on-fetch="(data) => (shelvingsOptions = data)"
-    />
-    <FetchData url="Parkings" auto-load @on-fetch="(data) => (parkingsOptions = data)" />
     <QTable
         :rows="sales"
         :columns="columns"
@@ -500,7 +493,7 @@ const qCheckBoxController = (sale, action) => {
             <template #body-cell-shelving="{ row }">
                 <QTd auto-width>
                     <VnSelect
-                        :options="shelvingsOptions"
+                        url="Shelvings"
                         hide-selected
                         option-label="code"
                         option-value="code"
@@ -513,7 +506,7 @@ const qCheckBoxController = (sale, action) => {
             <template #body-cell-parking="{ row }">
                 <QTd auto-width>
                     <VnSelect
-                        :options="parkingsOptions"
+                        url="Parkings"
                         hide-selected
                         option-label="code"
                         option-value="id"

From 7bac607746df61570dea888a54bc97b896264293 Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Thu, 29 Aug 2024 14:20:52 +0200
Subject: [PATCH 59/60] fix: refs #7524 add sort-by

---
 src/pages/Entry/EntryFilter.vue | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/pages/Entry/EntryFilter.vue b/src/pages/Entry/EntryFilter.vue
index c4d821842..3b88072fa 100644
--- a/src/pages/Entry/EntryFilter.vue
+++ b/src/pages/Entry/EntryFilter.vue
@@ -129,6 +129,8 @@ onMounted(async () => {
                         url="Suppliers"
                         option-value="id"
                         option-label="name"
+                        :fields="['id', 'name', 'nickname']"
+                        sort-by="nickname"
                         hide-selected
                         dense
                         outlined

From 56d226b36b7fbd5807dcf52a432cc56fa8aed0b2 Mon Sep 17 00:00:00 2001
From: jorgep <jorgep@verdnatura.es>
Date: Mon, 2 Sep 2024 10:03:01 +0200
Subject: [PATCH 60/60] fix: refs #7524 dynamic fetching

---
 src/pages/Item/Card/ItemBotanical.vue          | 12 ------------
 src/pages/Ticket/Card/TicketCreateTracking.vue | 12 +++---------
 src/pages/Ticket/Card/TicketSaleTracking.vue   |  1 -
 3 files changed, 3 insertions(+), 22 deletions(-)

diff --git a/src/pages/Item/Card/ItemBotanical.vue b/src/pages/Item/Card/ItemBotanical.vue
index 599cd2b32..e9f1f11ee 100644
--- a/src/pages/Item/Card/ItemBotanical.vue
+++ b/src/pages/Item/Card/ItemBotanical.vue
@@ -38,18 +38,6 @@ onMounted(async () => {
         }"
         @on-fetch="(data) => (itemBotanicals = data)"
     />
-    <FetchData
-        url="Genera"
-        :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
-        @on-fetch="(data) => (itemGenusOptions = data)"
-        auto-load
-    />
-    <FetchData
-        url="Species"
-        :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
-        @on-fetch="(data) => (itemSpeciesOptions = data)"
-        auto-load
-    />
     <FormModel
         url-update="ItemBotanicals"
         model="entry"
diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue
index 87ca8dd3f..d692f550d 100644
--- a/src/pages/Ticket/Card/TicketCreateTracking.vue
+++ b/src/pages/Ticket/Card/TicketCreateTracking.vue
@@ -17,9 +17,7 @@ const { t } = useI18n();
 const state = useState();
 const user = state.getUser();
 const stateFetchDataRef = ref(null);
-
 const statesOptions = ref([]);
-const workersOptions = ref([]);
 
 const onStateFkChange = (formData) => (formData.userFk = user.value.id);
 </script>
@@ -30,12 +28,6 @@ const onStateFkChange = (formData) => (formData.userFk = user.value.id);
         auto-load
         @on-fetch="(data) => (statesOptions = data)"
     />
-    <FetchData
-        url="Workers/search"
-        :filter="{ fields: ['id', 'name'], order: 'name ASC' }"
-        auto-load
-        @on-fetch="(data) => (workersOptions = data)"
-    />
     <FormModelPopup
         :title="t('Create tracking')"
         url-create="Tickets/state"
@@ -57,7 +49,9 @@ const onStateFkChange = (formData) => (formData.userFk = user.value.id);
                 <VnSelect
                     :label="t('tracking.worker')"
                     v-model="data.userFk"
-                    :options="workersOptions"
+                    url="Workers/search"
+                    fields=" ['id', 'name']"
+                    sort-by="name ASC"
                     hide-selected
                     option-label="name"
                     option-value="id"
diff --git a/src/pages/Ticket/Card/TicketSaleTracking.vue b/src/pages/Ticket/Card/TicketSaleTracking.vue
index b8e166e6a..e699d2bfd 100644
--- a/src/pages/Ticket/Card/TicketSaleTracking.vue
+++ b/src/pages/Ticket/Card/TicketSaleTracking.vue
@@ -26,7 +26,6 @@ const saleTrackingFetchDataRef = ref(null);
 const sales = ref([]);
 const saleTrackings = ref([]);
 const itemShelvignsSales = ref([]);
-const parkingsOptions = ref([]);
 const saleTrackingUrl = computed(() => `SaleTrackings/${route.params.id}/filter`);
 const oldQuantity = ref(null);