0
0
Fork 0

Merge pull request 'Create Orders Page' (#39) from feature/order into dev

Reviewed-on: hyervoni/salix-front-mindshore#39
This commit is contained in:
Kevin Martinez 2023-12-20 18:01:25 +00:00
commit 6e0ce27ee7
26 changed files with 1984 additions and 19 deletions

View File

@ -27,6 +27,10 @@ const $props = defineProps({
type: String,
default: '',
},
params: {
type: Object,
default: null,
}
});
const emit = defineEmits(['onFetch']);
@ -46,7 +50,7 @@ async function fetch() {
if ($props.limit) filter.limit = $props.limit;
const { data } = await axios.get($props.url, {
params: { filter: JSON.stringify(filter) },
params: { filter: JSON.stringify(filter), ...$props.params },
});
emit('onFetch', data);

View File

@ -55,6 +55,10 @@ const $props = defineProps({
description:
'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)',
},
mapper: {
type: Function,
default: null,
}
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
@ -83,6 +87,7 @@ onUnmounted(() => {
const isLoading = ref(false);
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({ ...$props.formInitialData });
const formData = computed(() => state.get($props.model));
@ -92,7 +97,10 @@ const startFormWatcher = () => {
watch(
() => formData.value,
(val) => {
if (val) hasChanges.value = true;
if (!isResetting.value && val) {
hasChanges.value = true;
}
isResetting.value = false;
},
{ deep: true }
);
@ -121,11 +129,12 @@ async function save() {
isLoading.value = true;
try {
const body = $props.mapper ? $props.mapper(formData.value) : formData.value
if ($props.urlCreate) {
await axios.post($props.urlCreate, formData.value);
await axios.post($props.urlCreate, body);
notify('globals.dataCreated', 'positive');
} else {
await axios.patch($props.urlUpdate || $props.url, formData.value);
await axios.patch($props.urlUpdate || $props.url, body);
}
emit('onDataSaved', formData.value);
} catch (err) {
@ -144,6 +153,7 @@ function reset() {
emit('onFetch', state.get($props.model));
if ($props.observeFormChanges) {
hasChanges.value = false;
isResetting.value = true;
}
}

View File

@ -31,11 +31,21 @@ const props = defineProps({
description:
'Algunos filtros vienen con parametros de búsqueda por default y necesitan tener si o si un valor, por eso de ser necesario, esta prop nos sirve para saber que filtros podemos remover y cuales no',
},
exprBuilder: {
type: Function,
default: null,
},
hiddenTags: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['refresh', 'clear', 'search']);
const emit = defineEmits(['refresh', 'clear', 'search', 'init']);
const arrayData = useArrayData(props.dataKey);
const arrayData = useArrayData(props.dataKey, {
exprBuilder: props.exprBuilder,
});
const store = arrayData.store;
const userParams = ref({});
@ -44,9 +54,11 @@ onMounted(() => {
if (Object.keys(store.userParams).length > 0) {
userParams.value = JSON.parse(JSON.stringify(store.userParams));
}
emit('init', { params: userParams.value });
});
const isLoading = ref(false);
async function search() {
isLoading.value = true;
const params = { ...userParams.value };
@ -93,17 +105,12 @@ async function clearFilters() {
}
const tags = computed(() => {
const params = [];
for (const param in userParams.value) {
if (!userParams.value[param]) continue;
params.push({
label: param,
value: userParams.value[param],
});
}
return params;
return Object.entries(userParams.value)
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
.map(([key, value]) => ({
label: key,
value: value,
}));
});
async function remove(key) {

View File

@ -31,7 +31,7 @@ const props = defineProps({
default: null,
},
order: {
type: String,
type: [String, Array],
default: '',
},
limit: {
@ -149,7 +149,7 @@ async function onLoad(...params) {
v-if="props.skeleton && props.autoLoad && !store.data"
class="card-list q-gutter-y-md"
>
<QCard class="card" v-for="$index in $props.limit" :key="$index">
<QCard class="card" v-for="$index in props.limit" :key="$index">
<QItem v-ripple class="q-pa-none items-start cursor-pointer q-hoverable">
<QItemSection class="q-pa-md">
<QSkeleton type="rect" class="q-mb-md" square />

View File

@ -53,6 +53,14 @@ const props = defineProps({
type: Object,
default: null,
},
staticParams: {
type: Array,
default: () => [],
},
exprBuilder: {
type: Function,
default: null,
},
});
const router = useRouter();
@ -69,8 +77,11 @@ onMounted(() => {
});
async function search() {
const staticParams = Object.entries(store.userParams)
.filter(([key, value]) => value && (props.staticParams || []).includes(key));
await arrayData.applyFilter({
params: {
...Object.fromEntries(staticParams),
search: searchText.value,
},
});

View File

@ -549,6 +549,61 @@ export default {
country: 'Country',
},
},
order: {
pageTitles: {
order: 'Orders',
orderList: 'List',
create: 'Create',
summary: 'Summary',
basicData: 'Basic Data',
catalog: 'Catalog',
},
field: {
salesPersonFk: 'Sales Person',
clientFk: 'Client',
isConfirmed: 'Confirmed',
created: 'Created',
landed: 'Landed',
hour: 'Hour',
agency: 'Agency',
total: 'Total'
},
form: {
clientFk: 'Client',
addressFk: 'Address',
landed: 'Landed',
agencyModeFk: 'Agency',
},
list: {
newOrder: 'New Order'
},
summary: {
basket: 'Basket',
nickname: 'Nickname',
company: 'Company',
confirmed: 'Confirmed',
notConfirmed: 'Not confirmed',
created: 'Created',
landed: 'Landed',
phone: 'Phone',
createdFrom: 'Created From',
address: 'Address',
notes: 'Notes',
subtotal: 'Subtotal',
total: 'Total',
vat: 'VAT',
state: 'State',
alias: 'Alias',
items: 'Items',
orderTicketList: 'Order Ticket List',
details: 'Details',
item: 'Item',
description: 'Description',
quantity: 'Quantity',
price: 'Price',
amount: 'Amount'
}
},
worker: {
pageTitles: {
workers: 'Workers',

View File

@ -458,6 +458,61 @@ export default {
},
},
},
order: {
pageTitles: {
order: 'Cesta',
orderList: 'Listado',
create: 'Crear',
summary: 'Resumen',
basicData: 'Datos básicos',
catalog: 'Catálogo',
},
field: {
salesPersonFk: 'Comercial',
clientFk: 'Cliente',
isConfirmed: 'Confirmada',
created: 'Creado',
landed: 'F. entrega',
hour: 'Hora',
agency: 'Agencia',
total: 'Total'
},
form: {
clientFk: 'Cliente',
addressFk: 'Dirección',
landed: 'F. entrega',
agencyModeFk: 'Agencia',
},
list: {
newOrder: 'Nuevo Pedido'
},
summary: {
basket: 'Cesta',
nickname: 'Alias',
company: 'Empresa',
confirmed: 'Confirmada',
notConfirmed: 'No confirmada',
created: 'Creado',
landed: 'F. entrega',
phone: 'Teléfono',
createdFrom: 'Creado desde',
address: 'Dirección',
notes: 'Notas',
subtotal: 'Subtotal',
total: 'Total',
vat: 'IVA',
state: 'Estado',
alias: 'Alias',
items: 'Items',
orderTicketList: 'Tickets del pedido',
details: 'Detalles',
item: 'Item',
description: 'Descripción',
quantity: 'Cantidad',
price: 'Precio',
amount: 'Monto'
}
},
shelving: {
pageTitles: {
shelving: 'Carros',

View File

@ -0,0 +1,12 @@
<script setup>
import OrderForm from 'pages/Order/Card/OrderForm.vue';
</script>
<template>
<QToolbar>
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<OrderForm />
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,23 @@
<script setup>
import LeftMenu from 'components/LeftMenu.vue';
import { useStateStore } from 'stores/useStateStore';
import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<OrderDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<div class="q-pa-md">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template>

View File

@ -0,0 +1,208 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import axios from 'axios';
import { useRoute } from 'vue-router';
const { t } = useI18n();
const route = useRoute();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const categoryList = ref(null);
const selectedCategoryFk = ref(null);
const typeList = ref(null);
const selectedTypeFk = ref(null);
const selectCategory = (params, category) => {
if (params.categoryFk === category?.id) {
selectedCategoryFk.value = null;
params.categoryFk = null;
typeList.value = null;
} else {
selectedCategoryFk.value = category?.id;
params.categoryFk = category?.id;
loadTypes(category?.id);
}
};
const loadTypes = async (categoryFk) => {
const { data } = await axios.get(`Orders/${route.params.id}/getItemTypeAvailable`, {
params: { itemCategoryId: 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;
}
};
const selectedCategory = computed(() =>
(categoryList.value || []).find(
(category) => category?.id === selectedCategoryFk.value
)
);
const selectedType = computed(() => {
return (typeList.value || []).find((type) => type?.id === selectedTypeFk.value);
});
function exprBuilder(param, value) {
switch (param) {
case 'categoryFk':
case 'typeFk':
return { [param]: value };
}
}
</script>
<template>
<FetchData
url="ItemCategories"
limit="30"
auto-load
@on-fetch="
(data) => {
categoryList = (data || [])
.filter((category) => category.display)
.map((category) => ({
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
}
"
/>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
:hidden-tags="['orderFk', 'orderBy']"
:expr-builder="exprBuilder"
@init="onFilterInit"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }}
</strong>
<strong v-else-if="tag.label === 'typeFk'">
{{ t(selectedType?.name || '') }}
</strong>
<div v-else class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QList dense>
<QItem class="category-filter q-mt-md">
<div
v-for="category in categoryList"
:key="category.name"
:class="[
'category',
category.id === params?.categoryFk && 'active',
]"
>
<QIcon
:name="category.icon"
class="category-icon"
@click="selectCategory(params, category)"
>
<QTooltip>
{{ t(category.name) }}
</QTooltip>
</QIcon>
</div>
</QItem>
<QItem class="q-mt-md">
<QItemSection>
<VnSelectFilter
:label="t('params.type')"
v-model="params.typeFk"
:options="typeList"
option-value="id"
option-label="name"
dense
outlined
rounded
emit-value
use-input
:disable="!selectedCategoryFk"
@update:model-value="(value) => (selectedTypeFk = value)"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.categoryName }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QItemSection>
</QItem>
</QList>
</template>
</VnFilterPanel>
</template>
<style lang="scss" scoped>
.category-filter {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
.category {
flex: 1;
flex-shrink: 0;
display: flex;
justify-content: center;
&.active {
.category-icon {
background-color: $primary;
}
}
}
.category-icon {
border-radius: 50%;
background-color: var(--vn-label);
font-size: 2.6rem;
padding: 8px;
cursor: pointer;
}
}
</style>
<i18n>
en:
params:
type: Type
orderBy: Order By
es:
params:
type: Tipo
orderBy: Ordenar por
Plant: Planta
Flower: Flor
Handmade: Confección
Green: Verde
Accessories: Complemento
Fruit: Fruta
</i18n>

View File

@ -0,0 +1,184 @@
<script setup>
import { useSession } from 'composables/useSession';
import VnLv from 'components/ui/VnLv.vue';
import { useI18n } from 'vue-i18n';
import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue';
import toCurrency from '../../../filters/toCurrency';
import { ref } from 'vue';
const session = useSession();
const token = session.getToken();
const { t } = useI18n();
defineProps({
item: {
type: Object,
required: true,
},
});
const dialog = ref(null);
</script>
<template>
<div class="container order-catalog-item overflow-hidden">
<div class="card shadow-6 bg-dark">
<div class="img-wrapper">
<QImg
:src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`"
spinner-color="primary"
:ratio="1"
height="192"
width="192"
class="image"
/>
<div v-if="item.hex" class="item-color-container">
<div
class="item-color"
:style="{ backgroundColor: `#${item.hex}` }"
></div>
</div>
</div>
<div class="content">
<span class="link">{{ item.name }}</span>
<p class="subName">{{ item.subName }}</p>
<template v-for="index in 4" :key="`tag-${index}`">
<VnLv
v-if="item?.[`tag${index + 4}`]"
:label="item?.[`tag${index + 4}`] + ':'"
:value="item?.[`value${index + 4}`]"
/>
</template>
<QRating
:model-value="item.stars"
icon="star"
icon-selected="star"
color="primary"
readonly
/>
<div class="footer">
<div class="price">
<p>{{ item.available }} {{ t('to') }} {{ item.price }}</p>
<QIcon name="add_circle" class="icon">
<QTooltip>{{ t('globals.add') }}</QTooltip>
<QPopupProxy ref="dialog">
<OrderCatalogItemDialog
:prices="item.prices"
@added="() => dialog.hide()"
/>
</QPopupProxy>
</QIcon>
</div>
<p v-if="item.priceKg" class="price-kg">
{{ t('price-kg') }} {{ toCurrency(item.priceKg) || 1123 }}
</p>
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.order-catalog-item {
.vn-label-value {
display: flex;
gap: 4px;
font-size: 11px;
.label {
color: var(--vn-label);
}
.value {
color: var(--vn-text);
}
}
}
</style>
<style lang="scss" scoped>
.container {
max-width: 448px;
width: 100%;
}
.card {
display: flex;
height: 100%;
max-height: 192px;
}
.card > * {
flex: 1;
}
.img-wrapper {
position: relative;
max-width: 192px;
}
.content {
padding: 12px;
display: flex;
flex-direction: column;
gap: 4px;
.subName {
color: var(--vn-label);
text-transform: uppercase;
}
p {
margin-bottom: 0;
}
}
.footer {
.price {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: flex;
align-items: center;
justify-content: space-between;
p {
font-size: 12px;
}
.icon {
color: $primary;
font-size: 24px;
cursor: pointer;
}
}
.price-kg {
font-size: 12px;
}
}
.item-color-container {
position: absolute;
bottom: 12px;
right: 12px;
background: linear-gradient($dark, $primary);
border-radius: 50%;
width: 40px;
height: 40px;
padding: 4px;
.item-color {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
</style>
<i18n>
es:
to: to
price-kg: Precio por Kg
en:
to: hasta
price-kg: Price per Kg
</i18n>

View File

@ -0,0 +1,83 @@
<script setup>
import toCurrency from '../../../filters/toCurrency';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useRoute } from 'vue-router';
import useNotify from 'composables/useNotify';
const { t } = useI18n();
const route = useRoute();
const { notify } = useNotify();
const props = defineProps({
prices: {
type: Array,
required: true,
},
});
const emit = defineEmits(['added']);
const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 })));
const addToOrder = async () => {
const items = (fields.value || []).filter((item) => Number(item.quantity) > 0);
await axios.post('/OrderRows/addToOrder', {
items,
orderFk: Number(route.params.id),
});
notify(t('globals.dataSaved'), 'positive');
emit('added');
};
</script>
<template>
<div class="container order-catalog-item q-pb-md">
<QForm @submit.prevent="addToOrder">
<QMarkupTable class="shadow-0">
<tbody>
<tr v-for="item in fields" :key="item.warehouse">
<td class="text-bold q-py-lg">
{{ item.warehouse }}
</td>
<td class="text-right">
<span
class="link"
@click="
() => {
item.quantity =
Number(item.quantity) + item.grouping;
}
"
>
{{ item.grouping }}
</span>
x {{ toCurrency(item.price) }}
</td>
<td class="text-right">
<QInput
v-model="item.quantity"
type="number"
:step="item.grouping"
min="0"
dense
/>
</td>
</tr>
</tbody>
</QMarkupTable>
<div class="flex justify-center q-mt-lg">
<QBtn color="primary" type="submit">
{{ t('globals.add') }}
</QBtn>
</div>
</QForm>
</div>
</template>
<style lang="scss" scoped>
.container {
max-width: 448px;
width: 100%;
}
</style>

View File

@ -0,0 +1,128 @@
<script setup>
import { ref, computed } from 'vue';
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";
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const state = useState();
const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const filter = {
include: [
{ relation: 'agencyMode', scope: { fields: ['name'] } },
{
relation: 'address',
scope: { fields: ['nickname'] },
},
{ relation: 'rows', scope: { fields: ['id'] } },
{
relation: 'client',
scope: {
fields: [
'salesPersonFk',
'name',
'isActive',
'isFreezed',
'isTaxDataChecked',
],
include: {
relation: 'salesPersonUser',
scope: { fields: ['id', 'name'] },
},
},
},
],
};
const data = ref(useCardDescription());
const setData = (entity) => {
if (!entity) return;
data.value = useCardDescription(entity.client.name, entity.id);
state.set('ClaimDescriptor', entity);
};
</script>
<template>
<CardDescriptor
ref="descriptor"
:url="`Orders/${entityId}`"
:filter="filter"
module="Order"
:title="data.title"
:subtitle="data.subtitle"
@on-fetch="setData"
data-key="orderData"
>
<template #menu="{ entity }">
<OrderDescriptorMenu :order="entity" />
</template>
<template #body="{ entity }">
<VnLv
:label="t('order.summary.state')"
:value="
t(
entity.isConfirmed
? 'order.summary.confirmed'
: 'order.summary.notConfirmed'
)
"
/>
<VnLv :label="t('order.field.salesPersonFk')">
<template #value>
<span class="link">
{{ entity?.client?.salesPersonUser?.name || '-' }}
<WorkerDescriptorProxy :id="entity?.client?.salesPersonFk" />
</span>
</template>
</VnLv>
<VnLv :label="t('order.summary.landed')" :value="toDate(entity?.landed)" />
<VnLv :label="t('order.field.agency')" :value="entity?.agencyMode?.name" />
<VnLv :label="t('order.summary.alias')" :value="entity?.address?.nickname" />
<VnLv :label="t('order.summary.items')" :value="entity?.rows?.length || 0" />
<VnLv :label="t('order.summary.total')" :value="toCurrency(entity?.total)" />
</template>
<template #actions="{ entity }">
<QCardActions>
<QBtn
size="md"
icon="vn:ticket"
color="primary"
:to="{
name: 'TicketList',
query: { params: JSON.stringify({ orderFk: entity.id }) },
}"
>
<QTooltip>{{ t('order.summary.orderTicketList') }}</QTooltip>
</QBtn>
<QBtn
size="md"
icon="vn:client"
color="primary"
:to="{ name: 'CustomerCard', params: { id: entity.clientFk } }"
>
<QTooltip>{{ t('claim.card.customerSummary') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>

View File

@ -0,0 +1,63 @@
<script setup>
import axios from 'axios';
import { ref } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnConfirm from 'components/ui/VnConfirm.vue';
const $props = defineProps({
order: {
type: Object,
required: true,
},
});
const router = useRouter();
const quasar = useQuasar();
const { t } = useI18n();
const order = ref($props.order);
function confirmRemove() {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('confirmDeletion'),
message: t('confirmDeletionMessage'),
promise: remove,
},
})
.onOk(async () => await router.push({ name: 'OrderList' }));
}
async function remove() {
const id = order.value.id;
await axios.delete(`Orders/${id}`);
quasar.notify({
message: t('globals.dataDeleted'),
type: 'positive',
});
}
</script>
<template>
<QItem @click="confirmRemove()" v-ripple clickable>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('deleteOrder') }}</QItemSection>
</QItem>
</template>
<i18n>
en:
deleteOrder: Delete order,
confirmDeletion: Confirm deletion,
confirmDeletionMessage: Are you sure you want to delete this order?
es:
deleteOrder: Eliminar pedido,
confirmDeletion: Confirmar eliminación,
confirmDeletionMessage: Seguro que quieres eliminar este pedido?
</i18n>

View File

@ -0,0 +1,265 @@
<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 VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const agencyFilter = { fields: ['id', 'name'] };
const agencyList = ref(null);
const salesPersonFilter = {
fields: ['id', 'nickname'],
};
const salesPersonList = ref(null);
const sourceFilter = { fields: ['value'] };
const sourceList = ref(null);
</script>
<template>
<FetchData
url="AgencyModes/isActive"
:filter="agencyFilter"
limit="30"
sort-by="name ASC"
auto-load
@on-fetch="(data) => (agencyList = data)"
/>
<FetchData
url="Workers/search"
:filter="salesPersonFilter"
limit="30"
sort-by="nickname ASC"
@on-fetch="(data) => (salesPersonList = data)"
:params="{ departmentCodes: ['VT'] }"
auto-load
/>
<FetchData
url="Orders/getSourceValues"
:filter="sourceFilter"
limit="30"
sort-by="value ASC"
@on-fetch="(data) => (sourceList = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<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 }">
<QList id="orderFilter" dense>
<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">
<VnSelectFilter
: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">
<VnSelectFilter
: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>
</VnSelectFilter>
</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">
<VnSelectFilter
: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>
</QList>
</template>
</VnFilterPanel>
</template>
<style lang="scss">
#orderFilter {
.q-item {
padding-top: 8px;
}
}
</style>
<i18n>
en:
params:
search: Includes
clientFk: Client
agencyModeFk: Agency
salesPersonFk: Sales Person
from: From
to: To
orderFk: Order
sourceApp: Application
myTeam: My Team
isConfirmed: Is Confirmed
showEmpty: Show Empty
customerId: Customer ID
agency: Agency
salesPerson: Sales Person
fromLanded: From Landed
toLanded: To Landed
orderId: Order ID
application: Application
myTeam: My Team
isConfirmed: Order Confirmed
showEmpty: Show Empty
es:
params:
search: Búsqueda
clientFk: Cliente
agencyModeFk: Agencia
salesPersonFk: Comercial
from: Desde
to: Hasta
orderFk: Cesta
sourceApp: Aplicación
myTeam: Mi Equipo
isConfirmed: Confirmado
showEmpty: Mostrar vacías
customerId: ID Cliente
agency: Agencia
salesPerson: Comercial
fromLanded: Desde F. entrega
toLanded: Hasta F. entrega
orderId: ID Cesta
application: Aplicación
myTeam: Mi Equipo
isConfirmed: Confirmado
showEmpty: Mostrar vacías
</i18n>

View File

@ -0,0 +1,209 @@
<script setup>
import { useRoute } from 'vue-router';
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useState } from 'composables/useState';
import FormModel from 'components/FormModel.vue';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const route = useRoute();
const state = useState();
const ORDER_MODEL = 'order';
const isNew = Boolean(!route.params.id);
const initialFormState = reactive({
clientFk: null,
addressFk: null,
agencyModeFk: null,
landed: null,
});
const clientList = ref([]);
const agencyList = ref([]);
const addressList = ref([]);
const fetchAddressList = async (addressId) => {
try {
const { data } = await axios.get('addresses', {
params: {
filter: JSON.stringify({
fields: ['id', 'nickname', 'street', 'city'],
where: { id: addressId },
}),
},
});
addressList.value = data;
// Set address by default
if (addressList.value?.length === 1) {
state.get(ORDER_MODEL).addressFk = addressList.value[0].id;
}
} catch (err) {
console.error(`Error fetching addresses`, err);
return err.response;
}
};
const fetchAgencyList = async (landed, addressFk) => {
if (!landed || !addressFk) {
return;
}
try {
const { data } = await axios.get('Agencies/landsThatDay', {
params: {
addressFk,
landed: new Date(landed).toISOString(),
},
});
agencyList.value = data;
} catch (err) {
console.error(`Error fetching agencies`, err);
return err.response;
}
};
const fetchOrderDetails = (order) => {
fetchAddressList(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 = {
include: [
{ relation: 'agencyMode', scope: { fields: ['name'] } },
{
relation: 'address',
scope: { fields: ['nickname'] },
},
{ relation: 'rows', scope: { fields: ['id'] } },
{
relation: 'client',
scope: {
fields: [
'salesPersonFk',
'name',
'isActive',
'isFreezed',
'isTaxDataChecked',
],
include: {
relation: 'salesPersonUser',
scope: { fields: ['id', 'name'] },
},
},
},
],
};
</script>
<template>
<QToolbar>
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<FetchData
url="Clients"
@on-fetch="(data) => (clientList = data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'] }"
auto-load
/>
<FormModel
:url="!isNew ? `Orders/${route.params.id}` : null"
:url-create="isNew ? 'Orders/new' : null"
:model="ORDER_MODEL"
:form-initial-data="isNew ? initialFormState : null"
:observe-form-changes="!isNew"
:mapper="isNew ? orderMapper : null"
:filter="orderFilter"
@on-fetch="fetchOrderDetails"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('order.form.clientFk')"
v-model="data.clientFk"
:options="clientList"
option-value="id"
option-label="name"
hide-selected
@update:model-value="
(client) => fetchAddressList(client.defaultAddressFk)
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
<div class="col">
<VnSelectFilter
:label="t('order.form.addressFk')"
v-model="data.addressFk"
:options="addressList"
option-value="id"
option-label="nickname"
hide-selected
:disable="!addressList?.length"
@update:model-value="
() => fetchAgencyList(data.landed, data.addressFk)
"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{
`${scope.opt.nickname}: ${scope.opt.street},${scope.opt.city}`
}}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInputDate
placeholder="dd-mm-aaa"
:label="t('order.form.landed')"
v-model="data.landed"
@update:model-value="
() => fetchAgencyList(data.landed, data.addressFk)
"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('order.form.agencyModeFk')"
v-model="data.agencyModeFk"
:options="agencyList"
option-value="agencyModeFk"
option-label="agencyMode"
hide-selected
:disable="!agencyList?.length"
>
</VnSelectFilter>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,25 @@
<script setup>
import { useI18n } from 'vue-i18n';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
const { t } = useI18n();
</script>
<template>
<VnSearchbar
data-key="OrderList"
url="Orders/filter"
:label="t('search-order')"
:info="t('search-order-info')"
/>
</template>
<style scoped lang="scss"></style>
<i18n>
en:
search-order: Search order
search-order-info: You can search orders by reference
es:
Search shelving: Buscar orden
You can search by shelving reference: Puedes buscar por referencia de la orden
</i18n>

View File

@ -0,0 +1,239 @@
<script setup>
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { dashIfEmpty, toCurrency, toDateHour } from 'src/filters';
import VnLv from 'components/ui/VnLv.vue';
import CardSummary from 'components/ui/CardSummary.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
const { t } = useI18n();
const route = useRoute();
const stateStore = useStateStore();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const entityId = computed(() => $props.id || route.params.id);
const detailsColumns = ref([
{
name: 'item',
label: t('order.summary.item'),
field: (row) => row?.item?.id,
sortable: true,
},
{
name: 'description',
label: t('order.summary.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>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<OrderSearchbar />
</Teleport>
<CardSummary ref="summary" :url="`Orders/${entityId}/summary`">
<template #header="{ entity }">
{{ t('order.summary.basket') }} #{{ entity?.id }} -
{{ entity?.client?.name }} ({{ entity?.clientFk }})
</template>
<template #body="{ entity }">
<QCard class="vn-one">
<VnLv label="ID" :value="entity.id" />
<VnLv :label="t('order.summary.nickname')" dash>
<template #value>
<span class="link">
{{ dashIfEmpty(entity?.address?.nickname) }}
<CustomerDescriptorProxy :id="entity?.clientFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.summary.company')"
:value="entity?.address?.companyFk"
/>
<VnLv
:label="t('order.summary.confirmed')"
:value="entity?.isConfirmed === 1"
/>
</QCard>
<QCard class="vn-one">
<VnLv
:label="t('order.summary.created')"
:value="toDateHour(entity?.created)"
/>
<VnLv
:label="t('order.summary.confirmed')"
:value="toDateHour(entity?.confirmed)"
/>
<VnLv
:label="t('order.summary.landed')"
:value="toDateHour(entity?.landed)"
/>
<VnLv :label="t('order.summary.phone')">
<template #value>
{{ dashIfEmpty(entity?.address?.phone) }}
<a
v-if="entity?.address?.phone"
:href="`tel:${entity?.address?.phone}`"
class="text-primary"
>
<QIcon name="phone" />
</a>
</template>
</VnLv>
<VnLv
:label="t('order.summary.createdFrom')"
:value="entity?.sourceApp"
/>
<VnLv :label="t('order.summary.address')">
<template #value>
<span class="address">
{{
`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`
}}
</span>
</template>
</VnLv>
</QCard>
<QCard class="vn-one">
<p class="header">
{{ t('order.summary.notes') }}
</p>
<p v-if="entity?.note" class="no-margin">
{{ entity?.note }}
</p>
</QCard>
<QCard class="vn-one">
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.subtotal') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.subTotal) }}</span>
</template>
</VnLv>
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.vat') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.VAT) }}</span>
</template>
</VnLv>
<VnLv>
<template #label>
<span class="text-h6">{{ t('order.summary.total') }}</span>
</template>
<template #value>
<span class="text-h6">{{ toCurrency(entity?.total) }}</span>
</template>
</VnLv>
</QCard>
<QCard>
<p class="header">
{{ t('order.summary.details') }}
</p>
<QTable
:columns="detailsColumns"
:rows="entity?.rows"
flat
hide-pagination
>
<template #header="props">
<QTr :props="props">
<QTh auto-width>{{ t('order.summary.item') }}</QTh>
<QTh>{{ t('order.summary.description') }}</QTh>
<QTh auto-width>{{ t('order.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTh auto-width>{{ t('order.summary.amount') }}</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd key="item" :props="props" class="item">
{{ props.row.item?.id }}
</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>
</div>
<fetched-tags :item="props.row.item" :max-length="5" />
</QTd>
<QTd key="quantity" :props="props">
{{ props.row.quantity }}
</QTd>
<QTd key="price" :props="props">
{{ props.row.price }}
</QTd>
<QTd key="amount" :props="props">
{{ toCurrency(props.row?.quantity * props.row?.price) }}
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped>
.address {
white-space: normal;
}
.item {
text-align: center;
}
.description {
display: flex;
flex-direction: column;
justify-content: center;
text-align: left;
height: auto;
padding-top: 12px;
padding-bottom: 12px;
.name {
display: flex;
align-items: center;
padding-bottom: 8px;
& > * {
flex: 1;
}
.subName {
text-transform: uppercase;
color: var(--vn-label);
}
}
}
</style>

View File

@ -0,0 +1,29 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import OrderSummary from "pages/Order/Card/OrderSummary.vue";
const $props = defineProps({
id: {
type: Number,
required: true,
},
});
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef" @hide="onDialogHide">
<OrderSummary v-if="$props.id" :id="$props.id" />
</QDialog>
</template>
<style lang="scss">
.q-dialog .summary .header {
position: sticky;
z-index: $z-max;
top: 0;
}
</style>

View File

@ -0,0 +1,93 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router';
import { onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue';
import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
const route = useRoute();
const stateStore = useStateStore();
const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const catalogParams = {
orderFk: route.params.id,
orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }),
};
function exprBuilder(param, value) {
switch (param) {
case 'search':
return { 'i.name': { like: `%${value}%` } };
}
}
</script>
<template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<VnSearchbar
data-key="OrderCatalogList"
url="Orders/CatalogFilter"
:limit="50"
:user-params="catalogParams"
:expr-builder="exprBuilder"
:static-params="['orderFk', 'orderBy']"
/>
</Teleport>
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click.stop="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<OrderCatalogFilter data-key="OrderCatalogList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="OrderCatalogList"
url="Orders/CatalogFilter"
:limit="50"
:user-params="catalogParams"
auto-load
>
<template #body="{ rows }">
<div class="catalog-list">
<OrderCatalogItem v-for="row in rows" :key="row.id" :item="row" />
</div>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<style lang="scss">
.card-list {
width: 100%;
}
.catalog-list {
display: flex;
align-items: flex-start;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
}
</style>

View File

@ -0,0 +1,162 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toDate } from 'src/filters';
import {useQuasar} from "quasar";
import CardList from 'components/ui/CardList.vue';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnLv from 'components/ui/VnLv.vue';
import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue';
import OrderFilter from 'pages/Order/Card/OrderFilter.vue';
import OrderSummaryDialog from "pages/Order/Card/OrderSummaryDialog.vue";
const stateStore = useStateStore();
const quasar = useQuasar();
const router = useRouter();
const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/order/${id}` });
}
function viewSummary(id) {
quasar.dialog({
component: OrderSummaryDialog,
componentProps: {
id,
},
});
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<OrderSearchbar />
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<OrderFilter data-key="OrderList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="card-list">
<VnPaginate
data-key="OrderList"
url="Orders/filter"
:limit="20"
:order="['landed DESC', 'clientFk', 'id DESC']"
:user-params="{ showEmpty: false }"
auto-load
>
<template #body="{ rows }">
<CardList
v-for="row of rows"
:key="row.id"
:id="row.id"
:title="`${row?.clientName} (${row?.clientFk})`"
@click="navigate(row.id)"
>
<template #list-items>
<VnLv
:label="t('order.field.salesPersonFk')"
:title-label="t('order.field.salesPersonFk')"
>
<template #value>
<span class="link" @click.stop>
{{ row?.name || '-' }}
<WorkerDescriptorProxy :id="row?.salesPersonFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.field.clientFk')"
:title-label="t('order.field.clientFk')"
>
<template #value>
<span class="link" @click.stop>
{{ row?.clientName || '-' }}
<CustomerDescriptorProxy :id="row?.clientFk" />
</span>
</template>
</VnLv>
<VnLv
:label="t('order.field.isConfirmed')"
:value="row?.isConfirmed === 1"
/>
<VnLv
:label="t('order.field.created')"
:value="toDate(row?.created)"
/>
<VnLv :label="t('order.field.landed')">
<template #value>
<QBadge color="positive" dense>
{{ toDate(row?.landed) }}
</QBadge>
</template>
</VnLv>
<VnLv
:label="t('order.field.hour')"
:value="row.hourTheoretical || row.hourEffective"
/>
<VnLv
:label="t('order.field.agency')"
:value="row?.agencyName"
/>
<VnLv
:label="t('order.field.total')"
:value="toCurrency(row?.total)"
/>
</template>
<template #actions>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'OrderCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('order.list.newOrder') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'src/components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -9,6 +9,7 @@ import Wagon from './wagon';
import Route from './route';
import Supplier from './Supplier';
import Travel from './travel';
import Order from './order';
export default [
Customer,
@ -21,5 +22,6 @@ export default [
Route,
Supplier,
Travel,
Order,
invoiceIn,
];

View File

@ -0,0 +1,78 @@
import { RouterView } from 'vue-router';
export default {
path: '/order',
name: 'Order',
meta: {
title: 'order',
icon: 'vn:basket',
},
component: RouterView,
redirect: { name: 'OrderMain' },
menus: {
main: ['OrderList'],
card: ['OrderBasicData', 'OrderCatalog'],
},
children: [
{
path: '',
name: 'OrderMain',
component: () => import('src/pages/Order/OrderMain.vue'),
redirect: { name: 'OrderList' },
children: [
{
path: 'list',
name: 'OrderList',
meta: {
title: 'orderList',
icon: 'view_list',
},
component: () => import('src/pages/Order/OrderList.vue'),
},
{
path: 'create',
name: 'OrderCreate',
meta: {
title: 'create',
},
component: () => import('src/pages/Order/Card/OrderForm.vue'),
},
],
},
{
name: 'OrderCard',
path: ':id',
component: () => import('src/pages/Order/Card/OrderCard.vue'),
redirect: { name: 'OrderSummary' },
children: [
{
name: 'OrderSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Order/Card/OrderSummary.vue'),
},
{
name: 'OrderBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () => import('src/pages/Order/Card/OrderForm.vue'),
},
{
name: 'OrderCatalog',
path: 'catalog',
meta: {
title: 'catalog',
icon: 'vn:basket',
},
component: () => import('src/pages/Order/OrderCatalog.vue'),
},
],
},
],
};

View File

@ -9,6 +9,7 @@ import supplier from './modules/Supplier';
import route from './modules/route';
import travel from './modules/travel';
import shelving from 'src/router/modules/shelving';
import order from "src/router/modules/order";
const routes = [
{
@ -56,6 +57,7 @@ const routes = [
invoiceOut,
invoiceIn,
wagon,
order,
route,
supplier,
travel,

View File

@ -13,6 +13,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'invoiceOut',
'worker',
'shelving',
'order',
'wagon',
'route',
'supplier',