refactor: refs #6896 Order migration
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Jon Elias 2024-07-04 13:35:27 +02:00
parent 3585f651b8
commit 1b839a89f7
11 changed files with 377 additions and 285 deletions

View File

@ -210,6 +210,7 @@ defineExpose({
v-model="params"
:disable-submit-event="true"
:search-url="searchUrl"
:redirect="!!redirect"
>
<template #body>
<VnTableFilter
@ -287,6 +288,7 @@ defineExpose({
:options="tableModes"
/>
<QBtn
v-if="$props.rightSearch"
icon="filter_alt"
title="asd"
class="bg-vn-section-color q-ml-md"

View File

@ -39,7 +39,7 @@ const arrayData = useArrayData(props.dataKey, {
onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false });
await arrayData.fetch({ append: false, updateRouter: false });
});
if (props.baseUrl) {

View File

@ -26,6 +26,10 @@ const props = defineProps({
type: Object,
default: null,
},
userFilter: {
type: Object,
default: null,
},
where: {
type: Object,
default: null,
@ -80,6 +84,7 @@ const pagination = ref({
const arrayData = useArrayData(props.dataKey, {
url: props.url,
filter: props.filter,
userFilter: props.userFilter,
where: props.where,
limit: props.limit,
order: props.order,

View File

@ -85,8 +85,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
Object.assign(filter, store.userFilter, exprFilter);
Object.assign(store.filter, filter);
const params = { filter: store.filter };
const where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {});
Object.assign(filter, store.filter);
filter.where = where;
const params = { filter };
Object.assign(params, userParams);
params.filter.skip = store.skip;
@ -148,7 +150,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
async function addFilter({ filter, params }) {
if (filter) store.userFilter = Object.assign(store.userFilter, filter);
if (filter) store.filter = filter;
let userParams = { ...store.userParams, ...params };
userParams = sanitizerParams(userParams, store?.exprBuilder);
@ -161,7 +163,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}
async function addFilterWhere(where) {
const storedFilter = { ...store.userFilter };
const storedFilter = { ...store.filter };
if (!storedFilter?.where) storedFilter.where = {};
where = { ...storedFilter.where, ...where };
await addFilter({ filter: { where } });

View File

@ -1,11 +1,10 @@
<script setup>
import { useRoute, useRouter } from 'vue-router';
import { reactive, ref } from 'vue';
import { 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 VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
@ -18,29 +17,9 @@ const ORDER_MODEL = 'order';
const router = useRouter();
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 clientId = ref(null);
const onClientsFetched = (data) => {
clientList.value = data;
initialFormState.clientFk = Number(route.query?.clientFk) || null;
clientId.value = initialFormState.clientFk;
const client = clientList.value.find(
(client) => client.id === initialFormState.clientFk
);
if (!client?.defaultAddressFk)
throw new Error(t(`No default address found for the client`));
fetchAddressList(client.defaultAddressFk);
};
const fetchAddressList = async (addressId) => {
try {
@ -129,27 +108,18 @@ const onClientChange = async (clientId) => {
};
async function onDataSaved(data) {
await router.push({ path: `/order/${data}/catalog` });
await router.push({ path: `/order/${data.id}/catalog` });
}
</script>
<template>
<FetchData
url="Clients"
@on-fetch="(data) => onClientsFetched(data)"
:filter="{ fields: ['id', 'name', 'defaultAddressFk'] }"
auto-load
/>
<VnSubToolbar v-if="isNew" />
<div class="q-pa-md">
<FormModel
:url="!isNew ? `Orders/${route.params.id}` : null"
url-create="Orders/new"
:url="`Orders/${route.params.id}`"
@on-data-saved="onDataSaved"
:model="ORDER_MODEL"
:form-initial-data="isNew ? initialFormState : null"
:observe-form-changes="!isNew"
:mapper="isNew ? orderMapper : null"
:mapper="orderMapper"
:filter="orderFilter"
@on-fetch="fetchOrderDetails"
auto-load
@ -157,11 +127,15 @@ async function onDataSaved(data) {
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
url="Clients"
:label="t('order.form.clientFk')"
v-model="data.clientFk"
:options="clientList"
option-value="id"
option-label="name"
:option-filter="{
fields: ['id', 'name', 'defaultAddressFk'],
}"
hide-selected
@update:model-value="onClientChange"
>

View File

@ -196,7 +196,7 @@ const detailsColumns = ref([
{{ props.row.quantity }}
</QTd>
<QTd key="price" :props="props">
{{ props.row.price }}
{{ toCurrency(props.row.price) }}
</QTd>
<QTd key="amount" :props="props">
{{

View File

@ -1,33 +1,67 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { ref } from 'vue';
import { ref, computed } from 'vue';
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 FetchedTags from 'components/ui/FetchedTags.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnImg from 'components/ui/VnImg.vue';
import { toCurrency, toDate } from 'src/filters';
import axios from 'axios';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import FetchData from 'src/components/FetchData.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import FetchedTags from 'src/components/ui/FetchedTags.vue';
const route = useRoute();
const { t } = useI18n();
const quasar = useQuasar();
const componentKey = ref(0);
const tableLinesRef = ref();
const refresh = () => {
componentKey.value += 1;
};
const orderSummary = ref({
total: null,
vat: null,
});
const componentKey = ref(0);
const order = ref(0);
const refresh = () => {
componentKey.value += 1;
};
const lineFilter = ref({
include: [
{
relation: 'item',
scope: {
fields: [
'id',
'name',
'subName',
'image',
'tag4',
'value4',
'tag5',
'value5',
'tag6',
'value6',
'tag7',
'value7',
'tag8',
'value8',
'tag9',
'value9',
'tag10',
'value10',
],
},
},
{
relation: 'warehouse',
scope: {
fields: ['id', 'name'],
},
},
],
where: { orderFk: route.params.id },
});
function confirmRemove(item) {
quasar.dialog({
@ -60,53 +94,103 @@ async function confirmOrder() {
});
}
const detailsColumns = ref([
const columns = computed(() => [
{
name: 'img',
label: '',
field: (row) => row?.item?.id,
align: 'center',
label: t('lines.image'),
name: 'image',
columnField: {
component: VnImg,
attrs: (id) => {
return {
id,
width: '50px',
};
},
},
columnFilter: false,
},
{
name: 'item',
label: t('order.summary.item'),
field: (row) => row?.item?.id,
sortable: true,
align: 'left',
name: 'id',
label: t('lines.item'),
chip: {
condition: () => true,
},
isId: true,
},
{
name: 'description',
align: 'left',
name: 'itemFk',
label: t('globals.description'),
field: (row) => row?.item?.name,
isTitle: true,
cardVisible: true,
component: 'select',
attrs: {
url: 'Items',
fields: ['id', 'name', 'subName'],
},
columnField: {
component: null,
},
format: (row) => row?.item?.name,
},
{
name: 'warehouse',
label: t('warehouse'),
field: (row) => row?.warehouse?.name,
sortable: true,
align: 'left',
name: 'warehouseFk',
label: t('lines.warehouse'),
cardVisible: true,
component: 'select',
attrs: {
url: 'Warehouses',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
format: (row) => row?.warehouse?.name,
},
{
align: 'left',
name: 'shipped',
label: t('shipped'),
field: (row) => toDate(row?.shipped),
label: t('lines.shipped'),
cardVisible: true,
component: 'date',
columnField: {
component: null,
},
format: (row) => toDate(row.shipped),
},
{
align: 'left',
name: 'quantity',
label: t('order.summary.quantity'),
field: (row) => row?.quantity,
label: t('lines.quantity'),
},
{
align: 'left',
name: 'price',
label: t('order.summary.price'),
field: (row) => toCurrency(row?.price),
label: t('lines.price'),
cardVisible: true,
format: (row) => toCurrency(row.price),
},
{
align: 'left',
name: 'amount',
label: t('order.summary.amount'),
field: (row) => toCurrency(row?.quantity * row?.price),
label: t('lines.amount'),
format: (row) => toCurrency(row.amount),
},
{
name: 'actions',
align: 'right',
label: '',
field: (row) => row?.id,
name: 'tableActions',
actions: [
{
title: t('delete'),
icon: 'delete',
click: (row) => confirmRemove(row.item),
isPrimary: true,
},
],
},
]);
</script>
@ -135,7 +219,6 @@ const detailsColumns = ref([
<div v-if="!orderSummary.total" class="no-result">
{{ t('globals.noResults') }}
</div>
<QDrawer side="right" :width="270" show-if-above>
<QCard class="order-lines-summary q-pa-lg">
<p class="header text-right block">
@ -143,107 +226,48 @@ const detailsColumns = ref([
</p>
<VnLv
v-if="orderSummary.vat && orderSummary.total"
:label="t('subtotal')"
:label="t('subtotal') + ': '"
:value="toCurrency(orderSummary.total - orderSummary.vat)"
/>
<VnLv
v-if="orderSummary.vat"
:label="t('VAT')"
:label="t('VAT') + ': '"
:value="toCurrency(orderSummary?.vat)"
/>
<VnLv
v-if="orderSummary.total"
:label="t('total')"
:label="t('total') + ': '"
:value="toCurrency(orderSummary?.total)"
/>
</QCard>
</QDrawer>
<VnPaginate
<VnTable
ref="tableLinesRef"
data-key="OrderLines"
url="OrderRows"
:limit="20"
:columns="columns"
default-mode="table"
:right-search="false"
:use-model="true"
auto-load
:filter="{
include: [
{
relation: 'item',
},
{
relation: 'warehouse',
},
],
where: { orderFk: route.params.id },
}"
:user-filter="lineFilter"
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
:columns="detailsColumns"
:rows="rows"
flat
class="full-width"
style="text-align: center"
>
<template #header="props">
<QTr class="tr-header" :props="props">
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
style="text-align: center"
>
{{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body-cell-img="{ value }">
<QTd>
<div class="image-wrapper">
<VnImg :id="value" class="rounded" />
</div>
</QTd>
</template>
<template #body-cell-item="{ value }">
<QTd class="item">
<span class="link">
<QBtn flat>
{{ value }}
</QBtn>
<ItemDescriptorProxy :id="value" />
</span>
</QTd>
</template>
<template #body-cell-description="{ row, value }">
<QTd>
<div
class="row column full-width justify-between items-start"
>
{{ value }}
<div v-if="value" class="subName">
{{ value.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row.item" :max-length="6" />
</QTd>
</template>
<template #body-cell-actions="{ value }">
<QTd>
<QIcon
name="delete"
color="primary"
size="sm"
class="cursor-pointer"
@click.stop="confirmRemove(value)"
>
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
<template #column-image="{ row }">
<div class="image-wrapper">
<VnImg :id="row?.item?.image" class="rounded" />
</div>
</template>
</VnPaginate>
<template #column-itemFk="{ row }">
<div class="row column full-width justify-between items-start">
{{ row?.item?.name }}
<div v-if="row?.item?.subName" class="subName">
{{ row?.item?.subName.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row?.item" :max-length="6" />
</template>
</VnTable>
</div>
<QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed">
<QBtn fab icon="check" color="primary" @click="confirmOrder()" />
@ -253,7 +277,8 @@ const detailsColumns = ref([
</QPageSticky>
</QPage>
</template>
<style lang="scss">
<style lang="scss" scoped>
.order-lines-summary {
.vn-label-value {
display: flex;
@ -274,8 +299,13 @@ const detailsColumns = ref([
}
}
}
</style>
<style lang="scss" scoped>
.image-wrapper {
height: 50px;
width: 50px;
margin-left: 30%;
}
.header {
color: $primary;
font-weight: bold;
@ -284,12 +314,6 @@ const detailsColumns = ref([
display: inline-block;
}
.image-wrapper {
height: 50px;
width: 50px;
margin-left: 30%;
}
.no-result {
font-size: 24px;
font-weight: bold;
@ -302,6 +326,7 @@ const detailsColumns = ref([
color: var(--vn-label-color);
}
</style>
<i18n>
en:
summary: Summary

View File

@ -1,31 +1,128 @@
<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 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 { computed, ref } from 'vue';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import OrderFilter from 'pages/Order/Card/OrderFilter.vue';
import OrderSummary from 'pages/Order/Card/OrderSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const tableRef = ref();
const clientList = ref([]);
const agencyList = ref([]);
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) {
router.push({ path: `/order/${id}` });
}
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: t('module.id'),
chip: {
condition: () => true,
},
isId: true,
},
{
align: 'left',
name: 'clientName',
label: t('module.customer'),
isTitle: true,
cardVisible: true,
create: true,
component: 'select',
attrs: {
url: 'Clients',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'name',
label: t('module.salesPerson'),
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'isConfirmed',
label: t('module.isConfirmed'),
},
{
align: 'left',
name: 'created',
label: t('module.created'),
component: 'date',
cardVisible: true,
format: (row) => toDate(row?.landed),
columnField: {
component: null,
},
},
{
align: 'left',
name: 'landed',
label: t('module.landed'),
component: 'date',
format: (row) => toDate(row?.landed),
columnField: {
component: null,
},
style: 'color="positive"',
},
{
align: 'left',
name: 'hour',
label: t('module.hour'),
format: ({ hourTheoretical, hourEffective }) =>
dashIfEmpty(hourTheoretical || hourEffective),
},
{
align: 'left',
name: 'agencyName',
label: t('module.agency'),
component: 'select',
cardVisible: true,
attrs: {
url: 'Agencies',
fields: ['id', 'name'],
},
columnField: {
component: null,
},
},
{
align: 'left',
name: 'total',
label: t('module.total'),
format: ({ total }) => toCurrency(total),
cardVisible: true,
},
{
align: 'right',
label: '',
name: 'tableActions',
actions: [
{
title: t('InvoiceOutSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, OrderSummary),
},
],
},
]);
</script>
<template>
<VnSearchbar
@ -33,100 +130,43 @@ function navigate(id) {
:label="t('Search order')"
:info="t('You can search orders by reference')"
/>
<RightMenu>
<template #right-panel>
<OrderFilter data-key="OrderList" />
<VnTable
ref="tableRef"
data-key="OrderList"
url="Orders/filter"
:create="{
urlCreate: 'Orders/new',
title: 'Create Order',
onDataSaved: (url) => {
tableRef.redirect(url);
},
formInitialData: {
active: true,
},
}"
:columns="columns"
default-mode="table"
redirect="order"
auto-load
>
<template #more-create-dialog="{ data }">
<VnSelect
url="Clients"
v-model="data.addressId"
:label="t('module.address')"
:options="clientList"
option-value="defaultAddressFk"
option-label="street"
/>
<VnInputDate v-model="data.landed" :label="t('module.landed')" />
<VnSelect
url="Agencies"
v-model="data.agencyModeId"
:label="t('module.agency')"
:options="agencyList"
option-value="id"
option-label="name"
/>
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="OrderList"
url="Orders/filter"
:limit="20"
:order="['landed DESC', 'clientFk', 'id DESC']"
:user-params="{ showEmpty: false }"
:keep-opts="['userParams']"
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 text-color="black" 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, OrderSummary)"
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>
</VnTable>
</template>

View File

@ -0,0 +1,22 @@
module:
id: ID
name: Name
customer: Client
isConfirmed: Confirmed
created: Created
landed: Landed
hour: Hour
agency: Agency
total: Total
salesPerson: Sales Person
address: Address
lines:
item: Item
warehouse: Warehouse
shipped: Shipped
quantity: Quantity
price: Price
amount: Amount
image: Image
params:
tagGroups: Tags

View File

@ -0,0 +1,22 @@
module:
id: ID
name: Nombre
customer: Cliente
isConfirmed: Confirmado
created: Creado
landed: F. Entrega
hour: Hora
agency: Agencia
total: Total
salesPerson: Comercial
address: Dirección
lines:
item: Artículo
warehouse: Almacén
shipped: Envío
quantity: Cantidad
price: Precio
amount: Importe
image: Imagen
params:
tagGroups: Tags

View File

@ -37,7 +37,7 @@ export default {
title: 'orderCreate',
icon: 'add',
},
component: () => import('src/pages/Order/Card/OrderForm.vue'),
component: () => import('src/pages/Order/Card/OrderList.vue'),
},
],
},