feat: quotation payment method
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 5s
This commit is contained in:
parent
c749e91ffa
commit
a7392e44b4
5 changed files with 594 additions and 467 deletions
|
|
@ -54,10 +54,11 @@ onMounted(() => {
|
|||
@click="$emit('click')"
|
||||
>
|
||||
<q-icon :name="icon" size="lg" :style="`color: ${color}`" />
|
||||
<article class="col column q-pl-md">
|
||||
<span class="ellipsis full-width">
|
||||
<div class="col column q-pl-md">
|
||||
<div class="ellipsis full-width" style="max-width: 65vw !important">
|
||||
{{ name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="text-caption app-text-muted-2">
|
||||
{{
|
||||
uploading.loaded
|
||||
|
|
@ -79,7 +80,7 @@ onMounted(() => {
|
|||
/>
|
||||
{{ idle ? `Pending` : progress !== 1 ? `Uploading...` : 'Completed' }}
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
<q-btn
|
||||
v-if="closeable"
|
||||
icon="mdi-close"
|
||||
|
|
|
|||
|
|
@ -777,6 +777,8 @@ export default {
|
|||
seller: 'Seller',
|
||||
paymentChannels: 'Payment Channels',
|
||||
channelsThat: 'Channels That',
|
||||
refNo: 'Reference Number',
|
||||
bankAccount: 'Bank Account',
|
||||
bankAccountNumber: 'Bank Account Number',
|
||||
bankAccountName: 'Bank Account Name',
|
||||
inTheNameOf: 'In The Name Of',
|
||||
|
|
|
|||
|
|
@ -775,6 +775,8 @@ export default {
|
|||
seller: 'ผู้ขาย',
|
||||
paymentChannels: 'ช่องทางชำระเงิน',
|
||||
channelsThat: 'ช่องทางที่',
|
||||
refNo: 'เลขที่อ้างอิง',
|
||||
bankAccount: 'บัญชีธนาคาร',
|
||||
bankAccountNumber: 'เลขบัญชีธนาคาร',
|
||||
bankAccountName: 'ชื่อบัญชี',
|
||||
inTheNameOf: 'ในนาม',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { baseUrl, dialog } from 'stores/utils';
|
|||
import { storeToRefs } from 'pinia';
|
||||
import { useConfigStore } from 'stores/config';
|
||||
import { formatNumberDecimal } from 'stores/utils';
|
||||
import { MainButton } from 'components/button';
|
||||
import { SaveButton, EditButton, UndoButton } from 'components/button';
|
||||
import SelectInput from 'src/components/shared/SelectInput.vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
|
@ -38,8 +39,15 @@ const firstCodePayment = defineModel<string>('firstCodePayment');
|
|||
const refQFile = ref<InstanceType<typeof QFile>[]>([]);
|
||||
const refQMenu = ref<InstanceType<typeof QMenu>[]>([]);
|
||||
const paymentData = ref<QuotationPaymentData[]>([]);
|
||||
const submitPaymentData = ref<QuotationPaymentData[]>([]);
|
||||
const payAll = ref<boolean>(false);
|
||||
const formPaymentMethod = ref<
|
||||
{
|
||||
id: string;
|
||||
channel: string | null;
|
||||
reference: string | null;
|
||||
account: string | null;
|
||||
isEdit?: boolean;
|
||||
}[]
|
||||
>([]);
|
||||
const paymentStatusOpts = [
|
||||
{
|
||||
status: 'PaymentInProcess',
|
||||
|
|
@ -188,18 +196,29 @@ async function selectStatus(
|
|||
refQMenu.value[index].hide();
|
||||
}
|
||||
|
||||
async function triggerSubmit() {
|
||||
submitPaymentData.value.forEach(async (p) => {
|
||||
async function triggerSubmit(id: string) {
|
||||
const index = paymentData.value.findIndex((p) => p.id === id);
|
||||
if (index === -1) return;
|
||||
|
||||
const p = paymentData.value[index];
|
||||
|
||||
const payload: PaymentPayload = {
|
||||
paymentStatus: p.paymentStatus,
|
||||
date: new Date(p.date),
|
||||
amount: p.amount,
|
||||
account: formPaymentMethod.value[index].account,
|
||||
channel: formPaymentMethod.value[index].channel,
|
||||
reference: formPaymentMethod.value[index].reference,
|
||||
};
|
||||
|
||||
await quotationPayment.updateQuotationPayment(p.id, payload);
|
||||
});
|
||||
formPaymentMethod.value[index].isEdit = false;
|
||||
setTimeout(async () => {
|
||||
await fetchData();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
async function fetchData() {
|
||||
if (!prop.data) return;
|
||||
const ret = await quotationPayment.getQuotationPayment({
|
||||
quotationId: prop.isDebitNote === true ? undefined : prop.data.id,
|
||||
|
|
@ -209,6 +228,13 @@ onMounted(async () => {
|
|||
});
|
||||
if (ret) {
|
||||
paymentData.value = ret.result;
|
||||
formPaymentMethod.value = ret.result.map((p, i) => ({
|
||||
id: p.id,
|
||||
channel: p.channel,
|
||||
reference: p.reference,
|
||||
account: p.account,
|
||||
isEdit: formPaymentMethod.value[i]?.isEdit || false,
|
||||
}));
|
||||
slipFile.value = paymentData.value.map((v, i) => {
|
||||
if (i === 0) {
|
||||
firstCodePayment.value = v.code;
|
||||
|
|
@ -219,17 +245,14 @@ onMounted(async () => {
|
|||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchData();
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<q-form
|
||||
greedy
|
||||
@submit.prevent
|
||||
@validation-success="triggerSubmit"
|
||||
class="column full-height"
|
||||
v-if="data"
|
||||
>
|
||||
<div class="col column no-wrap">
|
||||
<div class="column no-wrap">
|
||||
<!-- PRICE DETAIL -->
|
||||
<section class="bordered rounded surface-1" style="overflow: hidden">
|
||||
<q-expansion-item
|
||||
|
|
@ -293,10 +316,8 @@ onMounted(async () => {
|
|||
<span class="q-ml-auto" style="color: var(--foreground)">
|
||||
฿
|
||||
{{
|
||||
formatNumberDecimal(
|
||||
data.totalPrice - data.totalDiscount,
|
||||
2,
|
||||
) || '0.00'
|
||||
formatNumberDecimal(data.totalPrice - data.totalDiscount, 2) ||
|
||||
'0.00'
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
|
|
@ -487,7 +508,7 @@ onMounted(async () => {
|
|||
<!-- payment item -->
|
||||
<section class="row">
|
||||
<div
|
||||
v-for="(p, i) in paymentData"
|
||||
v-for="(payment, i) in paymentData"
|
||||
:key="i"
|
||||
class="bordered rounded surface-1 q-mb-md col-12"
|
||||
style="overflow: hidden"
|
||||
|
|
@ -496,7 +517,7 @@ onMounted(async () => {
|
|||
hide-expand-icon
|
||||
v-model="state.payExpansion[i]"
|
||||
header-style="padding:0px"
|
||||
@before-show="getSlipList(p, i)"
|
||||
@before-show="getSlipList(payment, i)"
|
||||
>
|
||||
<template v-slot:header>
|
||||
<div class="column full-width">
|
||||
|
|
@ -505,7 +526,7 @@ onMounted(async () => {
|
|||
>
|
||||
<span class="text-weight-medium column">
|
||||
{{ $t('quotation.periodNo') }} {{ i + 1 }}
|
||||
{{ monthDisplay(p.createdAt) }}
|
||||
{{ monthDisplay(payment.createdAt) }}
|
||||
<!-- {{ monthDisplay(p.date) }} -->
|
||||
<!-- <span -->
|
||||
<!-- class="text-caption app-text-muted-2" -->
|
||||
|
|
@ -529,25 +550,29 @@ onMounted(async () => {
|
|||
padding="4px 8px"
|
||||
class="rounded text-capitalize text-weight-regular payment-wait row items-center"
|
||||
:class="{
|
||||
'payment-pending': p.paymentStatus === 'PaymentWait',
|
||||
'payment-pending':
|
||||
payment.paymentStatus === 'PaymentWait',
|
||||
'payment-process':
|
||||
p.paymentStatus === 'PaymentInProcess',
|
||||
'payment-retry': p.paymentStatus === 'PaymentRetry',
|
||||
payment.paymentStatus === 'PaymentInProcess',
|
||||
'payment-retry':
|
||||
payment.paymentStatus === 'PaymentRetry',
|
||||
'payment-success':
|
||||
p.paymentStatus === 'PaymentSuccess',
|
||||
payment.paymentStatus === 'PaymentSuccess',
|
||||
}"
|
||||
>
|
||||
<q-icon
|
||||
size="xs"
|
||||
:name="
|
||||
paymentStatusOpts.find(
|
||||
(s) => s.status === p.paymentStatus,
|
||||
(s) => s.status === payment.paymentStatus,
|
||||
)?.icon || 'mdi-hand-coin-outline'
|
||||
"
|
||||
class="q-pr-sm"
|
||||
/>
|
||||
<span>
|
||||
{{ $t(`quotation.receiptDialog.${p.paymentStatus}`) }}
|
||||
{{
|
||||
$t(`quotation.receiptDialog.${payment.paymentStatus}`)
|
||||
}}
|
||||
</span>
|
||||
<q-icon
|
||||
v-if="!readonly"
|
||||
|
|
@ -568,16 +593,16 @@ onMounted(async () => {
|
|||
<q-item
|
||||
:id="`btn-payment-${opts.status}`"
|
||||
v-if="
|
||||
(p.paymentStatus === 'PaymentWait' &&
|
||||
(payment.paymentStatus === 'PaymentWait' &&
|
||||
opts.status !== 'PaymentRetry') ||
|
||||
(p.paymentStatus === 'PaymentInProcess' &&
|
||||
(payment.paymentStatus === 'PaymentInProcess' &&
|
||||
opts.status !== 'PaymentInProcess') ||
|
||||
(p.paymentStatus === 'PaymentRetry' &&
|
||||
(payment.paymentStatus === 'PaymentRetry' &&
|
||||
opts.status !== 'PaymentRetry')
|
||||
"
|
||||
clickable
|
||||
class="row items-center"
|
||||
@click="selectStatus(p, opts.status, i)"
|
||||
@click="selectStatus(payment, opts.status, i)"
|
||||
>
|
||||
<q-icon
|
||||
:name="opts.icon"
|
||||
|
|
@ -599,7 +624,7 @@ onMounted(async () => {
|
|||
class="q-px-sm q-ml-auto"
|
||||
style="color: var(--foreground)"
|
||||
>
|
||||
{{ formatNumberDecimal(p.amount, 2) }}
|
||||
{{ formatNumberDecimal(payment.amount, 2) }}
|
||||
</span>
|
||||
<q-btn
|
||||
dense
|
||||
|
|
@ -608,15 +633,105 @@ onMounted(async () => {
|
|||
padding="0"
|
||||
id="btn-show-file"
|
||||
:icon="`mdi-chevron-${state.payExpansion[i] ? 'down' : 'up'}`"
|
||||
@click.stop="
|
||||
state.payExpansion[i] = !state.payExpansion[i]
|
||||
"
|
||||
@click.stop="state.payExpansion[i] = !state.payExpansion[i]"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- วิธีการรับชำระ -->
|
||||
<section
|
||||
class="q-px-md q-py-xs text-weight-medium row items-center"
|
||||
style="background-color: hsla(var(--info-bg) / 0.1)"
|
||||
>
|
||||
{{ $t('quotation.receiptDialog.paymentMethod') }}
|
||||
</section>
|
||||
<div
|
||||
class="surface-2 q-px-md q-py-sm row q-col-gutter-sm items-center"
|
||||
>
|
||||
<SelectInput
|
||||
id="input-payment-channel"
|
||||
for="input-payment-channel"
|
||||
:readonly="
|
||||
readonly ||
|
||||
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||
"
|
||||
v-model="formPaymentMethod[i].channel"
|
||||
class="col-md-2 col-6"
|
||||
:option="[
|
||||
{
|
||||
label: $t('creditNote.label.Cash'),
|
||||
value: 'Cash',
|
||||
},
|
||||
{
|
||||
label: $t('creditNote.label.BankTransfer'),
|
||||
value: 'BankTransfer',
|
||||
},
|
||||
]"
|
||||
:label="$t('quotation.receiptDialog.paymentMethod')"
|
||||
@update:model-value="
|
||||
() => {
|
||||
if (formPaymentMethod[i].channel === 'Cash') {
|
||||
formPaymentMethod[i].reference = null;
|
||||
formPaymentMethod[i].account = null;
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
<q-input
|
||||
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
|
||||
dense
|
||||
outlined
|
||||
class="col-md-3 col-6"
|
||||
v-model="formPaymentMethod[i].reference"
|
||||
:readonly="
|
||||
readonly ||
|
||||
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||
"
|
||||
:label="$t('quotation.refNo')"
|
||||
/>
|
||||
<q-input
|
||||
v-if="formPaymentMethod[i].channel === 'BankTransfer'"
|
||||
dense
|
||||
outlined
|
||||
class="col"
|
||||
v-model="formPaymentMethod[i].account"
|
||||
:readonly="
|
||||
readonly ||
|
||||
(!formPaymentMethod[i].isEdit && !!payment.channel)
|
||||
"
|
||||
:label="$t('quotation.bankAccount')"
|
||||
/>
|
||||
<div class="q-ml-auto">
|
||||
<UndoButton
|
||||
v-if="formPaymentMethod[i].isEdit"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
formPaymentMethod[i].isEdit = false;
|
||||
formPaymentMethod[i].channel = payment.channel;
|
||||
formPaymentMethod[i].reference = payment.reference;
|
||||
formPaymentMethod[i].account = payment.account;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<SaveButton
|
||||
v-if="!payment.channel || formPaymentMethod[i].isEdit"
|
||||
icon-only
|
||||
@click="triggerSubmit(payment.id)"
|
||||
/>
|
||||
<EditButton
|
||||
v-if="
|
||||
payment.channel && formPaymentMethod[i].isEdit === false
|
||||
"
|
||||
icon-only
|
||||
@click="formPaymentMethod[i].isEdit = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- อัปโหลดใบเสร็จ -->
|
||||
<section
|
||||
class="q-px-md q-py-xs text-weight-medium row items-center"
|
||||
style="background-color: hsla(var(--info-bg) / 0.1)"
|
||||
>
|
||||
|
|
@ -625,7 +740,7 @@ onMounted(async () => {
|
|||
msg: $t('quotation.receiptDialog.slip'),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</section>
|
||||
<div class="surface-2 q-pa-md">
|
||||
<div
|
||||
class="upload-section column rounded q-py-md full-height items-center justify-center no-wrap"
|
||||
|
|
@ -652,7 +767,9 @@ onMounted(async () => {
|
|||
ref="refQFile"
|
||||
v-show="false"
|
||||
v-model="slipFile[i].file"
|
||||
@update:model-value="triggerUpload(p, i, slipFile[i].file)"
|
||||
@update:model-value="
|
||||
triggerUpload(payment, i, slipFile[i].file)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -672,12 +789,12 @@ onMounted(async () => {
|
|||
:name="d.name"
|
||||
:progress="d.progress"
|
||||
:uploading="{ loaded: d.loaded, total: d.total }"
|
||||
:url="`/quotation/${data.id}/payment/${p.id}/attachment/${d.name}`"
|
||||
:url="`/payment/${payment.id}/attachment/${d.name}`"
|
||||
icon="mdi-file-image-outline"
|
||||
color="hsl(var(--text-mute))"
|
||||
clickable
|
||||
@click="triggerViewSlip(p, d.name)"
|
||||
@close="triggerDelete(p, d.name, i)"
|
||||
@click="triggerViewSlip(payment, d.name)"
|
||||
@close="triggerDelete(payment, d.name, i)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -686,7 +803,6 @@ onMounted(async () => {
|
|||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</q-form>
|
||||
</template>
|
||||
<style scoped>
|
||||
.bg-color-orange {
|
||||
|
|
|
|||
|
|
@ -417,6 +417,9 @@ export type QuotationPaymentData = {
|
|||
date: string;
|
||||
id: string;
|
||||
code: string;
|
||||
account?: string;
|
||||
channel?: string;
|
||||
reference?: string;
|
||||
};
|
||||
|
||||
export type Details = {
|
||||
|
|
@ -435,6 +438,9 @@ export type PaymentPayload = {
|
|||
paymentStatus: string;
|
||||
date: Date;
|
||||
amount: number;
|
||||
account?: string;
|
||||
channel?: string;
|
||||
reference?: string;
|
||||
};
|
||||
|
||||
export type ProductServiceList = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue