Files
web/app/components/transaction/TransactionDetail.vue
2026-05-07 16:15:33 +07:00

280 lines
8.9 KiB
Vue

<script setup>
import { isNotNil } from "es-toolkit";
// receive either txndetail or txndetailCode
// pass index to show as timeline item
const props = defineProps({
txndetail: Object,
index: Number,
txndetailCode: Number,
});
const { $getdata, $insertapi, $snackbar } = useNuxtApp();
const store = useStore();
const txndetail = ref(props.txndetail);
const showModal = ref(null);
const paymentSchedules = ref(null);
const transaction = ref(null);
const isUploadingFile = ref(false);
onMounted(async () => {
try {
if (!props.txndetail) {
const txndetailData = await $getdata(
"reservation",
undefined,
{
filter: { code: props.txndetailCode },
values:
"id,code,date,amount,amount_remaining,amount_received,due_date,transaction,customer_old__fullname,customer_new__fullname,transaction__code,phase,phase__name,creator,creator__fullname,status,status__name,approver,approver__fullname,approve_time,create_time,update_time",
},
true,
);
txndetail.value = txndetailData;
}
const schedules = await $getdata("payment_schedule", undefined, {
filter: { txn_detail: txndetail.value.id },
values:
"id,code,txn_detail,txn_detail__transaction__policy__code,type__name,from_date,to_date,amount,cycle,cycle_days,status__name,detail,entry",
});
paymentSchedules.value = schedules;
// Fetch transaction info
if (txndetail.value.transaction) {
transaction.value = await $getdata("transaction", { id: txndetail.value.transaction }, undefined, true);
}
} catch (error) {
console.error("Error fetching data:", error);
paymentSchedules.value = [];
}
});
function openModal() {
showModal.value = {
title: "Hợp đồng",
height: "40vh",
width: "50%",
component: "transaction/TransactionFiles",
vbind: { txndetail: txndetail.value.id },
};
}
async function handleFileUpload(uploadedFiles) {
if (!uploadedFiles || uploadedFiles.length === 0) return;
isUploadingFile.value = true;
try {
for (const fileRecord of uploadedFiles) {
const payload = {
txn_detail: txndetail.value.id,
file: fileRecord.id,
phase: txndetail.value.phase,
};
const result = await $insertapi("transactionfile", payload);
if (result && result.error) {
throw new Error(result.error || "Lưu file không thành công.");
}
}
$snackbar("File đã được upload thành công!", { type: "is-success" });
} catch (error) {
console.error("Error uploading file:", error);
$snackbar(error.message || "Có lỗi khi upload file.", {
type: "is-danger",
});
} finally {
isUploadingFile.value = false;
}
}
</script>
<template>
<div
v-if="txndetail"
class="is-flex is-gap-2"
>
<div
v-if="props.index !== undefined"
class="is-flex is-flex-direction-column is-align-items-center is-gap-1"
>
<p
class="is-size-5 has-text-weight-semibold is-flex is-justify-content-center is-align-items-center"
style="border: 4px solid rgb(32, 72, 83); border-radius: 9999px; width: 2.5rem; height: 2.5rem"
>
{{ props.index + 1 }}
</p>
<div style="border: 3px solid #dddddd; border-radius: 9999px; flex-grow: 1; width: min-content"></div>
</div>
<div class="is-flex is-flex-direction-column is-align-items-baseline is-gap-2 is-flex-grow-1">
<div
class="columns is-flex-wrap-wrap is-1.5 fs-15"
style="width: 100%"
>
<div class="column is-12 my-0 columns">
<p class="column is-3">
<span>{{ txndetail.phase__name }} - </span>
<span class="has-text-weight-semibold">{{ txndetail.code }}</span>
</p>
<div
v-if="phase != 7"
class="column is-3 p-0"
>
<p>
<span>Số tiền: </span>
<span class="has-text-weight-semibold">{{ $numtoString(txndetail.amount) }}</span>
</p>
<p
class="fs-14 has-text-grey"
v-if="isNotNil(txndetail.amount_received) && isNotNil(txndetail.amount_remaining)"
>
<span>(</span>
<span
title="Đã thu"
class="has-text-weight-semibold"
style="color: hsl(120, 70%, 40%)"
>
{{ $numtoString(txndetail.amount_received) }}
</span>
<span> </span>
<span
title="Còn lại"
class="has-text-weight-semibold"
style="color: hsl(0, 50%, 50%)"
>
{{ $numtoString(txndetail.amount_remaining) }}
</span>
<span>)</span>
</p>
</div>
<p
v-if="phase != 7"
class="column is-3"
>
<span>Trạng thái: </span>
<span class="has-text-weight-semibold">{{ txndetail.status__name }}</span>
</p>
<p
v-if="phase === 7"
class="column is-3"
>
<span>Khách hàng mới: </span>
<span class="has-text-weight-semibold">{{ txndetail.customer_new__fullname }}</span>
</p>
<p
v-if="phase === 7"
class="column is-3"
>
<span>Người chuyển nhượng: </span>
<span class="has-text-weight-semibold">{{ txndetail.creator__fullname }}</span>
</p>
<div class="column is-3 p-0 is-flex is-align-items-center is-gap-3">
<a
class="fsb-15 has-text-primary"
@click.prevent="openModal"
>Hợp đồng</a
>
<!-- <span v-if="!store.dealer" class="mx-1 has-text-grey-light"></span> -->
<FileUpload
v-if="
txndetail.phase === 7 &&
!store.dealer &&
$getEditRights('edit', {
code: 'transaction',
category: 'topmenu',
})
"
:type="['image', 'pdf']"
@files="handleFileUpload"
position="right"
class="file-upload-inline"
style="display: inline-block"
/>
</div>
</div>
<hr class="column is-12 my-0 has-background-grey" />
<p
v-if="phase === 7"
class="column is-12 mt-1 ml-1 columns"
>
<span
>Khách hàng
<span class="has-text-weight-semibold">{{ txndetail.customer_old__fullname }} </span>
chuyển nhượng hợp đồng này cho khách hàng
<span class="has-text-weight-semibold">{{ txndetail.customer_new__fullname }} </span></span
>
</p>
<div
v-if="phase != 7"
class="column is-12 mt-0 columns"
>
<p class="column is-3">
<span>Từ ngày: </span>
<span class="has-text-weight-semibold">{{ txndetail.date ? $dayjs(txndetail.date).format("L") : "" }}</span>
</p>
<p class="column is-3">
<span>Đến ngày: </span>
<span class="has-text-weight-semibold">{{
txndetail.due_date ? $dayjs(txndetail.due_date).format("L") : ""
}}</span>
</p>
<p class="column is-3">
<span>Người tạo: </span>
<span class="has-text-weight-semibold">{{ txndetail.creator__fullname }}</span>
</p>
<p class="column is-3">
<span>Người duyệt: </span>
<span class="has-text-weight-semibold">{{ txndetail.approver__fullname }}</span>
</p>
</div>
</div>
<div style="width: 100%">
<DataView
v-if="paymentSchedules && paymentSchedules.length > 0"
v-bind="{
pagename: `txndetail-${txndetail.id}`,
api: 'payment_schedule',
params: {
filter: { txn_detail: txndetail.id },
sort: 'cycle',
values:
'penalty_paid,penalty_remain,penalty_amount,penalty_reduce,batch_date,remain_amount,paid_amount,remain_amount,code,txn_detail,txn_detail__transaction__policy__code,type__name,from_date,to_date,amount,cycle,cycle_days,status__name,detail,entry',
},
setting: 'payment_schedule_list_timeline',
}"
/>
</div>
</div>
</div>
<div v-else>Đã có lỗi khi lấy thông tin chi tiết giao dịch.</div>
<Modal
v-if="showModal"
v-bind="showModal"
@close="showModal = undefined"
/>
</template>
<style scoped>
:deep(.file-upload-inline) {
display: inline-block;
}
:deep(.file-upload-inline .button) {
height: 1.5em;
padding: 0.25em 0.5em;
font-size: 0.875rem;
background: none;
border: none;
color: #3273dc;
text-decoration: underline;
}
:deep(.file-upload-inline .button:hover) {
background: none;
color: #1a5490;
}
</style>