1186 lines
42 KiB
Vue
1186 lines
42 KiB
Vue
<template>
|
|
<div
|
|
v-if="productData"
|
|
class="grid px-3"
|
|
>
|
|
<div class="cell is-col-span-12">
|
|
<div
|
|
v-if="filteredPolicies.length > 1 && !policyId && !isPrecalculated"
|
|
class="tabs is-boxed mb-4"
|
|
>
|
|
<ul>
|
|
<li
|
|
v-for="pol in filteredPolicies"
|
|
:key="pol.id"
|
|
:class="{ 'is-active': activeTab === pol.id }"
|
|
>
|
|
<a @click="$emit('policy-selected', pol)">
|
|
<span
|
|
v-if="activeTab === pol.id"
|
|
class="has-text-weight-bold"
|
|
>{{ pol.code }}</span
|
|
>
|
|
<span v-else>{{ pol.code }}</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div
|
|
v-else-if="filteredPolicies.length === 0"
|
|
class="notification is-light is-size-6"
|
|
>
|
|
Không có chính sách thanh toán.
|
|
</div>
|
|
|
|
<div id="schedule-content">
|
|
<div
|
|
v-if="selectedPolicy"
|
|
id="print-area"
|
|
:class="{ 'is-loading': isLoadingPlans }"
|
|
>
|
|
<div class="mb-4 is-flex is-justify-content-space-between is-align-items-center">
|
|
<h3 class="title is-4 has-text-primary mb-1">
|
|
{{ selectedPolicy.name }}
|
|
</h3>
|
|
<div>
|
|
<span class="button is-white">
|
|
<span class="has-text-weight-semibold">Đơn vị: VNĐ</span>
|
|
</span>
|
|
<button
|
|
class="button is-light"
|
|
@click="$emit('print')"
|
|
id="ignore-print"
|
|
>
|
|
<span class="is-size-6">In</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div style="border-bottom: 1px solid #eee"></div>
|
|
<div class="fixed-grid has-4-cols-mobile has-7-cols-desktop">
|
|
<div class="grid">
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Sản phẩm</p>
|
|
<p class="has-text-primary has-text-weight-medium">
|
|
{{ productData.trade_code || productData.code }}
|
|
</p>
|
|
</div>
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Giá niêm yết</p>
|
|
<p class="has-text-primary">{{ $numtoString(originPrice) }}</p>
|
|
</div>
|
|
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Tổng chiết khấu</p>
|
|
<p class="has-text-danger has-text-weight-bold">
|
|
{{ $numtoString(discountValueDisplay) }}
|
|
</p>
|
|
</div>
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Giá sau chiết khấu</p>
|
|
<p class="has-text-black has-text-weight-bold">
|
|
{{ $numtoString(priceAfterDiscount) }}
|
|
</p>
|
|
</div>
|
|
<div
|
|
v-if="selectedPolicy.contract_allocation_percentage < 100"
|
|
class="cell is-col-span-6-mobile is-col-span-1-desktop"
|
|
>
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Giá trị bảo đảm</p>
|
|
<p class="has-text-primary">
|
|
{{ $numtoString(allocatedPrice) }}
|
|
</p>
|
|
</div>
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Đặt cọc</p>
|
|
<p class="has-text-primary">
|
|
{{ $numtoString(selectedPolicy.deposit) }}
|
|
</p>
|
|
</div>
|
|
<div class="cell is-col-span-6-mobile is-col-span-1-desktop">
|
|
<p class="is-size-6 has-text-weight-bold mb-1">Khách hàng</p>
|
|
<p
|
|
v-if="selectedCustomer"
|
|
class="has-text-primary has-text-weight-medium"
|
|
>
|
|
{{ selectedCustomer.code }} - {{ selectedCustomer.fullname }}
|
|
</p>
|
|
<p
|
|
v-else
|
|
class="has-text-grey is-italic is-size-6"
|
|
>
|
|
Chưa chọn
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border-bottom: 1px solid #eee"></div>
|
|
|
|
<div
|
|
v-if="detailedDiscounts.length > 0"
|
|
class="mt-4 mb-4"
|
|
>
|
|
<p class="has-text-weight-bold is-size-5 mb-2 has-text-primary is-underlined">CHI TIẾT CHIẾT KHẤU:</p>
|
|
<table class="table is-fullwidth is-bordered is-narrow">
|
|
<thead>
|
|
<tr class="has-background-primary">
|
|
<th
|
|
class="has-background-primary has-text-white"
|
|
colspan="2"
|
|
>
|
|
Diễn giải chiết khấu
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-right has-text-white"
|
|
width="15%"
|
|
>
|
|
Giá trị
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-right has-text-white"
|
|
width="20%"
|
|
>
|
|
Thành tiền
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-right has-text-white"
|
|
width="20%"
|
|
>
|
|
Còn lại
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="has-text-grey-light">
|
|
<td
|
|
colspan="4"
|
|
class="has-text-right pt-1 pb-1"
|
|
>
|
|
Giá gốc
|
|
</td>
|
|
<td class="has-text-right has-text-weight-bold pt-1 pb-1">
|
|
{{ $numtoString(originPrice) }}
|
|
</td>
|
|
</tr>
|
|
<tr
|
|
v-for="(item, idx) in detailedDiscounts"
|
|
:key="`discount-${idx}`"
|
|
>
|
|
<td
|
|
width="5%"
|
|
class="has-text-centered"
|
|
>
|
|
{{ idx + 1 }}
|
|
</td>
|
|
<td>
|
|
<span class="has-text-weight-semibold">{{ item.name }}</span>
|
|
<span class="tag is-primary has-text-white is-rounded border ml-1">{{ item.code }}</span>
|
|
</td>
|
|
<td class="has-text-right">
|
|
{{ item.customType === 1 ? item.customValue + "%" : $numtoString(item.customValue) }}
|
|
</td>
|
|
<td class="has-text-right has-text-danger">-{{ $numtoString(item.amount) }}</td>
|
|
<td class="has-text-right has-text-primary">
|
|
{{ $numtoString(item.remaining) }}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div
|
|
v-if="enableEarlyPayment && earlyPaymentCycles > 0"
|
|
class="mt-4"
|
|
>
|
|
<p class="has-text-weight-bold is-size-5 mb-2 has-text-primary is-underlined">
|
|
BẢNG DÙNG TIỀN THEO CHÍNH SÁCH BÁN HÀNG
|
|
</p>
|
|
<table class="table is-fullwidth is-hoverable is-bordered is-size-6">
|
|
<thead>
|
|
<tr class="has-background-primary">
|
|
<th class="has-background-primary has-text-white">Tiến độ</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Số tiền TT (VND)</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Ngày đến hạn TT</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Số ngày</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Tỷ lệ thanh toán</th>
|
|
<th class="has-background-primary has-text-white">Ghi chú</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>0</td>
|
|
<td class="has-text-right">
|
|
{{ $numtoString(selectedPolicy.deposit) }}
|
|
</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right">-</td>
|
|
<td>Tiền đặt cọc</td>
|
|
</tr>
|
|
<tr
|
|
v-for="plan in enhancedCashFlowPlans"
|
|
:key="plan.id"
|
|
>
|
|
<td>{{ plan.cycle }}</td>
|
|
<td class="has-text-right">
|
|
{{ $numtoString(plan.originalCalculatedAmount) }}
|
|
</td>
|
|
<td class="has-text-right">{{ plan.dueDate }}</td>
|
|
<td class="has-text-right">{{ plan.days }}</td>
|
|
<td class="has-text-right">{{ plan.displayValue }}</td>
|
|
<td>{{ plan.payment_note }}</td>
|
|
</tr>
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="has-background-light">
|
|
<th
|
|
colspan="1"
|
|
class="has-text-right has-text-weight-bold"
|
|
>
|
|
Tổng cộng
|
|
</th>
|
|
<th class="has-text-right has-text-primary has-text-weight-bold">
|
|
{{ $numtoString(priceAfterDiscount) }}
|
|
</th>
|
|
<th colspan="4"></th>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<div
|
|
v-if="selectedPolicy && selectedPolicy.method === 1 && selectedPolicy.contract_allocation_percentage == 100"
|
|
class="mb-4 mt-4"
|
|
>
|
|
<div class="level is-mobile mb-3">
|
|
<div class="level-left">
|
|
<label
|
|
class="checkbox"
|
|
id="ignore-print"
|
|
>
|
|
<a
|
|
class="mr-5"
|
|
@click="doTick()"
|
|
>
|
|
<SvgIcon
|
|
v-bind="{
|
|
name: enableEarlyPayment ? 'check4.svg' : 'uncheck.svg',
|
|
type: 'primary',
|
|
size: 28,
|
|
}"
|
|
/>
|
|
</a>
|
|
<span class="is-size-5 has-text-weight-semibold has-text-primary mr-5">
|
|
Thanh toán sớm (2 - {{ Math.max(2, plansToRender.length) }}) kỳ
|
|
</span>
|
|
</label>
|
|
<transition
|
|
name="fade"
|
|
id="ignore-print"
|
|
>
|
|
<div
|
|
v-if="enableEarlyPayment && plansToRender.length >= 2"
|
|
class="field"
|
|
>
|
|
<div class="control">
|
|
<input
|
|
class="input"
|
|
type="number"
|
|
v-model.number="earlyPaymentCycles"
|
|
:min="2"
|
|
:max="Math.max(2, plansToRender.length)"
|
|
@change="updateEarlyPaymentPlans"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="enableEarlyPayment && earlyPaymentCycles > 0"
|
|
class="mt-4"
|
|
>
|
|
<p class="has-text-weight-bold is-size-5 mb-2 has-text-primary is-underlined">
|
|
BẢNG DÙNG TIỀN THEO CHƯƠNG TRÌNH THANH TOÁN SỚM BẰNG VỐN TỰ CÓ
|
|
</p>
|
|
<table class="table is-fullwidth is-hoverable is-bordered is-size-6">
|
|
<thead>
|
|
<tr class="has-background-primary">
|
|
<th class="has-background-primary has-text-white">Tiến độ</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Số tiền TT (VND)</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Ngày đến hạn TT</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Ngày TT thực tế</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Số ngày TT trước hạn</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Lãi suất/ngày</th>
|
|
<th class="has-background-primary has-text-white has-text-right">Số tiền Chiết khấu TT (VND)</th>
|
|
<th class="has-background-primary has-text-white">Ghi chú</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>0</td>
|
|
<td class="has-text-right">
|
|
{{ $numtoString(selectedPolicy.deposit) }}
|
|
</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right">-</td>
|
|
<td class="has-text-right has-text-danger">0</td>
|
|
<td>Tiền đặt cọc</td>
|
|
</tr>
|
|
<tr
|
|
v-for="plan in enhancedCashFlowPlans"
|
|
:key="plan.id"
|
|
>
|
|
<td>{{ plan.cycle }}</td>
|
|
<td class="has-text-right">
|
|
{{ $numtoString(plan.originalCalculatedAmount) }}
|
|
</td>
|
|
<td class="has-text-right">{{ plan.dueDate }}</td>
|
|
<td class="has-text-right">
|
|
<span v-if="plan.isEarly">{{ plan.actualDueDate }}</span>
|
|
<span v-else>-</span>
|
|
</td>
|
|
<td class="has-text-right">
|
|
<span v-if="plan.isEarly">{{ plan.days }}</span>
|
|
<span v-else>-</span>
|
|
</td>
|
|
<td class="has-text-right">
|
|
<span v-if="plan.isEarly">0.019%</span>
|
|
<span v-else>-</span>
|
|
</td>
|
|
<td class="has-text-right has-text-danger">
|
|
{{ $numtoString(plan.discountAmount) }}
|
|
</td>
|
|
<td>{{ plan.payment_note }}</td>
|
|
</tr>
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="has-background-light">
|
|
<th
|
|
colspan="6"
|
|
class="has-text-right has-text-weight-bold"
|
|
>
|
|
Tổng cộng
|
|
</th>
|
|
<th class="has-text-right has-text-danger has-text-weight-bold">
|
|
{{ $numtoString(totalEarlyDiscount) }}
|
|
</th>
|
|
<th></th>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class=""
|
|
style="border-top: 1px solid #eee"
|
|
>
|
|
<div class="level is-mobile is-size-6 m-0">
|
|
<div class="level-right">
|
|
<div class="level-item">
|
|
<span class="is-uppercase is-size-4 has-text-weight-semibold">Tổng cộng: </span>
|
|
<div class="is-flex is-align-items-center is-flex-wrap-wrap">
|
|
<span class="has-text-success has-text-weight-bold is-size-4">
|
|
{{ $numtoString(allocatedPrice) }}
|
|
</span>
|
|
<span
|
|
v-if="totalEarlyDiscount > 0"
|
|
class="has-text-danger has-text-weight-bold is-size-4 ml-3"
|
|
>
|
|
- {{ $numtoString(totalEarlyDiscount) }}
|
|
</span>
|
|
<span
|
|
v-if="totalEarlyDiscount > 0"
|
|
class="has-text-success has-text-weight-bold is-size-4 ml-3"
|
|
>
|
|
= {{ $numtoString(totalRealPayment) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="displayPaymentPlans.length > 0">
|
|
<div class="level m-0 is-mobile mt-4">
|
|
<div class="level-left">
|
|
<p class="has-text-weight-bold is-size-5 mb-2 has-text-primary is-underlined">LỊCH THANH TOÁN</p>
|
|
<span
|
|
v-if="baseDate"
|
|
class="tag is-info is-light ml-2"
|
|
>
|
|
Từ ngày: {{ formatDate(baseDate) }}
|
|
</span>
|
|
</div>
|
|
<div
|
|
class="level-right"
|
|
id="ignore-print"
|
|
>
|
|
<div class="buttons are-small has-addons">
|
|
<button
|
|
class="button"
|
|
@click="viewMode = 'table'"
|
|
:class="viewMode === 'table' ? 'is-link is-selected' : 'is-light'"
|
|
>
|
|
<span class="is-size-6">Bảng</span>
|
|
</button>
|
|
<button
|
|
class="button"
|
|
@click="viewMode = 'list'"
|
|
:class="viewMode === 'list' ? 'is-link is-selected' : 'is-light'"
|
|
>
|
|
<span class="is-size-6">Thẻ</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="viewMode === 'table'"
|
|
class="table-container schedule-container"
|
|
>
|
|
<table class="table is-fullwidth is-hoverable is-size-6">
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal"
|
|
style="border: none"
|
|
>
|
|
Đợt thanh toán
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal"
|
|
style="border: none"
|
|
>
|
|
Diễn giải
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal has-text-right"
|
|
style="border: none"
|
|
>
|
|
Tỷ lệ
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal has-text-right"
|
|
style="border: none"
|
|
>
|
|
Số tiền
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal has-text-right"
|
|
style="border: none"
|
|
>
|
|
Thời gian
|
|
</th>
|
|
<th
|
|
class="has-background-primary has-text-white has-font-weight-normal has-text-right"
|
|
style="border: none"
|
|
>
|
|
Hạn thanh toán
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="(plan, index) in calculatedPlans"
|
|
:key="plan.id || index"
|
|
style="border-bottom: 1px solid #f5f5f5"
|
|
>
|
|
<td
|
|
class="is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<span class="has-text-primary has-text-weight-medium"> Đợt {{ plan.displayCycle }} </span>
|
|
<span
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="tag is-warning is-light ml-1 is-size-6"
|
|
>
|
|
GỘP SỚM
|
|
</span>
|
|
</td>
|
|
<td
|
|
class="is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<div
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="content is-size-6"
|
|
>
|
|
<p class="mb-1 has-text-weight-semibold">
|
|
Thanh toán sớm gộp (Đợt
|
|
{{ plan.mergedCycles.join(", ") }})
|
|
</p>
|
|
<p class="has-text-grey">
|
|
{{ plan.payment_note || "-" }}
|
|
</p>
|
|
</div>
|
|
<div v-else>
|
|
<span>{{ plan.payment_note || "-" }}</span>
|
|
<div
|
|
v-if="plan.due_note"
|
|
class="is-size-6 mt-1"
|
|
>
|
|
{{ plan.due_note }}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td
|
|
class="has-text-right is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<span v-if="plan.isEarlyPaymentMerged">{{ plan.mergedCyclesRates }}</span>
|
|
<span v-else>{{ plan.displayValue }}</span>
|
|
</td>
|
|
<td
|
|
class="has-text-right is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<div
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="content is-size-6"
|
|
>
|
|
<div class="is-size-6 has-text-info mb-1">
|
|
Tổng các đợt: {{ $numtoString(plan.mergedRawAmount) }}
|
|
</div>
|
|
<div class="is-size-6 has-text-danger mb-1">
|
|
- Số tiền chiết khấu sớm:
|
|
{{ $numtoString(totalEarlyDiscount) }}
|
|
</div>
|
|
<div
|
|
v-if="selectedPolicy && selectedPolicy.deposit > 0"
|
|
class="is-size-6 has-text-danger mb-1"
|
|
>
|
|
- Đặt cọc: {{ $numtoString(selectedPolicy.deposit) }}
|
|
</div>
|
|
<span class="has-text-primary has-text-weight-bold">
|
|
Còn lại: {{ $numtoString(plan.calculatedAmount) }}
|
|
</span>
|
|
</div>
|
|
<div v-else-if="plan.isFirstPlan && selectedPolicy && selectedPolicy.deposit > 0">
|
|
<div class="is-size-6 has-text-primary mb-1">
|
|
{{ $numtoString(plan.amountBeforeDeposit) }}
|
|
</div>
|
|
<div class="is-size-6 has-text-danger mb-1">- {{ $numtoString(selectedPolicy.deposit) }}</div>
|
|
<span class="has-text-primary has-text-weight-bold">
|
|
Còn lại: {{ $numtoString(plan.calculatedAmount) }}
|
|
</span>
|
|
</div>
|
|
<span
|
|
v-else
|
|
class="has-text-primary has-text-weight-bold"
|
|
>{{ $numtoString(plan.calculatedAmount) }}</span
|
|
>
|
|
</td>
|
|
<td
|
|
class="has-text-right pr-0 is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<span
|
|
v-if="plan.days"
|
|
class="has-text-success"
|
|
>{{ plan.days }} ngày</span
|
|
>
|
|
<span v-else>-</span>
|
|
</td>
|
|
<td
|
|
class="has-text-right pr-3 is-vcentered"
|
|
style="border: none"
|
|
>
|
|
<span
|
|
v-if="plan.dueDate"
|
|
class="has-text-success"
|
|
>{{ plan.dueDate }}</span
|
|
>
|
|
<span v-else>-</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div
|
|
v-else-if="viewMode === 'list'"
|
|
class="schedule-container"
|
|
>
|
|
<div
|
|
v-for="(plan, index) in calculatedPlans"
|
|
:key="plan.id || index"
|
|
class="mb-4 pr-2 pb-3"
|
|
style="border-bottom: 2px solid #f5f5f5"
|
|
:class="plan.isEarlyPaymentMerged ? ' p-3' : ''"
|
|
>
|
|
<div class="level is-mobile mb-1">
|
|
<div class="level-left">
|
|
<span class="tag is-white p-0 mr-2 border">{{ plan.displayCycle }}</span>
|
|
<span class="has-text-primary has-text-weight-medium is-size-6"> Đợt {{ plan.displayCycle }} </span>
|
|
<span
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="tag is-warning is-light ml-1 is-size-6"
|
|
>
|
|
GỘP SỚM
|
|
</span>
|
|
</div>
|
|
<div class="level-right">
|
|
<div
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="has-text-right"
|
|
>
|
|
<p class="has-text-info has-text-weight-bold is-size-6">
|
|
{{ $numtoString(plan.calculatedAmount) }}
|
|
</p>
|
|
<p class="has-text-grey is-size-6">Với chiết khấu sớm</p>
|
|
</div>
|
|
<div
|
|
v-else-if="plan.isFirstPlan && selectedPolicy && selectedPolicy.deposit > 0"
|
|
class="has-text-right"
|
|
>
|
|
<div class="is-size-6 has-text-grey">
|
|
{{ $numtoString(plan.amountBeforeDeposit) }} -
|
|
{{ $numtoString(selectedPolicy.deposit) }}
|
|
</div>
|
|
<span class="has-text-primary has-text-weight-bold is-size-6">{{
|
|
$numtoString(plan.calculatedAmount)
|
|
}}</span>
|
|
</div>
|
|
<span
|
|
v-else
|
|
class="has-text-primary has-text-weight-bold is-size-6"
|
|
>{{ $numtoString(plan.calculatedAmount) }}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="plan.isEarlyPaymentMerged"
|
|
class="content is-size-6 mb-2"
|
|
>
|
|
<p class="has-text-weight-semibold">Thanh toán sớm gộp (Đợt {{ plan.mergedCycles.join(", ") }})</p>
|
|
<p class="has-text-grey is-size-6 mt-1">
|
|
{{ plan.mergedCyclesRates }}
|
|
</p>
|
|
</div>
|
|
|
|
<div
|
|
v-if="plan.payment_note"
|
|
class="is-size-6 mb-1"
|
|
>
|
|
<span class="has-text-grey">Diễn giải:</span>
|
|
{{ plan.payment_note }}
|
|
</div>
|
|
<div
|
|
v-if="plan.due_note"
|
|
class="is-size-6 mb-1"
|
|
>
|
|
{{ plan.due_note }}
|
|
</div>
|
|
<div class="level is-mobile is-size-6">
|
|
<div class="level-left">
|
|
<span
|
|
v-if="plan.dueDate"
|
|
class="has-text-success"
|
|
>
|
|
Hạn: {{ plan.dueDate }} - {{ plan.days }}ngày
|
|
</span>
|
|
<span v-else>-</span>
|
|
</div>
|
|
<div class="level-right">
|
|
<span v-if="!plan.isEarlyPaymentMerged">{{ plan.displayValue }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-else
|
|
class="section has-text-centered"
|
|
>
|
|
<p
|
|
v-if="isLoadingPlans"
|
|
class="is-size-6 has-text-info is-flex is-align-items-center is-gap-2"
|
|
>
|
|
<SvgIcon v-bind="{ name: 'loading.svg', type: 'primary', size: 18 }" />
|
|
<span>Đang tải kế hoạch...</span>
|
|
</p>
|
|
<p
|
|
v-else
|
|
class="is-size-6"
|
|
>
|
|
Chưa có dữ liệu kế hoạch
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { useStore } from "@/stores/index";
|
|
import dayjs from "dayjs";
|
|
import { useNuxtApp } from "#app";
|
|
|
|
const EARLY_PAYMENT_DAILY_RATE = 0.019;
|
|
|
|
export default {
|
|
name: "PaymentScheduleComplete",
|
|
props: {
|
|
productData: Object,
|
|
policies: Array,
|
|
activeTab: [String, Number],
|
|
selectedPolicy: Object,
|
|
originPrice: Number,
|
|
discountValueDisplay: Number,
|
|
priceAfterDiscount: Number,
|
|
selectedCustomer: Object,
|
|
detailedDiscounts: Array,
|
|
paymentPlans: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
allPaymentPlans: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
policyId: {
|
|
type: [String, Number],
|
|
default: null,
|
|
},
|
|
baseDate: {
|
|
type: [String, Date],
|
|
default: null,
|
|
},
|
|
isPrecalculated: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
emits: ["policy-selected", "print", "plans-loaded", "early-payment-change", "calculated-plans-change"],
|
|
setup() {
|
|
const store = useStore();
|
|
const { $getdata } = useNuxtApp();
|
|
return { store, $getdata };
|
|
},
|
|
data() {
|
|
return {
|
|
viewMode: "table",
|
|
localPaymentPlans: [],
|
|
isLoadingPlans: false,
|
|
enableEarlyPayment: false,
|
|
earlyPaymentCycles: 0,
|
|
earlyPaymentDetails: [],
|
|
};
|
|
},
|
|
computed: {
|
|
filteredPolicies() {
|
|
if (!this.policies || this.policies.length === 0) return [];
|
|
if (this.policyId) {
|
|
const foundPolicy = this.policies.find((p) => p.id === this.policyId || p.id == this.policyId);
|
|
return foundPolicy ? [foundPolicy] : [];
|
|
}
|
|
return this.policies;
|
|
},
|
|
|
|
calculationStartDate() {
|
|
if (this.baseDate) {
|
|
return dayjs(this.baseDate);
|
|
}
|
|
return dayjs();
|
|
},
|
|
|
|
plansToRender() {
|
|
if (this.paymentPlans && this.paymentPlans.length > 0) {
|
|
return this.paymentPlans;
|
|
}
|
|
return this.localPaymentPlans;
|
|
},
|
|
|
|
allocatedPrice() {
|
|
if (!this.selectedPolicy) {
|
|
return this.priceAfterDiscount;
|
|
}
|
|
const basePrice = this.priceAfterDiscount;
|
|
if (this.selectedPolicy.contract_allocation_percentage > 0) {
|
|
const allocation = Number(this.selectedPolicy.contract_allocation_percentage);
|
|
return (basePrice * allocation) / 100;
|
|
}
|
|
return basePrice;
|
|
},
|
|
|
|
hasEarlyPaymentDiscount() {
|
|
return this.enableEarlyPayment && this.earlyPaymentDetails && this.earlyPaymentDetails.length > 0;
|
|
},
|
|
|
|
totalEarlyDiscount() {
|
|
if (!this.hasEarlyPaymentDiscount) return 0;
|
|
return this.earlyPaymentDetails.reduce((sum, d) => sum + d.discountAmount, 0);
|
|
},
|
|
|
|
totalEarlyPayment() {
|
|
if (!this.hasEarlyPaymentDiscount) return 0;
|
|
return this.earlyPaymentDetails.reduce((sum, d) => sum + d.netAmount, 0);
|
|
},
|
|
|
|
mergedRawAmount() {
|
|
if (!this.hasEarlyPaymentDiscount) return 0;
|
|
return this.earlyPaymentDetails.reduce((sum, d) => sum + d.rawAmount, 0);
|
|
},
|
|
|
|
finalBalanceAfterEarlyDiscount() {
|
|
if (this.detailedDiscounts.length === 0) {
|
|
return this.originPrice - this.totalEarlyDiscount;
|
|
}
|
|
const lastBalance = this.detailedDiscounts[this.detailedDiscounts.length - 1].remaining;
|
|
return lastBalance - this.totalEarlyDiscount;
|
|
},
|
|
|
|
enhancedCashFlowPlans() {
|
|
if (!this.enableEarlyPayment || !this.plansToRender.length) return [];
|
|
const earlyDetailsByCycle = new Map(this.earlyPaymentDetails.map((d) => [d.cycle, d]));
|
|
|
|
return this.plansToRender.map((plan) => {
|
|
const originalAmount = plan.type === 1 ? (this.allocatedPrice * Number(plan.value)) / 100 : Number(plan.value);
|
|
const earlyDetail = earlyDetailsByCycle.get(plan.cycle);
|
|
|
|
if (earlyDetail) {
|
|
return {
|
|
...plan,
|
|
isEarly: true,
|
|
originalCalculatedAmount: originalAmount,
|
|
discountAmount: earlyDetail.discountAmount,
|
|
netAmount: earlyDetail.netAmount,
|
|
actualDueDate: this.calculationStartDate.format("DD/MM/YYYY"),
|
|
dueDate: plan.days > 0 ? this.calculationStartDate.add(plan.days, "day").format("DD/MM/YYYY") : null,
|
|
displayValue: plan.type === 1 ? `${plan.value}%` : this.$numtoString(plan.value),
|
|
};
|
|
} else {
|
|
return {
|
|
...plan,
|
|
isEarly: false,
|
|
originalCalculatedAmount: originalAmount,
|
|
discountAmount: 0,
|
|
netAmount: originalAmount,
|
|
actualDueDate: null,
|
|
dueDate: plan.days > 0 ? this.calculationStartDate.add(plan.days, "day").format("DD/MM/YYYY") : null,
|
|
displayValue: plan.type === 1 ? `${plan.value}%` : this.$numtoString(plan.value),
|
|
};
|
|
}
|
|
});
|
|
},
|
|
|
|
displayPaymentPlans() {
|
|
if (!this.plansToRender.length || !this.selectedPolicy) return [];
|
|
|
|
if (!this.hasEarlyPaymentDiscount || this.earlyPaymentCycles === 0) {
|
|
let cycleCounter = 1;
|
|
return this.plansToRender.map((plan, index) => {
|
|
let calculatedAmount =
|
|
plan.type === 1 ? (this.allocatedPrice * Number(plan.value)) / 100 : Number(plan.value);
|
|
const isFirstPlan = index === 0;
|
|
const amountBeforeDeposit = calculatedAmount;
|
|
|
|
if (isFirstPlan && this.selectedPolicy && this.selectedPolicy.deposit > 0) {
|
|
calculatedAmount -= this.selectedPolicy.deposit;
|
|
}
|
|
|
|
let dueDate = null;
|
|
const daysDiff = plan.days || 0;
|
|
if (daysDiff > 0) {
|
|
const dueDateObj = this.calculationStartDate.add(daysDiff, "day");
|
|
dueDate = dueDateObj.format("DD/MM/YYYY");
|
|
}
|
|
|
|
return {
|
|
...plan,
|
|
displayCycle: cycleCounter++,
|
|
calculatedAmount: calculatedAmount,
|
|
amountBeforeDeposit: amountBeforeDeposit,
|
|
displayValue: plan.type === 1 ? `${plan.value}%` : this.$numtoString(plan.value),
|
|
dueDate: dueDate,
|
|
isEarlyPaymentMerged: false,
|
|
isFirstPlan: isFirstPlan,
|
|
};
|
|
});
|
|
}
|
|
|
|
const earlyPaymentCycles = this.earlyPaymentCycles;
|
|
const basePrice = this.allocatedPrice;
|
|
const displayPlans = [];
|
|
let cycleCounter = 1;
|
|
|
|
const mergedCyclesInfo = this.earlyPaymentDetails
|
|
.map((d) => {
|
|
return `Đợt ${d.cycle}: ${d.type === 1 ? d.value + "%" : this.$numtoString(d.value)}`;
|
|
})
|
|
.join(" + ");
|
|
|
|
displayPlans.push({
|
|
cycle: earlyPaymentCycles,
|
|
displayCycle: cycleCounter++,
|
|
mergedCycles: Array.from({ length: this.earlyPaymentCycles }, (_, i) => i + 1),
|
|
mergedCyclesRates: mergedCyclesInfo,
|
|
mergedRawAmount: this.mergedRawAmount,
|
|
isEarlyPaymentMerged: true,
|
|
calculatedAmount: this.totalEarlyPayment - (this.selectedPolicy.deposit || 0),
|
|
days: 0,
|
|
dueDate: this.calculationStartDate.format("DD/MM/YYYY"),
|
|
payment_note: "Thanh toán sớm gộp",
|
|
displayValue: "-",
|
|
isFirstPlan: true,
|
|
});
|
|
|
|
for (let i = earlyPaymentCycles; i < this.plansToRender.length; i++) {
|
|
const plan = this.plansToRender[i];
|
|
let calculatedAmount = plan.type === 1 ? (basePrice * Number(plan.value)) / 100 : Number(plan.value);
|
|
const amountBeforeDeposit = calculatedAmount;
|
|
|
|
let dueDate = null;
|
|
const daysDiff = plan.days || 0;
|
|
if (daysDiff > 0) {
|
|
const dueDateObj = this.calculationStartDate.add(daysDiff, "day");
|
|
dueDate = dueDateObj.format("DD/MM/YYYY");
|
|
}
|
|
|
|
displayPlans.push({
|
|
...plan,
|
|
displayCycle: cycleCounter++,
|
|
calculatedAmount: calculatedAmount,
|
|
amountBeforeDeposit: amountBeforeDeposit,
|
|
displayValue: plan.type === 1 ? `${plan.value}%` : this.$numtoString(plan.value),
|
|
dueDate: dueDate,
|
|
isEarlyPaymentMerged: false,
|
|
isFirstPlan: false,
|
|
});
|
|
}
|
|
|
|
return displayPlans;
|
|
},
|
|
|
|
calculatedPlans() {
|
|
if (this.isPrecalculated && this.paymentPlans && this.paymentPlans.length > 0) {
|
|
return this.paymentPlans.map((plan, index) => {
|
|
const dueDate = plan.due_days
|
|
? this.calculationStartDate.add(plan.due_days, "day").format("DD/MM/YYYY")
|
|
: null;
|
|
return {
|
|
...plan,
|
|
displayCycle: plan.cycle || index + 1,
|
|
calculatedAmount: plan.amount,
|
|
displayValue: plan.is_early_merged ? "Gộp sớm" : "-",
|
|
dueDate: dueDate,
|
|
days: plan.due_days,
|
|
payment_note: plan.note || "-",
|
|
isEarlyPaymentMerged: plan.is_early_merged,
|
|
mergedCycles: plan.merged_cycles,
|
|
mergedRawAmount: plan.raw_amount,
|
|
isFirstPlan: index === 0,
|
|
amountBeforeDeposit: plan.raw_amount,
|
|
};
|
|
});
|
|
}
|
|
return this.displayPaymentPlans;
|
|
},
|
|
|
|
totalRealPayment() {
|
|
let total = this.allocatedPrice;
|
|
if (this.hasEarlyPaymentDiscount) {
|
|
total -= this.totalEarlyDiscount;
|
|
}
|
|
return total;
|
|
},
|
|
},
|
|
watch: {
|
|
calculatedPlans: {
|
|
handler(newVal) {
|
|
this.$emit("calculated-plans-change", newVal);
|
|
},
|
|
immediate: true,
|
|
deep: true,
|
|
},
|
|
allocatedPrice: {
|
|
handler() {
|
|
if (this.enableEarlyPayment) {
|
|
this.updateEarlyPaymentPlans();
|
|
}
|
|
},
|
|
},
|
|
policyId: {
|
|
immediate: true,
|
|
handler(newPolicyId) {
|
|
if (newPolicyId && !this.paymentPlans.length && this.selectedPolicy) {
|
|
this.loadPaymentPlans(newPolicyId);
|
|
}
|
|
},
|
|
},
|
|
selectedPolicy: {
|
|
immediate: true,
|
|
handler(newPolicy) {
|
|
if (newPolicy && this.paymentPlans.length === 0 && this.localPaymentPlans.length === 0) {
|
|
const plans = this.allPaymentPlans.filter((p) => p.policy === newPolicy.id);
|
|
if (plans.length > 0) {
|
|
plans.sort((a, b) => a.cycle - b.cycle);
|
|
this.localPaymentPlans = plans;
|
|
}
|
|
}
|
|
this.enableEarlyPayment = false;
|
|
this.earlyPaymentCycles = 0;
|
|
this.earlyPaymentDetails = [];
|
|
},
|
|
},
|
|
},
|
|
methods: {
|
|
doTick() {
|
|
console.log("Hello");
|
|
this.enableEarlyPayment = !this.enableEarlyPayment;
|
|
return this.enableEarlyPayment;
|
|
},
|
|
formatDate(date) {
|
|
return dayjs(date).format("DD/MM/YYYY");
|
|
},
|
|
|
|
handleEarlyPaymentToggle() {
|
|
if (this.enableEarlyPayment) {
|
|
this.earlyPaymentCycles = Math.min(2, this.plansToRender.length);
|
|
this.updateEarlyPaymentPlans();
|
|
} else {
|
|
this.earlyPaymentCycles = 0;
|
|
this.earlyPaymentDetails = [];
|
|
this.$emit("early-payment-change", null);
|
|
}
|
|
},
|
|
|
|
updateEarlyPaymentPlans() {
|
|
if (!this.enableEarlyPayment || this.earlyPaymentCycles === 0) {
|
|
this.earlyPaymentDetails = [];
|
|
this.$emit("early-payment-change", null);
|
|
return;
|
|
}
|
|
|
|
this.earlyPaymentCycles = Math.max(2, Math.min(this.earlyPaymentCycles, this.plansToRender.length));
|
|
|
|
this.earlyPaymentDetails = this.plansToRender
|
|
.filter((plan) => plan.cycle <= this.earlyPaymentCycles)
|
|
.map((plan) => {
|
|
const rawAmount = plan.type === 1 ? (this.allocatedPrice * plan.value) / 100 : plan.value;
|
|
const days = plan.days || 0;
|
|
const discountAmount = (rawAmount * days * EARLY_PAYMENT_DAILY_RATE) / 100;
|
|
|
|
return {
|
|
cycle: plan.cycle,
|
|
type: plan.type,
|
|
value: plan.value,
|
|
days: days,
|
|
rawAmount: rawAmount,
|
|
discountAmount: discountAmount,
|
|
netAmount: rawAmount - discountAmount,
|
|
};
|
|
});
|
|
|
|
this.$emit("early-payment-change", {
|
|
cycles: this.earlyPaymentCycles,
|
|
details: this.earlyPaymentDetails,
|
|
});
|
|
},
|
|
|
|
async loadPaymentPlans(policyId) {
|
|
if (!policyId || this.isLoadingPlans) return;
|
|
this.isLoadingPlans = true;
|
|
this.localPaymentPlans = [];
|
|
try {
|
|
const plans = await this.$getdata(
|
|
"paymentplan",
|
|
{ policy: policyId, policy__enable: "True" },
|
|
undefined,
|
|
false,
|
|
);
|
|
if (plans) {
|
|
plans.sort((a, b) => a.cycle - b.cycle);
|
|
}
|
|
this.localPaymentPlans = plans || [];
|
|
this.$emit("plans-loaded", this.localPaymentPlans);
|
|
} catch (error) {
|
|
console.error("Error loading payment plans:", error);
|
|
this.localPaymentPlans = [];
|
|
} finally {
|
|
this.isLoadingPlans = false;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.table-container.schedule-container thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
background: white;
|
|
z-index: 2;
|
|
border-bottom: 1px solid #dbdbdb !important;
|
|
}
|
|
|
|
.table-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.border {
|
|
border: 1px solid #dbdbdb;
|
|
}
|
|
|
|
li.is-active a,
|
|
li a:hover {
|
|
color: white !important;
|
|
background-color: #204853 !important;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.content {
|
|
display: block;
|
|
}
|
|
|
|
.content p {
|
|
margin: 0;
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
tr,
|
|
td,
|
|
th {
|
|
page-break-inside: avoid !important;
|
|
}
|
|
|
|
@media print {
|
|
.schedule-container {
|
|
max-height: none;
|
|
overflow: visible;
|
|
}
|
|
|
|
.table-container.schedule-container thead th {
|
|
position: static;
|
|
}
|
|
|
|
#ignore-print {
|
|
display: none !important;
|
|
}
|
|
|
|
tr,
|
|
td,
|
|
th {
|
|
page-break-inside: avoid !important;
|
|
}
|
|
}
|
|
</style>
|