From 79240f53b039f5e37bde3cf0d66dfd0b92bb9458 Mon Sep 17 00:00:00 2001 From: Methapon Metanipat <162551568+Methapon-Frappet@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:04:08 +0700 Subject: [PATCH] feat: debit note (#172) * feat: new file * feat: function api debit * feat: add route debit * feat: new form page * refactor: show menu debit * refactor: add type debit note status * feat: add i18n * feat: add constants * feat: add stores * feat: layout * feat: add function * refactor: change name value * feat: form select quotation * refactor: change name url * refactor: use form debit * refactor: change src import * refactor: move file form debit * refactor: add i18n * feat: add type debit note * refactor: add columns * refactor: bind value columns * refactor: change name Table * refactor: edit type * refactor: bind type debit note * refactor: bind value debit * refactor: chame name function * fix: calculate page * refactor: delete table * refactor: change name get list * refactor: change i18n * refactor: change name value * refactor: bind navigate and trigger delete * refactor: format number deciml * refactor: add i18n * feat: new page * refactor: add color debit * feat: Debit tab #178 * feat: TableRequest * refactor: edit type pay condition * refactor: add i18n btn submit * refactor: use type enum * feat: edit layout product expansion * refactor: bind function * refactor: show code * feat: add input search and select status * feat: paymentform * refactor: edit type * refactor: add manage file and edit end point * feat: add form.ts * refactor: send mode * refactor: edit v-model of due date * feat: submit create debit * fix: status * refactor: handle data not allow * fix: call updateDebitNote in edit mode and simplify payload handling * refactor: hide edit * refactor: handle pay condition only full * refactor: delete pay split * refactor: add query * refactor: handle is debit note * refactor: handle is quotation * refactor: add props hide * refactor: tap payment and receipt * refactor: add i18n * feat: view document * refactor: handle btn view doc * refactor: use my remark --------- Co-authored-by: Thanaphon Frappet Co-authored-by: nwpptrs Co-authored-by: aif912752 --- src/components/12_debit-note/FormDebit.vue | 39 + src/i18n/eng.ts | 51 +- src/i18n/tha.ts | 49 + src/layouts/DrawerComponent.vue | 2 +- src/pages/05_quotation/PaymentForm.vue | 13 +- src/pages/05_quotation/QuotationForm.vue | 4 +- src/pages/05_quotation/QuotationFormInfo.vue | 6 + .../05_quotation/QuotationFormReceipt.vue | 42 +- src/pages/05_quotation/TableRequest.vue | 3 +- src/pages/12_debit-note/FormPage.vue | 1322 +++++++++++++++++ src/pages/12_debit-note/MainPage.vue | 499 +++++++ src/pages/12_debit-note/TableDebitNote.vue | 95 ++ src/pages/12_debit-note/constants.ts | 90 ++ .../document-view/BankComponents.vue | 113 ++ .../12_debit-note/document-view/MainPage.vue | 654 ++++++++ .../document-view/ViewFooter.vue | 98 ++ .../document-view/ViewHeader.vue | 192 +++ .../12_debit-note/document-view/ViewPdf.vue | 27 + .../expansion/DebitNoteExpansion.vue | 51 + .../expansion/DocumentExpansion.vue | 120 ++ .../expansion/PaymentExpansion.vue | 116 ++ .../expansion/ProductExpansion.vue | 105 ++ .../expansion/RemarkExpansion.vue | 67 + .../expansion/WorkerItemExpansion.vue | 78 + src/pages/12_debit-note/form.ts | 64 + src/router/routes.ts | 21 + src/stores/debit-note/index.ts | 94 ++ src/stores/debit-note/types.ts | 94 ++ src/stores/payment/index.ts | 3 + src/stores/quotations/index.ts | 9 +- tests/institution.spec.ts | 44 + tests/utils/index.ts | 19 + 32 files changed, 4172 insertions(+), 12 deletions(-) create mode 100644 src/components/12_debit-note/FormDebit.vue create mode 100644 src/pages/12_debit-note/FormPage.vue create mode 100644 src/pages/12_debit-note/MainPage.vue create mode 100644 src/pages/12_debit-note/TableDebitNote.vue create mode 100644 src/pages/12_debit-note/constants.ts create mode 100644 src/pages/12_debit-note/document-view/BankComponents.vue create mode 100644 src/pages/12_debit-note/document-view/MainPage.vue create mode 100644 src/pages/12_debit-note/document-view/ViewFooter.vue create mode 100644 src/pages/12_debit-note/document-view/ViewHeader.vue create mode 100644 src/pages/12_debit-note/document-view/ViewPdf.vue create mode 100644 src/pages/12_debit-note/expansion/DebitNoteExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/DocumentExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/PaymentExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/ProductExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/RemarkExpansion.vue create mode 100644 src/pages/12_debit-note/expansion/WorkerItemExpansion.vue create mode 100644 src/pages/12_debit-note/form.ts create mode 100644 src/stores/debit-note/index.ts create mode 100644 src/stores/debit-note/types.ts create mode 100644 tests/institution.spec.ts create mode 100644 tests/utils/index.ts diff --git a/src/components/12_debit-note/FormDebit.vue b/src/components/12_debit-note/FormDebit.vue new file mode 100644 index 00000000..1aa8e421 --- /dev/null +++ b/src/components/12_debit-note/FormDebit.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/i18n/eng.ts b/src/i18n/eng.ts index 6c06fd9f..fdef5fe2 100644 --- a/src/i18n/eng.ts +++ b/src/i18n/eng.ts @@ -1125,6 +1125,8 @@ export default { }, preview: { + dateAt: 'Date {msg}', + seller: 'Seller', taskOrder: 'Work Order', doc: 'View Document', productList: 'Product List', @@ -1135,15 +1137,17 @@ export default { vat: 'VAT', value: 'Value', netValue: 'Net Value', + dueDate: 'Due Date', + paymentMethods: 'Payment Methods', title: { creditNote: 'Credit Note', quotation: 'Quotation', invoice: 'Invoice', payment: 'Payment', receipt: 'Receipt', + debitNote: 'Debit Note', }, }, - address: { subDistrict: 'Sub District', subArea: 'Sub Area', @@ -1225,4 +1229,49 @@ export default { dataSum: 'Summary of all receipts/tax invoices', workSheetName: 'Worksheet name', }, + + debitNote: { + title: 'Debit Note', + caption: 'All Debit Notes', + expire: 'Expired', + payment: 'Payment', + receipt: 'Receipt', + succeed: 'Completed', + downloadReceipt: 'Download Receipt', + downloadTaxInvoice: 'Download Tax Invoice', + + label: { + additionalDetail: 'Additional Details', + specifyReasonForDebit: 'Specify Reason for Debit', + debitNoteInformation: 'Debit Note Information', + codeDebit: 'Debit Note Number', + codeQuotation: 'Quotation Number', + quotationWorkName: 'Work Name', + quotationPayment: 'Payment Method', + value: 'Net Value', + submit: 'Approve Debit Note', + }, + + stats: { + Pending: 'Debit Note', + Expire: 'Expired', + Payment: 'Payment', + Receipt: 'Receipt', + Succeed: 'Completed', + }, + + viewMode: { + payment: 'Payment', + receipt: 'Receipt/Tax Invoice', + processComplete: 'Completed', + }, + + status: { + Pending: 'Debit Note', + Expire: 'Expired', + Payment: 'Payment', + Receipt: 'Receipt', + Succeed: 'Completed', + }, + }, }; diff --git a/src/i18n/tha.ts b/src/i18n/tha.ts index 4654979d..b610bcb5 100644 --- a/src/i18n/tha.ts +++ b/src/i18n/tha.ts @@ -1106,6 +1106,8 @@ export default { }, preview: { + dateAt: 'วันที่{msg}', + seller: 'ผู้ขาย', taskOrder: 'ใบสั่งงาน', doc: 'ดูเอกสาร', productList: 'รายการสินค้า', @@ -1116,12 +1118,15 @@ export default { vat: 'ภาษี', value: 'มูลค่า', netValue: 'มูลค่าสุทธิ', + dueDate: 'วันครบกำหนดชำระ', + paymentMethods: 'ช่องทางชำระเงิน', title: { creditNote: 'ใบลดหนี้', quotation: 'ใบเสนอราคา', invoice: 'ใบแจ้งหนี้', payment: 'ชำระหนี้', receipt: 'ใบเสร็จรับเงิน', + debitNote: 'ใบเพิ่มหนี้', }, }, @@ -1206,4 +1211,48 @@ export default { dataSum: 'สรุปใบเสร็จรับเงิน/กำกับภาษีทั้งหมด', workSheetName: 'ชื่อใบงาน', }, + debitNote: { + title: 'ใบเพิ่มหนี้', + caption: 'ใบเพิ่มหนี้ทั้งหมด', + expire: 'พ้นกำหนด', + payment: 'ชำระเงิน', + receipt: 'ใบเสร็จรับเงิน', + succeed: 'เสร็จสิ้น', + downloadReceipt: 'ดาวน์โหลดใบเสร็จรับเงิน', + downloadTaxInvoice: 'ดาวน์โหลดใบกำกับภาษี', + + label: { + additionalDetail: 'อธิบายเพิ่มเติม', + specifyReasonForDebit: 'ระบุสาเหตุการเพิ่มหนี้', + debitNoteInformation: 'ข้อมูลการเพิ่มหนี้', + codeDebit: 'เลขที่ใบเพิ่มหนี้', + codeQuotation: 'เลขที่ใบเสนอราคา', + quotationWorkName: 'ชื่อใบงาน', + quotationPayment: 'วิธีการชำระ', + value: 'มูลค่าสุทธิ', + submit: 'อนุมัติใบเพิ่มหนี้', + }, + + stats: { + Pending: 'ใบเพิ่มหนี้', + Expire: 'พ้นกำหนด', + Payment: 'ชำระเงิน', + Receipt: 'ใบเสร็จรับเงิน', + Succeed: 'เสร็จสิ้น', + }, + + viewMode: { + payment: 'ชำระเงิน', + receipt: 'ใบเสร็จรับเงิน/ใบกำกับภาษี', + processComplete: 'เสร็จสิ้น', + }, + + status: { + Pending: 'ใบเพิ่มหนี้', + Expire: 'พ้นกำหนด', + Payment: 'ชำระเงิน', + Receipt: 'ใบเสร็จรับเงิน', + Succeed: 'เสร็จสิ้น', + }, + }, }; diff --git a/src/layouts/DrawerComponent.vue b/src/layouts/DrawerComponent.vue index bcdd4cc7..a5012b7d 100644 --- a/src/layouts/DrawerComponent.vue +++ b/src/layouts/DrawerComponent.vue @@ -159,7 +159,7 @@ onMounted(async () => { children: [ { label: 'receipt', route: '/receipt' }, { label: 'creditNote', route: '/credit-note', disabled: true }, - { label: 'debitNote', route: '', disabled: true }, + { label: 'debitNote', route: '/debit-note' }, ], }, diff --git a/src/pages/05_quotation/PaymentForm.vue b/src/pages/05_quotation/PaymentForm.vue index 4dd6cdd3..9258678c 100644 --- a/src/pages/05_quotation/PaymentForm.vue +++ b/src/pages/05_quotation/PaymentForm.vue @@ -21,12 +21,16 @@ import { dateFormatJS } from 'src/utils/datetime'; import { QFile, QMenu } from 'quasar'; import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue'; import { onMounted } from 'vue'; +import { DebitNote } from 'src/stores/debit-note'; const configStore = useConfigStore(); const quotationPayment = useQuotationPayment(); const { data: config } = storeToRefs(configStore); -const prop = defineProps<{ data?: Quotation | QuotationFull }>(); +const prop = defineProps<{ + data?: Quotation | QuotationFull | DebitNote; + isDebitNote?: boolean; +}>(); const refQFile = ref[]>([]); const refQMenu = ref[]>([]); @@ -194,7 +198,12 @@ async function triggerSubmit() { onMounted(async () => { if (!prop.data) return; - const ret = await quotationPayment.getQuotationPayment(prop.data.id); + const ret = await quotationPayment.getQuotationPayment({ + quotationId: prop.isDebitNote === true ? undefined : prop.data.id, + debitNoteId: prop.isDebitNote === true ? prop.data.id : undefined, + quotationOnly: !!prop.isDebitNote ? false : true, + debitNoteOnly: !!prop.isDebitNote ? true : false, + }); if (ret) { paymentData.value = ret.result; slipFile.value = paymentData.value.map((v) => ({ diff --git a/src/pages/05_quotation/QuotationForm.vue b/src/pages/05_quotation/QuotationForm.vue index 5a180905..6af0ee0b 100644 --- a/src/pages/05_quotation/QuotationForm.vue +++ b/src/pages/05_quotation/QuotationForm.vue @@ -443,6 +443,8 @@ async function fetchQuotation() { async function fetchReceipt() { const res = await useReceiptStore.getReceiptList({ quotationId: quotationFormData.value.id, + quotationOnly: true, + debitNoteOnly: false, }); if (res) { @@ -856,7 +858,7 @@ function convertToTable(nodes: Node[]) { } else { quotationFormData.value.paySplit = []; quotationFormData.value.paySplitCount = 0; - quotationFormData.value.payCondition = 'Full'; + quotationFormData.value.payCondition = PayCondition.Full; } tempPaySplit.value = JSON.parse( diff --git a/src/pages/05_quotation/QuotationFormInfo.vue b/src/pages/05_quotation/QuotationFormInfo.vue index d0ae7e2e..08c7266e 100644 --- a/src/pages/05_quotation/QuotationFormInfo.vue +++ b/src/pages/05_quotation/QuotationFormInfo.vue @@ -36,6 +36,7 @@ defineProps<{ }; taskOrder?: boolean; taskOrderComplete?: boolean; + debitNote?: boolean; }>(); const { t } = useI18n(); @@ -216,6 +217,7 @@ watch( 'invoice-color': view === View.Invoice, 'receipt-color': view === View.Receipt, 'task-order-color': taskOrder && !taskOrderComplete, + 'debit-note-color': debitNote, }" >
@@ -530,6 +532,10 @@ watch( --_color: var(--pink-7-hsl); } +.debit-note-color { + --_color: var(--cyan-7-hsl); +} + .bg-color { color: white; background: hsla(var(--_color)); diff --git a/src/pages/05_quotation/QuotationFormReceipt.vue b/src/pages/05_quotation/QuotationFormReceipt.vue index 093661f6..e02d847e 100644 --- a/src/pages/05_quotation/QuotationFormReceipt.vue +++ b/src/pages/05_quotation/QuotationFormReceipt.vue @@ -1,6 +1,7 @@ + + + + diff --git a/src/pages/12_debit-note/MainPage.vue b/src/pages/12_debit-note/MainPage.vue new file mode 100644 index 00000000..2143ba59 --- /dev/null +++ b/src/pages/12_debit-note/MainPage.vue @@ -0,0 +1,499 @@ + + + diff --git a/src/pages/12_debit-note/TableDebitNote.vue b/src/pages/12_debit-note/TableDebitNote.vue new file mode 100644 index 00000000..77d26d18 --- /dev/null +++ b/src/pages/12_debit-note/TableDebitNote.vue @@ -0,0 +1,95 @@ + + diff --git a/src/pages/12_debit-note/constants.ts b/src/pages/12_debit-note/constants.ts new file mode 100644 index 00000000..af2560cd --- /dev/null +++ b/src/pages/12_debit-note/constants.ts @@ -0,0 +1,90 @@ +import { QTableProps } from 'quasar'; +import { DebitNoteStatus, DebitNote } from 'src/stores/debit-note'; +import { formatNumberDecimal } from 'src/stores/utils'; + +export const taskStatusOpts = [ + { + status: DebitNoteStatus.Expire, + name: `debitNote.status.${DebitNoteStatus.Expire}`, + }, + { + status: DebitNoteStatus.Payment, + name: `debitNote.status.${DebitNoteStatus.Payment}`, + }, + { + status: DebitNoteStatus.Receipt, + name: `debitNote.status.${DebitNoteStatus.Receipt}`, + }, + { + status: DebitNoteStatus.Succeed, + name: `debitNote.status.${DebitNoteStatus.Succeed}`, + }, +]; + +export const pageTabs = [ + { label: 'Pending', value: DebitNoteStatus.Pending }, + { label: 'Expire', value: DebitNoteStatus.Expire }, + { label: 'Payment', value: DebitNoteStatus.Payment }, + { label: 'Receipt', value: DebitNoteStatus.Receipt }, + { label: 'Succeed', value: DebitNoteStatus.Succeed }, +]; + +export enum Status { + taskOrder = 'taskOrder', + receiveTaskOrder = 'receiveTaskOrder', + sendTaskOrder = 'sendTaskOrder', + payment = 'payment', + goodReceipt = 'goodReceipt', +} + +export const columns = [ + { + name: 'order', + align: 'center', + label: 'general.order', + field: ( + data: DebitNote & { _index: number; _page: number; _pageSize: number }, + ) => (data._page - 1) * data._pageSize + data._index + 1, + }, + { + name: 'code', + align: 'center', + label: 'debitNote.label.codeDebit', + field: (data: DebitNote) => data.code, + }, + { + name: 'quotationCode', + align: 'center', + label: 'debitNote.label.codeQuotation', + field: (data: DebitNote) => data.debitNoteQuotation?.code, + }, + { + name: 'quotationWorkName', + align: 'center', + label: 'debitNote.label.quotationWorkName', + field: (data: DebitNote) => data.workName, + }, + { + name: 'quotationPayment', + align: 'center', + label: 'debitNote.label.quotationPayment', + field: (data: DebitNote) => data.debitNoteQuotation?.payCondition, + }, + { + name: 'creditNoteValue', + align: 'center', + label: 'debitNote.label.value', + field: (data: DebitNote) => formatNumberDecimal(data.totalPrice), + }, + { + name: '#action', + align: 'center', + label: '', + field: (_) => '#action', + }, +] as const satisfies QTableProps['columns']; + +export const hslaColors: Record = { + Pending: '--blue-6-hsl', + Success: '--red-6-hsl', +}; diff --git a/src/pages/12_debit-note/document-view/BankComponents.vue b/src/pages/12_debit-note/document-view/BankComponents.vue new file mode 100644 index 00000000..3f6396c4 --- /dev/null +++ b/src/pages/12_debit-note/document-view/BankComponents.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/MainPage.vue b/src/pages/12_debit-note/document-view/MainPage.vue new file mode 100644 index 00000000..0e4432f4 --- /dev/null +++ b/src/pages/12_debit-note/document-view/MainPage.vue @@ -0,0 +1,654 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/ViewFooter.vue b/src/pages/12_debit-note/document-view/ViewFooter.vue new file mode 100644 index 00000000..fc9a34a5 --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewFooter.vue @@ -0,0 +1,98 @@ + + + + diff --git a/src/pages/12_debit-note/document-view/ViewHeader.vue b/src/pages/12_debit-note/document-view/ViewHeader.vue new file mode 100644 index 00000000..e9c4624d --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewHeader.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/pages/12_debit-note/document-view/ViewPdf.vue b/src/pages/12_debit-note/document-view/ViewPdf.vue new file mode 100644 index 00000000..f800e790 --- /dev/null +++ b/src/pages/12_debit-note/document-view/ViewPdf.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue b/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue new file mode 100644 index 00000000..a7649753 --- /dev/null +++ b/src/pages/12_debit-note/expansion/DebitNoteExpansion.vue @@ -0,0 +1,51 @@ + + + diff --git a/src/pages/12_debit-note/expansion/DocumentExpansion.vue b/src/pages/12_debit-note/expansion/DocumentExpansion.vue new file mode 100644 index 00000000..0d38ac7b --- /dev/null +++ b/src/pages/12_debit-note/expansion/DocumentExpansion.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/pages/12_debit-note/expansion/PaymentExpansion.vue b/src/pages/12_debit-note/expansion/PaymentExpansion.vue new file mode 100644 index 00000000..e1b5a7c5 --- /dev/null +++ b/src/pages/12_debit-note/expansion/PaymentExpansion.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/pages/12_debit-note/expansion/ProductExpansion.vue b/src/pages/12_debit-note/expansion/ProductExpansion.vue new file mode 100644 index 00000000..dca92aa2 --- /dev/null +++ b/src/pages/12_debit-note/expansion/ProductExpansion.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/pages/12_debit-note/expansion/RemarkExpansion.vue b/src/pages/12_debit-note/expansion/RemarkExpansion.vue new file mode 100644 index 00000000..d33fcf73 --- /dev/null +++ b/src/pages/12_debit-note/expansion/RemarkExpansion.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue new file mode 100644 index 00000000..aa4ee499 --- /dev/null +++ b/src/pages/12_debit-note/expansion/WorkerItemExpansion.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/pages/12_debit-note/form.ts b/src/pages/12_debit-note/form.ts new file mode 100644 index 00000000..9f92c0ff --- /dev/null +++ b/src/pages/12_debit-note/form.ts @@ -0,0 +1,64 @@ +import { dialog } from 'stores/utils'; +import { defineStore } from 'pinia'; +import { useI18n } from 'vue-i18n'; +import { ref } from 'vue'; +import { DebitNotePayload } from 'src/stores/debit-note'; +import { PayCondition } from 'src/stores/quotations'; + +// NOTE: Import types + +// NOTE: Import stores + +const DEFAULT_DATA: DebitNotePayload = { + productServiceList: [], + debitNoteQuotationId: '', + worker: [], + payBillDate: new Date(), + paySplit: [], + paySplitCount: 0, + payCondition: PayCondition.Full, + dueDate: new Date(), + discount: 0, + status: 'CREATED', + remark: '#[quotation-labor]

#[quotation-payment]', + quotationId: '', + agentPrice: false, +}; + +export const useDebitNoteForm = defineStore('form-debit-note', () => { + const { t } = useI18n(); + + let resetFormData = structuredClone(DEFAULT_DATA); + + const currentFormData = ref(structuredClone(resetFormData)); + + const currentFormState = ref<{ + mode: null | 'info' | 'create' | 'edit'; + }>({ + mode: null, + }); + + function isFormDataDifferent() { + const { ...resetData } = resetFormData; + const { ...currData } = currentFormData.value; + + return JSON.stringify(resetData) !== JSON.stringify(currData); + } + + function resetForm(clean = false) { + if (clean) { + currentFormData.value = structuredClone(DEFAULT_DATA); + resetFormData = structuredClone(DEFAULT_DATA); + return; + } + + currentFormData.value = structuredClone(resetFormData); + + currentFormState.value.mode = 'info'; + } + + return { + isFormDataDifferent, + resetForm, + }; +}); diff --git a/src/router/routes.ts b/src/router/routes.ts index dee71a15..785f6601 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -120,6 +120,11 @@ const routes: RouteRecordRaw[] = [ name: 'receipt', component: () => import('pages/13_receipt/MainPage.vue'), }, + { + path: '/debit-note', + name: 'debitNote', + component: () => import('pages/12_debit-note/MainPage.vue'), + }, ], }, @@ -183,6 +188,22 @@ const routes: RouteRecordRaw[] = [ name: 'receiptform', component: () => import('pages/13_receipt/MainPage.vue'), }, + { + path: '/debit-note/add', + name: 'DebitNoteNew', + component: () => import('pages/12_debit-note/FormPage.vue'), + }, + { + path: '/debit-note/:id', + name: 'DebitNoteView', + component: () => import('pages/12_debit-note/FormPage.vue'), + }, + + { + path: '/debit-note/document-view', + name: 'DebitNoteDocumentView', + component: () => import('pages/12_debit-note/document-view/MainPage.vue'), + }, // Always leave this as last one, // but you can also remove it diff --git a/src/stores/debit-note/index.ts b/src/stores/debit-note/index.ts new file mode 100644 index 00000000..7ef3b8b3 --- /dev/null +++ b/src/stores/debit-note/index.ts @@ -0,0 +1,94 @@ +import { + DebitNote as Data, + DebitNoteStatus as Status, + DebitNotePayload as Payload, +} from './types.ts'; +import { ref } from 'vue'; +import { defineStore } from 'pinia'; + +import { api } from 'src/boot/axios.ts'; +import { PaginationResult } from 'src/types.ts'; +import { manageAttachment, manageFile } from '../utils/index.ts'; + +const ENDPOINT = 'debit-note'; + +export * from './types.ts'; + +export async function getDebitNoteStats() { + const res = await api.get>(`/${ENDPOINT}/stats`); + if (res.status < 400) { + return res.data; + } + return null; +} + +export async function getDebitNoteList(params?: { + page?: number; + pageSize?: number; + query?: string; + deebitNoteStatus?: Status; + includeRegisteredBranch?: boolean; +}) { + const res = await api.get>(`/${ENDPOINT}`, { + params, + }); + if (res.status < 400) return res.data; + return null; +} + +export async function getDebitNote(id: string) { + const res = await api.get(`/${ENDPOINT}/${id}`); + if (res.status < 400) return res.data; + return null; +} + +export async function createDebitNote(body: Payload) { + const res = await api.post(`/${ENDPOINT}`, body); + if (res.status < 400) return res.data; + return null; +} + +export async function updateDebitNote(body: Payload) { + const { id, quotationId, ...payload } = body; + const res = await api.put(`/${ENDPOINT}/${id}`, payload); + if (res.status < 400) return res.data; + return null; +} + +export async function deleteDebitNote(id: string) { + const res = await api.delete(`/${ENDPOINT}/${id}`); + if (res.status < 400) return res.data; + return null; +} + +export const useDebitNote = defineStore('debit-note-store', () => { + const data = ref([]); + const page = ref(1); + const pageMax = ref(1); + const pageSize = ref(30); + const stats = ref>({ + [Status.Pending]: 0, + [Status.Expire]: 0, + [Status.Payment]: 0, + [Status.Receipt]: 0, + [Status.Succeed]: 0, + }); + + return { + data, + page, + pageMax, + pageSize, + stats, + + getDebitNoteStats, + getDebitNote, + getDebitNoteList, + createDebitNote, + updateDebitNote, + deleteDebitNote, + + ...manageAttachment(api, ENDPOINT), + ...manageFile<'slip'>(api, ENDPOINT), + }; +}); diff --git a/src/stores/debit-note/types.ts b/src/stores/debit-note/types.ts new file mode 100644 index 00000000..00f6b468 --- /dev/null +++ b/src/stores/debit-note/types.ts @@ -0,0 +1,94 @@ +import { + Quotation, + QuotationFull, + QuotationStatus, + QuotationPayload, + PayCondition, +} from '../quotations'; +import { RequestWork } from '../request-list'; +import { CreatedBy, UpdatedBy } from '../types'; +import { CustomerBranch } from '../customer'; +import { Branch } from '../branch/types'; + +export type DebitNotePayload = Omit< + QuotationPayload & { + id?: string; + quotationId: string; + debitNoteQuotationId?: string; + reason?: string; + detail?: string; + agentPrice: boolean; + }, + | 'customerBranchId' + | 'registeredBranchId' + | 'contactName' + | 'contactTel' + | '_count' + | 'urgent' + | 'workName' + | 'workerMax' + | 'productServiceList' + | 'paySplit' +> & { + productServiceList: { + installmentNo: number; + workerIndex: number[]; + discount: number; + amount: number; + productId: string; + workId: string; + serviceId: string; + }[]; +}; + +export type DebitNote = { + debitNoteQuotation?: QuotationFull; + updatedBy: UpdatedBy; + createdBy: CreatedBy; + productServiceList: QuotationFull['productServiceList']; + worker: QuotationFull['worker']; + paySplit: QuotationFull['paySplit']; + registeredBranch: Branch; + customerBranch: CustomerBranch; + _count: { worker: number }; + + updatedByUserId: string; + updatedAt: string; + createdByUserId: string; + createdAt: string; + debitNoteQuotationId: string; + isDebitNote: boolean; + finalPrice: number; + discount: number; + vatExcluded: number; + vat: number; + totalDiscount: number; + totalPrice: number; + agentPrice: boolean; + urgent: boolean; + workerMax: number; + payBillDate: string; + paySplitCount: number; + payCondition: PayCondition; + date: string; + dueDate: string; + contactTel: string; + contactName: string; + workName: string; + code: string; + remark: string; + quotationStatus: QuotationStatus; + statusOrder: number; + status: string; + customerBranchId: string; + registeredBranchId: string; + id: string; +}; + +export enum DebitNoteStatus { + Pending = 'Pending', + Expire = 'Expire', + Payment = 'Payment', + Receipt = 'Receipt', + Succeed = 'Succeed', +} diff --git a/src/stores/payment/index.ts b/src/stores/payment/index.ts index e85de252..93c210fc 100644 --- a/src/stores/payment/index.ts +++ b/src/stores/payment/index.ts @@ -98,6 +98,9 @@ export const useReceipt = defineStore('receipt-store', () => { pageSize?: number; query?: string; quotationId?: string; + debitNoteId?: string; + debitNoteOnly?: boolean; + quotationOnly?: boolean; }) { const res = await api.get>('/receipt', { params: opts, diff --git a/src/stores/quotations/index.ts b/src/stores/quotations/index.ts index e985f1ce..775dc7ac 100644 --- a/src/stores/quotations/index.ts +++ b/src/stores/quotations/index.ts @@ -192,10 +192,15 @@ export const useQuotationStore = defineStore('quotation-store', () => { * @deprecated Please use payment store instead. */ export const useQuotationPayment = defineStore('quotation-payment', () => { - async function getQuotationPayment(quotationId: string) { + async function getQuotationPayment(params: { + quotationId?: string; + debitNoteId?: string; + debitNoteOnly?: boolean; + quotationOnly?: boolean; + }) { const res = await api.get>( '/payment', - { params: { quotationId } }, + { params }, ); if (res.status < 400) { return res.data; diff --git a/tests/institution.spec.ts b/tests/institution.spec.ts new file mode 100644 index 00000000..906d571c --- /dev/null +++ b/tests/institution.spec.ts @@ -0,0 +1,44 @@ +import { fakerEN, fakerTH } from '@faker-js/faker'; +import test, { expect, Page } from '@playwright/test'; +import { login } from './utils'; + +test.describe.configure({ mode: 'serial' }); + +let page: Page; + +test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); +}); + +test.afterAll(async () => { + await page.close(); +}); + +test('JWS_INST_001 - Login', async () => { + await login(page); +}); + +test('JWS_INST_002 - Goto Institution', async () => { + await page.click('//span[text()="หน่วยงาน"]'); + await expect(page).toHaveURL(/.*agencies-management/); + await page.waitForTimeout(5000); +}); + +test('JWS_INST_003 - Click New Institution', async () => { + await page.click('i.q-icon.mdi.mdi-plus'); + await expect(page.locator('div.col.text-subtitle1')).toContainText( + 'เพิ่มหน่วยงาน', + ); + await page.waitForTimeout(5000); +}); + +test('JWS_INST_004 - Fill Form', async () => { + await page + .getByRole('textbox', { name: 'ชื่อหน่วยงาน' }) + .fill(fakerTH.company.name()); + await page + .getByRole('textbox', { name: 'Agencies Name' }) + .fill(fakerEN.company.name()); + fakerEN.location.buildingNumber(); + await page.waitForTimeout(5000); +}); diff --git a/tests/utils/index.ts b/tests/utils/index.ts new file mode 100644 index 00000000..3277a9aa --- /dev/null +++ b/tests/utils/index.ts @@ -0,0 +1,19 @@ +import { expect, Page } from '@playwright/test'; + +let isLoggedIn = false; + +export async function login(page: Page) { + if (!process.env.TEST_APP_URL) throw new Error('Expect TEST_APP_URL env.'); + + if (isLoggedIn) return; + + await page.goto('/'); + await expect(page).toHaveTitle(/^Sign in to /); + await page.fill("input[name='username']", 'admin'); + await page.fill("input[name='password']", '1234'); + await page.click('id=kc-login'); + await page.waitForTimeout(2000); + await expect(page).toHaveURL(new RegExp('^' + process.env.TEST_APP_URL)); + + isLoggedIn = true; +}