#6896 end orders migration #400

Merged
jon merged 17 commits from 6896-EndOrderMigration into dev 2024-06-20 12:31:45 +00:00
5 changed files with 330 additions and 182 deletions
Showing only changes of commit 98159de257 - Show all commits

View File

@ -3,14 +3,15 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import VnInput from 'components/common/VnInput.vue';
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';
const { t } = useI18n();
const route = useRoute();
const props = defineProps({
dataKey: {
@ -21,32 +22,34 @@ const props = defineProps({
type: Array,
required: true,
},
tagValue: {
type: Array,
required: true,
},
});
const categoryList = ref(null);
const selectedCategoryFk = ref(null);
const typeList = ref(null);
const selectedTypeFk = ref(null);
const validationsStore = useValidator();
const selectedOrder = ref(null);
const selectedOrderField = ref(null);
const moreFields = ref([]);
const moreFieldsOrder = ref([]);
const createValue = (val, done) => {
if (val.length > 2) {
// if (!tagOptions.value.includes(val)) {
// done(tagOptions.value, 'add-unique');
// }
tagValues.value.push({ value: val });
}
};
const resetCategory = () => {
selectedCategoryFk.value = null;
typeList.value = null;
};
const selectedOrder = ref(null);
const orderList = [
{ way: 'ASC', name: 'Ascendant' },
{ way: 'DESC', name: 'Descendant' },
];
const selectedOrderField = ref(null);
const OrderFields = [
{ field: 'relevancy DESC, name', name: 'Relevancy', priority: 999 },
{ field: 'showOrder, price', name: 'Color and price', priority: 999 },
{ field: 'name', name: 'Name', priority: 999 },
{ field: 'price', name: 'Price', priority: 999 },
];
const clearFilter = (key) => {
if (key === 'categoryFk') {
resetCategory();
@ -72,21 +75,6 @@ const loadTypes = async (categoryFk) => {
typeList.value = data;
};
const onFilterInit = async ({ params }) => {
if (params.typeFk) {
selectedTypeFk.value = params.typeFk;
}
if (params.categoryFk) {
await loadTypes(params.categoryFk);
selectedCategoryFk.value = params.categoryFk;
}
if (params.orderBy) {
orderByParam.value = JSON.parse(params.orderBy);
selectedOrder.value = orderByParam.value?.way;
selectedOrderField.value = orderByParam.value?.field;
}
};
const selectedCategory = computed(() =>
(categoryList.value || []).find(
(category) => category?.id === selectedCategoryFk.value
@ -109,12 +97,25 @@ function exprBuilder(param, value) {
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref(null);
const isButtonDisabled = computed(
() => !selectedTag.value || tagValues.value.some((item) => !item.value)
);
const tagOptions = ref([]);
const applyTagFilter = (params, search) => {
// console.log('params: ', params);
// if (!selectedTag.value) {
// console.log('values: ', selectedTag?.value?.name);
// params.tagGroups.push(
// JSON.stringify({
// values: selectedTag?.value?.name,
// // tagSelection: {
// // ...selectedTag.value,
// // orgShowField: selectedTag?.value?.name,
// // },
// })
// );
// search();
// return;
// }
if (!tagValues.value?.length) {
params.tagGroups = null;
search();
@ -125,12 +126,12 @@ const applyTagFilter = (params, search) => {
}
params.tagGroups.push(
JSON.stringify({
values: tagValues.value,
values: tagValues.value.filter((obj) => Object.keys(obj).length > 0),
tagSelection: {
...selectedTag.value,
orgShowField: selectedTag.value.name,
orgShowField: selectedTag?.value?.name,
},
tagFk: selectedTag.value.tagFk,
tagFk: selectedTag?.value?.tagFk,
})
);
search();
@ -155,12 +156,6 @@ const onOrderFieldChange = (value, params, search) => {
search();
};
const onOrderChange = (value, params, search) => {
const orderBy = Object.assign({}, orderByParam.value, { way: value.way });
params.orderBy = JSON.stringify(orderBy);
search();
};
const setCategoryList = (data) => {
categoryList.value = (data || [])
.filter((category) => category.display)
@ -168,6 +163,10 @@ const setCategoryList = (data) => {
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
const _moreFields = ['ASC', 'DESC'];
const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
moreFields.value = useLang(_moreFields);
moreFieldsOrder.value = useLang(_moreFieldsTypes);
};
const getCategoryClass = (category, params) => {
@ -175,6 +174,20 @@ const getCategoryClass = (category, params) => {
return 'active';
}
};
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,
};
});
};
</script>
<template>
@ -184,7 +197,6 @@ const getCategoryClass = (category, params) => {
:hidden-tags="['orderFk', 'orderBy']"
:expr-builder="exprBuilder"
:custom-tags="['tagGroups']"
@init="onFilterInit"
@remove="clearFilter"
>
<template #tags="{ tag, formatFn }">
@ -274,17 +286,13 @@ const getCategoryClass = (category, params) => {
<QItem class="q-my-md">
<QItemSection>
<VnSelect
:label="t('params.order')"
:label="t('Order')"
v-model="selectedOrder"
:options="orderList || []"
option-value="way"
option-label="name"
:options="moreFields"
option-label="label"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderChange(value, params, searchFn)
"
@ -294,17 +302,14 @@ const getCategoryClass = (category, params) => {
<QItem class="q-mb-md">
<QItemSection>
<VnSelect
:label="t('params.order')"
:label="t('Order by')"
v-model="selectedOrderField"
:options="OrderFields || []"
option-value="field"
option-label="name"
:options="moreFieldsOrder"
option-label="label"
option-value="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderFieldChange(value, params, searchFn)
"
@ -341,10 +346,9 @@ const getCategoryClass = (category, params) => {
class="filter-input"
/>
<VnSelect
v-else
:label="t('params.value')"
v-model="value.value"
:options="tagOptions || []"
:options="tagValue || []"
option-value="value"
option-label="value"
dense
@ -352,17 +356,18 @@ const getCategoryClass = (category, params) => {
rounded
emit-value
use-input
:disable="!selectedTag"
class="filter-input"
/>
@new-value="createValue"
>
</VnSelect>
<FetchData
v-if="selectedTag && !selectedTag.isFree"
:url="`Tags/${selectedTag?.id}/filterValue`"
v-if="selectedTag"
:url="`Tags/${selectedTag}/filterValue`"
limit="30"
auto-load
@on-fetch="(data) => (tagOptions = data)"
/>
{{ console.log('selectedTag: ', selectedTag) }}
<QIcon
name="delete"
@ -388,7 +393,6 @@ const getCategoryClass = (category, params) => {
rounded
type="button"
unelevated
:disable="isButtonDisabled"
@click.stop="applyTagFilter(params, searchFn)"
/>
</QItemSection>
@ -453,6 +457,12 @@ en:
tag: Tag
value: Value
order: Order
ASC: Ascendant
DESC: Descendant
Relevancy: Relevancy
ColorAndPrice: Color and price
Name: Name
Price: Price
es:
params:
type: Tipo
@ -460,6 +470,14 @@ es:
tag: Etiqueta
value: Valor
order: Orden
ASC: Ascendiente
DESC: Descendiente
Relevancy: Relevancia
ColorAndPrice: Color y precio
Name: Nombre
Price: Precio
Order: Orden
Order by: Ordenar por
Plant: Planta
Flower: Flor
Handmade: Confección

View File

@ -59,7 +59,10 @@ const dialog = ref(null);
</template>
<div class="footer">
<div class="price">
<p>{{ item.available }} {{ t('to') }} {{ item.price }}</p>
<p>
{{ item.available }} {{ t('to') }}
{{ toCurrency(item.price) }}
</p>
<QIcon name="add_circle" class="icon">
<QTooltip>{{ t('globals.add') }}</QTooltip>
<QPopupProxy ref="dialog">

View File

@ -1,6 +1,6 @@
<script setup>
import { useRoute } from 'vue-router';
import { reactive, ref, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useState } from 'composables/useState';
@ -16,6 +16,7 @@ const route = useRoute();
const state = useState();
const ORDER_MODEL = 'order';
const router = useRouter();
const isNew = Boolean(!route.params.id);
const initialFormState = reactive({
clientFk: null,
@ -26,21 +27,20 @@ const initialFormState = reactive({
const clientList = ref([]);
const agencyList = ref([]);
const addressList = ref([]);
const clientId = ref(null);
const onClientsFetched = async (data) => {
try {
const onClientsFetched = (data) => {
clientList.value = data;
initialFormState.clientFk = Number(route.query?.clientFk) || null;
clientId.value = initialFormState.clientFk;
if (initialFormState.clientFk) {
const { defaultAddressFk } = clientList.value.find(
const client = clientList.value.find(
(client) => client.id === initialFormState.clientFk
);
if (defaultAddressFk) await fetchAddressList(defaultAddressFk);
}
} catch (err) {
console.error('Error fetching clients', err);
if (client && client.defaultAddressFk) {
jon marked this conversation as resolved Outdated

podemos simplificarlo

if (!client?.defaultAddressFk)
        throw new Error(t(`No default address found for the client`));
    fetchAddressList(client.defaultAddressFk);
podemos simplificarlo ``` if (!client?.defaultAddressFk) throw new Error(t(`No default address found for the client`)); fetchAddressList(client.defaultAddressFk); ```
fetchAddressList(client.defaultAddressFk);
} else {
throw new Error(t(`No default address found for the client`));
}
};
@ -55,7 +55,6 @@ const fetchAddressList = async (addressId) => {
},
});
addressList.value = data;
// Set address by default
if (addressList.value?.length === 1) {
state.get(ORDER_MODEL).addressFk = addressList.value[0].id;
}
@ -121,6 +120,21 @@ const orderFilter = {
},
],
};
const onClientChange = async (clientId) => {
try {
const { data } = await axios.get(`Clients/${clientId}`);
console.log('info cliente: ', data);
await fetchAddressList(data.defaultAddressFk);
} catch (error) {
console.error('Error al cambiar el cliente:', error);
}
};
async function onDataSaved(data) {
await router.push({ path: `/order/${data}/catalog` });
}
</script>
<template>
@ -134,13 +148,15 @@ const orderFilter = {
<div class="q-pa-md">
<FormModel
:url="!isNew ? `Orders/${route.params.id}` : null"
:url-create="isNew ? 'Orders/new' : null"
:url-create="'Orders/new'"
jon marked this conversation as resolved Outdated

url-create="Orders/new"

url-create="Orders/new"
@on-data-saved="onDataSaved"
:model="ORDER_MODEL"
:form-initial-data="isNew ? initialFormState : null"
:observe-form-changes="!isNew"
:mapper="isNew ? orderMapper : null"
:filter="orderFilter"
@on-fetch="fetchOrderDetails"
auto-load
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
@ -151,9 +167,7 @@ const orderFilter = {
option-value="id"
option-label="name"
hide-selected
@update:model-value="
(client) => fetchAddressList(client.defaultAddressFk)
"
@update:model-value="onClientChange"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -170,12 +184,10 @@ const orderFilter = {
v-model="data.addressFk"
:options="addressList"
option-value="id"
option-label="nickname"
option-label="street"
hide-selected
:disable="!addressList?.length"
@update:model-value="
() => fetchAgencyList(data.landed, data.addressFk)
"
@update:model-value="onAddressChange"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
@ -216,3 +228,8 @@ const orderFilter = {
</FormModel>
</div>
</template>
<i18n>
es:
No default address found for the client: No hay ninguna dirección asociada a este cliente.
</i18n>

View File

@ -35,6 +35,20 @@ function extractTags(items) {
});
});
tags.value = resultTags;
extractValueTags(items);
}
const tagValue = ref([]);
function extractValueTags(items) {
const resultValueTags = items.flatMap((x) =>
Object.keys(x)
.filter((k) => /^value\d+$/.test(k))
.map((v) => x[v])
.filter((v) => v)
.sort()
);
tagValue.value = resultValueTags;
}
</script>
@ -66,7 +80,11 @@ function extractTags(items) {
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<OrderCatalogFilter data-key="OrderCatalogList" :tags="tags" />
<OrderCatalogFilter
data-key="OrderCatalogList"
:tag-value="tagValue"
:tags="tags"
/>
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">

View File

@ -7,13 +7,13 @@ import { useQuasar } from 'quasar';
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 VnConfirm from 'components/ui/VnConfirm.vue';
import { toCurrency, toDate } from 'src/filters';
import { useSession } from 'composables/useSession';
import axios from 'axios';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
const route = useRoute();
const { t } = useI18n();
@ -43,6 +43,7 @@ function confirmRemove(item) {
}
async function remove(item) {
console.log('item: ', item);
await axios.post('OrderRows/removes', {
jon marked this conversation as resolved Outdated

💣👀

💣👀
actualOrderId: route.params.id,
rows: [item.id],
@ -61,6 +62,35 @@ async function confirmOrder() {
type: 'positive',
});
}
const detailsColumns = ref([
{
name: 'item',
label: t('order.summary.item'),
field: (row) => row?.item?.id,
sortable: true,
},
{
name: 'description',
label: t('globals.description'),
field: (row) => row?.item?.name,
},
{
name: 'quantity',
label: t('order.summary.quantity'),
field: (row) => row?.quantity,
},
{
name: 'price',
label: t('order.summary.price'),
field: (row) => toCurrency(row?.price),
},
{
name: 'amount',
label: t('order.summary.amount'),
field: (row) => toCurrency(row?.quantity * row?.price),
},
]);
</script>
<template>
@ -83,11 +113,13 @@ async function confirmOrder() {
auto-load
/>
<QPage :key="componentKey" class="column items-center q-pa-md">
<div class="vn-card-list">
<div class="order-list">
<div v-if="!orderSummary.total" class="no-result">
{{ t('globals.noResults') }}
</div>
<QCard v-else class="order-lines-summary q-pa-lg">
<QDrawer side="right" :width="300" show-if-above>
<QCard class="order-lines-summary q-pa-lg">
<p class="header text-right block">
{{ t('summary') }}
</p>
@ -107,6 +139,7 @@ async function confirmOrder() {
:value="toCurrency(orderSummary?.total)"
/>
</QCard>
</QDrawer>
<VnPaginate
data-key="OrderLines"
url="OrderRows"
@ -125,74 +158,108 @@ async function confirmOrder() {
}"
>
<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"
<div class="q-pa-md">
<QTable
:columns="detailsColumns"
jon marked this conversation as resolved Outdated

Hemos cambiado CardList por QTable?? esta no es la manera correcta, si quieres vemos como definir bien las tablas.

Aunque te digo de dejarlo para el final porque se viene VnTable y a lo mejor cambia la cosa. O como tu veas

Hemos cambiado CardList por QTable?? esta no es la manera correcta, si quieres vemos como definir bien las tablas. Aunque te digo de dejarlo para el final porque se viene VnTable y a lo mejor cambia la cosa. O como tu veas
:rows="rows"
flat
style="text-align: center"
>
<template #title>
<div class="flex items-center">
<div class="image-wrapper q-mr-md">
<template #header="props">
<QTr :props="props" class="tr-header">
<QTh></QTh>
<QTh auto-width>{{ t('item') }}</QTh>
<QTh>{{ t('globals.description') }}</QTh>
<QTh>{{ t('warehouse') }}</QTh>
<QTh>{{ t('shipped') }}</QTh>
<QTh auto-width>
{{ t('order.summary.quantity') }}
</QTh>
<QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTh auto-width>{{ t('amount') }}</QTh>
<QTh></QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd>
{{ console.log('props: ', props.row.item.id) }}
<QImg
:src="`/api/Images/catalog/50x50/${row?.item?.id}/download?access_token=${token}`"
:src="`/api/Images/catalog/50x50/${props.row.item?.id}/download?access_token=${token}`"
spinner-color="primary"
:ratio="1"
height="50"
width="50"
class="image"
class="image-wrapper"
style="cursor: pointer"
/>
</QTd>
<QTd key="item" :props="props" class="item">
<span class="link">
<QBtn flat>
{{ props.row.item?.id }}
</QBtn>
<ItemDescriptorProxy
:id="props.row.item?.id"
/>
</div>
<div
class="title text-primary text-weight-bold text-h5"
>
{{ row?.item?.name }}
</div>
<QChip class="q-chip-color" outline size="sm">
{{ t('ID') }}: {{ row.id }}
</QChip>
</div>
</template>
<template #list-items>
<div class="q-mb-sm">
<span class="text-uppercase subname">
{{ row.item.subName }}
</span>
<fetched-tags :item="row.item" :max-length="5" />
</QTd>
<QTd
key="description"
:props="props"
class="description-cell"
>
<div class="row full-width justify-between">
{{ props.row.item.name }}
<div
v-if="props.row.item.subName"
class="subName"
>
{{ props.row.item.subName.toUpperCase() }}
jon marked this conversation as resolved Outdated

Nos e muestran porque te falta pasarle row.item

Nos e muestran porque te falta pasarle row.item
</div>
<VnLv :label="t('item')" :value="String(row.item.id)" />
<VnLv
:label="t('warehouse')"
:value="row.warehouse.name"
</div>
<FetchedTags
:item="props.row.item"
:max-length="6"
class="fetched-tags"
/>
<VnLv
:label="t('shipped')"
:value="toDate(row.shipped)"
/>
<VnLv
:label="t('quantity')"
:value="String(row.quantity)"
/>
<VnLv
:label="t('price')"
:value="toCurrency(row.price)"
/>
<VnLv
:label="t('amount')"
:value="toCurrency(row.price * row.quantity)"
/>
</template>
<template #actions v-if="!order?.isConfirmed">
<QBtn
:label="t('remove')"
@click.stop="confirmRemove(row)"
</QTd>
<QTd>
{{ props.row.warehouse.name }}
</QTd>
<QTd>
{{ toDate(props.row.shipped) }}
jon marked this conversation as resolved
Review

Eliminar body-cell-XXX cuando sólo se muestra valor

Eliminar body-cell-XXX cuando sólo se muestra valor
</QTd>
<QTd key="quantity" :props="props">
{{ props.row.quantity }}
</QTd>
<QTd key="price" :props="props">
{{ toCurrency(props.row.price) }}
</QTd>
<QTd key="amount" :props="props">
{{
toCurrency(
props.row?.quantity * props.row?.price
)
}}
</QTd>
<QTd>
<QIcon
name="delete"
color="primary"
style="margin-top: 15px"
/>
size="sm"
class="cursor-pointer"
@click.stop="confirmRemove(props.row)"
style="margin-left: 40%"
>
<QTooltip>{{
t('Remove thermograph')
}}</QTooltip>
</QIcon>
</QTd>
</QTr>
</template>
</CardList>
</QTable>
</div>
</template>
jon marked this conversation as resolved Outdated

es necesario?

es necesario?
</VnPaginate>
@ -239,14 +306,7 @@ async function confirmOrder() {
.image-wrapper {
height: 50px;
width: 50px;
.image {
border-radius: 50%;
}
}
.subname {
color: var(--vn-label-color);
}
.no-result {
@ -255,6 +315,38 @@ async function confirmOrder() {
color: var(--vn-label-color);
text-align: center;
}
.description {
display: flex;
flex-direction: column;
justify-content: center;
jon marked this conversation as resolved Outdated

Mmm...has definido una css class para cada columna?
Lo vemos con detalle 1 a 1

Mmm...has definido una css class para cada columna? Lo vemos con detalle 1 a 1
text-align: left;
height: auto;
padding-top: 12px;
padding-bottom: 12px;
.name {
jon marked this conversation as resolved Outdated

poniendo 5, 10, 15, 20 o 25 tenemos el mismo resultado

poniendo 5, 10, 15, 20 o 25 tenemos el mismo resultado
display: flex;
jon marked this conversation as resolved Outdated

hay mucho estilo, en general no hay que usar estilo, sino que se use lo nativo, pongo a @jsegarra para que lo valide y/o te explique como implementarlo.

hay mucho estilo, en general no hay que usar estilo, sino que se use lo nativo, pongo a @jsegarra para que lo valide y/o te explique como implementarlo.
align-items: center;
padding-bottom: 8px;
& > * {
flex: 1;
}
}
}
.description-cell {
width: 25%;
}
.fetched-tags {
max-width: 90%;
}
.subName {
text-transform: uppercase;
color: var(--vn-label-color);
}
</style>
<i18n>
en: