chore: install prettier
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<p class="">
|
||||
Bạn có chắc chắn muốn chuyển sản phẩm này sang trạng thái<br />
|
||||
<strong :class="newStatus === 2 ? 'has-text-success' : 'has-text-danger'">
|
||||
{{ newStatus === 2 ? 'ĐANG BÁN' : 'KHÓA' }}
|
||||
{{ newStatus === 2 ? "ĐANG BÁN" : "KHÓA" }}
|
||||
</strong>
|
||||
không?
|
||||
</p>
|
||||
@@ -12,15 +12,23 @@
|
||||
|
||||
<div class="field is-grouped is-grouped-centered">
|
||||
<div class="control">
|
||||
<button class="button " :class="newStatus === 2 ? 'is-success' : 'is-danger'" :disabled="isSaving"
|
||||
@click="confirmChange">
|
||||
<button
|
||||
class="button"
|
||||
:class="newStatus === 2 ? 'is-success' : 'is-danger'"
|
||||
:disabled="isSaving"
|
||||
@click="confirmChange"
|
||||
>
|
||||
<span v-if="isSaving">Đang xử lý...</span>
|
||||
<span v-else>Đồng ý</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button class="button is-dark " :disabled="isSaving" @click="close">
|
||||
<button
|
||||
class="button is-dark"
|
||||
:disabled="isSaving"
|
||||
@click="close"
|
||||
>
|
||||
Hủy bỏ
|
||||
</button>
|
||||
</div>
|
||||
@@ -29,70 +37,70 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { useNuxtApp } from '#app'
|
||||
import { ref, computed } from "vue";
|
||||
import { useNuxtApp } from "#app";
|
||||
|
||||
const emit = defineEmits(['close', 'modalevent'])
|
||||
const emit = defineEmits(["close", "modalevent"]);
|
||||
|
||||
const props = defineProps({
|
||||
product: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { $patchapi, $snackbar } = useNuxtApp()
|
||||
const { $patchapi, $snackbar } = useNuxtApp();
|
||||
|
||||
const isSaving = ref(false)
|
||||
const isSaving = ref(false);
|
||||
|
||||
const currentStatus = computed(() => Number(props.product?.status) || null)
|
||||
const newStatus = computed(() => (currentStatus.value === 15 ? 2 : 15))
|
||||
const currentStatus = computed(() => Number(props.product?.status) || null);
|
||||
const newStatus = computed(() => (currentStatus.value === 15 ? 2 : 15));
|
||||
|
||||
async function confirmChange() {
|
||||
if (!props.product?.id || newStatus.value === currentStatus.value) {
|
||||
close()
|
||||
return
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
isSaving.value = true;
|
||||
|
||||
try {
|
||||
const result = await $patchapi(
|
||||
'product',
|
||||
"product",
|
||||
{
|
||||
id: props.product.id,
|
||||
status: newStatus.value
|
||||
status: newStatus.value,
|
||||
},
|
||||
{},
|
||||
false
|
||||
)
|
||||
false,
|
||||
);
|
||||
|
||||
if (result === 'error' || !result) {
|
||||
$snackbar('Cập nhật thất bại', 'Lỗi', 'Error')
|
||||
return
|
||||
if (result === "error" || !result) {
|
||||
$snackbar("Cập nhật thất bại", "Lỗi", "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
$snackbar('Cập nhật trạng thái thành công', 'Thành công', 'Success')
|
||||
$snackbar("Cập nhật trạng thái thành công", "Thành công", "Success");
|
||||
|
||||
// Phát sự kiện để component cha (hoặc bảng) cập nhật lại dữ liệu
|
||||
emit('modalevent', {
|
||||
name: 'update',
|
||||
emit("modalevent", {
|
||||
name: "update",
|
||||
data: {
|
||||
id: props.product.id,
|
||||
status: newStatus.value
|
||||
}
|
||||
})
|
||||
status: newStatus.value,
|
||||
},
|
||||
});
|
||||
|
||||
close()
|
||||
close();
|
||||
} catch (error) {
|
||||
console.error('Lỗi đổi trạng thái:', error)
|
||||
$snackbar('Có lỗi xảy ra', 'Lỗi', 'Error')
|
||||
console.error("Lỗi đổi trạng thái:", error);
|
||||
$snackbar("Có lỗi xảy ra", "Lỗi", "Error");
|
||||
} finally {
|
||||
isSaving.value = false
|
||||
isSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('close')
|
||||
emit("close");
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
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">
|
||||
<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 class="mt-2">
|
||||
{{ isVietnamese ? "Đang tải thông tin công nợ..." : "Loading payment schedule information..." }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -14,122 +18,166 @@
|
||||
<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>
|
||||
<strong>{{ isVietnamese ? "Mã:" : "Schedule Code:" }}</strong>
|
||||
<p>{{ paymentScheduleData.code || "-" }}</p>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<strong>{{ isVietnamese ? 'Trạng thái:' : 'Status:' }}</strong>
|
||||
<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 || '-' }}
|
||||
: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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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' }}
|
||||
{{ formatDate(paymentScheduleData.batch_date) || "Chưa tính lãi" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
<p class="title is-6">{{ isVietnamese ? 'Thông tin Giao dịch liên quan' : 'Related Transaction Information' }}
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<strong>{{ isVietnamese ? "Chính sách:" : "Policy:" }}</strong>
|
||||
<p>
|
||||
{{ paymentScheduleData.txn_detail__transaction__policy__code || "-" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<hr />
|
||||
|
||||
<div v-if="canEditDueDate">
|
||||
<Caption class="mb-4" v-bind="{ title: 'Thay đổi ngày đến hạn', size: 20 }" />
|
||||
|
||||
<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>
|
||||
<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"
|
||||
<Datepicker
|
||||
:record="dateRecord"
|
||||
attr="newDueDate"
|
||||
@date="updateDueDate"
|
||||
position="is-bottom-left"
|
||||
:mindate="minDate"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="dateError" class="help is-danger">{{ dateError }}</p>
|
||||
<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>
|
||||
<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"
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
:placeholder="isVietnamese ? 'Nhập mã xác nhận' : 'Enter confirmation code'"
|
||||
v-model="userInputCaptcha" @keydown.enter="handleUpdate">
|
||||
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;">
|
||||
<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'">
|
||||
<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">Mã xác nhận không đúng.</p>
|
||||
<p
|
||||
v-if="!isCaptchaValid && userInputCaptcha.length > 0"
|
||||
class="help is-danger"
|
||||
>
|
||||
Mã xác nhận không đúng.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="notification is-warning">
|
||||
<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' }}
|
||||
{{ 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
|
||||
<button
|
||||
v-if="canEditDueDate"
|
||||
class="button is-success has-text-white"
|
||||
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>
|
||||
@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
|
||||
class="button"
|
||||
@click="emit('close')"
|
||||
>
|
||||
<span>{{ isVietnamese ? "Đóng" : "Close" }}</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -137,19 +185,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useNuxtApp } from '#app';
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useNuxtApp } from "#app";
|
||||
import { useStore } from "@/stores/index";
|
||||
import dayjs from 'dayjs';
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const props = defineProps({
|
||||
scheduleItemId: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'updated']);
|
||||
const emit = defineEmits(["close", "updated"]);
|
||||
|
||||
const store = useStore();
|
||||
const { $getdata, $patchapi, $snackbar } = useNuxtApp();
|
||||
@@ -158,14 +206,15 @@ const isLoading = ref(false);
|
||||
const loadingData = ref(true);
|
||||
const paymentScheduleData = ref(null);
|
||||
const dateRecord = ref({ newDueDate: null });
|
||||
const captchaCode = ref('');
|
||||
const userInputCaptcha = ref('');
|
||||
const captchaCode = ref("");
|
||||
const userInputCaptcha = ref("");
|
||||
|
||||
const isVietnamese = computed(() => store.lang === 'vi');
|
||||
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 hasNoPenaltyCalculated =
|
||||
paymentScheduleData.value?.batch_date === null || paymentScheduleData.value?.batch_date === undefined;
|
||||
const isNotPaid = paymentScheduleData.value?.status !== 2;
|
||||
return hasNoPenaltyCalculated && isNotPaid;
|
||||
});
|
||||
@@ -174,25 +223,25 @@ const canEditDueDate = computed(() => {
|
||||
const minDate = computed(() => {
|
||||
const currentDate = paymentScheduleData.value?.to_date;
|
||||
if (currentDate) {
|
||||
return dayjs(currentDate).add(1, 'day').format('YYYY-MM-DD');
|
||||
return dayjs(currentDate).add(1, "day").format("YYYY-MM-DD");
|
||||
}
|
||||
return dayjs().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 '';
|
||||
|
||||
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 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 '';
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
const updateDueDate = (date) => {
|
||||
@@ -201,7 +250,7 @@ const updateDueDate = (date) => {
|
||||
|
||||
const generateCaptcha = () => {
|
||||
captchaCode.value = Math.random().toString(36).substring(2, 7).toUpperCase();
|
||||
userInputCaptcha.value = '';
|
||||
userInputCaptcha.value = "";
|
||||
};
|
||||
|
||||
const isCaptchaValid = computed(() => {
|
||||
@@ -209,24 +258,26 @@ const isCaptchaValid = computed(() => {
|
||||
});
|
||||
|
||||
const isUpdateValid = computed(() => {
|
||||
return canEditDueDate.value
|
||||
&& dateRecord.value.newDueDate
|
||||
&& !dateError.value
|
||||
&& isCaptchaValid.value
|
||||
&& userInputCaptcha.value.length > 0;
|
||||
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');
|
||||
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);
|
||||
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;
|
||||
@@ -234,9 +285,9 @@ const fetchPaymentScheduleData = async () => {
|
||||
} 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'
|
||||
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;
|
||||
@@ -247,17 +298,17 @@ 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'
|
||||
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');
|
||||
$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'
|
||||
isVietnamese.value ? "Không thể thay đổi ngày đến hạn." : "Cannot change due date.",
|
||||
"Cảnh báo",
|
||||
"Warning",
|
||||
);
|
||||
}
|
||||
return;
|
||||
@@ -266,32 +317,32 @@ const handleUpdate = async () => {
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const response = await $patchapi('payment_schedule', {
|
||||
id: props.scheduleItemId,
|
||||
to_date: dateRecord.value.newDueDate
|
||||
const response = await $patchapi("payment_schedule", {
|
||||
id: props.scheduleItemId,
|
||||
to_date: dateRecord.value.newDueDate,
|
||||
});
|
||||
|
||||
if (response !== 'error') {
|
||||
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'
|
||||
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');
|
||||
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'
|
||||
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'
|
||||
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;
|
||||
@@ -308,4 +359,4 @@ onMounted(() => {
|
||||
.notification {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<div class="mt-0" v-if="loading">
|
||||
<p>{{ isVietnamese ? 'Đang tải dữ liệu...' : 'Loading...' }}</p>
|
||||
<div
|
||||
class="mt-0"
|
||||
v-if="loading"
|
||||
>
|
||||
<p>{{ isVietnamese ? "Đang tải dữ liệu..." : "Loading..." }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-0" v-else-if="entries.length === 0">
|
||||
<p>{{ isVietnamese ? 'Không có dữ liệu' : 'No data' }}</p>
|
||||
<div
|
||||
class="mt-0"
|
||||
v-else-if="entries.length === 0"
|
||||
>
|
||||
<p>{{ isVietnamese ? "Không có dữ liệu" : "No data" }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div v-for="(entry, index) in entries" :key="entry.code || index" class="entry-item mb-5">
|
||||
<h3 class="title is-5 mb-3">
|
||||
{{ isVietnamese ? 'Bút toán' : 'Entry' }} #{{ index + 1 }}
|
||||
</h3>
|
||||
|
||||
<div
|
||||
v-for="(entry, index) in entries"
|
||||
:key="entry.code || index"
|
||||
class="entry-item mb-5"
|
||||
>
|
||||
<h3 class="title is-5 mb-3">{{ isVietnamese ? "Bút toán" : "Entry" }} #{{ index + 1 }}</h3>
|
||||
|
||||
<div class="columns is-multiline mx-0">
|
||||
<!-- Mã bút toán -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Mã bút toán' : 'Code' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Mã bút toán" : "Code" }}</label>
|
||||
<div class="control">
|
||||
<span class="hyperlink" @click="$copyToClipboard(entry.code)">{{ entry.code }}</span>
|
||||
<span
|
||||
class="hyperlink"
|
||||
@click="$copyToClipboard(entry.code)"
|
||||
>{{ entry.code }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +40,7 @@
|
||||
<!-- Ngày bút toán -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Ngày bút toán' : 'Date' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Ngày bút toán" : "Date" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.date ? $dayjs(entry.date).format("DD/MM/YYYY") : "/" }}</span>
|
||||
</div>
|
||||
@@ -38,7 +50,7 @@
|
||||
<!-- Amount -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Số tiền' : 'Amount' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Số tiền" : "Amount" }}</label>
|
||||
<div class="control">
|
||||
<span>
|
||||
{{ $numtoString(entry.amount) }}
|
||||
@@ -50,9 +62,12 @@
|
||||
<!-- Mã quỹ -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Mã quỹ' : 'Account Code' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Mã quỹ" : "Account Code" }}</label>
|
||||
<div class="control">
|
||||
<span class="hyperlink" @click="$copyToClipboard(entry.account__code)">
|
||||
<span
|
||||
class="hyperlink"
|
||||
@click="$copyToClipboard(entry.account__code)"
|
||||
>
|
||||
{{ entry.account__code || "/" }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -62,7 +77,7 @@
|
||||
<!-- Tên quỹ -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Tên quỹ' : 'Account Type' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Tên quỹ" : "Account Type" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.account__type__name || "/" }}</span>
|
||||
</div>
|
||||
@@ -72,7 +87,7 @@
|
||||
<!-- Loại bút toán -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Loại bút toán' : 'Category' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Loại bút toán" : "Category" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.category__name || "/" }}</span>
|
||||
</div>
|
||||
@@ -82,7 +97,7 @@
|
||||
<!-- Loại tiền -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Loại tiền' : 'Currency' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Loại tiền" : "Currency" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.account__currency__code || "/" }}</span>
|
||||
</div>
|
||||
@@ -92,7 +107,7 @@
|
||||
<!-- Dư trước -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Dư trước' : 'Balance Before' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Dư trước" : "Balance Before" }}</label>
|
||||
<div class="control">
|
||||
<span>
|
||||
{{ $numtoString(entry.balance_before) }}
|
||||
@@ -104,9 +119,9 @@
|
||||
<!-- Ghi có -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Ghi có' : 'Credit' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Ghi có" : "Credit" }}</label>
|
||||
<div class="control">
|
||||
<span v-if="entry.type===1">
|
||||
<span v-if="entry.type === 1">
|
||||
{{ $numtoString(entry.amount) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -116,9 +131,9 @@
|
||||
<!-- Trích nợ (Ghi nợ) -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Ghi nợ' : 'Debit' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Ghi nợ" : "Debit" }}</label>
|
||||
<div class="control">
|
||||
<span v-if="entry.type===2">
|
||||
<span v-if="entry.type === 2">
|
||||
{{ $numtoString(entry.amount) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -128,7 +143,7 @@
|
||||
<!-- Dư sau -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Dư sau' : 'Balance After' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Dư sau" : "Balance After" }}</label>
|
||||
<div class="control">
|
||||
<span>
|
||||
{{ $numtoString(entry.balance_after) }}
|
||||
@@ -140,7 +155,7 @@
|
||||
<!-- Loại (Ghi có/ghi nợ) -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Loại' : 'Type' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Loại" : "Type" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.type__name || "/" }}</span>
|
||||
</div>
|
||||
@@ -150,9 +165,12 @@
|
||||
<!-- Người nhập -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Người nhập' : 'Inputer' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Người nhập" : "Inputer" }}</label>
|
||||
<div class="control">
|
||||
<span class="hyperlink" @click="openUser(entry.inputer)">
|
||||
<span
|
||||
class="hyperlink"
|
||||
@click="openUser(entry.inputer)"
|
||||
>
|
||||
{{ entry.inputer__fullname || "/" }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -162,9 +180,12 @@
|
||||
<!-- Người duyệt -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Người duyệt' : 'Approver' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Người duyệt" : "Approver" }}</label>
|
||||
<div class="control">
|
||||
<span class="hyperlink" @click="openUser(entry.approver)">
|
||||
<span
|
||||
class="hyperlink"
|
||||
@click="openUser(entry.approver)"
|
||||
>
|
||||
{{ entry.approver__fullname || "/" }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -184,7 +205,7 @@
|
||||
<!-- Thời gian -->
|
||||
<div class="column is-3 pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Thời gian' : 'Create Time' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Thời gian" : "Create Time" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.create_time ? $dayjs(entry.create_time).format("DD/MM/YYYY HH:mm") : "/" }}</span>
|
||||
</div>
|
||||
@@ -194,7 +215,7 @@
|
||||
<!-- Nội dung -->
|
||||
<div class="column is-full pb-1 px-0">
|
||||
<div class="field">
|
||||
<label class="label">{{ isVietnamese ? 'Nội dung' : 'Content' }}</label>
|
||||
<label class="label">{{ isVietnamese ? "Nội dung" : "Content" }}</label>
|
||||
<div class="control">
|
||||
<span>{{ entry.content || "/" }}</span>
|
||||
</div>
|
||||
@@ -202,11 +223,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr v-if="index < entries.length - 1" class="my-4">
|
||||
<hr
|
||||
v-if="index < entries.length - 1"
|
||||
class="my-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
v-if="showmodal"
|
||||
></Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -221,8 +249,8 @@ export default {
|
||||
props: {
|
||||
entryList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -247,34 +275,34 @@ export default {
|
||||
handler() {
|
||||
this.fetchEntries();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
parseEntryItem(item) {
|
||||
// Nếu item đã là object thì return luôn
|
||||
if (typeof item === 'object' && item !== null) {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
// Nếu item là string thì parse
|
||||
if (typeof item === 'string') {
|
||||
if (typeof item === "string") {
|
||||
try {
|
||||
// Thay thế single quotes bằng double quotes để parse JSON
|
||||
const jsonString = item.replace(/'/g, '"');
|
||||
return JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
console.error('Lỗi khi parse string thành JSON:', item, error);
|
||||
console.error("Lỗi khi parse string thành JSON:", item, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
async fetchEntries() {
|
||||
const { $getdata } = useNuxtApp();
|
||||
|
||||
|
||||
if (!this.entryList || this.entryList.length === 0) {
|
||||
this.entries = [];
|
||||
return;
|
||||
@@ -288,7 +316,7 @@ export default {
|
||||
for (const item of this.entryList) {
|
||||
// Parse item từ string sang object nếu cần
|
||||
const parsedItem = this.parseEntryItem(item);
|
||||
|
||||
|
||||
if (parsedItem && parsedItem.code) {
|
||||
try {
|
||||
const entryData = await $getdata("internalentry", { code: parsedItem.code }, undefined, true);
|
||||
@@ -300,7 +328,7 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(`Đã load ${this.entries.length} bút toán thành công`);
|
||||
} catch (error) {
|
||||
console.error("Lỗi khi load danh sách bút toán:", error);
|
||||
@@ -320,4 +348,4 @@ export default {
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<div class="px-3">
|
||||
<div v-if="loadingData" class="has-text-centered py-5">
|
||||
<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..." }}
|
||||
@@ -17,11 +20,17 @@
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Trạng thái:" : "Status:" }}</strong>
|
||||
<p :class="{
|
||||
'has-text-success': paymentScheduleData.status__name === 'Đã xác nhận' || paymentScheduleData.status__name === 'Paid',
|
||||
'has-text-warning': paymentScheduleData.status__name === 'Chưa xác nhận' || paymentScheduleData.status__name === 'Pending',
|
||||
'has-text-danger': paymentScheduleData.status__name === 'Quá hạn' || paymentScheduleData.status__name === 'Overdue'
|
||||
}">
|
||||
<p
|
||||
:class="{
|
||||
'has-text-success':
|
||||
paymentScheduleData.status__name === 'Đã xác nhận' || paymentScheduleData.status__name === 'Paid',
|
||||
'has-text-warning':
|
||||
paymentScheduleData.status__name === 'Chưa xác nhận' ||
|
||||
paymentScheduleData.status__name === 'Pending',
|
||||
'has-text-danger':
|
||||
paymentScheduleData.status__name === 'Quá hạn' || paymentScheduleData.status__name === 'Overdue',
|
||||
}"
|
||||
>
|
||||
{{ paymentScheduleData.status__name || "-" }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -35,24 +44,37 @@
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<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>
|
||||
<p class="has-text-weight-bold has-text-primary">
|
||||
{{ $numtoString(paymentScheduleData.amount) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Số tiền gốc đã thanh toán:" : "Paid Amount:" }}</strong>
|
||||
<p class="has-text-weight-bold has-text-primary">{{ $numtoString(paymentScheduleData.paid_amount) }}</p>
|
||||
<p class="has-text-weight-bold has-text-primary">
|
||||
{{ $numtoString(paymentScheduleData.paid_amount) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Số tiền gốc còn lại:" : "Remaining Principal:" }}</strong>
|
||||
<p class="has-text-weight-bold"
|
||||
:class="paymentScheduleData.amount_remain > 0 ? 'has-text-danger' : 'has-text-success'">
|
||||
<p
|
||||
class="has-text-weight-bold"
|
||||
:class="paymentScheduleData.amount_remain > 0 ? 'has-text-danger' : 'has-text-success'"
|
||||
>
|
||||
{{ $numtoString(paymentScheduleData.amount_remain) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Ngày đến hạn:" : "Due Date:" }}</strong>
|
||||
<p :class="{ 'has-text-danger': isOverdue(paymentScheduleData.to_date && paymentScheduleData.ovd_days > 0) }">
|
||||
<p
|
||||
:class="{
|
||||
'has-text-danger': isOverdue(paymentScheduleData.to_date && paymentScheduleData.ovd_days > 0),
|
||||
}"
|
||||
>
|
||||
{{ formatDate(paymentScheduleData.to_date) }}
|
||||
<span v-if="isOverdue(paymentScheduleData.to_date && paymentScheduleData.ovd_days > 0 )" class="has-text-weight-bold">
|
||||
<span
|
||||
v-if="isOverdue(paymentScheduleData.to_date && paymentScheduleData.ovd_days > 0)"
|
||||
class="has-text-weight-bold"
|
||||
>
|
||||
(Quá hạn {{ paymentScheduleData.ovd_days }} ngày)
|
||||
</span>
|
||||
</p>
|
||||
@@ -83,30 +105,39 @@
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Tổng tiền phải thanh toán:" : "Total Remaining:" }}</strong>
|
||||
<p class="has-text-weight-bold has-text-primary">{{ $numtoString(paymentScheduleData.remain_amount) }}</p>
|
||||
<p class="has-text-weight-bold has-text-primary">
|
||||
{{ $numtoString(paymentScheduleData.remain_amount) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<strong>{{ isVietnamese ? "Ghi chú:" : "Note:" }}</strong>
|
||||
<p class="is-size-6">{{ paymentScheduleData.detail?.note || "-" }}</p>
|
||||
<p class="is-size-6">
|
||||
{{ paymentScheduleData.detail?.note || "-" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Timeline lịch sử -->
|
||||
|
||||
|
||||
<div v-if="processedEntries.length > 0" class="is-flex is-flex-direction-column is-gap-5" >
|
||||
|
||||
<div v-for="(item, index) in processedEntries" :key="index" class="is-flex is-align-items-start is-gap-4">
|
||||
<div style="min-width: 3rem;">
|
||||
<div
|
||||
v-if="processedEntries.length > 0"
|
||||
class="is-flex is-flex-direction-column is-gap-5"
|
||||
>
|
||||
<div
|
||||
v-for="(item, index) in processedEntries"
|
||||
:key="index"
|
||||
class="is-flex is-align-items-start is-gap-4"
|
||||
>
|
||||
<div style="min-width: 3rem">
|
||||
<p class="is-size-5 has-text-weight-bold has-text-primary">
|
||||
{{ formatDate(item.entry.date) }}
|
||||
</p>
|
||||
<p v-if="item.entry.code" class="is-size-6 has-text-grey">
|
||||
<p
|
||||
v-if="item.entry.code"
|
||||
class="is-size-6 has-text-grey"
|
||||
>
|
||||
{{ item.entry.code }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -114,48 +145,71 @@
|
||||
<div class="is-flex-grow-1">
|
||||
<p class="is-size-5 has-text-weight-bold has-text-dark mb-2">
|
||||
{{ getEntryTypeLabel(item.entry.type) }}
|
||||
<span v-if="item.entry.code" class="tag is-link is-light ml-2">
|
||||
<span
|
||||
v-if="item.entry.code"
|
||||
class="tag is-link is-light ml-2"
|
||||
>
|
||||
{{ item.entry.code }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="box is-shadowless p-4" style="border-left: 5px solid #204853;">
|
||||
<div
|
||||
class="box is-shadowless p-4"
|
||||
style="border-left: 5px solid #204853"
|
||||
>
|
||||
<div class="columns is-mobile is-multiline is-gap-3">
|
||||
<div class="column is-6-mobile">
|
||||
<span class="has-text-grey-light">Gốc trả:</span><br />
|
||||
<span v-if="item.entry.principal > 0" class="has-text-success has-text-weight-semibold is-size-5">
|
||||
<span
|
||||
v-if="item.entry.principal > 0"
|
||||
class="has-text-success has-text-weight-semibold is-size-5"
|
||||
>
|
||||
{{ $numtoString(item.entry.principal) }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey-light">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="has-text-grey-light"
|
||||
>-</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-mobile">
|
||||
<span class="has-text-grey-light">Lãi trả:</span><br />
|
||||
<span v-if="item.entry.penalty > 0" class="has-text-danger has-text-weight-semibold is-size-5">
|
||||
<span
|
||||
v-if="item.entry.penalty > 0"
|
||||
class="has-text-danger has-text-weight-semibold is-size-5"
|
||||
>
|
||||
{{ $numtoString(item.entry.penalty) }}
|
||||
</span>
|
||||
<span v-else class="has-text-grey-light">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="has-text-grey-light"
|
||||
>-</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="column is-6-mobile">
|
||||
<span class="has-text-grey-light">Gốc còn lại :</span><br />
|
||||
<strong :class="item.principalRemain > 0 ? 'has-text-danger' : 'has-text-success'"
|
||||
class="is-size-5">
|
||||
<strong
|
||||
:class="item.principalRemain > 0 ? 'has-text-danger' : 'has-text-success'"
|
||||
class="is-size-5"
|
||||
>
|
||||
{{ $numtoString(item.principalRemain) }}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Chi tiết lãi phát sinh -->
|
||||
<div class="">
|
||||
<!-- Diễn giải chi tiết cách tính lãi kỳ này -->
|
||||
<p v-if="item.penaltyThisPeriod > 0" class="is-size-6 has-text-grey">
|
||||
Dư nợ gốc còn lại: {{ $numtoString(item.principalBefore) }} ×
|
||||
{{ item.penaltyDetail.days }} ngày (từ {{ item.penaltyDetail.from }} đến {{ item.penaltyDetail.to
|
||||
}}) × {{item.rate}}%/ngày = {{ $numtoString(item.penaltyThisPeriod) }}
|
||||
<p
|
||||
v-if="item.penaltyThisPeriod > 0"
|
||||
class="is-size-6 has-text-grey"
|
||||
>
|
||||
Dư nợ gốc còn lại:
|
||||
{{ $numtoString(item.principalBefore) }} × {{ item.penaltyDetail.days }} ngày (từ
|
||||
{{ item.penaltyDetail.from }} đến {{ item.penaltyDetail.to }}) × {{ item.rate }}%/ngày =
|
||||
{{ $numtoString(item.penaltyThisPeriod) }}
|
||||
</p>
|
||||
|
||||
<p class="is-size-6 mt-3">
|
||||
@@ -171,18 +225,30 @@
|
||||
</div>
|
||||
|
||||
<!-- Tiền lãi hiện tại (nếu chưa hết nợ) -->
|
||||
<div v-if="hasUnpaidDebt && latestPenaltyToThisEntry != paymentScheduleData.penalty_amount && processedEntries.length > 0 && latestPenaltyToThisEntry >0" class="mt-5 box has-background-warning-light">
|
||||
|
||||
<div
|
||||
v-if="
|
||||
hasUnpaidDebt &&
|
||||
latestPenaltyToThisEntry != paymentScheduleData.penalty_amount &&
|
||||
processedEntries.length > 0 &&
|
||||
latestPenaltyToThisEntry > 0
|
||||
"
|
||||
class="mt-5 box has-background-warning-light"
|
||||
>
|
||||
<p class="is-size-5">
|
||||
<strong>Lãi đến ngày thanh toán gần nhất ({{ latestEntryDate }}) :</strong> {{ $numtoString(latestPenaltyToThisEntry) }}
|
||||
<strong>Lãi đến ngày thanh toán gần nhất ({{ latestEntryDate }}) :</strong>
|
||||
{{ $numtoString(latestPenaltyToThisEntry) }}
|
||||
</p>
|
||||
<p v-if="latestAdditionalPenalty > 0" class="is-size-5 mt-2">
|
||||
<strong>Lãi phát sinh từ ngày {{ latestEntryDate }} đến nay:</strong> {{
|
||||
$numtoString(latestAdditionalPenalty) }}
|
||||
<p
|
||||
v-if="latestAdditionalPenalty > 0"
|
||||
class="is-size-5 mt-2"
|
||||
>
|
||||
<strong>Lãi phát sinh từ ngày {{ latestEntryDate }} đến nay:</strong>
|
||||
{{ $numtoString(latestAdditionalPenalty) }}
|
||||
</p>
|
||||
<p class="is-size-5 has-text-weight-bold mt-3">
|
||||
Tổng lãi hiện tại : {{
|
||||
$numtoString(latestPenaltyToThisEntry) }} + {{ $numtoString(latestAdditionalPenalty) }} = {{ $numtoString(paymentScheduleData.penalty_amount) }}
|
||||
Tổng lãi hiện tại : {{ $numtoString(latestPenaltyToThisEntry) }} +
|
||||
{{ $numtoString(latestAdditionalPenalty) }} =
|
||||
{{ $numtoString(paymentScheduleData.penalty_amount) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -192,44 +258,68 @@
|
||||
<div class="columns is-mobile is-multiline">
|
||||
<div class="column is-3-tablet is-6-mobile">
|
||||
<p class="heading">Gốc đã trả</p>
|
||||
<p class="title is-5 has-text-success">{{ $numtoString(paymentScheduleData.paid_amount) }}</p>
|
||||
<p class="title is-5 has-text-success">
|
||||
{{ $numtoString(paymentScheduleData.paid_amount) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3-tablet is-6-mobile">
|
||||
<p class="heading">Gốc còn lại</p>
|
||||
<p class="title is-5"
|
||||
:class="paymentScheduleData.amount_remain > 0 ? 'has-text-danger' : 'has-text-success'">
|
||||
<p
|
||||
class="title is-5"
|
||||
:class="paymentScheduleData.amount_remain > 0 ? 'has-text-danger' : 'has-text-success'"
|
||||
>
|
||||
{{ $numtoString(paymentScheduleData.amount_remain) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3-tablet is-6-mobile">
|
||||
<p class="heading">Lãi đã trả</p>
|
||||
<p class="title is-5 has-text-success">{{ $numtoString(paymentScheduleData.penalty_paid) }}</p>
|
||||
<p class="title is-5 has-text-success">
|
||||
{{ $numtoString(paymentScheduleData.penalty_paid) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3-tablet is-6-mobile">
|
||||
<p class="heading">Lãi còn lại</p>
|
||||
<p class="title is-5"
|
||||
:class="paymentScheduleData.penalty_remain > 0 ? 'has-text-danger' : 'has-text-success'">
|
||||
<p
|
||||
class="title is-5"
|
||||
:class="paymentScheduleData.penalty_remain > 0 ? 'has-text-danger' : 'has-text-success'"
|
||||
>
|
||||
{{ $numtoString(paymentScheduleData.penalty_remain) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-3-tablet is-6-mobile">
|
||||
<p class="heading">Tổng tiền phải thanh toán</p>
|
||||
<p class="title is-5 has-text-primary">{{ $numtoString(paymentScheduleData.remain_amount) }}</p>
|
||||
<p class="title is-5 has-text-primary">
|
||||
{{ $numtoString(paymentScheduleData.remain_amount) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isConfirmAllowed && $getEditRights()" class="field is-grouped is-grouped-left ">
|
||||
<div
|
||||
v-if="isConfirmAllowed && $getEditRights()"
|
||||
class="field is-grouped is-grouped-left"
|
||||
>
|
||||
<p class="control">
|
||||
<button class="button is-info mx-3 has-text-white" @click="handleViewEmail">
|
||||
<button
|
||||
class="button is-info mx-3 has-text-white"
|
||||
@click="handleViewEmail"
|
||||
>
|
||||
<span>Gửi thông báo</span>
|
||||
</button>
|
||||
<button v-if="paymentScheduleData.batch_date == null" class="button is-danger has-text-white"
|
||||
:class="{ 'is-loading': isLoading }" @click="getPenalty" :disabled="isLoading">
|
||||
<button
|
||||
v-if="paymentScheduleData.batch_date == null"
|
||||
class="button is-danger has-text-white"
|
||||
:class="{ 'is-loading': isLoading }"
|
||||
@click="getPenalty"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<span>Tính lãi</span>
|
||||
</button>
|
||||
</p>
|
||||
<Modal @close="showModalViewEmail = undefined" v-bind="showModalViewEmail" v-if="showModalViewEmail"></Modal>
|
||||
<Modal
|
||||
@close="showModalViewEmail = undefined"
|
||||
v-bind="showModalViewEmail"
|
||||
v-if="showModalViewEmail"
|
||||
></Modal>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -293,7 +383,7 @@ const processedEntries = computed(() => {
|
||||
if (!paymentScheduleData.value?.entry) return [];
|
||||
|
||||
const relevantEntries = paymentScheduleData.value.entry.filter(
|
||||
(e) => e.type === "PAYMENT" && e.penalty_added_to_entry !== undefined
|
||||
(e) => e.type === "PAYMENT" && e.penalty_added_to_entry !== undefined,
|
||||
);
|
||||
|
||||
relevantEntries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||
@@ -337,7 +427,7 @@ const processedEntries = computed(() => {
|
||||
penaltyAccumulated: totalPenaltyAccumulated,
|
||||
penaltyThisPeriod,
|
||||
penaltyRemain,
|
||||
rate:Number(entry.DAILY_PENALTY_RATE || 0)*100,
|
||||
rate: Number(entry.DAILY_PENALTY_RATE || 0) * 100,
|
||||
totalDebt: Number(entry.amount_remain_after_allocation || 0) + penaltyThisPeriod,
|
||||
penaltyDetail: {
|
||||
from: lastEventDate.format("DD/MM/YYYY"),
|
||||
@@ -354,17 +444,14 @@ const processedEntries = computed(() => {
|
||||
const latestEntry = computed(() => {
|
||||
if (!paymentScheduleData.value?.entry?.length) return null;
|
||||
const paymentEntries = paymentScheduleData.value.entry.filter(
|
||||
e => e.type === "PAYMENT" && e.penalty_to_this_entry !== undefined
|
||||
(e) => e.type === "PAYMENT" && e.penalty_to_this_entry !== undefined,
|
||||
);
|
||||
if (paymentEntries.length === 0) return null;
|
||||
return paymentEntries[paymentEntries.length - 1];
|
||||
});
|
||||
|
||||
const hasUnpaidDebt = computed(() => {
|
||||
return (
|
||||
paymentScheduleData.value?.amount_remain > 0 ||
|
||||
paymentScheduleData.value?.penalty_remain > 0
|
||||
);
|
||||
return paymentScheduleData.value?.amount_remain > 0 || paymentScheduleData.value?.penalty_remain > 0;
|
||||
});
|
||||
|
||||
const latestPenaltyToThisEntry = computed(() => {
|
||||
@@ -407,7 +494,7 @@ const fetchPaymentScheduleData = async () => {
|
||||
$snackbar(
|
||||
isVietnamese.value ? "Không thể tải thông tin công nợ." : "Failed to load payment schedule information.",
|
||||
"Lỗi",
|
||||
"Error"
|
||||
"Error",
|
||||
);
|
||||
} finally {
|
||||
loadingData.value = false;
|
||||
@@ -439,7 +526,7 @@ const handleUpdateInvoice = async () => {
|
||||
$snackbar(
|
||||
isVietnamese.value ? "Vui lòng nhập đầy đủ link và mã xác thực" : "Please enter both link and reference code",
|
||||
"Cảnh báo",
|
||||
"Warning"
|
||||
"Warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -454,22 +541,18 @@ const handleUpdateInvoice = async () => {
|
||||
ref_code: invoiceData.value.ref_code,
|
||||
},
|
||||
undefined,
|
||||
false
|
||||
false,
|
||||
);
|
||||
if (response === "error" || !response) throw new Error("Update failed");
|
||||
$snackbar(
|
||||
isVietnamese.value ? "Cập nhật hóa đơn thành công!" : "Invoice updated successfully!",
|
||||
"Thành công",
|
||||
"Success"
|
||||
"Success",
|
||||
);
|
||||
await fetchPaymentScheduleData();
|
||||
} catch (error) {
|
||||
console.error("Error updating invoice:", error);
|
||||
$snackbar(
|
||||
isVietnamese.value ? "Có lỗi xảy ra khi cập nhật hóa đơn" : "Error updating invoice",
|
||||
"Lỗi",
|
||||
"Error"
|
||||
);
|
||||
$snackbar(isVietnamese.value ? "Có lỗi xảy ra khi cập nhật hóa đơn" : "Error updating invoice", "Lỗi", "Error");
|
||||
} finally {
|
||||
isUpdatingInvoice.value = false;
|
||||
}
|
||||
@@ -497,7 +580,7 @@ async function handleViewEmail() {
|
||||
"emailtemplate",
|
||||
{ name: "Mail Thông báo đến hạn thanh toán" },
|
||||
undefined,
|
||||
false
|
||||
false,
|
||||
);
|
||||
showModalViewEmail.value = {
|
||||
component: "marketing/email/viewEmail/ViewEmail",
|
||||
@@ -507,7 +590,7 @@ async function handleViewEmail() {
|
||||
idEmailTemplate: emailTemplate[0]?.id || null,
|
||||
scheduleItemId: props.scheduleItemId,
|
||||
},
|
||||
onConfirm: () => { },
|
||||
onConfirm: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -522,4 +605,4 @@ const handleConfirmDelete = () => {
|
||||
onMounted(() => {
|
||||
fetchPaymentScheduleData();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<ViewList :vbind="{
|
||||
api: 'payment_schedule',
|
||||
params: {
|
||||
filter: { txn_detail: scheduleDetailId },
|
||||
values: 'link,ref_code,batch_date,amount_remain,penalty_remain,penalty_paid,penalty_amount,penalty_reduce,ovd_days,remain_amount,paid_amount,txn_detail__transaction__product__trade_code,txn_detail__status,txn_detail__transaction__product__code,txn_detail__phase__name,txn_detail,id,txn_detail__transaction__customer__fullname,txn_detail__transaction__customer__code,txn_detail__transaction__customer__legal_code,status__name,type__name,code,from_date,txn_detail__transaction__policy__code,to_date,amount,cycle,cycle_days,txn_detail__transaction,type,status,updater,entry,detail,txn_detail__transaction__code,txn_detail__code',
|
||||
},
|
||||
setting: 'payment_schedule_list_timeline',
|
||||
}" />
|
||||
<ViewList
|
||||
:vbind="{
|
||||
api: 'payment_schedule',
|
||||
params: {
|
||||
filter: { txn_detail: scheduleDetailId },
|
||||
values:
|
||||
'link,ref_code,batch_date,amount_remain,penalty_remain,penalty_paid,penalty_amount,penalty_reduce,ovd_days,remain_amount,paid_amount,txn_detail__transaction__product__trade_code,txn_detail__status,txn_detail__transaction__product__code,txn_detail__phase__name,txn_detail,id,txn_detail__transaction__customer__fullname,txn_detail__transaction__customer__code,txn_detail__transaction__customer__legal_code,status__name,type__name,code,from_date,txn_detail__transaction__policy__code,to_date,amount,cycle,cycle_days,txn_detail__transaction,type,status,updater,entry,detail,txn_detail__transaction__code,txn_detail__code',
|
||||
},
|
||||
setting: 'payment_schedule_list_timeline',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
scheduleDetailId: [Number, String],
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,12 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">ID<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.id" disabled>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.id"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,7 +17,11 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Mã giữ chỗ<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.code">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,7 +29,11 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Mã giao dịch<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.transaction__code">
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.transaction__code"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +41,12 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Người tạo<span class="has-text-danger ml-1">*</span></label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="record.creator__fullname" disabled>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
v-model="record.creator__fullname"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,7 +54,11 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Ngày bắt đầu</label>
|
||||
<div class="control">
|
||||
<input class="input" type="date" v-model="record.date">
|
||||
<input
|
||||
class="input"
|
||||
type="date"
|
||||
v-model="record.date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +66,11 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Hạn thanh toán</label>
|
||||
<div class="control">
|
||||
<input class="input" type="date" v-model="record.due_date">
|
||||
<input
|
||||
class="input"
|
||||
type="date"
|
||||
v-model="record.due_date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,7 +78,11 @@
|
||||
<div class="field">
|
||||
<label class="label has-text-left">Sô tiền đặt cọc</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" v-model="record.amount">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
v-model="record.amount"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,11 +104,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12 pt-5">
|
||||
<a class="button is-primary has-text-white" @click="updateData()">Lưu lại</a>
|
||||
<a class="button is-dark has-text-white ml-5" @click="updateData(true)" v-if="record.id">Tạo mới</a>
|
||||
<a
|
||||
<a
|
||||
class="button is-primary has-text-white"
|
||||
@click="updateData()"
|
||||
>Lưu lại</a
|
||||
>
|
||||
<a
|
||||
class="button is-dark has-text-white ml-5"
|
||||
@click="updateData(true)"
|
||||
v-if="record.id"
|
||||
>Tạo mới</a
|
||||
>
|
||||
<a
|
||||
v-if="contractData"
|
||||
class="button is-info has-text-white ml-5"
|
||||
class="button is-info has-text-white ml-5"
|
||||
@click="openContractModal"
|
||||
>
|
||||
<span class="icon">
|
||||
@@ -86,14 +125,12 @@
|
||||
</span>
|
||||
<span>Xem hợp đồng</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@texteditor="updateText"
|
||||
<Modal
|
||||
@close="showmodal = undefined"
|
||||
v-bind="showmodal"
|
||||
@texteditor="updateText"
|
||||
@update="updateAttr"
|
||||
@dataevent="handleModalEvent"
|
||||
v-if="showmodal"
|
||||
@@ -101,102 +138,110 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits([])
|
||||
const emit = defineEmits([]);
|
||||
var props = defineProps({
|
||||
pagename: String,
|
||||
row: Object,
|
||||
api: String
|
||||
})
|
||||
api: String,
|
||||
});
|
||||
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $getdata } = useNuxtApp()
|
||||
var record = ref(props.row ? $copy(props.row) : {})
|
||||
var showmodal = ref()
|
||||
var vapi = props.api || 'reservation'
|
||||
var contractData = ref(null)
|
||||
const { $copy, $resetNull, $insertrow, $updaterow, $getdata } = useNuxtApp();
|
||||
var record = ref(props.row ? $copy(props.row) : {});
|
||||
var showmodal = ref();
|
||||
var vapi = props.api || "reservation";
|
||||
var contractData = ref(null);
|
||||
|
||||
const loadContract = async () => {
|
||||
if (record.value.transaction__code) {
|
||||
try {
|
||||
contractData.value = await $getdata('contract', { transaction__code: record.value.transaction__code }, undefined, true)
|
||||
contractData.value = await $getdata(
|
||||
"contract",
|
||||
{ transaction__code: record.value.transaction__code },
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error loading contract:', error)
|
||||
contractData.value = null
|
||||
console.error("Error loading contract:", error);
|
||||
contractData.value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const contractModalConfig = computed(() => ({
|
||||
component: 'application/Contract',
|
||||
title: 'Hợp đồng',
|
||||
width: '90%',
|
||||
height: '90vh',
|
||||
component: "application/Contract",
|
||||
title: "Hợp đồng",
|
||||
width: "90%",
|
||||
height: "90vh",
|
||||
vbind: {
|
||||
row: contractData.value,
|
||||
api: 'transaction',
|
||||
api: "transaction",
|
||||
},
|
||||
}))
|
||||
}));
|
||||
|
||||
const openContractModal = () => {
|
||||
showmodal.value = contractModalConfig.value
|
||||
}
|
||||
showmodal.value = contractModalConfig.value;
|
||||
};
|
||||
|
||||
const handleModalEvent = (eventData) => {
|
||||
if (eventData?.data?.transaction) {
|
||||
contractData.value = { ...contractData.value, ...eventData.data }
|
||||
contractData.value = { ...contractData.value, ...eventData.data };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var selectedStatus = function(option) {
|
||||
record.value.approve_status = option.id
|
||||
}
|
||||
var selectedStatus = function (option) {
|
||||
record.value.approve_status = option.id;
|
||||
};
|
||||
|
||||
var updateText = function (content) {
|
||||
record.value.vi = content
|
||||
}
|
||||
record.value.vi = content;
|
||||
};
|
||||
|
||||
var openEditor = function () {
|
||||
showmodal.value = {
|
||||
component: 'common/TextEditor',
|
||||
vbind: { content: record.value.vi },
|
||||
title: 'Text editor',
|
||||
width: '40%',
|
||||
height: '150px'
|
||||
}
|
||||
}
|
||||
showmodal.value = {
|
||||
component: "common/TextEditor",
|
||||
vbind: { content: record.value.vi },
|
||||
title: "Text editor",
|
||||
width: "40%",
|
||||
height: "150px",
|
||||
};
|
||||
};
|
||||
|
||||
var editDetail = function () {
|
||||
let detail = record.value.detail ? record.value.detail : {}
|
||||
showmodal.value = {
|
||||
component: 'datatable/FieldAttribute',
|
||||
vbind: { field: detail, close: true },
|
||||
title: 'Sửa thuộc tính',
|
||||
width: '40%',
|
||||
height: '150px'
|
||||
}
|
||||
}
|
||||
let detail = record.value.detail ? record.value.detail : {};
|
||||
showmodal.value = {
|
||||
component: "datatable/FieldAttribute",
|
||||
vbind: { field: detail, close: true },
|
||||
title: "Sửa thuộc tính",
|
||||
width: "40%",
|
||||
height: "150px",
|
||||
};
|
||||
};
|
||||
|
||||
var updateAttr = function (detail) {
|
||||
record.value.detail = detail
|
||||
}
|
||||
record.value.detail = detail;
|
||||
};
|
||||
|
||||
var updateData = async function (isNew) {
|
||||
let ele = record.value
|
||||
if (ele.create_time === null) ele.create_time = new Date()
|
||||
ele = $resetNull(ele)
|
||||
if (isNew) delete ele.id
|
||||
let result = ele.id
|
||||
let ele = record.value;
|
||||
if (ele.create_time === null) ele.create_time = new Date();
|
||||
ele = $resetNull(ele);
|
||||
if (isNew) delete ele.id;
|
||||
let result = ele.id
|
||||
? await $updaterow(vapi, ele, undefined, props.pagename)
|
||||
: await $insertrow(vapi, ele, undefined, props.pagename)
|
||||
if (isNew) emit('close')
|
||||
|
||||
await loadContract()
|
||||
}
|
||||
: await $insertrow(vapi, ele, undefined, props.pagename);
|
||||
if (isNew) emit("close");
|
||||
|
||||
await loadContract();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadContract()
|
||||
})
|
||||
loadContract();
|
||||
});
|
||||
|
||||
watch(() => record.value.transaction, () => {
|
||||
loadContract()
|
||||
})
|
||||
</script>
|
||||
watch(
|
||||
() => record.value.transaction,
|
||||
() => {
|
||||
loadContract();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<div
|
||||
:class="['is-clickable p-3', i !== 0 && 'mt-2', getStyle(v)]"
|
||||
style="width: 120px; border-radius: 4px;"
|
||||
style="width: 120px; border-radius: 4px"
|
||||
v-for="(v, i) in tabsArray"
|
||||
:key="i"
|
||||
@click="changeTab(v)"
|
||||
@@ -17,11 +17,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div
|
||||
<div
|
||||
:class="`column pl-4 ${viewport < 2 ? 'px-0' : 'pr-0 py-0'}`"
|
||||
style="min-width: 0"
|
||||
>
|
||||
<div v-if="isLoading" class="has-text-centered">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="has-text-centered"
|
||||
>
|
||||
<span class="icon is-large">
|
||||
<SvgIcon v-bind="{ name: 'loading.svg', type: 'primary', size: 18 }" />
|
||||
</span>
|
||||
@@ -58,10 +61,10 @@ const tabsArray = computed(() => {
|
||||
if (Array.isArray(props.tabs)) {
|
||||
return props.tabs;
|
||||
}
|
||||
if (typeof props.tabs === 'object' && props.tabs !== null) {
|
||||
if (typeof props.tabs === "object" && props.tabs !== null) {
|
||||
return Object.keys(props.tabs)
|
||||
.sort()
|
||||
.map(key => props.tabs[key]);
|
||||
.map((key) => props.tabs[key]);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@@ -80,11 +83,13 @@ const isVietnamese = computed(() => lang.value === "vi");
|
||||
const viewport = computed(() => store.viewport);
|
||||
|
||||
const record = ref(props.row || {});
|
||||
const activeTab = ref(tabsArray.value.find(t => t.active === true || t.active === 'true')?.code || tabsArray.value[0]?.code || '');
|
||||
const activeTab = ref(
|
||||
tabsArray.value.find((t) => t.active === true || t.active === "true")?.code || tabsArray.value[0]?.code || "",
|
||||
);
|
||||
const tabData = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
const activeComponent = computed(() => tabsArray.value.find(t => t.code === activeTab.value));
|
||||
const activeComponent = computed(() => tabsArray.value.find((t) => t.code === activeTab.value));
|
||||
|
||||
const componentProps = computed(() => {
|
||||
const baseProps = {
|
||||
@@ -100,7 +105,7 @@ const componentProps = computed(() => {
|
||||
if (record.value) {
|
||||
for (const key in tabSpecificProps) {
|
||||
const value = tabSpecificProps[key];
|
||||
if (typeof value === 'string' && value.startsWith('row.')) {
|
||||
if (typeof value === "string" && value.startsWith("row.")) {
|
||||
const recordKey = value.substring(4);
|
||||
processedProps[key] = record.value[recordKey];
|
||||
} else {
|
||||
@@ -112,14 +117,18 @@ const componentProps = computed(() => {
|
||||
return { ...baseProps, ...processedProps };
|
||||
});
|
||||
|
||||
watch(activeTab, async (newTabCode) => {
|
||||
const tabConfig = tabsArray.value.find(t => t.code === newTabCode);
|
||||
if (tabConfig && tabConfig.api) {
|
||||
await fetchTabData(tabConfig);
|
||||
} else {
|
||||
tabData.value = null;
|
||||
}
|
||||
}, { immediate: true });
|
||||
watch(
|
||||
activeTab,
|
||||
async (newTabCode) => {
|
||||
const tabConfig = tabsArray.value.find((t) => t.code === newTabCode);
|
||||
if (tabConfig && tabConfig.api) {
|
||||
await fetchTabData(tabConfig);
|
||||
} else {
|
||||
tabData.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
async function fetchTabData(tabConfig) {
|
||||
isLoading.value = true;
|
||||
@@ -136,16 +145,16 @@ async function fetchTabData(tabConfig) {
|
||||
let params = $copy(tabConfig.api.params);
|
||||
for (const key in params.filter) {
|
||||
const value = params.filter[key];
|
||||
if (typeof value === 'string' && value.startsWith('record.')) {
|
||||
if (typeof value === "string" && value.startsWith("record.")) {
|
||||
const recordKey = value.substring(7);
|
||||
params.filter[key] = record.value[recordKey];
|
||||
}
|
||||
}
|
||||
conn.params = params;
|
||||
}
|
||||
|
||||
|
||||
const result = await $getapi([conn]);
|
||||
const apiResult = result.find(r => r.name === tabConfig.api.name);
|
||||
const apiResult = result.find((r) => r.name === tabConfig.api.name);
|
||||
if (apiResult && apiResult.data) {
|
||||
tabData.value = apiResult.data.rows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user