#7717 fix OrderCatalog #558

Merged
jon merged 39 commits from 7717_fixOrderCatalog into dev 2024-08-19 09:22:35 +00:00
21 changed files with 658 additions and 605 deletions

View File

@ -26,7 +26,10 @@ const url = computed(() => {
if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`; if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`;
return props.customUrl; return props.customUrl;
}); });
const searchRightDataKey = computed(() => {
if (!props.searchDataKey) return route.name;
return props.searchDataKey;
});
const arrayData = useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: url.value, url: url.value,
filter: props.filter, filter: props.filter,
@ -62,10 +65,9 @@ if (props.baseUrl) {
<slot name="searchbar" v-if="props.searchDataKey"> <slot name="searchbar" v-if="props.searchDataKey">
<VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" /> <VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" />
</slot> </slot>
<slot v-else name="searchbar" />
<RightMenu> <RightMenu>
<template #right-panel v-if="props.filterPanel"> <template #right-panel v-if="props.filterPanel">
<component :is="props.filterPanel" :data-key="props.searchDataKey" /> <component :is="props.filterPanel" :data-key="searchRightDataKey" />
</template> </template>
</RightMenu> </RightMenu>
<QPageContainer> <QPageContainer>

View File

@ -111,6 +111,7 @@ watch(
const isLoading = ref(false); const isLoading = ref(false);
async function search(evt) { async function search(evt) {
try {
if (evt && $props.disableSubmitEvent) return; if (evt && $props.disableSubmitEvent) return;
store.filter.where = {}; store.filter.where = {};
@ -123,9 +124,10 @@ async function search(evt) {
userParams.value = newParams; userParams.value = newParams;
if (!$props.showAll && !Object.values(filter).length) store.data = []; if (!$props.showAll && !Object.values(filter).length) store.data = [];
isLoading.value = false;
emit('search'); emit('search');
} finally {
isLoading.value = false;
}
} }
async function reload() { async function reload() {
@ -140,6 +142,7 @@ async function reload() {
} }
async function clearFilters() { async function clearFilters() {
try {
isLoading.value = true; isLoading.value = true;
store.userParamsChanged = true; store.userParamsChanged = true;
arrayData.reset(['skip', 'filter.skip', 'page']); arrayData.reset(['skip', 'filter.skip', 'page']);
@ -159,10 +162,11 @@ async function clearFilters() {
if (!$props.showAll) { if (!$props.showAll) {
store.data = []; store.data = [];
} }
isLoading.value = false;
emit('clear'); emit('clear');
emit('update:modelValue', userParams.value); emit('update:modelValue', userParams.value);
} finally {
isLoading.value = false;
}
} }
const tagsList = computed(() => { const tagsList = computed(() => {

View File

@ -104,9 +104,7 @@ onMounted(() => {
}); });
async function search() { async function search() {
const staticParams = Object.entries(store.userParams).filter( const staticParams = Object.entries(store.userParams);
([key, value]) => value && (props.staticParams || []).includes(key)
);
arrayData.reset(['skip', 'page']); arrayData.reset(['skip', 'page']);
if (props.makeFetch) if (props.makeFetch)

View File

@ -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;
}

View File

@ -11,6 +11,7 @@ import dashIfEmpty from './dashIfEmpty';
import dateRange from './dateRange'; import dateRange from './dateRange';
import toHour from './toHour'; import toHour from './toHour';
import dashOrCurrency from './dashOrCurrency'; import dashOrCurrency from './dashOrCurrency';
import getParamWhere from './getParamWhere';
export { export {
toLowerCase, toLowerCase,
@ -26,4 +27,5 @@ export {
toPercentage, toPercentage,
dashIfEmpty, dashIfEmpty,
dateRange, dateRange,
getParamWhere,
}; };

View File

@ -93,6 +93,7 @@ globals:
since: Since since: Since
from: From from: From
to: To to: To
notes: Notes
pageTitles: pageTitles:
logIn: Login logIn: Login
summary: Summary summary: Summary

View File

@ -93,6 +93,7 @@ globals:
since: Desde since: Desde
from: Desde from: Desde
to: Hasta to: Hasta
notes: Notas
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
summary: Resumen summary: Resumen

View File

@ -19,7 +19,7 @@ const props = defineProps({
<VnFilterPanel <VnFilterPanel
:data-key="props.dataKey" :data-key="props.dataKey"
:search-button="true" :search-button="true"
:unremovable-params="['from', 'to']" :un-removable-params="['from', 'to']"
:hidden-tags="['from', 'to']" :hidden-tags="['from', 'to']"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
@ -7,6 +7,7 @@ import { useState } from 'composables/useState';
import FormModel from 'components/FormModel.vue'; import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
@ -15,7 +16,6 @@ const route = useRoute();
const state = useState(); const state = useState();
const ORDER_MODEL = 'order'; const ORDER_MODEL = 'order';
const router = useRouter();
const isNew = Boolean(!route.params.id); const isNew = Boolean(!route.params.id);
const clientList = ref([]); const clientList = ref([]);
const agencyList = ref([]); const agencyList = ref([]);
@ -64,13 +64,6 @@ const fetchOrderDetails = (order) => {
fetchAgencyList(order?.landed, order?.addressFk); fetchAgencyList(order?.landed, order?.addressFk);
}; };
const orderMapper = (order) => {
return {
addressId: order.addressFk,
agencyModeId: order.agencyModeFk,
landed: new Date(order.landed).toISOString(),
};
};
const orderFilter = { const orderFilter = {
Review

esta función está sin uso

esta función está sin uso
include: [ include: [
{ relation: 'agencyMode', scope: { fields: ['name'] } }, { relation: 'agencyMode', scope: { fields: ['name'] } },
@ -106,10 +99,6 @@ const onClientChange = async (clientId) => {
console.error('Error al cambiar el cliente:', error); console.error('Error al cambiar el cliente:', error);
} }
}; };
async function onDataSaved({ id }) {
await router.push({ path: `/order/${id}/catalog` });
}
</script> </script>
<template> <template>
@ -117,9 +106,8 @@ async function onDataSaved({ id }) {
<div class="q-pa-md"> <div class="q-pa-md">
<FormModel <FormModel
:url="`Orders/${route.params.id}`" :url="`Orders/${route.params.id}`"
@on-data-saved="onDataSaved" :url-update="`Orders/${route.params.id}/updateBasicData`"
:model="ORDER_MODEL" :model="ORDER_MODEL"
:mapper="orderMapper"
:filter="orderFilter" :filter="orderFilter"
@on-fetch="fetchOrderDetails" @on-fetch="fetchOrderDetails"
auto-load auto-load
@ -180,8 +168,6 @@ async function onDataSaved({ id }) {
() => fetchAgencyList(data.landed, data.addressFk) () => fetchAgencyList(data.landed, data.addressFk)
" "
/> />
</VnRow>
<VnRow>
<VnSelect <VnSelect
:label="t('order.form.agencyModeFk')" :label="t('order.form.agencyModeFk')"
v-model="data.agencyModeFk" v-model="data.agencyModeFk"
@ -189,9 +175,29 @@ async function onDataSaved({ id }) {
option-value="agencyModeFk" option-value="agencyModeFk"
option-label="agencyMode" option-label="agencyMode"
hide-selected hide-selected
:disable="!agencyList?.length" :disable="!agencyList?.length && data.isConfirmed === 1"
> clearable
</VnSelect> emit-value
map-options
:model-value="
!data.isConfirmed &&
agencyList?.length &&
agencyList.some(
(agency) => agency.agencyModeFk === data.agency_id
)
? data.agencyModeFk
: null
"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('globals.notes')"
type="textarea"
v-model="data.note"
fill-input
autogrow
/>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,16 +1,35 @@
<script setup> <script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import VnCard from 'components/common/VnCard.vue'; import VnCard from 'components/common/VnCard.vue';
import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue'; import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
import OrderFilter from './OrderFilter.vue'; import OrderFilter from './OrderFilter.vue';
import OrderSearchbar from './OrderSearchbar.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(() => {
Review

Esto para que hace falta?

Esto para que hace falta?
Review

Para eliminar el duplicado de la searchbar, ya que la searchbar de catalog es distinta al del resto del módulo

Para eliminar el duplicado de la searchbar, ya que la searchbar de catalog es distinta al del resto del módulo
const route = config[routeName.value];
if (route) return null;
return 'OrderList';
});
const customFilterPanel = computed(() => {
const filterPanel = config[routeName.value] ?? OrderFilter;
return filterPanel;
});
</script> </script>
<template> <template>
<VnCard <VnCard
data-key="Order" data-key="Order"
base-url="Orders" base-url="Orders"
:descriptor="OrderDescriptor" :descriptor="OrderDescriptor"
:filter-panel="OrderFilter" :filter-panel="customFilterPanel"
search-data-key="OrderList" :search-data-key="customRouteRedirectName"
> >
<template #searchbar> <template #searchbar>
<OrderSearchbar /> <OrderSearchbar />

View File

@ -1,17 +1,24 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import CatalogItem from 'components/ui/CatalogItem.vue'; import CatalogItem from 'components/ui/CatalogItem.vue';
import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue'; import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const tags = ref([]);
onMounted(() => (stateStore.rightDrawer = true)); onMounted(() => {
stateStore.rightDrawer = true;
checkOrderConfirmation();
});
onUnmounted(() => (stateStore.rightDrawer = false)); onUnmounted(() => (stateStore.rightDrawer = false));
const catalogParams = { const catalogParams = {
@ -19,7 +26,12 @@ const catalogParams = {
orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }), 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) { function extractTags(items) {
const resultTags = []; const resultTags = [];
@ -52,6 +64,15 @@ function extractValueTags(items) {
</script> </script>
<template> <template>
<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 items by name or id')"
/>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<OrderCatalogFilter <OrderCatalogFilter
@ -68,7 +89,6 @@ function extractValueTags(items) {
url="Orders/CatalogFilter" url="Orders/CatalogFilter"
:limit="50" :limit="50"
:user-params="catalogParams" :user-params="catalogParams"
auto-load
@on-fetch="extractTags" @on-fetch="extractTags"
:update-router="false" :update-router="false"
> >
@ -106,3 +126,8 @@ function extractValueTags(items) {
text-align: center; text-align: center;
} }
</style> </style>
<i18n>
es:
You can search items by name or id: Puedes buscar items por nombre o id
</i18n>

View File

@ -7,8 +7,8 @@ import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import { useValidator } from 'src/composables/useValidator';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import getParamWhere from 'src/filters/getParamWhere';
const { t } = useI18n(); const { t } = useI18n();
@ -27,19 +27,26 @@ const props = defineProps({
required: true, required: true,
}, },
}); });
const categoryList = ref(null); const categoryList = ref(null);
const selectedCategoryFk = ref(null); const selectedCategoryFk = ref(getParamWhere(route, 'categoryFk'));
const typeList = ref(null); const typeList = ref([]);
const selectedTypeFk = ref(null); 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 selectedTag = ref(null);
const tagValues = ref([{}]); const tagValues = ref([{}]);
const tagOptions = 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) => { const createValue = (val, done) => {
if (val.length > 2) { if (val.length > 2) {
@ -72,7 +79,7 @@ const selectCategory = (params, category, search) => {
search(); search();
}; };
const loadTypes = async (categoryFk) => { const loadTypes = async (categoryFk = selectedCategoryFk.value) => {
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, { const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
params: { itemCategoryId: categoryFk }, params: { itemCategoryId: categoryFk },
}); });
@ -84,7 +91,14 @@ const selectedCategory = computed(() =>
(category) => category?.id === selectedCategoryFk.value (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(() => { const selectedType = computed(() => {
return (typeList.value || []).find((type) => type?.id === selectedTypeFk.value); return (typeList.value || []).find((type) => type?.id === selectedTypeFk.value);
}); });
@ -95,7 +109,8 @@ function exprBuilder(param, value) {
case 'typeFk': case 'typeFk':
return { [param]: value }; return { [param]: value };
case 'search': case 'search':
return { 'i.name': { like: `%${value}%` } }; if (/^\d+$/.test(value)) return { 'i.id': value };
else return { 'i.name': { like: `%${value}%` } };
} }
} }
@ -132,36 +147,6 @@ const removeTagChip = (selection, params, search) => {
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) => { const setCategoryList = (data) => {
categoryList.value = (data || []) categoryList.value = (data || [])
.filter((category) => category.display) .filter((category) => category.display)
@ -169,8 +154,8 @@ const setCategoryList = (data) => {
...category, ...category,
icon: `vn:${(category.icon || '').split('-')[1]}`, icon: `vn:${(category.icon || '').split('-')[1]}`,
})); }));
moreFields.value = useLang(_moreFields);
moreFieldsOrder.value = useLang(_moreFieldsTypes); selectedCategoryFk.value && loadTypes();
}; };
const getCategoryClass = (category, params) => { const getCategoryClass = (category, params) => {
@ -179,27 +164,22 @@ const getCategoryClass = (category, params) => {
} }
}; };
const useLang = (values) => { function addOrder(value, field, params) {
const { models } = validationsStore; let { orderBy } = params;
const properties = models.Item?.properties || {}; orderBy = JSON.parse(orderBy);
return values.map((name) => { orderBy[field] = value;
let prop = properties[name]; params.orderBy = JSON.stringify(orderBy);
const label = t(`params.${name}`); vnFilterPanelRef.value.search();
return { }
name,
label,
type: prop ? prop.type : null,
};
});
};
</script> </script>
<template> <template>
<FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" /> <FetchData url="ItemCategories" limit="30" auto-load @on-fetch="setCategoryList" />
<VnFilterPanel <VnFilterPanel
ref="vnFilterPanelRef"
:data-key="props.dataKey" :data-key="props.dataKey"
:hidden-tags="['orderFk', 'orderBy']" :hidden-tags="['orderFk', 'orderBy']"
:unremovable-params="['orderFk', 'orderBy']" :un-removable-params="['orderFk', 'orderBy']"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups']" :custom-tags="['tagGroups']"
@remove="clearFilter" @remove="clearFilter"
@ -289,33 +269,29 @@ const useLang = (values) => {
</QItemSection> </QItemSection>
</QItem> </QItem>
<QSeparator /> <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"> <QItem class="q-mb-md">
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('Order by')" :label="t('Order by')"
v-model="selectedOrderField" v-model="orderBySelected"
:options="moreFieldsOrder" :options="orderByList"
option-label="label"
option-value="name"
dense dense
outlined outlined
rounded 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> </QItemSection>
</QItem> </QItem>
@ -352,7 +328,7 @@ const useLang = (values) => {
v-if="!selectedTag" v-if="!selectedTag"
:label="t('params.value')" :label="t('params.value')"
v-model="value.value" v-model="value.value"
:options="tagValue || []" :options="tagOptions || []"
option-value="value" option-value="value"
option-label="value" option-label="value"
dense dense
@ -362,6 +338,8 @@ const useLang = (values) => {
use-input use-input
class="filter-input" class="filter-input"
@new-value="createValue" @new-value="createValue"
@filter="filterFn"
@update:model-value="applyTagFilter(params, searchFn)"
/> />
<VnSelect <VnSelect
v-else-if="selectedTag === 1" v-else-if="selectedTag === 1"
@ -377,6 +355,7 @@ const useLang = (values) => {
use-input use-input
class="filter-input" class="filter-input"
@new-value="createValue" @new-value="createValue"
@update:model-value="applyTagFilter(params, searchFn)"
/> />
<VnInput <VnInput
v-else v-else
@ -386,6 +365,7 @@ const useLang = (values) => {
outlined outlined
rounded rounded
class="filter-input" class="filter-input"
@keyup.enter="applyTagFilter(params, searchFn)"
/> />
<QIcon <QIcon
name="delete" name="delete"
@ -400,7 +380,7 @@ const useLang = (values) => {
@click="tagValues.push({})" @click="tagValues.push({})"
/> />
</QItem> </QItem>
<QItem> <!-- <QItem>
<QItemSection class="q-py-sm"> <QItemSection class="q-py-sm">
<QBtn <QBtn
:label="t('Search')" :label="t('Search')"
@ -414,7 +394,7 @@ const useLang = (values) => {
@click.stop="applyTagFilter(params, searchFn)" @click.stop="applyTagFilter(params, searchFn)"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem> -->
<QSeparator /> <QSeparator />
</template> </template>
</VnFilterPanel> </VnFilterPanel>
@ -477,10 +457,10 @@ en:
order: Order order: Order
ASC: Ascendant ASC: Ascendant
DESC: Descendant DESC: Descendant
Relevancy: Relevancy relevancy: Relevancy
ColorAndPrice: Color and price colorAndPrice: Color and price
Name: Name name: Name
Price: Price price: Price
es: es:
params: params:
type: Tipo type: Tipo
@ -490,10 +470,10 @@ es:
order: Orden order: Orden
ASC: Ascendiente ASC: Ascendiente
DESC: Descendiente DESC: Descendiente
Relevancy: Relevancia relevancy: Relevancia
ColorAndPrice: Color y precio colorAndPrice: Color y precio
Name: Nombre name: Nombre
Price: Precio price: Precio
Order: Orden Order: Orden
Order by: Ordenar por Order by: Ordenar por
Plant: Planta Plant: Planta

View File

@ -5,10 +5,12 @@ import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import useNotify from 'composables/useNotify'; import useNotify from 'composables/useNotify';
import { useArrayData } from 'composables/useArrayData';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify(); const { notify } = useNotify();
const emit = defineEmits(['added']);
const route = useRoute();
const props = defineProps({ const props = defineProps({
prices: { prices: {
type: Array, type: Array,
@ -16,9 +18,8 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(['added']);
const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 }))); const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
const descriptorData = useArrayData('orderData');
const addToOrder = async () => { const addToOrder = async () => {
const items = (fields.value || []).filter((item) => Number(item.quantity) > 0); const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
@ -28,19 +29,20 @@ const addToOrder = async () => {
}); });
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
emit('added'); emit('added');
descriptorData.fetch({});
}; };
</script> </script>
<template> <template>
<div class="container order-catalog-item q-pb-md"> <div class="container order-catalog-item q-pa-md">
<QForm @submit="addToOrder"> <QForm @submit="addToOrder">
<QMarkupTable class="shadow-0"> <QMarkupTable class="shadow-0">
<tbody> <tbody>
<tr v-for="item in fields" :key="item.warehouse"> <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 }} {{ item.warehouse }}
</td> </td>
<td class="text-right"> <td class="text-right" style="width: 35%">
<span <span
class="link" class="link"
@click=" @click="
@ -75,8 +77,11 @@ const addToOrder = async () => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { // .container {
max-width: 448px; // max-width: 768px;
width: 100%; // width: 100%;
// }
.td {
width: 200px;
} }
</style> </style>

View File

@ -4,13 +4,13 @@ import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue';
import FetchData from 'components/FetchData.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; const DEFAULT_ITEMS = 0;
@ -25,6 +25,8 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const state = useState(); const state = useState();
const { t } = useI18n(); const { t } = useI18n();
const data = ref(useCardDescription());
const getTotalRef = ref();
const entityId = computed(() => { const entityId = computed(() => {
return $props.id || route.params.id; return $props.id || route.params.id;
@ -57,11 +59,11 @@ const filter = {
], ],
}; };
const data = ref(useCardDescription());
const setData = (entity) => { const setData = (entity) => {
if (!entity) return; if (!entity) return;
getTotalRef.value && getTotalRef.value.fetch();
data.value = useCardDescription(entity?.client?.name, entity?.id); data.value = useCardDescription(entity?.client?.name, entity?.id);
state.set('OrderDescriptor', entity); state.set('orderData', entity);
}; };
const getConfirmationValue = (isConfirmed) => { const getConfirmationValue = (isConfirmed) => {
@ -69,13 +71,17 @@ const getConfirmationValue = (isConfirmed) => {
}; };
const total = ref(null); const total = ref(null);
function ticketFilter(order) {
return JSON.stringify({ id: order.id });
}
</script> </script>
<template> <template>
<FetchData <FetchData
ref="getTotalRef"
:url="`Orders/${entityId}/getTotal`" :url="`Orders/${entityId}/getTotal`"
@on-fetch="(response) => (total = response)" @on-fetch="(response) => (total = response)"
auto-load
/> />
<CardDescriptor <CardDescriptor
ref="descriptor" ref="descriptor"
@ -120,7 +126,7 @@ const total = ref(null);
color="primary" color="primary"
:to="{ :to="{
name: 'TicketList', name: 'TicketList',
query: { params: JSON.stringify({ orderFk: entity.id }) }, query: { table: ticketFilter(entity) },
}" }"
> >
<QTooltip>{{ t('order.summary.orderTicketList') }}</QTooltip> <QTooltip>{{ t('order.summary.orderTicketList') }}</QTooltip>

View File

@ -21,15 +21,13 @@ const salesPersonFilter = {
fields: ['id', 'nickname'], fields: ['id', 'nickname'],
}; };
const salesPersonList = ref(null); const salesPersonList = ref(null);
const sourceFilter = { fields: ['value'] }; const sourceList = ref([]);
const sourceList = ref(null);
</script> </script>
<template> <template>
<FetchData <FetchData
url="AgencyModes/isActive" url="AgencyModes/isActive"
:filter="agencyFilter" :filter="agencyFilter"
limit="30"
sort-by="name ASC" sort-by="name ASC"
auto-load auto-load
@on-fetch="(data) => (agencyList = data)" @on-fetch="(data) => (agencyList = data)"
@ -37,7 +35,6 @@ const sourceList = ref(null);
<FetchData <FetchData
url="Workers/search" url="Workers/search"
:filter="salesPersonFilter" :filter="salesPersonFilter"
limit="30"
sort-by="nickname ASC" sort-by="nickname ASC"
@on-fetch="(data) => (salesPersonList = data)" @on-fetch="(data) => (salesPersonList = data)"
:params="{ departmentCodes: ['VT'] }" :params="{ departmentCodes: ['VT'] }"
@ -45,8 +42,7 @@ const sourceList = ref(null);
/> />
<FetchData <FetchData
url="Orders/getSourceValues" url="Orders/getSourceValues"
:filter="sourceFilter" :filter="{ fields: ['value'] }"
limit="30"
sort-by="value ASC" sort-by="value ASC"
@on-fetch="(data) => (sourceList = data)" @on-fetch="(data) => (sourceList = data)"
auto-load auto-load
@ -59,56 +55,34 @@ const sourceList = ref(null);
</div> </div>
</template> </template>
<template #body="{ params }"> <template #body="{ params }">
<QItem> <div class="q-px-md q-gutter-y-sm">
<QItemSection>
<VnInput <VnInput
is-outlined
:label="t('customerId')" :label="t('customerId')"
v-model="params.clientFk" v-model="params.clientFk"
lazy-rules lazy-rules
> dense
<template #prepend> outlined
<QIcon name="badge" size="sm"></QIcon> rounded
</template> />
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="agencyList">
<VnSelect <VnSelect
:label="t('agency')" :label="t('agency')"
v-model="params.agencyModeFk" v-model="params.agencyModeFk"
:options="agencyList" :options="agencyList"
option-value="id" :input-debounce="0"
option-label="name"
dense dense
outlined outlined
rounded 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 <VnSelect
:label="t('salesPerson')" :label="t('salesPerson')"
v-model="params.workerFk" v-model="params.workerFk"
:options="salesPersonList" url="Workers/search"
option-value="id" :filter="{ departmentCodes: ['VT'] }"
option-label="name" sort-by="nickname ASC"
option-label="nickname"
dense dense
outlined outlined
rounded rounded
emit-value
map-options
use-input
:input-debounce="0"
> >
<template #option="{ itemProps, opt }"> <template #option="{ itemProps, opt }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
@ -121,13 +95,6 @@ const sourceList = ref(null);
</QItem> </QItem>
</template> </template>
</VnSelect> </VnSelect>
</QItemSection>
<QItemSection v-else>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate <VnInputDate
v-model="params.from" v-model="params.from"
:label="t('fromLanded')" :label="t('fromLanded')"
@ -135,10 +102,6 @@ const sourceList = ref(null);
outlined outlined
rounded rounded
/> />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate <VnInputDate
v-model="params.to" v-model="params.to"
:label="t('toLanded')" :label="t('toLanded')"
@ -146,61 +109,34 @@ const sourceList = ref(null);
outlined outlined
rounded rounded
/> />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput <VnInput
:label="t('orderId')" :label="t('orderId')"
v-model="params.orderFk" v-model="params.orderFk"
lazy-rules lazy-rules
is-outlined is-outlined
/> />
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="sourceList">
<VnSelect <VnSelect
:label="t('application')" :label="t('application')"
v-model="params.sourceApp" v-model="params.sourceApp"
:options="sourceList" :options="sourceList"
option-label="value" option-label="value"
emit-value
map-options
use-input
dense dense
outlined outlined
rounded rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection>
<QItemSection v-else>
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox <QCheckbox
v-model="params.myTeam" v-model="params.myTeam"
:label="t('myTeam')" :label="t('myTeam')"
toggle-indeterminate toggle-indeterminate
/> />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox <QCheckbox
v-model="params.isConfirmed" v-model="params.isConfirmed"
:label="t('isConfirmed')" :label="t('isConfirmed')"
toggle-indeterminate toggle-indeterminate
/> />
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox v-model="params.showEmpty" :label="t('showEmpty')" /> <QCheckbox v-model="params.showEmpty" :label="t('showEmpty')" />
</QItemSection> </div>
</QItem>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
</template> </template>

View File

@ -1,23 +1,26 @@
<script setup> <script setup>
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ref, computed } from 'vue'; import { ref, computed, watch } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
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 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 VnTable from 'src/components/VnTable/VnTable.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import FetchedTags from 'src/components/ui/FetchedTags.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue';
import { useStateStore } from 'stores/useStateStore';
const router = useRouter();
const stateStore = useStateStore(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const descriptorData = useArrayData('orderData');
const componentKey = ref(0); const componentKey = ref(0);
const tableLinesRef = ref(); const tableLinesRef = ref();
const order = ref(); const order = ref();
@ -25,6 +28,8 @@ const orderSummary = ref({
total: null, total: null,
vat: null, vat: null,
}); });
const getTotalRef = ref();
const getVATRef = ref();
const lineFilter = ref({ const lineFilter = ref({
include: [ include: [
@ -59,6 +64,13 @@ const lineFilter = ref({
fields: ['id', 'name'], fields: ['id', 'name'],
}, },
}, },
{
relation: 'order',
scope: {
fields: ['id', 'isConfirmed'],
where: { id: route.params.id },
},
},
], ],
where: { orderFk: route.params.id }, where: { orderFk: route.params.id },
}); });
@ -104,6 +116,7 @@ const columns = computed(() => [
component: null, component: null,
}, },
format: (row) => row?.item?.name, format: (row) => row?.item?.name,
columnClass: 'expand',
}, },
{ {
align: 'left', align: 'left',
@ -147,7 +160,6 @@ const columns = computed(() => [
align: 'left', align: 'left',
name: 'amount', name: 'amount',
label: t('lines.amount'), label: t('lines.amount'),
format: (row) => toCurrency(row.amount),
}, },
{ {
align: 'right', align: 'right',
@ -155,8 +167,9 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('delete'), title: t('Delete'),
icon: 'delete', icon: 'delete',
show: (row) => !row.order.isConfirmed,
action: (row) => confirmRemove(row), action: (row) => confirmRemove(row),
isPrimary: true, isPrimary: true,
}, },
@ -185,6 +198,9 @@ async function remove(item) {
type: 'positive', type: 'positive',
}); });
tableLinesRef.value.reload(); tableLinesRef.value.reload();
descriptorData.fetch({});
getTotalRef.value.fetch();
getVATRef.value.fetch();
} }
async function confirmOrder() { async function confirmOrder() {
@ -193,7 +209,22 @@ async function confirmOrder() {
message: t('globals.confirm'), message: t('globals.confirm'),
type: 'positive', type: 'positive',
}); });
router.push({
name: 'TicketList',
query: {
table: JSON.stringify({ clientFk: descriptorData.store.data.clientFk }),
},
});
} }
watch(
() => router.currentRoute.value.params.id,
() => {
lineFilter.value.where.orderFk = router.currentRoute.value.params.id;
tableLinesRef.value.reload();
}
);
</script> </script>
<template> <template>
@ -204,44 +235,36 @@ async function confirmOrder() {
auto-load auto-load
/> />
<FetchData <FetchData
ref="getTotalRef"
:key="componentKey" :key="componentKey"
:url="`Orders/${route.params.id}/getTotal`" :url="`Orders/${route.params.id}/getTotal`"
@on-fetch="(data) => (orderSummary.total = data)" @on-fetch="(data) => (orderSummary.total = data)"
auto-load auto-load
/> />
<FetchData <FetchData
ref="getVATRef"
:key="componentKey" :key="componentKey"
:url="`Orders/${route.params.id}/getVAT`" :url="`Orders/${route.params.id}/getVAT`"
@on-fetch="(data) => (orderSummary.vat = data)" @on-fetch="(data) => (orderSummary.vat = data)"
auto-load auto-load
/> />
<QDrawer side="right" :width="270" v-model="stateStore.rightDrawer"> <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"> <p class="header text-right block">
{{ t('summary') }} {{ t('summary') }}
</p> </p>
<VnLv <VnLv
v-if="orderSummary.vat && orderSummary.total"
:label="t('subtotal') + ': '" :label="t('subtotal') + ': '"
:value="toCurrency(orderSummary.total - orderSummary.vat)" :value="toCurrency(orderSummary.total - orderSummary.vat)"
/> />
<VnLv <VnLv :label="t('VAT') + ': '" :value="toCurrency(orderSummary?.vat)" />
v-if="orderSummary.vat" <VnLv :label="t('total') + ': '" :value="toCurrency(orderSummary?.total)" />
:label="t('VAT') + ': '"
:value="toCurrency(orderSummary?.vat)"
/>
<VnLv
v-if="orderSummary.total"
:label="t('total') + ': '"
:value="toCurrency(orderSummary?.total)"
/>
</QCard> </QCard>
</QDrawer> </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 <VnTable
ref="tableLinesRef" ref="tableLinesRef"
data-key="OrderLines" data-key="OrderLines"
@ -267,15 +290,25 @@ async function confirmOrder() {
</div> </div>
<FetchedTags :item="row?.item" :max-length="6" /> <FetchedTags :item="row?.item" :max-length="6" />
</template> </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> </VnTable>
</div>
<QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2"> <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed" style="z-index: 2">
<QBtn fab icon="check" color="primary" @click="confirmOrder()" /> <QBtn fab icon="check" color="primary" @click="confirmOrder()" />
<QTooltip> <QTooltip>
{{ t('confirm') }} {{ t('confirm') }}
</QTooltip> </QTooltip>
</QPageSticky> </QPageSticky>
</QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -180,9 +180,10 @@ const detailsColumns = ref([
<ItemDescriptorProxy :id="props.row.item?.id" /> <ItemDescriptorProxy :id="props.row.item?.id" />
</span> </span>
</QTd> </QTd>
<QTd key="description" :props="props" class="description"> <QTd key="description" :props="props">
<div class="description">
<div class="name"> <div class="name">
<span>{{ props.row.item.name }}</span> {{ props.row.item.name }}
<span <span
v-if="props.row.item.subName" v-if="props.row.item.subName"
class="subName" class="subName"
@ -190,6 +191,7 @@ const detailsColumns = ref([
{{ props.row.item.subName }} {{ props.row.item.subName }}
</span> </span>
</div> </div>
</div>
<FetchedTags :item="props.row.item" :max-length="5" /> <FetchedTags :item="props.row.item" :max-length="5" />
</QTd> </QTd>
<QTd key="quantity" :props="props"> <QTd key="quantity" :props="props">
@ -228,24 +230,13 @@ const detailsColumns = ref([
} }
.description { .description {
display: flex;
flex-direction: column;
justify-content: center;
text-align: left; text-align: left;
height: auto; padding-top: 15px;
padding-top: 12px; padding-bottom: 15px;
padding-bottom: 12px;
.name { .name {
display: flex;
align-items: center;
padding-bottom: 8px;
& > * {
flex: 1;
}
.subName { .subName {
margin-left: 5%;
text-transform: uppercase; text-transform: uppercase;
color: var(--vn-label-color); color: var(--vn-label-color);
} }

View File

@ -0,0 +1,140 @@
<script setup>
import axios from 'axios';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { dashIfEmpty } from 'src/filters';
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 volumes = ref([]);
const volumeFilter = ref({
include: [
{
relation: 'item',
},
],
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) => {
(data.volumes || []).forEach((volume) => {
if (order.itemFk === volume.itemFk) order.volume = volume.volume;
});
});
volumes.value = rows;
};
</script>
<template>
<FetchData
:url="`Orders/${route.params.id}/getTotalVolume`"
@on-fetch="(data) => (volumeSummary = data)"
auto-load
/>
<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">
.order-volume-summary {
.vn-label-value {
display: flex;
justify-content: flex-end;
gap: 2%;
.label {
color: var(--vn-label-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>
<i18n>
en:
summary: Summary
total: Total
boxes: Boxes
item: Item
quantity: Quantity
volume: per quantity
es:
summary: Resumen
total: Total
boxes: Cajas
item: Artículo
quantity: Cantidad
volume: por cantidad
</i18n>

View File

@ -3,19 +3,21 @@ import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.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 { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const tableRef = ref(); const tableRef = ref();
const clientList = ref([]);
const agencyList = ref([]); const agencyList = ref([]);
const selectedAddress = ref(); const addressesList = ref([]);
const clientId = ref();
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -29,7 +31,7 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'clientName', name: 'clientFk',
label: t('module.customer'), label: t('module.customer'),
isTitle: true, isTitle: true,
cardVisible: true, cardVisible: true,
@ -41,20 +43,26 @@ const columns = computed(() => [
columnField: { columnField: {
component: null, component: null,
}, },
format: (row) => row?.clientName,
}, },
{ {
align: 'left', align: 'left',
name: 'name', name: 'salesPersonFk',
label: t('module.salesPerson'), label: t('module.salesPerson'),
columnFilter: {
component: 'select', component: 'select',
inWhere: true,
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'], fields: ['id', 'name'],
where: { role: 'salesPerson' }, where: { role: 'salesPerson' },
useLike: false,
optionValue: 'id',
optionLabel: 'name',
optionFilter: 'firstName',
}, },
columnField: {
component: null,
}, },
format: (row) => row?.name,
}, },
{ {
align: 'left', align: 'left',
@ -92,18 +100,22 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'agencyName', name: 'agencyModeFk',
label: t('module.agency'), label: t('module.agency'),
format: (row) => row?.agencyName,
columnFilter: {
component: 'select', component: 'select',
cardVisible: true,
attrs: { attrs: {
url: 'Agencies', url: 'agencyModes',
fields: ['id', 'name'], fields: ['id', 'name'],
find: {
value: 'agencyModeFk',
label: 'agencyName',
}, },
columnField: {
component: null,
}, },
}, },
cardVisible: true,
},
{ {
align: 'left', align: 'left',
name: 'total', name: 'total',
@ -125,22 +137,36 @@ const columns = computed(() => [
}, },
]); ]);
async function fetchClientAddress(id, data) { async function fetchClientAddress(id, formData) {
const clientData = await axios.get(`Clients/${id}`); const { data } = await axios.get(`Clients/${id}`, {
selectedAddress.value = clientData.data.defaultAddressFk; params: { filter: { include: { relation: 'addresses' } } },
data.addressId = selectedAddress.value; });
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> </script>
<template> <template>
<VnSearchbar <OrderSearchbar />
data-key="OrderList" <RightMenu>
:label="t('Search order')" <template #right-panel>
:info="t('You can search orders by reference')" <OrderFilter data-key="OrderList" />
/> </template>
</RightMenu>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="OrderList" data-key="OrderList"
url="Orders/filter" url="Orders/filter"
:order="['landed DESC', 'clientFk ASC', 'id DESC']"
:create="{ :create="{
urlCreate: 'Orders/new', urlCreate: 'Orders/new',
title: 'Create Order', title: 'Create Order',
@ -152,36 +178,49 @@ async function fetchClientAddress(id, data) {
addressId: null, addressId: null,
}, },
}" }"
:user-params="{ showEmpty: false }"
:right-search="false"
:columns="columns" :columns="columns"
redirect="order" redirect="order"
auto-load
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnSelect <VnSelect
url="Clients" url="Clients"
v-model="data.id" :include="{ relation: 'addresses' }"
v-model="clientId"
:label="t('module.customer')" :label="t('module.customer')"
:options="clientList"
option-value="id"
option-label="name"
@update:model-value="(id) => fetchClientAddress(id, data)" @update:model-value="(id) => fetchClientAddress(id, data)"
/> />
<VnSelect <VnSelect
url="Clients" v-model="data.addressId"
v-model="selectedAddress" :options="addressesList"
:label="t('module.address')" :label="t('module.address')"
:options="selectedAddress" option-value="id"
option-value="defaultAddressFk" option-label="nickname"
option-label="street" @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 <VnSelect
url="Agencies"
v-model="data.agencyModeId" v-model="data.agencyModeId"
:label="t('module.agency')" :label="t('module.agency')"
:options="agencyList" :options="agencyList"
option-value="id" option-value="agencyModeFk"
option-label="name" option-label="agencyMode"
/> />
</template> </template>
</VnTable> </VnTable>

View File

@ -1,156 +0,0 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
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 { dashIfEmpty } from 'src/filters';
import axios from 'axios';
const route = useRoute();
const { t } = useI18n();
const volumeSummary = ref(null);
const loadVolumes = async (rows) => {
const { data } = await axios.get(`Orders/${route.params.id}/getVolumes`);
(rows || []).forEach((order) => {
(data.volumes || []).forEach((volume) => {
if (order.itemFk === volume.itemFk) {
order.volume = volume.volume;
}
});
});
};
</script>
<template>
<FetchData
:url="`Orders/${route.params.id}/getTotalVolume`"
@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">
<p class="header text-right block">
{{ t('summary') }}
</p>
<VnLv :label="t('total')" :value="`${volumeSummary?.totalVolume} m³`" />
<VnLv
:label="t('boxes')"
:value="`${dashIfEmpty(volumeSummary?.totalBoxes)} U`"
/>
</QCard>
<VnPaginate
data-key="OrderCatalogVolume"
url="OrderRows"
:limit="20"
auto-load
:filter="{
include: {
relation: 'item',
},
where: { orderFk: route.params.id },
}"
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>
</template>
<style lang="scss">
.order-volume-summary {
.vn-label-value {
display: flex;
justify-content: flex-end;
gap: 2%;
.label {
color: var(--vn-label-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</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: per quantity
es:
summary: Resumen
total: Total
boxes: Cajas
item: Artículo
subName: Subname
quantity: Cantidad
volume: por cantidad
</i18n>

View File

@ -63,7 +63,7 @@ export default {
title: 'basicData', title: 'basicData',
icon: 'vn:settings', icon: 'vn:settings',
}, },
component: () => import('src/pages/Order/Card/OrderForm.vue'), component: () => import('src/pages/Order/Card/OrderBasicData.vue'),
}, },
{ {
name: 'OrderCatalog', name: 'OrderCatalog',
@ -72,7 +72,7 @@ export default {
title: 'catalog', title: 'catalog',
icon: 'vn:basket', icon: 'vn:basket',
}, },
component: () => import('src/pages/Order/OrderCatalog.vue'), component: () => import('src/pages/Order/Card/OrderCatalog.vue'),
}, },
{ {
name: 'OrderVolume', name: 'OrderVolume',
@ -81,7 +81,7 @@ export default {
title: 'volume', title: 'volume',
icon: 'vn:volume', icon: 'vn:volume',
}, },
component: () => import('src/pages/Order/OrderVolume.vue'), component: () => import('src/pages/Order/Card/OrderVolume.vue'),
}, },
{ {
name: 'OrderLines', name: 'OrderLines',
@ -90,7 +90,7 @@ export default {
title: 'lines', title: 'lines',
icon: 'vn:lines', icon: 'vn:lines',
}, },
component: () => import('src/pages/Order/OrderLines.vue'), component: () => import('src/pages/Order/Card/OrderLines.vue'),
}, },
], ],
}, },