feat: payment view

This commit is contained in:
Methapon Metanipat 2024-10-28 16:44:14 +07:00
parent 639dc36d71
commit d9f8d0f971
4 changed files with 53 additions and 76 deletions

View file

@ -3,27 +3,25 @@ import { baseUrl } from 'stores/utils';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useConfigStore } from 'stores/config'; import { useConfigStore } from 'stores/config';
import { formatNumberDecimal } from 'stores/utils'; import { formatNumberDecimal } from 'stores/utils';
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 { useQuotationPayment } from 'src/stores/quotations';
import { import {
PaymentPayload, PaymentPayload,
Quotation, Quotation,
QuotationFull,
QuotationPaymentData, QuotationPaymentData,
} from 'src/stores/quotations/types'; } from 'src/stores/quotations/types';
import { dateFormat } from 'src/utils/datetime'; import { dateFormat } from 'src/utils/datetime';
import { QFile, QMenu } from 'quasar'; import { QFile, QMenu } from 'quasar';
import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue'; import UploadFileCard from 'src/components/upload-file/UploadFileCard.vue';
import { onMounted } from 'vue';
const configStore = useConfigStore(); const configStore = useConfigStore();
const quotationPayment = useQuotationPayment(); const quotationPayment = useQuotationPayment();
const { data: config } = storeToRefs(configStore); const { data: config } = storeToRefs(configStore);
const model = defineModel<boolean>({ default: false, required: true }); const prop = defineProps<{ data?: Quotation | QuotationFull }>();
const data = defineModel<Quotation | undefined>('data', { required: true });
const fileManager =
ref<ReturnType<typeof quotationPayment.createPaymentFileManager>>();
const refQFile = ref<InstanceType<typeof QFile>[]>([]); const refQFile = ref<InstanceType<typeof QFile>[]>([]);
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]); const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
const paymentData = ref<QuotationPaymentData[]>([]); const paymentData = ref<QuotationPaymentData[]>([]);
@ -51,7 +49,6 @@ const paymentStatusOpts = [
]; ];
const slipFile = ref< const slipFile = ref<
{ {
quotationId: string;
paymentId: string; paymentId: string;
data: { name: string; progress: number; loaded: number; total: number }[]; data: { name: string; progress: number; loaded: number; total: number }[];
file?: File; file?: File;
@ -74,8 +71,7 @@ function pickFile(index: number) {
} }
async function getSlipList(payment: QuotationPaymentData, index: number) { async function getSlipList(payment: QuotationPaymentData, index: number) {
if (!fileManager.value) return; const slipList = await quotationPayment.listAttachment({
const slipList = await fileManager.value.listAttachment({
parentId: payment.id, parentId: payment.id,
}); });
@ -106,19 +102,18 @@ async function triggerUpload(
index: number, index: number,
file?: File, file?: File,
) { ) {
if (!data.value || !fileManager.value || !file) return; if (!file) return;
slipFile.value[index].data.push({ slipFile.value[index].data.push({
name: file.name, name: file.name,
progress: 0, progress: 0,
loaded: 0, loaded: 0,
total: 0, total: 0,
}); });
const ret = await fileManager.value.putAttachment({ const ret = await quotationPayment.putAttachment({
parentId: payment.id, parentId: payment.id,
name: file.name, name: file.name,
file: file, file: file,
onUploadProgress: (e) => { onUploadProgress: (e) => {
console.log(e);
slipFile.value[index].data[slipFile.value[index].data.length - 1] = { slipFile.value[index].data[slipFile.value[index].data.length - 1] = {
name: file.name, name: file.name,
progress: e.progress || 0, progress: e.progress || 0,
@ -136,14 +131,12 @@ async function triggerDelete(
name: string, name: string,
index: number, index: number,
) { ) {
if (!data.value || !fileManager.value) return; await quotationPayment.delAttachment({ parentId: payment.id, name });
await fileManager.value.delAttachment({ parentId: payment.id, name });
await getSlipList(payment, index); await getSlipList(payment, index);
} }
function triggerViewSlip(payment: QuotationPaymentData, name: string) { function triggerViewSlip(payment: QuotationPaymentData, name: string) {
if (!data.value || !fileManager.value) return; const url = `${baseUrl}/payment/${payment.id}/attachment/${name}`;
const url = `${baseUrl}/quotation/${data.value.id}/payment/${payment.id}/attachment/${name}`;
window.open(url, '_blank'); window.open(url, '_blank');
} }
@ -168,60 +161,36 @@ function selectStatus(
async function triggerSubmit() { async function triggerSubmit() {
submitPaymentData.value.forEach(async (p) => { submitPaymentData.value.forEach(async (p) => {
if (!data.value) return;
const payload: PaymentPayload = { const payload: PaymentPayload = {
paymentStatus: p.paymentStatus, paymentStatus: p.paymentStatus,
remark: p.remark || '',
date: new Date(p.date), date: new Date(p.date),
amount: p.amount, amount: p.amount,
}; };
await quotationPayment.updateQuotationPayment(data.value.id, p.id, payload); await quotationPayment.updateQuotationPayment(p.id, payload);
}); });
model.value = false;
} }
watch( onMounted(async () => {
() => model.value, if (!prop.data) return;
async (open) => { const ret = await quotationPayment.getQuotationPayment(prop.data.id);
if (!data.value) return; if (ret) {
if (!open) { paymentData.value = ret.result;
paymentData.value = []; slipFile.value = paymentData.value.map((v) => ({
state.payExpansion = []; paymentId: v.id,
slipFile.value = []; data: [],
submitPaymentData.value = []; }));
} else { }
fileManager.value = quotationPayment.createPaymentFileManager( });
data.value.id,
);
const ret = await quotationPayment.getQuotationPayment(data.value.id);
if (ret) {
paymentData.value = ret.quotationPaymentData;
slipFile.value = paymentData.value.map((v) => ({
quotationId: ret.id,
paymentId: v.id,
data: [],
}));
}
}
},
);
</script> </script>
<template> <template>
<DialogForm <q-form
:title="$t('quotation.receipt')" greedy
v-model:modal="model" @submit.prevent
width="65%" @validation-success="triggerSubmit"
:submit="() => triggerSubmit()" class="column full-height"
v-if="data"
> >
<div <div class="col column no-wrap">
v-if="data"
class="col column no-wrap"
:class="{
'q-mx-lg q-my-md': $q.screen.gt.sm,
'q-mx-md q-my-sm': !$q.screen.gt.sm,
}"
>
<!-- PRICE DETAIL --> <!-- 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
@ -662,7 +631,7 @@ watch(
</section> </section>
</section> </section>
</div> </div>
</DialogForm> </q-form>
</template> </template>
<style scoped> <style scoped>
.bg-color-orange { .bg-color-orange {

View file

@ -67,6 +67,7 @@ import { precisionRound } from 'src/utils/arithmetic';
import { useConfigStore } from 'src/stores/config'; import { useConfigStore } from 'src/stores/config';
import QuotationFormMetadata from './QuotationFormMetadata.vue'; import QuotationFormMetadata from './QuotationFormMetadata.vue';
import BadgeComponent from 'src/components/BadgeComponent.vue'; import BadgeComponent from 'src/components/BadgeComponent.vue';
import PaymentForm from './PaymentForm.vue';
// defineProps<{ // defineProps<{
// readonly?: boolean; // readonly?: boolean;
@ -905,7 +906,15 @@ const view = ref<View>(View.Quotation);
/> />
</div> </div>
</div> </div>
<template v-if="true"> <template
v-if="
view === View.Quotation ||
view === View.Accepted ||
view === View.Invoice ||
view === View.Payment ||
view === View.Receipt
"
>
<q-expansion-item <q-expansion-item
for="item-up" for="item-up"
id="item-up" id="item-up"
@ -1204,6 +1213,9 @@ const view = ref<View>(View.Quotation);
</div> </div>
</q-expansion-item> </q-expansion-item>
</template> </template>
<template v-else>
<PaymentForm :data="quotationFormState.source" />
</template>
</div> </div>
</section> </section>
</article> </article>

View file

@ -13,7 +13,6 @@ import {
QuotationStatus, QuotationStatus,
} from './types'; } from './types';
import { PaginationResult } from 'src/types'; import { PaginationResult } from 'src/types';
import { AxiosProgressEvent } from 'axios';
import { manageAttachment } from '../utils'; import { manageAttachment } from '../utils';
@ -140,9 +139,9 @@ export const useQuotationStore = defineStore('quotation-store', () => {
export const useQuotationPayment = defineStore('quotation-payment', () => { export const useQuotationPayment = defineStore('quotation-payment', () => {
async function getQuotationPayment(quotationId: string) { async function getQuotationPayment(quotationId: string) {
const res = await api.get< const res = await api.get<PaginationResult<QuotationPaymentData>>(
Quotation & { quotationPaymentData: QuotationPaymentData[] } `/payment/${quotationId}`,
>(`/quotation/${quotationId}/payment`); );
if (res.status < 400) { if (res.status < 400) {
return res.data; return res.data;
} }
@ -150,12 +149,11 @@ export const useQuotationPayment = defineStore('quotation-payment', () => {
} }
async function updateQuotationPayment( async function updateQuotationPayment(
quotationId: string,
paymentId: string, paymentId: string,
payload: PaymentPayload, payload: PaymentPayload,
) { ) {
const res = await api.put<PaymentPayload & { id: string }>( const res = await api.put<PaymentPayload & { id: string }>(
`/quotation/${quotationId}/payment/${paymentId}`, `/payment/${paymentId}`,
payload, payload,
); );
if (res.status < 400) { if (res.status < 400) {
@ -164,16 +162,12 @@ export const useQuotationPayment = defineStore('quotation-payment', () => {
return null; return null;
} }
function createPaymentFileManager( const fileManager = manageAttachment(api, `payment`);
quotationId: string,
opts?: { onUploadProgress: (e: AxiosProgressEvent) => void },
) {
return manageAttachment(api, `quotation/${quotationId}/payment`, opts);
}
return { return {
getQuotationPayment, getQuotationPayment,
createPaymentFileManager,
updateQuotationPayment, updateQuotationPayment,
...fileManager,
}; };
}); });

View file

@ -270,6 +270,8 @@ export type QuotationFull = {
id: string; id: string;
finalPrice: number; finalPrice: number;
vat: number; vat: number;
vatExcluded: number;
discount: number;
totalDiscount: number; totalDiscount: number;
totalPrice: number; totalPrice: number;
urgent: boolean; urgent: boolean;
@ -283,6 +285,7 @@ export type QuotationFull = {
contactTel: string; contactTel: string;
contactName: string; contactName: string;
workName: string; workName: string;
workerMax: number | null;
code: string; code: string;
statusOrder: number; statusOrder: number;
status: Status; status: Status;
@ -297,6 +300,7 @@ export type QuotationFull = {
| 'Canceled'; | 'Canceled';
customerBranchId: string; customerBranchId: string;
customerBranch: CustomerBranchRelation;
registeredBranchId: string; registeredBranchId: string;
registeredBranch: { id: string; name: string }; registeredBranch: { id: string; name: string };
@ -372,7 +376,6 @@ export type ProductGroup = {
}; };
export type QuotationPaymentData = { export type QuotationPaymentData = {
quotationId: string;
paymentStatus: string; paymentStatus: string;
amount: number; amount: number;
remark: string; remark: string;
@ -393,7 +396,6 @@ export type Details = {
export type PaymentPayload = { export type PaymentPayload = {
paymentStatus: string; paymentStatus: string;
remark: string;
date: Date; date: Date;
amount: number; amount: number;
}; };