diff --git a/src/i18n/eng.ts b/src/i18n/eng.ts index f91043b3..2e1fc0c7 100644 --- a/src/i18n/eng.ts +++ b/src/i18n/eng.ts @@ -192,6 +192,7 @@ export default { sales: { title: 'Sales', quotation: 'Quotation', + invoice: 'Invoice', }, order: { @@ -1114,6 +1115,7 @@ export default { discount: 'Discount', vat: 'VAT', value: 'Value', + netValue: 'Net Value', title: { quotation: 'Quotation', invoice: 'Invoice', @@ -1186,4 +1188,15 @@ export default { Success: 'Refund Completed', }, }, + + invoice: { + title: 'Invoice', + caption: 'All Invoices', + workSheetName: 'Worksheet Name', + paymentDueDate: 'Payment Duedate', + status: { + PaymentSuccess: 'Payment Success', + PaymentWait: 'Waiting For Payment', + }, + }, }; diff --git a/src/i18n/tha.ts b/src/i18n/tha.ts index b40f9b81..60d6d66d 100644 --- a/src/i18n/tha.ts +++ b/src/i18n/tha.ts @@ -194,6 +194,7 @@ export default { sales: { title: 'งานซื้อขาย', quotation: 'ใบเสนอราคา', + invoice: 'ใบแจ้งหนี้', }, order: { @@ -1095,6 +1096,7 @@ export default { discount: 'ส่วนลด', vat: 'ภาษี', value: 'มูลค่า', + netValue: 'มูลค่าสุทธิ', title: { quotation: 'ใบเสนอราคา', invoice: 'ใบแจ้งหนี้', @@ -1167,4 +1169,15 @@ export default { Success: 'คืนเงินเสร็จสิ้น', }, }, + + invoice: { + title: 'ใบแจ้งหนี้', + caption: 'ใบแจ้งหนี้ทั้งหมด', + workSheetName: 'ชื่อใบงาน', + paymentDueDate: 'กำหนดชำระ', + status: { + PaymentSuccess: 'ชำระเงินแล้ว', + PaymentWait: 'ยังไม่ชำระ', + }, + }, }; diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue index 8c7a7009..cc80c525 100644 --- a/src/layouts/DrawerComponent.vue +++ b/src/layouts/DrawerComponent.vue @@ -134,7 +134,10 @@ onMounted(async () => { { label: 'menu.sales', icon: 'mdi-store-settings-outline', - children: [{ label: 'quotation', route: '/quotation' }], + children: [ + { label: 'quotation', route: '/quotation' }, + { label: 'invoice', route: '/invoice' }, + ], }, { label: 'menu.order', diff --git a/src/pages/05_quotation/QuotationForm.vue b/src/pages/05_quotation/QuotationForm.vue index efac31ea..44873ee4 100644 --- a/src/pages/05_quotation/QuotationForm.vue +++ b/src/pages/05_quotation/QuotationForm.vue @@ -356,18 +356,8 @@ async function fetchStatus() { code.value = ''; selectedInstallmentNo.value = []; selectedInstallment.value = []; - view.value = - quotationFormData.value.payCondition === 'Full' || - quotationFormData.value.payCondition === 'BillFull' - ? View.Invoice - : View.InvoicePre; - if ( - quotationFormData.value.payCondition === 'Full' || - quotationFormData.value.payCondition === 'BillFull' - ) { - getInvoiceCodeFullPay(); - } + getInvoice(); }, }, { @@ -1011,35 +1001,7 @@ onMounted(async () => { pageState.isLoaded = true; if (route.query['tab'] === 'invoice') { - if (route.query['id']) { - const queryInvoiceId = route.query['id'] as string; - const queryInvoiceAmount = Number(route.query['amount']) || 0; - - await getInvoiceCode(queryInvoiceId); - - selectedInstallmentNo.value = - quotationFormState.value.source?.paySplit - .filter((v) => v.invoiceId === queryInvoiceId) - .map((v) => v.no) || []; - - installmentAmount.value = queryInvoiceAmount; - view.value = View.Invoice; - return; - } - selectedInstallmentNo.value = []; - selectedInstallment.value = []; - view.value = - quotationFormData.value.payCondition === 'Full' || - quotationFormData.value.payCondition === 'BillFull' - ? View.Invoice - : View.InvoicePre; - - if ( - quotationFormData.value.payCondition === 'Full' || - quotationFormData.value.payCondition === 'BillFull' - ) { - getInvoiceCodeFullPay(); - } + getInvoice(); } if (route.query['tab'] === 'receipt') { await fetchReceipt(); @@ -1241,6 +1203,23 @@ async function exampleReceipt(id: string) { } } +function getInvoice() { + view.value = + quotationFormData.value.payCondition === 'Full' || + quotationFormData.value.payCondition === 'BillFull' + ? View.Invoice + : View.InvoicePre; + + if ( + quotationFormData.value.payCondition === 'Full' || + quotationFormData.value.payCondition === 'BillFull' + ) { + getInvoiceCodeFullPay(); + } else { + code.value = ''; + } +} + watch( [ () => quotationFormState.value.statusFilterRequest, diff --git a/src/pages/05_quotation/preview/ViewForm.vue b/src/pages/05_quotation/preview/ViewForm.vue index fac2c010..1b7ce701 100644 --- a/src/pages/05_quotation/preview/ViewForm.vue +++ b/src/pages/05_quotation/preview/ViewForm.vue @@ -5,9 +5,7 @@ import { precisionRound } from 'src/utils/arithmetic'; import ThaiBahtText from 'thai-baht-text'; // NOTE: Import stores -import useOptionStore from 'stores/options'; import { formatNumberDecimal } from 'stores/utils'; -import { useQuotationForm } from 'pages/05_quotation/form'; import { useConfigStore } from 'stores/config'; import useBranchStore from 'stores/branch'; import { baseUrl } from 'stores/utils'; @@ -19,8 +17,8 @@ import { CustomerBranch } from 'stores/customer/types'; import { BankBook, Branch } from 'stores/branch/types'; import { QuotationPayload, - CustomerBranchRelation, Details, + QuotationFull, } from 'src/stores/quotations/types'; // NOTE: Import Components @@ -70,9 +68,7 @@ const attachmentList = ref< isPDF?: boolean; }[] >([]); -const data = ref< - QuotationPayload & { customerBranch: CustomerBranchRelation; id: string } ->(); +const data = ref(); const summaryPrice = ref({ totalPrice: 0, @@ -82,6 +78,12 @@ const summaryPrice = ref({ finalPrice: 0, }); +async function fetchQuotationById(id: string) { + const res = await quotationStore.getQuotation(id); + + if (res) data.value = res; +} + async function getAttachment(quotationId: string) { const attachment = await quotationStore.listAttachment({ parentId: quotationId, @@ -178,10 +180,13 @@ onMounted(async () => { data.value = 'data' in parsed ? parsed.data : undefined; if (data.value) { - await getAttachment(data.value.id); + if (!!data.value.id) { + await getAttachment(data.value.id); + await fetchQuotationById(data.value.id); + } const resCustomerBranch = await customerStore.getBranchById( - data.value.customerBranchId, + data.value?.customerBranchId, ); if (resCustomerBranch) { @@ -189,19 +194,27 @@ onMounted(async () => { } details.value = { - code: parsed.meta.source.code, - createdAt: parsed.meta.source.createdAt, - createdBy: `${parsed.meta.createdBy} ${!parsed.meta.source.createdBy ? '' : parsed.meta.source.createdBy.telephoneNo}`, - payCondition: parsed.meta.source.payCondition, - contactName: parsed.meta.source.contactName, - contactTel: parsed.meta.source.contactTel, - workName: parsed.meta.source.workName, - dueDate: parsed.meta.source.dueDate, - worker: parsed.meta.selectedWorker, + code: parsed?.meta?.source?.code ?? data.value?.code, + createdAt: + parsed?.meta?.source?.createdAt ?? + new Date(data.value?.createdAt || ''), + createdBy: + `${parsed?.meta?.source?.createdBy?.firstName ?? ''} ${parsed?.meta?.source?.createdBy?.telephoneNo ?? ''}`.trim() || + `${data.value?.createdBy?.firstName ?? ''} ${data.value?.createdBy?.telephoneNo ?? ''}`.trim(), + payCondition: + parsed?.meta?.source?.payCondition ?? data.value?.payCondition, + contactName: parsed?.meta?.source?.contactName ?? data.value?.contactName, + contactTel: parsed?.meta?.source?.contactTel ?? data.value?.contactTel, + workName: parsed?.meta?.source?.workName ?? data.value?.workName, + dueDate: + parsed?.meta?.source?.dueDate ?? new Date(data.value?.dueDate || ''), + worker: + parsed?.meta?.selectedWorker ?? + data.value?.worker.map((v) => v.employee), }; const resBranch = await branchStore.fetchById( - data.value?.registeredBranchId, + data.value?.registeredBranchId ?? data.value?.registeredBranchId, ); if (resBranch) { @@ -214,26 +227,31 @@ onMounted(async () => { } productList.value = - data.value?.productServiceList.map((v) => ({ - id: v.product.id, - code: v.product.code, - detail: v.product.name, - amount: v.amount || 0, - priceUnit: v.pricePerUnit || 0, - discount: v.discount || 0, - vat: v.vat || 0, - value: precisionRound( - (v.pricePerUnit || 0) * v.amount - - (v.discount || 0) + - (v.product.calcVat - ? ((v.pricePerUnit || 0) * v.amount - (v.discount || 0)) * - (config.value?.vat || 0.07) - : 0), - ), - })) || []; + (data?.value?.productServiceList ?? data.value?.productServiceList).map( + (v) => ({ + id: v.product.id, + code: v.product.code, + detail: v.product.name, + amount: v.amount || 0, + priceUnit: v.pricePerUnit || 0, + discount: v.discount || 0, + vat: v.vat || 0, + value: precisionRound( + (v.pricePerUnit || 0) * v.amount - + (v.discount || 0) + + (v.product.calcVat + ? ((v.pricePerUnit || 0) * v.amount - (v.discount || 0)) * + (config.value?.vat || 0.07) + : 0), + ), + }), + ) || []; } - summaryPrice.value = (data.value?.productServiceList || []).reduce( + summaryPrice.value = ( + (data?.value?.productServiceList ?? data.value?.productServiceList) || + [] + ).reduce( (a, c) => { const price = precisionRound((c.pricePerUnit || 0) * c.amount); const vat = precisionRound( diff --git a/src/pages/05_quotation/preview/ViewHeader.vue b/src/pages/05_quotation/preview/ViewHeader.vue index 7d76ae71..0f1c3f8c 100644 --- a/src/pages/05_quotation/preview/ViewHeader.vue +++ b/src/pages/05_quotation/preview/ViewHeader.vue @@ -121,14 +121,7 @@ function titleMode(mode: View): string {
เงื่อนไขการชำระ
- {{ - { - Full: $t('quotation.type.fullAmountCash'), - Split: $t('quotation.type.installmentsCash'), - BillFull: $t('quotation.type.fullAmountBill'), - BillSplit: $t('quotation.type.installmentsBill'), - }[details.payCondition] - }} + {{ $t(`quotation.type.${details.payCondition}`) }}
diff --git a/src/pages/10_invoice/MainPage.vue b/src/pages/10_invoice/MainPage.vue new file mode 100644 index 00000000..fd21a427 --- /dev/null +++ b/src/pages/10_invoice/MainPage.vue @@ -0,0 +1,412 @@ + + + diff --git a/src/pages/10_invoice/TableInvoice.vue b/src/pages/10_invoice/TableInvoice.vue new file mode 100644 index 00000000..640e2aed --- /dev/null +++ b/src/pages/10_invoice/TableInvoice.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/pages/10_invoice/constants.ts b/src/pages/10_invoice/constants.ts new file mode 100644 index 00000000..e21573f6 --- /dev/null +++ b/src/pages/10_invoice/constants.ts @@ -0,0 +1,111 @@ +import { QTableProps } from 'quasar'; +import { Invoice } from 'src/stores/payment/types'; +import { formatNumberDecimal } from 'src/stores/utils'; +import { dateFormatJS } from 'src/utils/datetime'; + +export const columns = [ + { + name: '#order', + align: 'center', + label: 'general.order', + field: (v: Invoice & { _index: number }) => v._index + 1, + }, + + { + name: 'invoiceCode', + align: 'center', + label: 'requestList.invoiceCode', + field: 'code', + }, + + { + name: 'workSheetName', + align: 'center', + label: 'invoice.workSheetName', + field: (v: Invoice) => v.quotation.workName, + }, + + { + name: 'customer', + align: 'center', + label: 'general.customer', + field: (v: Invoice) => v.quotation.customerBranch.customerName, + }, + + { + name: 'issueDate', + align: 'center', + label: 'taskOrder.issueDate', + field: (v: Invoice) => dateFormatJS({ date: v.createdAt }), + }, + + { + name: 'paymentDueDate', + align: 'center', + label: 'invoice.paymentDueDate', + field: (v: Invoice) => dateFormatJS({ date: v.quotation.dueDate }), + }, + + { + name: 'value', + align: 'center', + label: 'preview.value', + field: (v: Invoice) => formatNumberDecimal(v.amount, 2), + }, + { + name: '#status', + align: 'center', + label: 'general.status', + field: (_) => '#status', + }, + { + name: '#action', + align: 'center', + label: '', + field: (_) => '#action', + }, +] as const satisfies QTableProps['columns']; + +export const docColumn = [ + { + name: 'order', + align: 'center', + label: 'general.order', + field: 'no', + }, + { + name: 'document', + align: 'left', + label: 'general.document', + field: 'document', + }, + { + name: 'attachment', + align: 'left', + label: 'requestList.attachment', + field: 'attachment', + }, + { + name: 'amount', + align: 'center', + label: 'general.amount', + field: 'amount', + }, + { + name: 'documentInSystem', + align: 'center', + label: 'requestList.documentInSystem', + field: 'documentInSystem', + }, + { + name: 'status', + align: 'center', + label: 'general.status', + field: 'status', + }, +] as const satisfies QTableProps['columns']; + +export const hslaColors: Record = { + PaymentWait: '--orange-5-hsl', + PaymentSuccess: '--green-8-hsl', +}; diff --git a/src/router/routes.ts b/src/router/routes.ts index 0a91c9fa..9830d39e 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -110,6 +110,11 @@ const routes: RouteRecordRaw[] = [ name: 'CreditNote', component: () => import('pages/11_credit-note/MainPage.vue'), }, + { + path: '/invoice', + name: '/Invoice', + component: () => import('pages/10_invoice/MainPage.vue'), + }, ], }, diff --git a/src/stores/payment/index.ts b/src/stores/payment/index.ts index 4b5b716d..10280b7c 100644 --- a/src/stores/payment/index.ts +++ b/src/stores/payment/index.ts @@ -8,6 +8,7 @@ import { Payment, Receipt, PaymentFlowAccount, + PaymentDataStatus, } from './types'; import { manageAttachment } from '../utils'; @@ -122,6 +123,19 @@ export const useInvoice = defineStore('invoice-store', () => { const page = ref(1); const pageMax = ref(1); const pageSize = ref(30); + const stats = ref>({ + [PaymentDataStatus.Success]: 0, + [PaymentDataStatus.Wait]: 0, + }); + + async function getInvoiceStats(params?: { quotationId?: string }) { + const res = await api.get>( + '/invoice/stats', + { params }, + ); + if (res.status >= 400) return null; + return res.data; + } async function getInvoice(id: string) { const res = await api.get(`/invoice/${id}`); @@ -136,6 +150,7 @@ export const useInvoice = defineStore('invoice-store', () => { pageSize?: number; query?: string; quotationId?: string; + pay?: boolean; }) { const res = await api.get>('/invoice', { params, @@ -171,7 +186,9 @@ export const useInvoice = defineStore('invoice-store', () => { page, pageSize, pageMax, + stats, + getInvoiceStats, getInvoice, getInvoiceList, createInvoice, diff --git a/src/stores/payment/types.ts b/src/stores/payment/types.ts index 31a6a8ae..273bac5a 100644 --- a/src/stores/payment/types.ts +++ b/src/stores/payment/types.ts @@ -16,9 +16,14 @@ export type Invoice = { createdBy: CreatedBy; createdByUserId: string; - payment?: Payment; + payment: Payment; }; +export enum PaymentDataStatus { + Success = 'PaymentSuccess', + Wait = 'PaymentWait', +} + export type InvoicePayload = { quotationId: string; amount: number;