refactor: add page invoice

This commit is contained in:
Thanaphon Frappet 2024-10-29 18:02:20 +07:00
parent 1f4edc7363
commit f4d225985a
4 changed files with 313 additions and 180 deletions

View file

@ -105,6 +105,9 @@ const $q = useQuasar();
const {
currentFormData: quotationFormData,
currentFormState: quotationFormState,
invoicePayload: invoiceFormData,
newWorkerList,
fileItemNewWorker,
quotationFull,
@ -140,6 +143,7 @@ const workerList = ref<Employee[]>([]);
const selectedProductGroup = ref('');
const selectedInstallmentNo = ref<number[]>([]);
const selectedInstallment = ref();
const agentPrice = ref(false);
function getPrice(
@ -248,6 +252,79 @@ const productServiceList = ref<
Required<QuotationPayload['productServiceList'][number]>[]
>([]);
async function fetchStatus() {
statusQuotationForm.value = [
{
title: 'ใบเสนอราคา',
status: getStatus(quotationFormData.value.quotationStatus, -1, -1),
handler: () => (view.value = View.Quotation),
},
{
title: 'ลูกค้าตอบรับ',
status: getStatus(quotationFormData.value.quotationStatus, 0, -1),
handler: () => (view.value = View.Accepted),
},
{
title: 'ใบแจ้งหนี้',
status: getStatus(quotationFormData.value.quotationStatus, 4, 0),
handler: () => {
view.value =
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Invoice
: View.InvoicePre;
},
},
{
title: 'ชำระเงิน',
status: getStatus(quotationFormData.value.quotationStatus, 4, 1),
handler: () => {
view.value =
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Payment
: View.PaymentPre;
},
},
{
title: 'ใบเสร็จรับเงิน',
status: getStatus(quotationFormData.value.quotationStatus, 4, 1),
handler: () => {
view.value =
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Receipt
: View.ReceiptPre;
},
},
{
title: 'เสร็จสิ้น',
status: getStatus(quotationFormData.value.quotationStatus, 5, 4),
handler: () => {
view.value = View.Complete;
},
},
];
}
async function fetchQuotation() {
if (
currentQuotationId.value !== undefined &&
quotationFormState.value.mode &&
quotationFormState.value.mode !== 'create'
) {
await quotationForm.assignFormData(
currentQuotationId.value,
quotationFormState.value.mode,
);
await assignWorkerToSelectedWorker();
}
await assignToProductServiceList();
await fetchStatus();
}
async function closeTab() {
if (quotationFormState.value.mode === 'edit') {
quotationForm.resetForm();
@ -306,16 +383,33 @@ async function submitAccepted() {
action: async () => {
if (!quotationFormData.value.id) return;
await quotationForm.accepted(quotationFormData.value.id);
const res = await quotationForm.accepted(quotationFormData.value.id);
if (res) {
await fetchQuotation();
}
},
cancel: () => {},
});
}
async function convertInvoiceToSubmit() {
if (quotationFormData.value.id) {
invoiceFormData.value = {
installmentNo: selectedInstallmentNo.value,
amount: summaryPrice.value.finalPrice,
quotationId: quotationFormData.value.id,
};
const res = await quotationForm.submitInvoice();
if (res) view.value = View.Payment;
}
}
async function convertDataToFormSubmit() {
quotationFormData.value.productServiceList = JSON.parse(
JSON.stringify(
productServiceList.value.map((v) => ({
installmentNo: v.installmentNo,
workerIndex: v.workerIndex,
discount: v.discount,
amount: v.amount,
@ -386,6 +480,7 @@ async function convertDataToFormSubmit() {
if (res === true) {
quotationFormState.value.mode = 'info';
await fetchQuotation();
}
}
@ -493,19 +588,24 @@ function convertToTable(nodes: Node[]) {
};
const list = nodes.flatMap(_recursive).map((v) => v.value);
quotationFormData.value.paySplitCount = Math.max(
...list.map((v) => v.installmentNo || 0),
);
list.forEach((v) => {
v.amount = Math.max(selectedWorker.value.length, 1);
if (!v.workerIndex) v.workerIndex = [];
for (let i = 0; i < selectedWorker.value.length; i++) {
v.workerIndex.push(i);
}
if (!v.installmentNo) {
v.installmentNo = quotationFormData.value.paySplitCount;
}
});
productServiceList.value = list;
quotationFormData.value.paySplitCount = Math.max(
...list.map((v) => v.installmentNo || 0),
);
quotationFormData.value.paySplit = Array.apply(
null,
new Array(quotationFormData.value.paySplitCount),
@ -627,17 +727,7 @@ onMounted(async () => {
quotationFormData.value.customerBranchId = parsed.customerBranchId;
currentQuotationId.value = parsed.quotationId;
agentPrice.value = parsed.agentPrice;
if (
currentQuotationId.value !== undefined &&
quotationFormState.value.mode &&
quotationFormState.value.mode !== 'create'
) {
await quotationForm.assignFormData(
currentQuotationId.value,
quotationFormState.value.mode,
);
await assignWorkerToSelectedWorker();
}
await fetchQuotation();
sessionData.value = parsed;
}
@ -647,63 +737,20 @@ onMounted(async () => {
if (locale.value === 'eng') optionStore.globalOption = rawOption.eng;
if (locale.value === 'tha') optionStore.globalOption = rawOption.tha;
await assignToProductServiceList();
statusQuotationForm.value = [
{
title: 'ใบเสนอราคา',
status: getStatus(quotationFormData.value.quotationStatus, -1, -1),
handler: () => (view.value = View.Quotation),
},
{
title: 'ลูกค้าตอบรับ',
status: getStatus(quotationFormData.value.quotationStatus, 0, -1),
handler: () => (view.value = View.Accepted),
},
{
title: 'ใบแจ้งหนี้',
status: getStatus(quotationFormData.value.quotationStatus, 4, 0),
handler: () => {
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Invoice
: View.InvoicePre;
},
},
{
title: 'ชำระเงิน',
status: getStatus(quotationFormData.value.quotationStatus, 4, 1),
handler: () => {
view.value =
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Payment
: View.PaymentPre;
},
},
{
title: 'ใบเสร็จรับเงิน',
status: getStatus(quotationFormData.value.quotationStatus, 4, 1),
handler: () => {
view.value =
quotationFormData.value.payCondition === 'Full' ||
quotationFormData.value.payCondition === 'BillFull'
? View.Receipt
: View.ReceiptPre;
},
},
{
title: 'เสร็จสิ้น',
status: getStatus(quotationFormData.value.quotationStatus, 5, 4),
handler: () => {
view.value = View.Complete;
},
},
];
await fetchStatus();
pageState.isLoaded = true;
});
watch(
() => quotationFormData.value.status,
() => {
fetchStatus();
statusQuotationForm.value.filter((v) => v.title === 'ลูกค้าตอบรับ');
},
);
watch(
() => quotationFormData.value.customerBranchId,
async (v) => {
@ -934,13 +981,6 @@ const view = ref<View>(View.Quotation);
/>
{{ value.title }}
</button>
<div class="col">
<MainButton
color="red"
icon="mdi-check-all"
@click="submitAccepted()"
/>
</div>
</div>
<template
v-if="
@ -1014,83 +1054,6 @@ const view = ref<View>(View.Quotation);
</div>
</q-expansion-item>
<q-expansion-item
for="item-up"
id="item-up"
dense
class="overflow-hidden"
switch-toggle-side
default-opened
style="border-radius: var(--radius-2)"
expand-icon="mdi-chevron-down-circle"
header-class="surface-1"
>
<div class="surface-1 q-pa-md full-width">
<SwitchItem :value="view">
<template #[View.Quotation]>
<q-table
flat
bordered
:columns="columnPaySplit"
:rows="quotationFormData.paySplit"
selection="multiple"
v-model:selected="selectedInstallmentNo"
row-key="no"
>
<template v-slot:header="props">
<q-tr
:props="props"
:style="`background-color:hsla(var(--info-bg) / 0.07)`"
>
<q-th auto-width>
<q-checkbox v-model="props.selected" />
</q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ $t(col.label) }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-checkbox v-model="props.selected" />
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<template v-if="col.name === 'installmentNo'">
{{ col.value }}
</template>
<template v-if="col.name === 'amount'">
{{ formatNumberDecimal(col.value, 2) }}
</template>
<template v-if="col.name === 'status'">
<BadgeComponent
:title="
!!col.value
? 'สร้างใบแจ้งหนี้เเล้ว'
: 'ยังไม่สร้างใบแจ้งหนี้'
"
:hsla-color="!!col.value ? '' : '--red-7-hsl'"
/>
</template>
</q-td>
</q-tr>
</template>
</q-table>
</template>
</SwitchItem>
</div>
</q-expansion-item>
<q-expansion-item
for="item-up"
id="item-up"
@ -1211,11 +1174,13 @@ const view = ref<View>(View.Quotation);
@delete="toggleDeleteProduct"
:rows="
selectedInstallmentNo.length > 0
? productServiceList.filter(
(v) =>
? productServiceList.filter((v) => {
console.log(v.installmentNo, selectedInstallmentNo);
return (
v.installmentNo &&
selectedInstallmentNo.includes(v.installmentNo),
)
selectedInstallmentNo.includes(v.installmentNo)
);
})
: productServiceList
"
@update:rows="
@ -1249,26 +1214,20 @@ const view = ref<View>(View.Quotation);
</template>
<div class="surface-1 q-pa-md full-width">
<SwitchItem :value="view">
<template #[View.Quotation]>
<QuotationFormInfo
:type="view"
v-model:pay-type="quotationFormData.payCondition"
v-model:pay-bank="payBank"
v-model:pay-split-count="quotationFormData.paySplitCount"
v-model:pay-split="quotationFormData.paySplit"
:readonly
v-model:final-discount="quotationFormData.discount"
v-model:pay-bill-date="quotationFormData.payBillDate"
v-model:summary-price="summaryPrice"
class="q-mb-md"
/>
</template>
<template #b>
<div>bbb</div>
</template>
</SwitchItem>
<template v-if="true">
<QuotationFormInfo
:info-type="view"
v-model:pay-type="quotationFormData.payCondition"
v-model:pay-bank="payBank"
v-model:pay-split-count="quotationFormData.paySplitCount"
v-model:pay-split="quotationFormData.paySplit"
:readonly
v-model:final-discount="quotationFormData.discount"
v-model:pay-bill-date="quotationFormData.payBillDate"
v-model:summary-price="summaryPrice"
class="q-mb-md"
/>
</template>
</div>
</q-expansion-item>
<q-expansion-item
@ -1329,6 +1288,89 @@ const view = ref<View>(View.Quotation);
</template>
<template v-else>
<PaymentForm :data="quotationFormState.source" />
<q-expansion-item
for="item-up"
id="item-up"
dense
class="overflow-hidden"
switch-toggle-side
default-opened
style="border-radius: var(--radius-2)"
expand-icon="mdi-chevron-down-circle"
header-class="surface-1"
>
<div class="surface-1 q-pa-md full-width">
<SwitchItem :value="view">
<template #[View.InvoicePre]>
<q-table
flat
bordered
:columns="columnPaySplit"
:rows="quotationFormData.paySplit"
selection="multiple"
v-model:selected="selectedInstallment"
@update:selected="
(v) => {
selectedInstallmentNo = v.map((value) => value.no);
}
"
row-key="no"
>
<template v-slot:header="props">
<q-tr
:props="props"
:style="`background-color:hsla(var(--info-bg) / 0.07)`"
>
<q-th auto-width>
<q-checkbox v-model="props.selected" />
</q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ $t(col.label) }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-checkbox v-model="props.selected" />
</q-td>
<q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
>
<template v-if="col.name === 'installmentNo'">
{{ col.value }}
</template>
<template v-if="col.name === 'amount'">
{{ formatNumberDecimal(col.value, 2) }}
</template>
<template v-if="col.name === 'status'">
<BadgeComponent
:title="
!!col.value
? 'สร้างใบแจ้งหนี้เเล้ว'
: 'ยังไม่สร้างใบแจ้งหนี้'
"
:hsla-color="!!col.value ? '' : '--red-7-hsl'"
/>
</template>
</q-td>
</q-tr>
</template>
</q-table>
</template>
</SwitchItem>
</div>
.
</q-expansion-item>
</template>
</div>
</section>
@ -1344,6 +1386,48 @@ const view = ref<View>(View.Quotation);
>
{{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton>
<div v-if="view === View.Accepted">
<MainButton
outlined
icon="mdi-play-box-outline"
color="207 96% 32%"
@click="storeDataLocal"
>
{{ $t('general.view', { msg: $t('general.example') }) }}
</MainButton>
</div>
<div v-if="view === View.InvoicePre">
<MainButton
solid
icon="mdi-account-multiple-check-outline"
color="207 96% 32%"
@click="
() => {
view = View.Invoice;
}
"
>
{{ $t('quotation.selectInvoice') }}
</MainButton>
</div>
<div v-if="view === View.Invoice">
<MainButton
solid
icon="mdi-account-multiple-check-outline"
color="207 96% 32%"
@click="
() => {
convertInvoiceToSubmit();
}
"
>
{{ $t('quotation.approveInvoice') }}
</MainButton>
</div>
<div
class="row"
style="gap: var(--size-2)"
@ -1380,6 +1464,19 @@ const view = ref<View>(View.Quotation);
@click="quotationFormState.mode = 'edit'"
solid
/>
<MainButton
v-if="view === View.Quotation"
solid
icon="mdi-account-multiple-check-outline"
color="207 96% 32%"
@click="
() => {
submitAccepted();
}
"
>
{{ $t('quotation.customerAcceptance') }}
</MainButton>
</div>
</div>
</footer>

View file

@ -77,7 +77,7 @@ const summaryPrice = defineModel<{
const finalDiscount = defineModel<number>('finalDiscount', { default: 0 });
const finalDiscount4Show = ref<string>(finalDiscount.value.toString());
const payTypeOpion = computed(() => [
const payTypeOption = computed(() => [
{
value: 'Full',
label: t('quotation.type.fullAmountCash'),
@ -201,7 +201,7 @@ watch(
<SelectInput
class="col-6"
:label="$t('quotation.payType')"
:option="payTypeOpion"
:option="payTypeOption"
:readonly
id="pay-type"
v-model="payType"

View file

@ -47,7 +47,7 @@ export const columnPaySplit = [
name: 'status',
align: 'center',
label: 'general.status',
field: 'status',
field: 'invoice',
},
] satisfies QTableProps['columns'];

View file

@ -10,8 +10,11 @@ import {
QuotationFull,
} from 'src/stores/quotations/types';
import { InvoicePayload } from 'src/stores/payment/types';
// NOTE: Import stores
import { useQuotationStore } from 'stores/quotations';
import { useInvoice } from 'stores/payment';
import useEmployeeStore from 'stores/employee';
import { getName } from 'src/services/keycloak';
@ -36,12 +39,23 @@ const DEFAULT_DATA: QuotationPayload = {
remark: undefined,
};
const DEFAULT_DATA_INVOICE: InvoicePayload = {
quotationId: '',
amount: 0,
productServiceListId: [],
installmentNo: [],
};
export const useQuotationForm = defineStore('form-quotation', () => {
const { t } = useI18n();
const quotationStore = useQuotationStore();
const employeeStore = useEmployeeStore();
const invoiceStore = useInvoice();
const quotationFull = ref<QuotationFull | undefined>(undefined);
const invoicePayload = ref<InvoicePayload & { id?: string }>(
DEFAULT_DATA_INVOICE,
);
const newWorkerList = ref<
(EmployeeWorker & {
attachment?: {
@ -122,7 +136,7 @@ export const useQuotationForm = defineStore('form-quotation', () => {
payBillDate: data.payBillDate ? new Date(data.payBillDate) : undefined,
paySplit: data.paySplit.map((p, index) => ({
no: index + 1,
date: p.date,
invoice: p.invoice,
amount: p.amount,
})),
worker: data.worker.map((v) =>
@ -144,6 +158,8 @@ export const useQuotationForm = defineStore('form-quotation', () => {
if (mode === 'assign') return;
currentFormState.value.mode = mode;
console.log(currentFormData.value);
}
async function submitQuotation() {
@ -242,6 +258,22 @@ export const useQuotationForm = defineStore('form-quotation', () => {
return false;
}
async function submitInvoice() {
let status = false;
if (invoicePayload.value.id === undefined) {
const res = await invoiceStore.creatInvoice(invoicePayload.value);
if (res) status = true;
}
if (invoicePayload.value.id !== undefined) {
const res = await invoiceStore.editInvoice(invoicePayload.value);
if (res) status = true;
}
return status;
}
return {
DEFAULT_DATA,
currentFormState,
@ -250,12 +282,16 @@ export const useQuotationForm = defineStore('form-quotation', () => {
fileItemNewWorker,
quotationFull,
invoicePayload,
isFormDataDifferent,
injectNewEmployee,
assignFormData,
dialogDelete,
resetForm,
submitInvoice,
accepted,
submitQuotation,