Files
web/app/components/transaction/TransactionConfirmModal.vue
2026-05-05 11:06:49 +07:00

445 lines
15 KiB
Vue

<template>
<div>
<div class="content p-0">
<div class="columns">
<!-- Left Column: Transaction & Customer Info -->
<div class="column is-half">
<div class="mb-5">
<p class="title is-5 mb-3">Thông tin Giao dịch</p>
<p><strong>Loại giao dịch:</strong> {{ phaseInfo.name || "-" }}</p>
<p>
<strong>Chính sách tài chính:</strong>
{{ selectedPolicy.name || "-" }}
</p>
<p><strong>Giá gốc:</strong> {{ $numtoString(originPrice) }}</p>
<p>
<strong>Số tiền đặt cọc:</strong>
<span class="has-text-primary has-text-weight-bold">{{ $numtoString(depositAmount) }}</span>
</p>
<p>
<strong>Ngày hợp đồng:</strong>
<span class="has-text-primary has-text-weight-bold">{{ formatDate(initialContractDate) }}</span>
</p>
<p>
<strong>Ngày hết hạn GD:</strong>
<span class="has-text-primary has-text-weight-bold">{{ formatDate(editableDueDate) }}</span>
</p>
</div>
<hr />
<div>
<p class="title is-5 mb-3">Thông tin Khách hàng</p>
<div class="columns">
<!-- Original Customer Column -->
<div class="column">
<p>
<strong>{{ isIndividual ? "Khách hàng" : "Tổ chức" }}</strong>
</p>
<p><strong> KH:</strong> {{ selectedCustomer.code || "-" }}</p>
<p>
<strong>{{ isIndividual ? "Họ và tên:" : "Tên tổ chức:" }}</strong>
{{ selectedCustomer.fullname || "-" }}
</p>
<p>
<strong>Số điện thoại:</strong>
{{ selectedCustomer.phone || "-" }}
</p>
<p>
<strong>{{ isIndividual ? "CCCD:" : "GPKD/Mã số thuế:" }}</strong>
{{ selectedCustomer.legal_code || "-" }}
</p>
</div>
<!-- Co-owner Column -->
<div
class="column"
v-if="coOwner && isIndividual"
>
<p class="is-flex is-justify-content-space-between">
<strong>Đồng sở hữu</strong>
<button
type="button"
class="delete is-small"
@click.stop="clearCoOwner()"
></button>
</p>
<p><strong>:</strong> {{ coOwner.people__code || "-" }}</p>
<p>
<strong>Họ tên:</strong>
{{ coOwner.people__fullname || "-" }}
</p>
<p>
<strong>Số điện thoại:</strong>
{{ coOwner.people__phone || "-" }}
</p>
<p><strong>CCCD:</strong> {{ coOwner.people__legal_code || "-" }}</p>
</div>
</div>
<!-- Co-owner SearchBox -->
<div
v-if="isIndividual && relatedPeople.length > 0"
class="mt-2"
>
<label class="label">Đồng sở hữu</label>
<SearchBox
:key="coOwner ? coOwner.id : 'empty'"
v-bind="{
vdata: relatedPeople,
field: 'people__fullname',
column: ['people__code', 'people__fullname'],
first: true,
optionid: coOwner ? coOwner.id : null,
}"
@option="selectCoOwnerByPerson"
/>
</div>
<div
v-else-if="isIndividual && relatedPeople.length === 0"
class="mt-2"
>
<p class="has-text-grey-light is-size-7">Không có người liên quan nào được thêm</p>
</div>
</div>
</div>
<!-- Right Column: Product Info -->
<div
class="column is-half"
style="border-left: 1px solid #dbdbdb; padding-left: 2rem"
>
<div class="mb-5">
<p class="title is-5 mb-3">Thông tin Sản phẩm</p>
<div class="columns is-multiline is-mobile">
<div class="column is-half">
<strong>Mã thương mại:</strong>
{{ productData.trade_code || "-" }}
</div>
<div class="column is-half">
<strong>Số tờ thửa:</strong>
{{ productData.land_lot_code || "-" }}
</div>
<div class="column is-half">
<strong>Mã quy hoạch:</strong>
{{ productData.zone_code || "-" }}
</div>
<div class="column is-half">
<strong>Phân khu:</strong>
{{ productData.zone_type__name || "-" }}
</div>
<div class="column is-half">
<strong>Loại sản phẩm:</strong>
{{ productData.type__name || "-" }}
</div>
<div class="column is-half">
<strong>Hướng cửa:</strong>
{{ productData.direction__name || "-" }}
</div>
<div class="column is-half">
<strong>Kích thước lô:</strong>
{{ productData.land_lot_size || "-" }}
</div>
<div class="column is-half">
<strong>Diện tích đất:</strong>
{{ productData.lot_area ? $numtoString(productData.lot_area) + " m²" : "-" }}
</div>
<div class="column is-half">
<strong>DT xây dựng:</strong>
{{ productData.building_area ? $numtoString(productData.building_area) + " m²" : "-" }}
</div>
<div class="column is-half">
<strong>DT sàn:</strong>
{{ productData.total_built_area ? $numtoString(productData.total_built_area) + " m²" : "-" }}
</div>
<div class="column is-half">
<strong>Tầng cao:</strong>
{{ productData.number_of_floors || "-" }}
</div>
<div class="column is-half">
<strong>Trạng thái:</strong>
{{ productData.status__name || "-" }}
</div>
<div class="column is-half"><strong>Dự án:</strong> {{ productData.project__name || "-" }}</div>
</div>
</div>
</div>
</div>
<div class="">
<div class="columns is-vcentered">
<div class="column">
<div class="field">
<label class="label">Số tiền thanh toán</label>
<div class="control">
<InputNumber
:record="paymentRecord"
attr="paymentAmount"
placeholder="Nhập số tiền"
:disabled="isPaymentInputDisabled"
@number="updatePaymentAmount"
:class="{ 'is-danger': !isPaymentAmountValid }"
></InputNumber>
</div>
<p
v-if="!isPaymentAmountValid"
class="help is-danger"
>
{{ paymentAmountError }}
</p>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Ngày ký hợp đồng</label>
<p class="control has-text-weight-bold pt-2">
{{ formatDate(initialContractDate) }}
</p>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Ngày hết hạn giao dịch</label>
<Datepicker
:record="dateRecord"
attr="dueDate"
@date="updateDueDate"
position="is-top-right"
/>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Mã xác nhận</label>
<div class="control">
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input"
type="text"
placeholder="Nhập mã"
v-model="userInputCaptcha"
/>
</div>
<div class="control">
<a
class="button is-static has-text-weight-bold has-background-grey-lighter"
style="font-family: &quot;Courier New&quot;, monospace; letter-spacing: 2px"
>
{{ captchaCode }}
</a>
</div>
<div class="control">
<button
class="button"
@click="generateCaptcha"
:title="'Tạo mã mới'"
>
<span class="icon">
<SvgIcon
v-bind="{
name: 'refresh.svg',
type: 'primary',
size: 23,
}"
></SvgIcon>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="is-flex is-justify-content-flex-end mt-4">
<button
class="button has-text-white is-success mr-2"
:disabled="!isConfirmed"
@click="handleConfirm"
>
<span>Tạo giao dịch</span>
</button>
<button
class="button"
@click="$emit('close')"
>
Hủy
</button>
</div>
</div>
</template>
<script>
import { useStore } from "@/stores/index";
import dayjs from "dayjs";
import Datepicker from "~/components/datepicker/Datepicker.vue";
import InputNumber from "~/components/common/InputNumber.vue";
import SearchBox from "~/components/SearchBox";
export default {
components: { Datepicker, InputNumber, SearchBox },
props: {
productData: { type: Object, required: true },
phaseInfo: { type: Object, required: true },
selectedPolicy: { type: Object, required: true },
originPrice: { type: Number, required: true },
discountValueDisplay: { type: Number, required: true },
selectedCustomer: { type: Object, required: true },
initialContractDate: { type: String, required: true },
},
setup() {
const store = useStore();
return { store };
},
emits: ["close", "modalevent"],
data() {
const initialDueDate = dayjs(this.initialContractDate).add(0, "day").format("YYYY-MM-DD");
return {
editableDueDate: initialDueDate,
dateRecord: { dueDate: initialDueDate },
captchaCode: "",
userInputCaptcha: "",
paymentRecord: { paymentAmount: null },
coOwner: null,
relatedPeople: [],
peopleAddon: {
component: "people/People",
width: "65%",
height: "600px",
title: "Người liên quan",
},
};
},
watch: {
depositAmount: {
immediate: true,
handler(newVal) {
if (this.phaseInfo.id === "p0" && newVal === 0) {
this.paymentRecord.paymentAmount = null;
} else {
this.paymentRecord.paymentAmount = newVal;
}
},
},
selectedCustomer: {
immediate: true,
async handler(newVal) {
if (newVal && newVal.id) {
await this.fetchRelatedPeople();
} else {
this.relatedPeople = [];
this.coOwner = null;
}
},
},
},
computed: {
isIndividual() {
return this.selectedCustomer?.type === 1;
},
paymentAmount() {
return this.paymentRecord.paymentAmount;
},
isPaymentInputDisabled() {
return this.phaseInfo.id !== "p0";
},
isPaymentAmountValid() {
return this.paymentAmount > 0;
},
paymentAmountError() {
if (this.isPaymentAmountValid) return "";
return "Số tiền thanh toán phải lớn hơn 0.";
},
isConfirmed() {
return (
this.userInputCaptcha.toLowerCase() === this.captchaCode.toLowerCase() &&
this.userInputCaptcha !== "" &&
this.isPaymentAmountValid
);
},
depositAmount() {
return this.selectedPolicy?.deposit || 0;
},
},
created() {
this.generateCaptcha();
},
methods: {
async fetchRelatedPeople() {
try {
const { $getdata } = useNuxtApp();
const apiName = this.isIndividual ? "customerpeople" : "legalrep";
const filterKey = this.isIndividual ? "customer" : "organization";
let customerId = this.selectedCustomer.id;
// Nếu organization, cần lấy organization ID
if (!this.isIndividual) {
const org = await $getdata("organization", { customer: customerId }, undefined, true);
if (org) {
customerId = org.id;
} else {
this.relatedPeople = [];
this.coOwner = null;
return;
}
}
const people = await $getdata(apiName, { [filterKey]: customerId });
this.relatedPeople = Array.isArray(people) ? people : people ? [people] : [];
// Auto-select người đầu tiên nếu
if (this.relatedPeople.length > 0) {
this.coOwner = this.relatedPeople[0];
} else {
this.coOwner = null;
}
} catch (error) {
console.error("Error fetching related people:", error);
this.relatedPeople = [];
this.coOwner = null;
}
},
clearCoOwner() {
this.coOwner = null;
},
selectCoOwnerByPerson(selectedPerson) {
// selectedPerson là một record từ customerpeople
this.coOwner = selectedPerson || null;
},
formatDate(date) {
return date ? dayjs(date).format("DD/MM/YYYY") : "-";
},
updateDueDate(newDate) {
this.editableDueDate = newDate;
this.dateRecord.dueDate = newDate;
},
updatePaymentAmount(value) {
this.paymentRecord.paymentAmount = value;
},
generateCaptcha() {
this.captchaCode = Math.random().toString(36).substring(2, 7).toUpperCase();
this.userInputCaptcha = "";
},
handleConfirm() {
if (this.isConfirmed) {
this.$emit("modalevent", {
name: "confirm",
data: {
currentDate: this.initialContractDate,
dueDate: this.editableDueDate,
paymentAmount: this.depositAmount,
depositReceived: this.paymentAmount,
people: this.isIndividual && this.coOwner ? this.coOwner.people : null,
},
});
}
},
},
};
</script>