refactor: receipt dialog & type

This commit is contained in:
puriphatt 2024-10-18 16:14:19 +07:00
parent a6955929e9
commit 0a9b9dfb79
3 changed files with 271 additions and 66 deletions

View file

@ -38,6 +38,7 @@ import {
} from 'src/pages/03_customer-management/components'; } from 'src/pages/03_customer-management/components';
import { useCustomerForm } from 'src/pages/03_customer-management/form'; import { useCustomerForm } from 'src/pages/03_customer-management/form';
import { Quotation } from 'src/stores/quotations/types';
const quotationFormStore = useQuotationForm(); const quotationFormStore = useQuotationForm();
const customerFormStore = useCustomerForm(); const customerFormStore = useCustomerForm();
@ -60,6 +61,7 @@ const onCreateImageList = ref<{
selectedImage: string; selectedImage: string;
list: { url: string; imgFile: File | null; name: string }[]; list: { url: string; imgFile: File | null; name: string }[];
}>({ selectedImage: '', list: [] }); }>({ selectedImage: '', list: [] });
const currentQuotationPayment = ref<Quotation>();
const pageState = reactive({ const pageState = reactive({
hideStat: false, hideStat: false,
@ -204,7 +206,8 @@ function triggerQuotationDialog(opts: {
window.open(url.toString(), '_blank'); window.open(url.toString(), '_blank');
} }
function triggerReceiptDialog() { function triggerReceiptDialog(data: Quotation) {
currentQuotationPayment.value = data;
pageState.receiptModal = true; pageState.receiptModal = true;
} }
@ -528,7 +531,11 @@ async function storeDataLocal(id: string) {
</article> </article>
<article v-else class="col q-pa-md surface-2 scroll"> <article v-else class="col q-pa-md surface-2 scroll">
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div v-for="v in quotationData" :key="v.id" class="col-md-4 col-12"> <div
v-for="v in quotationData"
:key="v.id"
class="col-md-4 col-sm-6 col-12"
>
<QuotationCard <QuotationCard
:urgent="v.urgent" :urgent="v.urgent"
:type=" :type="
@ -572,10 +579,9 @@ async function storeDataLocal(id: string) {
branchId: v.customerBranch.customer.registeredBranchId, branchId: v.customerBranch.customer.registeredBranchId,
}) })
" "
@link="triggerReceiptDialog()" @link="triggerReceiptDialog(v)"
@upload="console.log('upload')" @upload="console.log('upload')"
@delete="triggerDialogDeleteQuottaion(v.id)" @delete="triggerDialogDeleteQuottaion(v.id)"
@change-status="console.log('change')"
/> />
</div> </div>
</div> </div>
@ -940,7 +946,10 @@ async function storeDataLocal(id: string) {
</div> </div>
</DialogForm> </DialogForm>
<ReceiptDialog v-model="pageState.receiptModal"></ReceiptDialog> <ReceiptDialog
v-model:data="currentQuotationPayment"
v-model="pageState.receiptModal"
></ReceiptDialog>
</template> </template>
<style scoped></style> <style scoped></style>

View file

@ -3,9 +3,13 @@ import { storeToRefs } from 'pinia';
import { useConfigStore } from 'stores/config'; import { useConfigStore } from 'stores/config';
import { formatNumberDecimal, commaInput } from 'stores/utils'; import { formatNumberDecimal, commaInput } from 'stores/utils';
import DialogForm from 'src/components/DialogForm.vue'; import DialogForm from 'src/components/DialogForm.vue';
import { reactive, ref } from 'vue'; import { reactive, ref, watch } from 'vue';
import { useQuotationPayment } from 'src/stores/quotations';
import { Quotation, QuotationPaymentData } from 'src/stores/quotations/types';
import { dateFormat } from 'src/utils/datetime';
const configStore = useConfigStore(); const configStore = useConfigStore();
const quotationPayment = useQuotationPayment();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
defineEmits<{ defineEmits<{
@ -13,15 +17,36 @@ defineEmits<{
}>(); }>();
const model = defineModel<boolean>({ default: false, required: true }); const model = defineModel<boolean>({ default: false, required: true });
const data = defineModel<Quotation | undefined>('data', { required: true });
const paymentData = ref<QuotationPaymentData[]>([]);
const payAll = ref<boolean>(false);
const state = reactive({ const state = reactive({
waitExpansion: true, waitExpansion: true,
payExpansion: [] as boolean[], payExpansion: [] as boolean[],
}); });
defineProps<{ function monthDisplay(date: Date | string) {
urgent?: boolean; const arr = dateFormat(date, true).split(' ');
}>(); arr.shift();
return arr.join(' ');
}
watch(
() => model.value,
async (open) => {
if (!data.value) return;
if (!open) {
paymentData.value = [];
} else {
const ret = await quotationPayment.getQuotationPayment(data.value.id);
if (ret) {
paymentData.value = ret.quotationPaymentData;
}
}
},
);
</script> </script>
<template> <template>
<DialogForm <DialogForm
@ -30,12 +55,14 @@ defineProps<{
width="65%" width="65%"
> >
<div <div
class="col column" v-if="data"
class="col column no-wrap"
:class="{ :class="{
'q-mx-lg q-my-md': $q.screen.gt.sm, 'q-mx-lg q-my-md': $q.screen.gt.sm,
'q-mx-md q-my-sm': !$q.screen.gt.sm, 'q-mx-md q-my-sm': !$q.screen.gt.sm,
}" }"
> >
<!-- PRICE DETAIL -->
<section class="bordered rounded surface-1" style="overflow: hidden"> <section class="bordered rounded surface-1" style="overflow: hidden">
<q-expansion-item <q-expansion-item
hide-expand-icon hide-expand-icon
@ -46,7 +73,7 @@ defineProps<{
<div class="column full-width bordered-b"> <div class="column full-width bordered-b">
<header class="bg-color-orange text-bold text-body1 q-pa-sm"> <header class="bg-color-orange text-bold text-body1 q-pa-sm">
<q-avatar class="surface-1 q-mr-sm" size="10px" /> <q-avatar class="surface-1 q-mr-sm" size="10px" />
รอชำระเง {{ $t('quotation.receiptDialog.paymentWait') }}
</header> </header>
<span class="q-pa-md row items-center"> <span class="q-pa-md row items-center">
<q-img <q-img
@ -56,21 +83,21 @@ defineProps<{
/> />
<div class="column col"> <div class="column col">
<span class="text-bold text-body1"> <span class="text-bold text-body1">
MOU Myanmar (Re-turn) {{ data.workName }}
</span> </span>
<span <span
class="row items-center" class="row items-center"
:class="urgent ? 'urgent' : 'app-text-muted'" :class="data.urgent ? 'urgent' : 'app-text-muted'"
> >
QT240120S0002 {{ data.code }}
<q-icon <q-icon
v-if="urgent" v-if="data.urgent"
class="q-pl-sm" class="q-pl-sm"
name="mdi-fire" name="mdi-fire"
size="xs" size="xs"
/> />
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿ {{ formatNumberDecimal(data.finalPrice, 2) || '0.00' }}
</span> </span>
</span> </span>
</div> </div>
@ -82,25 +109,33 @@ defineProps<{
<span class="row"> <span class="row">
{{ $t('general.total') }} {{ $t('general.total') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿
{{ formatNumberDecimal(data.totalPrice, 2) || '0.00' }}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
{{ $t('quotation.discountList') }} {{ $t('quotation.discountList') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿
{{ formatNumberDecimal(data.totalDiscount, 2) || '0.00' }}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
{{ $t('general.totalAfterDiscount') }} {{ $t('general.totalAfterDiscount') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿
{{
formatNumberDecimal(
data.totalPrice - data.totalDiscount,
2,
) || '0.00'
}}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
{{ $t('general.totalVatExcluded') }} {{ $t('general.totalVatExcluded') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿ {{ formatNumberDecimal(data.vatExcluded, 2) || '0.00' }}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
@ -110,19 +145,25 @@ defineProps<{
}) })
}} }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿ {{ formatNumberDecimal(data.vat, 2) || '0.00' }}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
{{ $t('general.totalVatIncluded') }} {{ $t('general.totalVatIncluded') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿
{{
formatNumberDecimal(
data.totalPrice - data.totalDiscount + data.vat,
2,
) || '0.00'
}}
</span> </span>
</span> </span>
<span class="row"> <span class="row">
{{ $t('general.discountAfterVat') }} {{ $t('general.discountAfterVat') }}
<span class="q-ml-auto" style="color: var(--foreground)"> <span class="q-ml-auto" style="color: var(--foreground)">
฿ {{ 0 }} ฿ {{ formatNumberDecimal(data.discount, 2) || '0.00' }}
</span> </span>
</span> </span>
</span> </span>
@ -133,7 +174,7 @@ defineProps<{
> >
{{ $t('quotation.totalPriceBaht') }}: {{ $t('quotation.totalPriceBaht') }}:
<span class="q-px-sm" style="color: var(--foreground)"> <span class="q-px-sm" style="color: var(--foreground)">
{{ 0 }} {{ formatNumberDecimal(data.finalPrice, 2) || '0.00' }}
</span> </span>
<q-btn <q-btn
dense dense
@ -146,62 +187,143 @@ defineProps<{
</div> </div>
</section> </section>
<!-- PAYMENT DETAIL -->
<section <section
class="surface-1 rounded bordered column q-mt-md q-pa-md col scroll no-wrap" class="surface-1 rounded bordered column q-mt-md q-pa-md col scroll no-wrap"
> >
<span class="text-weight-bold text-body1">การชำระ</span> <span class="text-weight-bold text-body1">
<div class="row items-center"> {{ $t('general.payment') }}
<span class="app-text-muted-2">การชำระเง</span> </span>
<q-btn <div class="row items-center q-pb-md">
dense <span class="app-text-muted-2">{{ $t('quotation.payType') }}</span>
unelevated <div
padding="2px 8px" class="badge-card q-ml-auto rounded"
label="เงินสดแบ่งจ่าย" :class="`badge-card__${data.payCondition}`"
class="q-ml-auto rounded" >
style="background: var(--blue-8); color: var(--surface-1)" {{ $t(`quotation.type.${data.payCondition}`) }}
/> </div>
</div> </div>
<div class="row items-center q-pt-md q-pb-sm"> <!-- split summary -->
<span class="app-text-muted-2">จำนวนงวด</span> <div
<span class="q-ml-auto">งหมด</span> v-if="
<span class="bordered rounded surface-2 number-box q-mx-sm">2</span> data.payCondition === 'BillSplit' || data.payCondition === 'Split'
งวด ายไปแล "
<span class="bordered rounded surface-2 number-box q-mx-sm">1</span> class="row items-center q-pb-sm"
งวด คงเหล >
<span class="bordered rounded surface-2 number-box q-mx-sm">1</span> <span class="app-text-muted-2">
งวด {{ $t('quotation.paySplitCount') }}
</span>
<span class="q-ml-auto">
{{ $t('quotation.receiptDialog.total') }}
</span>
<span class="bordered rounded surface-2 number-box q-mx-sm">
{{ data.paySplitCount }}
</span>
{{ $t('quotation.receiptDialog.installments') }}
{{ $i18n.locale === 'eng' ? ',' : '' }}
{{ $t('quotation.receiptDialog.paid') }}
<span class="bordered rounded surface-2 number-box q-mx-sm">
{{
paymentData.reduce(
(c, i) => (i.paymentStatus === 'PaymentSuccess' ? c + 1 : c),
0,
)
}}
</span>
{{
$i18n.locale === 'tha'
? $t('quotation.receiptDialog.installments')
: ','
}}
{{ $t('quotation.receiptDialog.remain') }}
<span class="bordered rounded surface-2 number-box q-mx-sm">
{{
paymentData.reduce(
(c, i) => (i.paymentStatus === 'PaymentWait' ? c + 1 : c),
0,
)
}}
</span>
{{
$i18n.locale === 'tha'
? $t('quotation.receiptDialog.installments')
: ''
}}
</div> </div>
<!-- summary total, paid, remain -->
<div class="row items-center"> <div class="row items-center">
<span <span
class="row col rounded q-px-sm q-py-md" class="row col rounded q-px-sm q-py-md"
style="border: 1px solid hsl(var(--info-bg))" style="border: 1px solid hsl(var(--info-bg))"
> >
ยอดทงหมด {{ $t('quotation.receiptDialog.totalAmount') }}
<span class="q-ml-auto">1,0000.00</span> <span class="q-ml-auto">
{{ formatNumberDecimal(data.finalPrice, 2) }}
</span>
</span> </span>
<span <span
class="row col rounded q-px-sm q-py-md q-mx-md" class="row col rounded q-px-sm q-py-md q-mx-md"
style="border: 1px solid hsl(var(--positive-bg))" style="border: 1px solid hsl(var(--positive-bg))"
> >
ชำระไปแล {{ $t('quotation.receiptDialog.paid') }}
<span class="q-ml-auto">1,0000.00</span> <span class="q-ml-auto">
{{
formatNumberDecimal(
paymentData.reduce(
(c, i) =>
i.paymentStatus === 'PaymentSuccess' ? c + i.amount : c,
0,
),
2,
)
}}
</span>
</span> </span>
<span <span
class="row col rounded q-px-sm q-py-md" class="row col rounded q-px-sm q-py-md"
style="border: 1px solid hsl(var(--warning-bg))" style="border: 1px solid hsl(var(--warning-bg))"
> >
คงเหล {{ $t('quotation.receiptDialog.remain') }}
<span class="q-ml-auto">1,0000.00</span> <span class="q-ml-auto">
{{
data.payCondition === 'BillSplit' ||
data.payCondition === 'Split'
? formatNumberDecimal(
paymentData.reduce(
(c, i) =>
i.paymentStatus === 'PaymentWait' ? c + i.amount : c,
0,
),
2,
)
: data.quotationStatus === 'PaymentPending'
? paymentData[0]?.amount &&
formatNumberDecimal(paymentData[0]?.amount, 2)
: '0.00'
}}
</span>
</span> </span>
</div> </div>
<span class="app-text-muted-2 q-pt-md">ลการชำระ</span> <!-- bill -->
<q-checkbox size="xs" :model-value="false" label="จ่ายเงินทั้งหมด" /> <span class="app-text-muted-2 q-pt-md">
{{ $t('quotation.receiptDialog.billOfPayment') }}
</span>
<q-checkbox
v-if="
data.payCondition === 'BillSplit' || data.payCondition === 'Split'
"
size="xs"
v-model="payAll"
:label="$t('quotation.receiptDialog.payAll')"
/>
<!-- payment item -->
<section class="row"> <section class="row">
<div <div
v-for="i in 3" v-for="(p, i) in paymentData"
:key="i" :key="i"
class="bordered rounded surface-1 q-mb-md col-12" class="bordered rounded surface-1 q-mb-md col-12"
style="overflow: hidden" style="overflow: hidden"
@ -217,26 +339,45 @@ defineProps<{
class="row full-width items-center surface-2 bordered-b q-px-md q-py-sm" class="row full-width items-center surface-2 bordered-b q-px-md q-py-sm"
> >
<span class="text-weight-medium column"> <span class="text-weight-medium column">
งวดทงหมด นยายน 2567 {{ $t('quotation.receiptDialog.allInstallments') }}
<span class="text-caption app-text-muted-2"> {{ monthDisplay(p.date) }}
นครบกำหนดชำระเง นท 1 ลาคม 2567 <span
class="text-caption app-text-muted-2"
v-if="data.payCondition !== 'Full'"
>
{{ $t('quotation.receiptDialog.paymentDueDate') }}
{{
data.payCondition !== 'BillFull'
? dateFormat(p.date, true)
: dateFormat(data.payBillDate)
}}
</span> </span>
</span> </span>
<div <div
class="q-ml-auto items-center flex bg-color-orange-light q-py-xs q-px-sm rounded" class="q-ml-auto items-center flex q-py-xs q-px-sm rounded"
style="color: var(--orange-6)" :class="{
'payment-success': p.paymentStatus === 'PaymentSuccess',
'payment-wait':
p.paymentStatus === 'PaymentWait' ||
p.paymentStatus === 'PaymentPending',
}"
> >
<q-avatar size="5px" class="bg-color-orange q-mr-sm" /> <q-icon size="6px" class="q-mr-sm" name="mdi-circle" />
งไมไดชำระเง {{
p.paymentStatus === 'PaymentWait' ||
p.paymentStatus === 'PaymentPending'
? $t('quotation.receiptDialog.notYetPaid')
: $t('quotation.receiptDialog.alreadyPaid')
}}
</div> </div>
</section> </section>
<section class="row items-center q-px-md q-py-sm"> <section class="row items-center q-px-md q-py-sm">
ยอดเงนทองชำระ {{ $t('quotation.receiptDialog.amountToBePaid') }}
<span <span
class="q-px-sm q-ml-auto" class="q-px-sm q-ml-auto"
style="color: var(--foreground)" style="color: var(--foreground)"
> >
{{ 0 }} {{ formatNumberDecimal(p.amount, 2) }}
</span> </span>
<q-btn <q-btn
dense dense
@ -252,7 +393,7 @@ defineProps<{
</div> </div>
</template> </template>
<div <!-- <div
class="q-px-md q-py-xs text-weight-medium row items-center" class="q-px-md q-py-xs text-weight-medium row items-center"
style="background-color: hsla(var(--info-bg) / 0.1)" style="background-color: hsla(var(--info-bg) / 0.1)"
> >
@ -267,18 +408,29 @@ defineProps<{
</div> </div>
<div class="q-px-md q-py-sm"> <div class="q-px-md q-py-sm">
<span class="app-text-muted">งไมพบงาน</span> <span class="app-text-muted">งไมพบงาน</span>
</div> </div> -->
<div <div
class="q-px-md q-py-xs text-weight-medium row items-center" class="q-px-md q-py-xs text-weight-medium row items-center"
style="background-color: hsla(var(--info-bg) / 0.1)" style="background-color: hsla(var(--info-bg) / 0.1)"
> >
ปโหลดใบเสร {{
$t('general.upload', {
msg: $t('quotation.receiptDialog.slip'),
})
}}
</div> </div>
<div class="surface-2" style="height: 200px"> <div class="surface-2" style="height: 200px">
<div class="column full-height items-center justify-center"> <div class="column full-height items-center justify-center">
<q-img src="/images/upload.png" width="150px" /> <q-img src="/images/upload.png" width="150px" />
ปโหลด E-Slip หร ปโหลดเอกสารการชำระเง {{ $t('general.upload', { msg: ' E-slip' }) }}
{{
$t('general.or', {
msg: $t('general.upload', {
msg: $t('quotation.receiptDialog.paymentDocs'),
}),
})
}}
<q-btn <q-btn
unelevated unelevated
:label="$t('general.upload')" :label="$t('general.upload')"
@ -314,6 +466,16 @@ defineProps<{
--_color: var(--orange-6-hsl / 0.2); --_color: var(--orange-6-hsl / 0.2);
} }
.payment-success {
color: hsl(var(--positive-bg));
background: hsla(var(--positive-bg) / 0.1);
}
.payment-wait {
color: hsl(var(--warning-bg));
background: hsla(var(--warning-bg) / 0.15);
}
.urgent { .urgent {
color: hsl(var(--red-6-hsl)); color: hsl(var(--red-6-hsl));
} }
@ -323,4 +485,30 @@ defineProps<{
width: 25px; width: 25px;
height: 25px; height: 25px;
} }
.badge-card {
padding: 4px 8px;
color: hsla(var(--gray-0-hsl) / 1);
background: hsla(var(--_color) / 1);
}
.badge-card__Full {
--_color: var(--red-6-hsl);
}
.badge-card__Split {
--_color: var(--blue-6-hsl);
}
.badge-card__BillFull {
--_color: var(--jungle-8-hsl);
}
.badge-card__BillSplit {
--_color: var(--purple-7-hsl);
}
.dark .badge-card__Split {
--_color: var(--blue-10-hsl);
}
</style> </style>

View file

@ -180,6 +180,7 @@ export type Quotation = {
id: string; id: string;
finalPrice: number; finalPrice: number;
vat: number; vat: number;
discount: number;
totalDiscount: number; totalDiscount: number;
totalPrice: number; totalPrice: number;
urgent: boolean; urgent: boolean;
@ -198,7 +199,14 @@ export type Quotation = {
workName: string; workName: string;
code: string; code: string;
statusOrder: number; statusOrder: number;
vatExcluded: number;
status: Status; status: Status;
quotationStatus:
| 'PaymentPending'
| 'PaymentInProcess'
| 'PaymentSuccess'
| 'ProcessComplete'
| 'Canceled';
registeredBranchId: string; registeredBranchId: string;