1254 lines
42 KiB
Vue
1254 lines
42 KiB
Vue
<template>
|
|
<div
|
|
:id="docid"
|
|
class="container-fluid is-flex is-flex-direction-column is-gap-2"
|
|
>
|
|
<div class="is-flex is-flex-direction-column is-gap-3">
|
|
<!-- PRODUCT SECTION -->
|
|
<div v-if="product">
|
|
<Caption v-bind="{ title: isVietnamese ? 'Sản phẩm' : 'Product' }"></Caption>
|
|
<div class="columns is-multiline mx-0">
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Tên thương mại" : "Trade Code" }}</label>
|
|
<div class="control">
|
|
<a
|
|
class="has-text-primary"
|
|
@click="openLink()"
|
|
>{{ product.trade_code }}</a
|
|
>
|
|
<a
|
|
class="ml-4"
|
|
id="ignore"
|
|
@click="$copyToClipboard(product.trade_code)"
|
|
><SvgIcon
|
|
name="copy.svg"
|
|
type="primary"
|
|
:size="18"
|
|
/></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Tên quy hoạch" : "Zone Code" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(product.zone_code)"
|
|
>{{ product.zone_code }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Loại sản phẩm" : "Type" }}</label>
|
|
<div class="control">
|
|
<span>{{ product.type__name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Mẫu biệt thự" : "Villa Model" }}</label>
|
|
<div class="control">
|
|
<span>{{ product.template_name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Phân khu" : "Zone Type" }}</label>
|
|
<div class="control">
|
|
<span>{{ product.zone_type__name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Hướng cửa" : "Direction" }}</label>
|
|
<div class="control">
|
|
<span>{{ product.direction__name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Diện tích đất" : "Lot Area" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(product.lot_area)"
|
|
>{{ $numtoString(product.lot_area) }}m²</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Diện tích xây dựng" : "Building Area" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(product.building_area)"
|
|
>{{ $numtoString(product.building_area) }}m²</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">
|
|
{{
|
|
isVietnamese
|
|
? `Diện tích sàn (${product.number_of_floors} tầng)`
|
|
: `Total Built Area (${product.number_of_floors} floors)`
|
|
}}
|
|
</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(product.total_built_area)"
|
|
>{{ $numtoString(product.total_built_area) }}m²</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Kích thước lô" : "Land Lot Size" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(product.land_lot_size)"
|
|
>{{ product.land_lot_size }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Dự án" : "Project" }}</label>
|
|
<div class="control">
|
|
<span>{{ product.project__name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Trạng thái" : "Status" }}</label>
|
|
<div class="control">
|
|
<span>{{ productstatus?.name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Giá gốc" : "Original price" }}</label>
|
|
<div class="control">
|
|
<span>{{ $numtoString(product?.origin_price) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Giỏ hàng" : "Cart" }}</label>
|
|
<div class="control">
|
|
<span>{{ product?.cart__name || "/" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Đại lý bán hàng" : "Dealer" }}</label>
|
|
<div class="control">
|
|
<span>{{ product?.cart__dealer__name || "/" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- QUICK ADD SECTION -->
|
|
<div
|
|
v-if="
|
|
quickAdd &&
|
|
(product.status === 2 || quickAddedTransaction) &&
|
|
($getEditRights('edit', { code: 'product', category: 'submenu' }) ||
|
|
$getEditRights('edit', {
|
|
code: 'product-setting',
|
|
category: 'submenu',
|
|
}))
|
|
"
|
|
>
|
|
<Caption v-bind="{ title: 'Giữ chỗ nhanh' }"></Caption>
|
|
<div class="columns is-multiline mx-0 mt-0">
|
|
<div class="column is-narrow pl-0">
|
|
<span
|
|
class="label is-size-7"
|
|
style="color: inherit"
|
|
>{{ isVietnamese ? "Khóa sản phẩm" : "Lock Product" }}</span
|
|
>
|
|
<div class="field">
|
|
<div
|
|
class="is-flex is-align-items-center is-clickable"
|
|
:style="{
|
|
opacity: product.status !== 2 ? 0.5 : 1,
|
|
cursor: product.status !== 2 ? 'not-allowed' : 'pointer',
|
|
}"
|
|
@click="product.status === 2 && (isLockingProduct = !isLockingProduct)"
|
|
>
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: isLockingProduct ? 'checked.svg' : 'uncheck.svg',
|
|
type: isLockingProduct ? 'primary' : 'twitter',
|
|
size: 32,
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="isLockingProduct"
|
|
class="column is-2"
|
|
>
|
|
<div class="field">
|
|
<label class="label is-size-7">{{ isVietnamese ? "Thời gian (phút)" : "Duration (min)" }}</label>
|
|
<div class="control">
|
|
<input
|
|
class="input"
|
|
type="number"
|
|
v-model.number="lockDurationMinutes"
|
|
min="1"
|
|
:placeholder="isVietnamese ? 'Nhập số phút' : 'Enter minutes'"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="isLockingProduct"
|
|
class="column is-narrow is-flex is-align-items-flex-end"
|
|
>
|
|
<button
|
|
class="button is-primary is-fullwidth"
|
|
@click="confirmQuickAddTransaction"
|
|
>
|
|
{{ isVietnamese ? "Xác nhận" : "Confirm" }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- <div v-if="productnotes?.length > 0" class="column is-full mt-3">
|
|
<p v-for="{ detail } in productnotes" :key="detail">
|
|
<span>{{ detail }}</span>
|
|
</p>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CUSTOMER SECTION -->
|
|
<div v-if="record && $getEditRights('view', { code: 'customer', category: 'topmenu' })">
|
|
<Caption v-bind="{ title: data && findFieldName('customer')[lang] }"></Caption>
|
|
<div class="columns is-multiline mx-0">
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("custcode")[lang] }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink has-text-primary"
|
|
@click="openCustomer()"
|
|
>{{ record.code }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("name")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.fullname }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("phone_number")[lang] }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="openPhone()"
|
|
>{{ record.phone }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">Email:</label>
|
|
<div
|
|
class="control"
|
|
style="word-break: break-all"
|
|
>
|
|
{{ record.email || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("personal_id")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.legal_type__name || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("idnum")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.legal_code || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("issued_date")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.issued_date ? $dayjs(record.issued_date).format("L") : "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("issued_place")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.issued_place__name || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("country")[lang] }}</label>
|
|
<div class="control">
|
|
{{ isVietnamese ? record.country__name : record.country__en || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("address")[lang] }}</label>
|
|
<div class="control">
|
|
{{ record.address || "/" }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- <div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("created_by")[lang] }}</label>
|
|
<div class="control">
|
|
<span class="hyperlink" @click="openUser(record.creator)">{{ record.creator__fullname || "/" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("updated_by")[lang] }}</label>
|
|
<div class="control">
|
|
<span class="hyperlink" @click="openUser(record.updater)">{{ record.updater__fullname || "/" }}</span>
|
|
</div>
|
|
</div>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TRANSACTION SECTION -->
|
|
<div v-if="transaction">
|
|
<Caption v-bind="{ title: isVietnamese ? 'Giao dịch' : 'Transaction' }"></Caption>
|
|
|
|
<div class="columns is-multiline mx-0">
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Mã giao dịch" : "Transaction code" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.code)"
|
|
>{{ transaction.code }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="column is-one-fifth pb-1 px-0"
|
|
v-if="transactionphase"
|
|
>
|
|
<div class="field">
|
|
<label class="label">Giai đoạn</label>
|
|
<div class="control">
|
|
<span>{{ transactionphase.name }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ data && findFieldName("create-time")[lang] }}</label>
|
|
<div class="control">
|
|
<span>{{ $dayjs(transaction.create_time).format("L HH:mm") }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="column is-one-fifth pb-1 px-0"
|
|
v-if="salepolicy"
|
|
>
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Chính sách bán hàng" : "Sale Policy" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink has-text-primary"
|
|
@click="openPoli()"
|
|
>{{ salepolicy.code }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Chiết khấu" : "Discount" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.discount_amount)"
|
|
>{{ $numtoString(transaction.discount_amount) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Giá hợp đồng" : "Sale Price" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.sale_price)"
|
|
>{{ $numtoString(transaction.sale_price) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Yêu cầu đặt cọc" : "Deposit Required" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.deposit_amount)"
|
|
>{{ $numtoString(transaction.deposit_amount) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Đã cọc" : "Deposit Received" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.deposit_received)"
|
|
>{{ $numtoString(transaction.deposit_received) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Tiền cọc còn lại" : "Deposit Remaining" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink"
|
|
@click="$copyToClipboard(transaction.deposit_remaining)"
|
|
>{{ $numtoString(transaction.deposit_remaining) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-one-fifth pb-1 px-0">
|
|
<div class="field">
|
|
<label class="label">{{ isVietnamese ? "Tổng số tiền đã nhận" : "Total Amount Received" }}</label>
|
|
<div class="control">
|
|
<span
|
|
class="hyperlink has-text-primary"
|
|
@click="openMoney()"
|
|
>{{ $numtoString(transaction.amount_received) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GIFT SECTION -->
|
|
<div v-if="transactionId && gift && gift.length > 0">
|
|
<Caption
|
|
class="mb-1"
|
|
v-bind="{
|
|
title: isVietnamese ? 'Danh sách quà tặng' : 'Payment/Reservation Schedule',
|
|
}"
|
|
></Caption>
|
|
<DataView
|
|
:key="paymentScheduleKey"
|
|
v-bind="{
|
|
api: 'transactiongift',
|
|
params: {
|
|
filter: { transaction: transactionId },
|
|
values: 'gift__code,gift__name,gift__detail',
|
|
},
|
|
pagename: `gift_${transactionId}_${Date.now()}`,
|
|
setting: 'transaction-gift-fields',
|
|
component: `gift_${transactionId}`,
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- PAYMENT SCHEDULE SECTION -->
|
|
<div v-if="transactionId">
|
|
<Caption
|
|
class="mb-1"
|
|
v-bind="{
|
|
title: isVietnamese ? 'Diễn biến giao dịch' : 'Payment/Reservation Schedule',
|
|
}"
|
|
></Caption>
|
|
<DataView
|
|
:key="paymentScheduleKey"
|
|
v-bind="{
|
|
api: 'payment_schedule',
|
|
params: {
|
|
filter: { txn_detail__transaction: transactionId },
|
|
values:
|
|
'txn_detail,txn_detail__amount,txn_detail__creator__fullname,txn_detail__code,txn_detail__date,txn_detail__due_date,txn_detail__phase__name,txn_detail__status__name,txn_detail__approver__fullname,txn_detail__approve_time',
|
|
summary: 'annotate',
|
|
distinct_values: {
|
|
count_id: {
|
|
type: 'Count',
|
|
field: 'id',
|
|
distinct: 'true',
|
|
},
|
|
},
|
|
},
|
|
pagename: `payment_schedule_${transactionId}_${Date.now()}`,
|
|
setting: 'transaction-fields',
|
|
component: `payment_schedule_${transactionId}`,
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- ACTION BUTTONS -->
|
|
<div
|
|
class="buttons is-justify-content-flex-start mt-3"
|
|
id="ignore"
|
|
v-if="reservation"
|
|
>
|
|
<button
|
|
class="button is-info"
|
|
@click="$exportpdf(docid, transaction?.code || 'document', 'a4', 'landscape')"
|
|
>
|
|
<span>{{ data && findFieldName("print")[lang] }}</span>
|
|
</button>
|
|
|
|
<template v-if="$getEditRights()">
|
|
<button
|
|
v-if="activeTabCode === 'approval' && !isContractApproved && !isContractDeclined"
|
|
class="button is-success ml-2"
|
|
@click="confirmContract"
|
|
:disabled="isApproving"
|
|
:class="{ 'is-loading': isApproving }"
|
|
>
|
|
<span>{{ isVietnamese ? "Duyệt giao dịch" : "Approve" }}</span>
|
|
</button>
|
|
<button
|
|
v-if="activeTabCode === 'approval' && !isContractApproved && !isContractDeclined"
|
|
class="button is-warning ml-2"
|
|
@click="confirmSupplement"
|
|
:disabled="isSupplementing"
|
|
:class="{ 'is-loading': isSupplementing }"
|
|
>
|
|
<span>{{ isVietnamese ? "Yêu cầu bổ sung thông tin" : "Request Supplement" }}</span>
|
|
</button>
|
|
<button
|
|
v-if="activeTabCode === 'approval' && !isContractDeclined"
|
|
class="button is-danger ml-2"
|
|
@click="confirmReject"
|
|
:disabled="isRejecting"
|
|
:class="{ 'is-loading': isRejecting }"
|
|
>
|
|
<span>{{ isVietnamese ? "Từ chối giao dịch" : "Reject" }}</span>
|
|
</button>
|
|
</template>
|
|
|
|
<div
|
|
v-if="isContractApproved && activeTabCode === 'approval'"
|
|
class="notification is-success is-light ml-2 mb-0 py-2 px-3"
|
|
>
|
|
<span class="icon-text">
|
|
<span class="has-text-weight-semibold">{{ isVietnamese ? "Đã duyệt" : "Approved" }}</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MODAL -->
|
|
<Modal
|
|
@close="showmodal = undefined"
|
|
v-bind="showmodal"
|
|
@modalevent="handleModalEvent"
|
|
@confirm="handleModalEvent({ name: 'confirm' })"
|
|
@noteConfirm="handleNoteConfirmEvent"
|
|
v-if="showmodal"
|
|
></Modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { useStore } from "@/stores/index";
|
|
import { useAdvancedWorkflow } from "@/composables/useAdvancedWorkflow";
|
|
|
|
export default {
|
|
setup() {
|
|
const store = useStore();
|
|
const { $getdata, $patchapi, $insertapi, $deleteapi } = useNuxtApp();
|
|
const { approveTransactionDetail, isLoading } = useAdvancedWorkflow();
|
|
return {
|
|
store,
|
|
$getdata,
|
|
$patchapi,
|
|
$insertapi,
|
|
$deleteapi,
|
|
approveTransactionDetail,
|
|
isApproving: isLoading,
|
|
};
|
|
},
|
|
|
|
props: {
|
|
pagename: String,
|
|
transactionId: [Number, String],
|
|
customerId: [Number, String],
|
|
productId: [Number, String],
|
|
reservationId: [Number, String],
|
|
quickAdd: Boolean,
|
|
},
|
|
|
|
emits: ["close"],
|
|
|
|
data() {
|
|
return {
|
|
record: undefined,
|
|
product: undefined,
|
|
transaction: undefined,
|
|
reservation: undefined,
|
|
transactionphase: undefined,
|
|
salepolicy: undefined,
|
|
productstatus: undefined,
|
|
producttype: undefined,
|
|
zonetype: undefined,
|
|
gift: undefined,
|
|
direction: undefined,
|
|
project: undefined,
|
|
productstatuses: undefined,
|
|
productnotes: undefined,
|
|
transactionFiles: [],
|
|
errors: {},
|
|
showmodal: undefined,
|
|
docid: this.$id(),
|
|
data: this.store.common,
|
|
actionToConfirm: null,
|
|
paymentScheduleKey: 0,
|
|
// Quick Add Properties
|
|
isLockingProduct: false,
|
|
lockDurationMinutes: 30,
|
|
// New properties for supplement/reject
|
|
isSupplementing: false,
|
|
isRejecting: false,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
lang() {
|
|
return this.store.lang;
|
|
},
|
|
|
|
isVietnamese() {
|
|
return this.store.lang === "vi";
|
|
},
|
|
|
|
activeTabCode() {
|
|
if (this.store.tabinfo && this.store.tabinfo.tab) {
|
|
return this.store.tabinfo.tab.code;
|
|
}
|
|
if (this.store.tabinfo && this.store.tabinfo.vbind) {
|
|
return this.store.tabinfo.vbind.tab;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
isContractDeclined() {
|
|
return this.reservation?.status === 4;
|
|
},
|
|
|
|
isContractApproved() {
|
|
return this.reservation?.status === 3;
|
|
},
|
|
|
|
paymentSchedulePagename() {
|
|
return `payment_schedule_${this.transactionId}_${this.paymentScheduleKey}`;
|
|
},
|
|
},
|
|
|
|
async created() {
|
|
// Load transaction
|
|
if (this.transactionId && !this.transaction) {
|
|
this.transaction = await this.$getdata("transaction", { id: this.transactionId }, undefined, true);
|
|
this.gift = await this.$getdata("transactiongift", {
|
|
transaction: this.transactionId,
|
|
});
|
|
}
|
|
|
|
// Load reservation
|
|
if (this.reservationId && !this.reservation) {
|
|
this.reservation = await this.$getdata("reservation", { id: this.reservationId }, undefined, true);
|
|
if (this.reservation && !this.transactionId) {
|
|
this.transactionId = this.reservation.transaction;
|
|
}
|
|
}
|
|
|
|
// Load transaction again if reservation provided ID
|
|
if (this.transactionId && !this.transaction) {
|
|
this.transaction = await this.$getdata("transaction", { id: this.transactionId }, undefined, true);
|
|
}
|
|
|
|
// Load product
|
|
if (!this.product) {
|
|
const prodId = this.productId || this.transaction?.product;
|
|
if (prodId) {
|
|
this.product = await this.$getdata("product", { id: prodId }, undefined, true);
|
|
}
|
|
}
|
|
|
|
// Load customer
|
|
if (!this.record) {
|
|
const custId = this.customerId || this.transaction?.customer;
|
|
if (custId) {
|
|
this.record = await this.$getdata("customer", { id: custId }, undefined, true);
|
|
}
|
|
}
|
|
|
|
// Load related data
|
|
const loadPromises = [];
|
|
|
|
if (this.transaction?.phase) {
|
|
loadPromises.push(
|
|
this.$getdata("transactionphase", { id: this.transaction.phase }, undefined, true).then(
|
|
(data) => (this.transactionphase = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.transaction?.policy) {
|
|
loadPromises.push(
|
|
this.$getdata("salepolicy", { id: this.transaction.policy }, undefined, true).then(
|
|
(data) => (this.salepolicy = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.product?.status) {
|
|
loadPromises.push(
|
|
this.$getdata("productstatus", { id: this.product.status }, undefined, true).then(
|
|
(data) => (this.productstatus = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.product?.type) {
|
|
loadPromises.push(
|
|
this.$getdata("producttype", { id: this.product.type }, undefined, true).then(
|
|
(data) => (this.producttype = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.product?.zone_type) {
|
|
loadPromises.push(
|
|
this.$getdata("zonetype", { id: this.product.zone_type }, undefined, true).then(
|
|
(data) => (this.zonetype = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.product?.direction) {
|
|
loadPromises.push(
|
|
this.$getdata("direction", { id: this.product.direction }, undefined, true).then(
|
|
(data) => (this.direction = data),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (this.product?.project) {
|
|
loadPromises.push(
|
|
this.$getdata("project", { id: this.product.project }, undefined, true).then((data) => (this.project = data)),
|
|
);
|
|
}
|
|
|
|
if (this.product) {
|
|
loadPromises.push(
|
|
this.$getdata("productnote", { ref: this.product.id }).then((data) => (this.productnotes = data)),
|
|
);
|
|
}
|
|
|
|
await Promise.all(loadPromises);
|
|
|
|
if (this.reservationId) {
|
|
await this.fetchTransactionFiles();
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
async fetchTransactionFiles() {
|
|
try {
|
|
const transactionFilesData = await this.$getdata("transactionfile", {
|
|
txn_detail: this.reservationId,
|
|
});
|
|
if (transactionFilesData && transactionFilesData.length > 0) {
|
|
this.transactionFiles = transactionFilesData
|
|
.map((fileData, index) => ({
|
|
id: fileData.id,
|
|
file: fileData.file,
|
|
file__file: fileData.file__file,
|
|
name: fileData.file__file || `File ${index + 1}`,
|
|
phase: fileData.phase,
|
|
create_time: fileData.create_time,
|
|
}))
|
|
.filter((file) => file.file__file);
|
|
}
|
|
} catch (error) {
|
|
console.error("Lỗi khi fetch transaction files:", error);
|
|
this.transactionFiles = [];
|
|
}
|
|
},
|
|
|
|
confirmContract() {
|
|
this.actionToConfirm = "contract";
|
|
this.showmodal = {
|
|
component: "dialog/Confirm",
|
|
title: "Duyệt hợp đồng",
|
|
width: "auto",
|
|
height: "auto",
|
|
vbind: {
|
|
content: "Bạn có chắc chắn muốn duyệt hợp đồng này không?",
|
|
},
|
|
};
|
|
},
|
|
|
|
async executeConfirmContract() {
|
|
if (this.$getdata("transaction", { id: this.transactionId }).status != 3) {
|
|
if (this.isApproving || this.isContractApproved) return;
|
|
|
|
const result = await this.approveTransactionDetail(this.reservationId, "approved");
|
|
|
|
if (result && result.success) {
|
|
const updatedDetail = this.findWorkflowStepResult(result.result, "update_detail_status_and_approver");
|
|
this.$emit("close");
|
|
}
|
|
} else {
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(
|
|
this.isVietnamese ? "Giao dịch này đã được phê duyệt rồi" : "Error supplementing transaction",
|
|
"Error",
|
|
"Danger",
|
|
);
|
|
}
|
|
},
|
|
|
|
// New: Confirm supplement (open NoteInput)
|
|
confirmSupplement() {
|
|
this.actionToConfirm = "supplement";
|
|
this.showmodal = {
|
|
component: "dialog/NoteInput",
|
|
title: this.isVietnamese ? "Yêu cầu bổ sung thông tin" : "Request Supplement Info",
|
|
width: "auto",
|
|
height: "auto",
|
|
vbind: {
|
|
label: this.isVietnamese ? "Lý do yêu cầu bổ sung" : "Reason for Supplement Request",
|
|
placeholder: this.isVietnamese ? "Nhập lý do..." : "Enter reason...",
|
|
type: "warning",
|
|
confirmText: this.isVietnamese ? "Xác nhận" : "Confirm",
|
|
cancelText: this.isVietnamese ? "Hủy" : "Cancel",
|
|
},
|
|
};
|
|
},
|
|
|
|
// New: Execute supplement
|
|
async executeSupplement(note) {
|
|
if (this.isSupplementing) return;
|
|
this.isSupplementing = true;
|
|
|
|
const { id: userId } = this.store.login;
|
|
|
|
try {
|
|
// Insert note to Product_Note
|
|
await this.$insertapi("productnote", {
|
|
ref: this.product.id,
|
|
detail: note,
|
|
user: userId,
|
|
});
|
|
|
|
// Patch Transaction_Detail status to 5
|
|
await this.$patchapi("reservation", {
|
|
id: this.reservation.id,
|
|
status: 5,
|
|
});
|
|
|
|
// Refresh data
|
|
this.reservation = await this.$getdata("reservation", { id: this.reservation.id }, undefined, true);
|
|
this.productnotes = await this.$getdata("productnote", {
|
|
ref: this.product.id,
|
|
});
|
|
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(
|
|
this.isVietnamese ? "Yêu cầu bổ sung thành công" : "Supplement request successful",
|
|
"Success",
|
|
"Success",
|
|
);
|
|
|
|
this.$emit("close");
|
|
} catch (error) {
|
|
console.error("Error supplementing transaction:", error);
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(this.isVietnamese ? "Lỗi khi yêu cầu bổ sung" : "Error supplementing transaction", "Error", "Danger");
|
|
} finally {
|
|
this.isSupplementing = false;
|
|
}
|
|
},
|
|
|
|
// New: Confirm reject (open NoteInput)
|
|
confirmReject() {
|
|
this.actionToConfirm = "reject";
|
|
this.showmodal = {
|
|
component: "dialog/NoteInput",
|
|
title: this.isVietnamese ? "Từ chối giao dịch" : "Reject Transaction",
|
|
width: "auto",
|
|
height: "auto",
|
|
vbind: {
|
|
label: this.isVietnamese ? "Lý do từ chối" : "Reason for Rejection",
|
|
placeholder: this.isVietnamese ? "Nhập lý do..." : "Enter reason...",
|
|
type: "danger",
|
|
confirmText: this.isVietnamese ? "Xác nhận" : "Confirm",
|
|
cancelText: this.isVietnamese ? "Hủy" : "Cancel",
|
|
},
|
|
};
|
|
},
|
|
|
|
// New: Execute reject
|
|
async executeReject(note) {
|
|
if (this.isRejecting) return;
|
|
this.isRejecting = true;
|
|
|
|
const { id: userId } = this.store.login;
|
|
const prdbkId = this.product?.prdbk; // Giả sử prdbk là ID Product_Booked
|
|
|
|
try {
|
|
// Insert note to Product_Note
|
|
await this.$insertapi("productnote", {
|
|
ref: this.product.id,
|
|
detail: note,
|
|
user: userId,
|
|
});
|
|
|
|
// Patch Transaction_Detail status to 4
|
|
await this.$patchapi("reservation", {
|
|
id: this.reservation.id,
|
|
status: 4,
|
|
});
|
|
|
|
// Delete Product_Booked if exists
|
|
if (prdbkId) {
|
|
await this.$deleteapi("productbooked", prdbkId);
|
|
}
|
|
|
|
// Patch Product to null transaction/policy and status 1
|
|
await this.$patchapi("product", {
|
|
id: this.product.id,
|
|
transaction: null,
|
|
policy: null,
|
|
status: 1,
|
|
});
|
|
|
|
// Refresh data
|
|
this.reservation = await this.$getdata("reservation", { id: this.reservation.id }, undefined, true);
|
|
this.product = await this.$getdata("product", { id: this.product.id }, undefined, true);
|
|
this.productstatus = await this.$getdata("productstatus", { id: this.product.status }, undefined, true);
|
|
this.productnotes = await this.$getdata("productnote", {
|
|
ref: this.product.id,
|
|
});
|
|
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(
|
|
this.isVietnamese ? "Từ chối giao dịch thành công" : "Transaction rejected successfully",
|
|
"Success",
|
|
"Success",
|
|
);
|
|
|
|
this.$emit("close");
|
|
} catch (error) {
|
|
console.error("Error rejecting transaction:", error);
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(this.isVietnamese ? "Lỗi khi từ chối giao dịch" : "Error rejecting transaction", "Error", "Danger");
|
|
} finally {
|
|
this.isRejecting = false;
|
|
}
|
|
},
|
|
|
|
findWorkflowStepResult(steps, stepCode) {
|
|
if (!Array.isArray(steps)) return null;
|
|
const step = steps.find((s) => s.step === stepCode);
|
|
return step?.results?.[0]?.result;
|
|
},
|
|
|
|
findFieldName(code) {
|
|
return this.data.find((v) => v.code === code);
|
|
},
|
|
|
|
copy(value) {
|
|
this.$copyToClipboard(value);
|
|
this.$snackbar(this.isVietnamese ? "Đã copy vào clipboard." : "Copied to clipboard", "Copy", "Success");
|
|
},
|
|
|
|
openPhone() {
|
|
this.showmodal = {
|
|
title: "Điện thoại",
|
|
height: "180px",
|
|
width: "400px",
|
|
component: "common/Phone",
|
|
vbind: { row: this.record, pagename: this.pagename },
|
|
};
|
|
},
|
|
|
|
openCustomer() {
|
|
this.showmodal = {
|
|
title: "Khách hàng",
|
|
height: "40vh",
|
|
width: "50%",
|
|
component: "customer/CustomerView",
|
|
vbind: { row: this.record, pagename: this.pagename },
|
|
};
|
|
},
|
|
|
|
openPoli() {
|
|
if (!this.transaction || !this.product || !this.salepolicy) {
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(
|
|
this.isVietnamese
|
|
? "Không đủ thông tin để hiển thị lịch thanh toán"
|
|
: "Insufficient information to display payment schedule",
|
|
"Warning",
|
|
"Warning",
|
|
);
|
|
return;
|
|
}
|
|
|
|
let plans = [];
|
|
if (this.transaction.payment_plan) {
|
|
if (typeof this.transaction.payment_plan === "string") {
|
|
try {
|
|
plans = JSON.parse(this.transaction.payment_plan);
|
|
} catch (e) {
|
|
console.error("Could not parse payment_plan:", e);
|
|
plans = [];
|
|
}
|
|
} else {
|
|
plans = this.transaction.payment_plan;
|
|
}
|
|
}
|
|
const isPrecalculated = Array.isArray(plans) && plans.length > 0;
|
|
|
|
this.showmodal = {
|
|
title: this.isVietnamese ? "Lịch thanh toán" : "Payment Schedule",
|
|
height: "60vh",
|
|
width: "80%",
|
|
component: "application/PaymentSchedule",
|
|
vbind: {
|
|
productData: this.product,
|
|
policies: [this.salepolicy],
|
|
policyId: this.salepolicy.id,
|
|
selectedPolicy: this.salepolicy,
|
|
isVietnamese: this.isVietnamese,
|
|
originPrice: parseFloat(this.transaction.origin_price || 0),
|
|
discountValueDisplay: parseFloat(this.transaction.discount_amount || 0),
|
|
priceAfterDiscount: parseFloat(this.transaction.sale_price || 0),
|
|
selectedCustomer: this.record,
|
|
detailedDiscounts: [],
|
|
paymentPlans: isPrecalculated ? plans : [],
|
|
isPrecalculated: isPrecalculated,
|
|
baseDate: this.transaction.date || new Date(),
|
|
},
|
|
};
|
|
},
|
|
|
|
openLink() {
|
|
window.open(`https://info.utopia.com.vn/${this.product.link}`, "_blank");
|
|
},
|
|
|
|
openMoney() {
|
|
this.showmodal = {
|
|
title: "Danh sách bút toán",
|
|
height: "50vh",
|
|
width: "70%",
|
|
component: "datatable/DataView",
|
|
pagename: "iddnwjndujwi",
|
|
vbind: {
|
|
api: "payment_schedule",
|
|
setting: "internal-entry-list",
|
|
params: {
|
|
sort: "-id",
|
|
filter: {
|
|
txn_detail__transaction: this.transactionId,
|
|
entry__ref: this.reservation.code,
|
|
},
|
|
exclude: {
|
|
entry__code: "null",
|
|
},
|
|
values:
|
|
"entry__ref,entry__account__currency__code,entry__balance_before,entry__type__name,entry__inputer__fullname,entry__approver__fullname,entry__balance_after,entry,entry__code,entry__date,entry__amount,entry__create_time,entry__account__code,entry__account__type__name,entry__content,entry__category__name",
|
|
},
|
|
},
|
|
};
|
|
},
|
|
|
|
openUser(userId) {
|
|
if (!userId) return;
|
|
this.showmodal = {
|
|
component: "user/UserInfo",
|
|
width: "50%",
|
|
height: "200px",
|
|
title: "User",
|
|
vbind: { userId: userId },
|
|
};
|
|
},
|
|
|
|
handleModalEvent(event) {
|
|
console.log("Modal event received:", event); // Debug log để kiểm tra event
|
|
|
|
if (event.name === "confirm") {
|
|
if (this.actionToConfirm === "contract") {
|
|
this.executeConfirmContract();
|
|
}
|
|
if (this.actionToConfirm === "quickAddTransaction") {
|
|
this.quickAddTransaction();
|
|
}
|
|
} else if (event.name === "noteConfirm") {
|
|
const note = event.data.note;
|
|
if (this.actionToConfirm === "supplement") {
|
|
this.executeSupplement(
|
|
`Yêu cầu bổ sung thông tin cho giao dịch ${this.transaction.code} của sản phẩm với lý do: ${note}`,
|
|
);
|
|
} else if (this.actionToConfirm === "reject") {
|
|
this.executeReject(`Từ chối giao dịch ${this.transaction.code} của sản phẩm với lý do: ${note}`);
|
|
}
|
|
} else {
|
|
this.changeInfo(event);
|
|
}
|
|
},
|
|
|
|
// New handler for noteConfirm event
|
|
handleNoteConfirmEvent(data) {
|
|
console.log("noteConfirm event received:", data); // Debug log
|
|
this.handleModalEvent({ name: "noteConfirm", data });
|
|
},
|
|
|
|
changeInfo(v) {
|
|
this.record = this.$copy(v);
|
|
},
|
|
|
|
confirmQuickAddTransaction() {
|
|
this.actionToConfirm = "quickAddTransaction";
|
|
this.showmodal = {
|
|
component: "dialog/Confirm",
|
|
title: this.isVietnamese ? "Xác nhận giữ chỗ nhanh" : "Confirm Quick Add",
|
|
width: "auto",
|
|
height: "auto",
|
|
vbind: {
|
|
content: this.isVietnamese
|
|
? "Bạn có chắc chắn muốn khóa sản phẩm này?"
|
|
: "Are you sure you want to lock this product?",
|
|
},
|
|
};
|
|
},
|
|
|
|
async quickAddTransaction() {
|
|
const { id, username } = this.store.login;
|
|
|
|
// Tính thời gian khóa từ bây giờ + số phút nhập vào
|
|
const lockedUntilValue = this.$dayjs().add(this.lockDurationMinutes, "minutes").toISOString();
|
|
|
|
const patchProductPayload = {
|
|
id: this.product.id,
|
|
status: 15, // Cố định status thành 15
|
|
locked_until: lockedUntilValue,
|
|
};
|
|
|
|
const postProductNotePayload = {
|
|
ref: this.product.id,
|
|
user: id,
|
|
detail: `${username} ${this.isVietnamese ? "khóa sản phẩm trong" : "locked product for"} ${this.lockDurationMinutes} ${this.isVietnamese ? "phút đến lúc" : "minutes until"} ${this.$dayjs(lockedUntilValue).format("HH:mm:ss - L")}`,
|
|
};
|
|
|
|
try {
|
|
await this.$patchapi("product", patchProductPayload);
|
|
await this.$insertapi("productnote", postProductNotePayload);
|
|
|
|
// Refresh data
|
|
const prodId = this.productId || this.transaction?.product;
|
|
if (prodId) {
|
|
this.product = await this.$getdata("product", { id: prodId }, undefined, true);
|
|
this.productstatus = await this.$getdata("productstatus", { id: this.product.status }, undefined, true);
|
|
this.productnotes = await this.$getdata("productnote", {
|
|
ref: prodId,
|
|
});
|
|
}
|
|
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(this.isVietnamese ? "Khóa sản phẩm thành công" : "Product locked successfully", "Success", "Success");
|
|
|
|
// Reset
|
|
this.isLockingProduct = false;
|
|
this.lockDurationMinutes = 30;
|
|
} catch (error) {
|
|
console.error("Error locking product:", error);
|
|
const { $snackbar } = useNuxtApp();
|
|
$snackbar(this.isVietnamese ? "Lỗi khi khóa sản phẩm" : "Error locking product", "Error", "Danger");
|
|
}
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
transactionId() {
|
|
this.paymentScheduleKey++;
|
|
},
|
|
|
|
isLockingProduct(newVal) {
|
|
if (newVal) {
|
|
this.lockDurationMinutes = 30; // Reset về 30 phút khi tích
|
|
}
|
|
},
|
|
},
|
|
|
|
beforeUnmount() {
|
|
if (this.paymentSchedulePagename) {
|
|
this.store.commit(this.paymentSchedulePagename, undefined);
|
|
}
|
|
},
|
|
};
|
|
</script>
|