Files
web/app/components/modal/EditDueDateModal.vue
2026-03-02 09:45:33 +07:00

311 lines
11 KiB
Vue

<template>
<div class="">
<p class="has-text-centered title is-4 has-text-danger mb-4">
Vui lòng kiểm tra kỹ thông tin trước khi thay đổi ngày đến hạn
</p>
<div v-if="loadingData" class="has-text-centered py-5">
<SvgIcon v-bind="{ name: 'loading.svg', type: 'primary', size: 18 }" />
<p class="mt-2">{{ isVietnamese ? 'Đang tải thông tin công nợ...' : 'Loading payment schedule information...' }}
</p>
</div>
<div v-else-if="paymentScheduleData">
<div class="content">
<div class="columns is-multiline is-mobile">
<div class="column is-4">
<strong>{{ isVietnamese ? 'Mã:' : 'Schedule Code:' }}</strong>
<p>{{ paymentScheduleData.code || '-' }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Trạng thái:' : 'Status:' }}</strong>
<p
:class="{ 'has-text-success': paymentScheduleData.status__name === 'Đã xác nhận', 'has-text-warning': paymentScheduleData.status__name === 'Chưa xác nhận' }">
{{ paymentScheduleData.status__name || '-' }}
</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Loại thanh toán:' : 'Payment Type:' }}</strong>
<p>{{ paymentScheduleData.type__name || '-' }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Tiền gốc theo kỳ thanh toán:' : 'Amount:' }}</strong>
<p class="has-text-weight-bold has-text-primary">{{ $numtoString(paymentScheduleData.amount) }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Ngày đến hạn hiện tại:' : 'Current Due Date:' }}</strong>
<p class="has-text-weight-bold">{{ formatDate(paymentScheduleData.to_date) }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Ngày tính lãi:' : 'Penalty Date:' }}</strong>
<p :class="paymentScheduleData.batch_date ? 'has-text-danger' : ''">
{{ formatDate(paymentScheduleData.batch_date) || 'Chưa tính lãi' }}
</p>
</div>
</div>
<hr>
<p class="title is-6">{{ isVietnamese ? 'Thông tin Giao dịch liên quan' : 'Related Transaction Information' }}
</p>
<div class="columns is-multiline is-mobile">
<div class="column is-4">
<strong>{{ isVietnamese ? 'Mã giao dịch:' : 'Transaction Code:' }}</strong>
<p>{{ paymentScheduleData.txn_detail__transaction__code || '-' }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Khách hàng:' : 'Customer:' }}</strong>
<p>{{ paymentScheduleData.txn_detail__transaction__customer__fullname || '-' }}</p>
</div>
<div class="column is-4">
<strong>{{ isVietnamese ? 'Chính sách:' : 'Policy:' }}</strong>
<p>{{ paymentScheduleData.txn_detail__transaction__policy__code || '-' }} </p>
</div>
</div>
</div>
<hr>
<div v-if="canEditDueDate">
<Caption class="mb-4" v-bind="{ title: 'Thay đổi ngày đến hạn', size: 20 }" />
<div class="field">
<label class="label has-text-weight-bold">{{ isVietnamese ? 'Ngày đến hạn mới' : 'New Due Date' }}</label>
<div class="control">
<Datepicker
:record="dateRecord"
attr="newDueDate"
@date="updateDueDate"
position="is-bottom-left"
:mindate="minDate"
/>
</div>
<p v-if="dateError" class="help is-danger">{{ dateError }}</p>
</div>
<div class="field mt-5">
<label class="label has-text-weight-bold">{{ isVietnamese ? 'Mã xác nhận' : 'Confirmation Code' }}</label>
<div class="control">
<div class="field has-addons">
<div class="control is-expanded">
<input class="input" type="text"
:placeholder="isVietnamese ? 'Nhập mã xác nhận' : 'Enter confirmation code'"
v-model="userInputCaptcha" @keydown.enter="handleUpdate">
</div>
<div class="control">
<a class="button is-static has-text-weight-bold has-background-grey-lighter"
style="font-family: 'Courier New', monospace; letter-spacing: 2px;">
{{ captchaCode }}
</a>
</div>
<div class="control">
<button class="button is-info is-light" @click="generateCaptcha"
:title="isVietnamese ? 'Tạo mã mới' : 'Generate new code'">
<SvgIcon v-bind="{ name: 'refresh.svg', type: 'primary', size: 23 }"></SvgIcon>
</button>
</div>
</div>
</div>
<p v-if="!isCaptchaValid && userInputCaptcha.length > 0" class="help is-danger"> xác nhận không đúng.</p>
</div>
</div>
<div v-else class="notification is-warning">
<p class="has-text-weight-bold">
{{ isVietnamese ? 'Không thể thay đổi ngày đến hạn' : 'Cannot change due date' }}
</p>
</div>
</div>
<div class="field is-grouped is-grouped-left mt-5">
<p class="control">
<button
v-if="canEditDueDate"
class="button is-success has-text-white"
:class="{ 'is-loading': isLoading }"
@click="handleUpdate"
:disabled="!isUpdateValid || isLoading">
<span>{{ isVietnamese ? 'Cập nhật ngày đến hạn' : 'Update Due Date' }}</span>
</button>
<button class="button" @click="emit('close')">
<span>{{ isVietnamese ? 'Đóng' : 'Close' }}</span>
</button>
</p>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useNuxtApp } from '#app';
import { useStore } from "@/stores/index";
import dayjs from 'dayjs';
const props = defineProps({
scheduleItemId: {
type: [Number, String],
required: true
}
});
const emit = defineEmits(['close', 'updated']);
const store = useStore();
const { $getdata, $patchapi, $snackbar } = useNuxtApp();
const isLoading = ref(false);
const loadingData = ref(true);
const paymentScheduleData = ref(null);
const dateRecord = ref({ newDueDate: null });
const captchaCode = ref('');
const userInputCaptcha = ref('');
const isVietnamese = computed(() => store.lang === 'vi');
// Kiểm tra xem có thể sửa ngày đến hạn không (chưa tính lãi và chưa thanh toán)
const canEditDueDate = computed(() => {
const hasNoPenaltyCalculated = paymentScheduleData.value?.batch_date === null || paymentScheduleData.value?.batch_date === undefined;
const isNotPaid = paymentScheduleData.value?.status !== 2;
return hasNoPenaltyCalculated && isNotPaid;
});
// Ngày tối thiểu (phải lớn hơn ngày hiện tại)
const minDate = computed(() => {
const currentDate = paymentScheduleData.value?.to_date;
if (currentDate) {
return dayjs(currentDate).add(1, 'day').format('YYYY-MM-DD');
}
return dayjs().add(1, 'day').format('YYYY-MM-DD');
});
// Kiểm tra lỗi ngày
const dateError = computed(() => {
if (!dateRecord.value.newDueDate) return '';
const selectedDate = dayjs(dateRecord.value.newDueDate);
const currentDueDate = dayjs(paymentScheduleData.value?.to_date);
if (selectedDate.isBefore(currentDueDate) || selectedDate.isSame(currentDueDate)) {
return isVietnamese.value
? 'Ngày đến hạn mới phải lớn hơn ngày đến hạn hiện tại'
: 'New due date must be after current due date';
}
return '';
});
const updateDueDate = (date) => {
dateRecord.value.newDueDate = date;
};
const generateCaptcha = () => {
captchaCode.value = Math.random().toString(36).substring(2, 7).toUpperCase();
userInputCaptcha.value = '';
};
const isCaptchaValid = computed(() => {
return userInputCaptcha.value.toLowerCase() === captchaCode.value.toLowerCase();
});
const isUpdateValid = computed(() => {
return canEditDueDate.value
&& dateRecord.value.newDueDate
&& !dateError.value
&& isCaptchaValid.value
&& userInputCaptcha.value.length > 0;
});
const formatDate = (dateString) => {
if (!dateString) return '-';
return dayjs(dateString).format('DD/MM/YYYY');
};
const fetchPaymentScheduleData = async () => {
loadingData.value = true;
try {
const data = await $getdata('payment_schedule', { id: props.scheduleItemId }, undefined, true);
paymentScheduleData.value = data;
// Set initial value for new due date
if (data?.to_date) {
dateRecord.value.newDueDate = data.to_date;
}
} catch (e) {
console.error("Error fetching payment schedule data:", e);
$snackbar(
isVietnamese.value ? 'Không thể tải thông tin công nợ.' : 'Failed to load payment schedule information.',
'Lỗi',
'Error'
);
} finally {
loadingData.value = false;
}
};
const handleUpdate = async () => {
if (!isUpdateValid.value) {
if (!isCaptchaValid.value) {
$snackbar(
isVietnamese.value ? 'Vui lòng nhập đúng mã xác nhận.' : 'Please enter the correct confirmation code.',
'Cảnh báo',
'Warning'
);
} else if (dateError.value) {
$snackbar(dateError.value, 'Cảnh báo', 'Warning');
} else if (!canEditDueDate.value) {
$snackbar(
isVietnamese.value ? 'Không thể thay đổi ngày đến hạn.' : 'Cannot change due date.',
'Cảnh báo',
'Warning'
);
}
return;
}
isLoading.value = true;
try {
const response = await $patchapi('payment_schedule', {
id: props.scheduleItemId,
to_date: dateRecord.value.newDueDate
});
if (response !== 'error') {
$snackbar(
isVietnamese.value ? 'Cập nhật ngày đến hạn thành công!' : 'Due date updated successfully!',
'Thành công',
'Success'
);
emit('updated');
emit('close');
} else {
$snackbar(
isVietnamese.value ? 'Có lỗi xảy ra khi cập nhật ngày đến hạn.' : 'An error occurred while updating due date.',
'Lỗi',
'Error'
);
}
} catch (e) {
console.error("Error updating due date:", e);
$snackbar(
isVietnamese.value ? 'Có lỗi xảy ra khi cập nhật ngày đến hạn.' : 'An error occurred while updating due date.',
'Lỗi',
'Error'
);
} finally {
isLoading.value = false;
}
};
onMounted(() => {
fetchPaymentScheduleData();
generateCaptcha();
});
</script>
<style scoped>
.notification {
margin-top: 1rem;
}
</style>