feat: quotation stats
This commit is contained in:
parent
c5b3e4dbd7
commit
1d73d8084e
4 changed files with 89 additions and 179 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
branch: {
|
branch: {
|
||||||
|
hidden?: boolean;
|
||||||
icon: string;
|
icon: string;
|
||||||
count: number;
|
count: number;
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -32,7 +33,7 @@ const props = withDefaults(
|
||||||
<template>
|
<template>
|
||||||
<div class="row no-wrap" style="gap: var(--size-4)" :class="{ dark }">
|
<div class="row no-wrap" style="gap: var(--size-4)" :class="{ dark }">
|
||||||
<div
|
<div
|
||||||
v-for="v in props.branch"
|
v-for="v in branch.filter((v) => !v.hidden)"
|
||||||
class="no-padding stat-card"
|
class="no-padding stat-card"
|
||||||
:class="{ [`stat-card__${v.color}`]: true }"
|
:class="{ [`stat-card__${v.color}`]: true }"
|
||||||
:key="v.label"
|
:key="v.label"
|
||||||
|
|
|
||||||
|
|
@ -97,20 +97,6 @@ const tabFieldRequired = ref<{ [key: string]: (keyof CustomerBranchCreate)[] }>(
|
||||||
const productServiceStore = useProductServiceStore();
|
const productServiceStore = useProductServiceStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
type ProductGroupId = string;
|
|
||||||
|
|
||||||
const productGroup = ref<ProductGroup[]>([]);
|
|
||||||
const productList = ref<Partial<Record<ProductGroupId, Product[]>>>({});
|
|
||||||
const serviceList = ref<Partial<Record<ProductGroupId, Service[]>>>({});
|
|
||||||
|
|
||||||
type Id = string;
|
|
||||||
const product = ref<Record<Id, Product>>({});
|
|
||||||
const service = ref<Record<Id, Service>>({});
|
|
||||||
|
|
||||||
const selectedGroup = ref<ProductGroup | null>(null);
|
|
||||||
const selectedGroupSub = ref<'product' | 'service' | null>(null);
|
|
||||||
const selectedProductServiceId = ref('');
|
|
||||||
|
|
||||||
const selectedEmployee = ref<Employee[]>([]);
|
const selectedEmployee = ref<Employee[]>([]);
|
||||||
|
|
||||||
const pageState = reactive({
|
const pageState = reactive({
|
||||||
|
|
@ -127,58 +113,11 @@ const pageState = reactive({
|
||||||
productServiceModal: false,
|
productServiceModal: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const statData = ref<
|
const CUSTOMER_BRANCH_DEFAULT: CustomerBranchCreate & {
|
||||||
{
|
id?: string;
|
||||||
icon: string;
|
branchCode?: string;
|
||||||
count: number;
|
codeCustomer?: string;
|
||||||
label: string;
|
} = {
|
||||||
color:
|
|
||||||
| 'pink'
|
|
||||||
| 'purple'
|
|
||||||
| 'green'
|
|
||||||
| 'orange'
|
|
||||||
| 'cyan'
|
|
||||||
| 'yellow'
|
|
||||||
| 'red'
|
|
||||||
| 'magenta'
|
|
||||||
| 'blue'
|
|
||||||
| 'lime'
|
|
||||||
| 'light-purple';
|
|
||||||
}[]
|
|
||||||
>([
|
|
||||||
{
|
|
||||||
icon: 'mdi-cash',
|
|
||||||
count: 0,
|
|
||||||
label: 'quotation.type.fullAmountCash',
|
|
||||||
color: 'red',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'mdi-hand-coin-outline',
|
|
||||||
count: 0,
|
|
||||||
label: 'quotation.type.installmentsCash',
|
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'mdi-receipt-text-outline',
|
|
||||||
count: 0,
|
|
||||||
label: 'quotation.type.fullAmountBill',
|
|
||||||
color: 'lime',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'mdi-receipt-text-send-outline',
|
|
||||||
count: 0,
|
|
||||||
label: 'quotation.type.installmentsBill',
|
|
||||||
color: 'light-purple',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const formDataCustomerBranch = ref<
|
|
||||||
CustomerBranchCreate & {
|
|
||||||
id?: string;
|
|
||||||
branchCode?: string;
|
|
||||||
codeCustomer?: string;
|
|
||||||
}
|
|
||||||
>({
|
|
||||||
customerCode: '',
|
customerCode: '',
|
||||||
customerId: '',
|
customerId: '',
|
||||||
legalPersonNo: '',
|
legalPersonNo: '',
|
||||||
|
|
@ -226,58 +165,18 @@ const formDataCustomerBranch = ref<
|
||||||
authorizedName: '',
|
authorizedName: '',
|
||||||
authorizedNameEN: '',
|
authorizedNameEN: '',
|
||||||
code: '',
|
code: '',
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const formDataCustomerBranch = ref<
|
||||||
|
CustomerBranchCreate & {
|
||||||
|
id?: string;
|
||||||
|
branchCode?: string;
|
||||||
|
codeCustomer?: string;
|
||||||
|
}
|
||||||
|
>(structuredClone(CUSTOMER_BRANCH_DEFAULT));
|
||||||
|
|
||||||
function setDefaultCustomerd() {
|
function setDefaultCustomerd() {
|
||||||
formDataCustomerBranch.value = {
|
formDataCustomerBranch.value = structuredClone(CUSTOMER_BRANCH_DEFAULT);
|
||||||
customerCode: '',
|
|
||||||
customerId: '',
|
|
||||||
legalPersonNo: '',
|
|
||||||
citizenId: '',
|
|
||||||
namePrefix: '',
|
|
||||||
firstName: '',
|
|
||||||
lastName: '',
|
|
||||||
firstNameEN: '',
|
|
||||||
lastNameEN: '',
|
|
||||||
telephoneNo: '',
|
|
||||||
gender: '',
|
|
||||||
birthDate: new Date().toString(),
|
|
||||||
businessType: '',
|
|
||||||
jobPosition: '',
|
|
||||||
jobDescription: '',
|
|
||||||
payDate: '',
|
|
||||||
payDateEN: '',
|
|
||||||
wageRate: 0,
|
|
||||||
wageRateText: '',
|
|
||||||
homeCode: '',
|
|
||||||
employmentOffice: '',
|
|
||||||
employmentOfficeEN: '',
|
|
||||||
address: '',
|
|
||||||
addressEN: '',
|
|
||||||
street: '',
|
|
||||||
streetEN: '',
|
|
||||||
moo: '',
|
|
||||||
mooEN: '',
|
|
||||||
soi: '',
|
|
||||||
soiEN: '',
|
|
||||||
provinceId: '',
|
|
||||||
districtId: '',
|
|
||||||
subDistrictId: '',
|
|
||||||
contactName: '',
|
|
||||||
email: '',
|
|
||||||
contactTel: '',
|
|
||||||
officeTel: '',
|
|
||||||
agent: '',
|
|
||||||
status: 'CREATED',
|
|
||||||
customerName: '',
|
|
||||||
registerName: '',
|
|
||||||
registerNameEN: '',
|
|
||||||
registerDate: new Date(),
|
|
||||||
authorizedCapital: '',
|
|
||||||
authorizedName: '',
|
|
||||||
authorizedNameEN: '',
|
|
||||||
code: '',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitCustomer() {
|
async function submitCustomer() {
|
||||||
|
|
@ -335,70 +234,21 @@ function triggerProductServiceDialog() {
|
||||||
// TODO: form and state controll
|
// TODO: form and state controll
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAllProduct(
|
|
||||||
groupId: string,
|
|
||||||
opts?: { force?: false; page?: number; pageSize?: number },
|
|
||||||
) {
|
|
||||||
selectedGroupSub.value = 'product';
|
|
||||||
if (!opts?.force && productList.value[groupId] !== undefined) return;
|
|
||||||
const ret = await productServiceStore.fetchListProduct({
|
|
||||||
page: opts?.page ?? 1,
|
|
||||||
pageSize: opts?.pageSize ?? 9999,
|
|
||||||
productGroupId: groupId,
|
|
||||||
});
|
|
||||||
if (ret) productList.value[groupId] = ret.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAllService(
|
|
||||||
groupId: string,
|
|
||||||
opts?: { force?: false; page?: number; pageSize?: number },
|
|
||||||
) {
|
|
||||||
selectedGroupSub.value = 'service';
|
|
||||||
if (!opts?.force && serviceList.value[groupId] !== undefined) return;
|
|
||||||
const ret = await productServiceStore.fetchListService({
|
|
||||||
page: opts?.page ?? 1,
|
|
||||||
pageSize: opts?.pageSize ?? 9999,
|
|
||||||
productGroupId: groupId,
|
|
||||||
fullDetail: true,
|
|
||||||
});
|
|
||||||
if (ret) serviceList.value[groupId] = ret.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getProduct(id: string, force = false) {
|
|
||||||
selectedGroupSub.value = 'product';
|
|
||||||
selectedProductServiceId.value = id;
|
|
||||||
if (!force && product.value[id] !== undefined) return;
|
|
||||||
const ret = await productServiceStore.fetchListProductById(id);
|
|
||||||
if (ret) product.value[id] = ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getService(id: string, force = false) {
|
|
||||||
selectedGroupSub.value = 'service';
|
|
||||||
selectedProductServiceId.value = id;
|
|
||||||
if (!force && service.value[id] !== undefined) return;
|
|
||||||
const ret = await productServiceStore.fetchListServiceById(id);
|
|
||||||
if (ret) service.value[id] = ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertToTree() {
|
|
||||||
// TODO: convert quotation product service data to tree
|
|
||||||
}
|
|
||||||
|
|
||||||
const quotationStore = useQuotationStore();
|
const quotationStore = useQuotationStore();
|
||||||
const {
|
const {
|
||||||
data: quotationData,
|
data: quotationData,
|
||||||
page: quotationPage,
|
page: quotationPage,
|
||||||
pageSize: quotationPageSize,
|
pageSize: quotationPageSize,
|
||||||
pageMax: quotationPageMax,
|
pageMax: quotationPageMax,
|
||||||
|
stats: quotationStats,
|
||||||
} = storeToRefs(quotationStore);
|
} = storeToRefs(quotationStore);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
{
|
{
|
||||||
const ret = await productServiceStore.fetchListProductService({
|
const ret = await quotationStore.getQuotationStats();
|
||||||
page: 1,
|
if (ret) {
|
||||||
pageSize: 9999,
|
quotationStats.value = Object.assign(quotationStats.value, ret);
|
||||||
});
|
}
|
||||||
if (ret) productGroup.value = ret.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -517,13 +367,44 @@ watch(() => pageState.currentTab, fetchQuotationList);
|
||||||
<div style="display: inline-block">
|
<div style="display: inline-block">
|
||||||
<StatCardComponent
|
<StatCardComponent
|
||||||
labelI18n
|
labelI18n
|
||||||
:branch="
|
:branch="[
|
||||||
pageState.currentTab === 'all'
|
{
|
||||||
? statData
|
icon: 'mdi-cash',
|
||||||
: statData.filter((i) =>
|
count: quotationStats.full,
|
||||||
i.label.split('.').pop()?.includes(pageState.currentTab),
|
label: 'quotation.type.fullAmountCash',
|
||||||
)
|
color: 'red',
|
||||||
"
|
hidden:
|
||||||
|
pageState.currentTab !== 'all' &&
|
||||||
|
pageState.currentTab !== 'fullAmountCash',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-hand-coin-outline',
|
||||||
|
count: quotationStats.split,
|
||||||
|
label: 'quotation.type.installmentsCash',
|
||||||
|
color: 'blue',
|
||||||
|
hidden:
|
||||||
|
pageState.currentTab !== 'all' &&
|
||||||
|
pageState.currentTab !== 'installmentsCash',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-receipt-text-outline',
|
||||||
|
count: quotationStats.billFull,
|
||||||
|
label: 'quotation.type.fullAmountBill',
|
||||||
|
color: 'lime',
|
||||||
|
hidden:
|
||||||
|
pageState.currentTab !== 'all' &&
|
||||||
|
pageState.currentTab !== 'fullAmountBill',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'mdi-receipt-text-send-outline',
|
||||||
|
count: quotationStats.billSplit,
|
||||||
|
label: 'quotation.type.installmentsBill',
|
||||||
|
color: 'light-purple',
|
||||||
|
hidden:
|
||||||
|
pageState.currentTab !== 'all' &&
|
||||||
|
pageState.currentTab !== 'installmentsBill',
|
||||||
|
},
|
||||||
|
]"
|
||||||
:dark="$q.dark.isActive"
|
:dark="$q.dark.isActive"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,12 @@ import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { api } from 'src/boot/axios';
|
import { api } from 'src/boot/axios';
|
||||||
|
|
||||||
import { Quotation, QuotationFull, QuotationPayload } from './types';
|
import {
|
||||||
|
Quotation,
|
||||||
|
QuotationFull,
|
||||||
|
QuotationPayload,
|
||||||
|
QuotationStats,
|
||||||
|
} from './types';
|
||||||
import { PaginationResult } from 'src/types';
|
import { PaginationResult } from 'src/types';
|
||||||
|
|
||||||
export const useQuotationStore = defineStore('quotation-store', () => {
|
export const useQuotationStore = defineStore('quotation-store', () => {
|
||||||
|
|
@ -11,6 +16,20 @@ export const useQuotationStore = defineStore('quotation-store', () => {
|
||||||
const page = ref<number>(1);
|
const page = ref<number>(1);
|
||||||
const pageMax = ref<number>(1);
|
const pageMax = ref<number>(1);
|
||||||
const pageSize = ref<number>(30);
|
const pageSize = ref<number>(30);
|
||||||
|
const stats = ref<QuotationStats>({
|
||||||
|
full: 0,
|
||||||
|
split: 0,
|
||||||
|
billFull: 0,
|
||||||
|
billSplit: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getQuotationStats() {
|
||||||
|
const res = await api.get<QuotationStats>(`/quotation/stats`);
|
||||||
|
if (res.status < 400) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
async function getQuotation(id: string) {
|
async function getQuotation(id: string) {
|
||||||
const res = await api.get<QuotationFull>(`/quotation/${id}`);
|
const res = await api.get<QuotationFull>(`/quotation/${id}`);
|
||||||
|
|
@ -92,6 +111,8 @@ export const useQuotationStore = defineStore('quotation-store', () => {
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
pageMax,
|
pageMax,
|
||||||
|
stats,
|
||||||
|
getQuotationStats,
|
||||||
getQuotation,
|
getQuotation,
|
||||||
getQuotationList,
|
getQuotationList,
|
||||||
createQuotation,
|
createQuotation,
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,13 @@ type ServiceRelation = {
|
||||||
updatedByUserId: string;
|
updatedByUserId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type QuotationStats = {
|
||||||
|
full: number;
|
||||||
|
split: number;
|
||||||
|
billFull: number;
|
||||||
|
billSplit: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type Quotation = {
|
export type Quotation = {
|
||||||
_count: { worker: number };
|
_count: { worker: number };
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue