feat(payments): automatically notify admin and show existing invoice details when selecting student
This commit is contained in:
@@ -119,15 +119,28 @@
|
|||||||
<!-- Duplicate Payment Warning -->
|
<!-- Duplicate Payment Warning -->
|
||||||
<div
|
<div
|
||||||
v-if="duplicateWarning && duplicateWarning.hasDuplicate"
|
v-if="duplicateWarning && duplicateWarning.hasDuplicate"
|
||||||
class="p-3 border-round flex align-items-start gap-2 duplicate-warning-box"
|
class="p-3 border-round flex flex-column gap-2 duplicate-warning-box"
|
||||||
>
|
>
|
||||||
<i class="pi pi-exclamation-triangle text-xl text-yellow-400 mt-1 flex-shrink-0"></i>
|
<div class="flex align-items-center gap-2">
|
||||||
<div class="flex flex-column gap-1">
|
<i class="pi pi-exclamation-triangle text-xl text-yellow-500 flex-shrink-0"></i>
|
||||||
<span class="font-bold text-yellow-300 text-sm">هشدار: برای این دانشجو قبلاً در این کلاس صورتحساب ثبت شده است.</span>
|
<span class="font-bold text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<span class="text-xs text-yellow-200" v-if="duplicateWarning.payments && duplicateWarning.payments.length">
|
توجه: برای این دانشجو قبلاً در این کلاس صورتحساب ثبت شده است.
|
||||||
صورتحساب قبلی با کد {{ duplicateWarning.payments.map(p => p.uniqueCode || p._id).join('، ') }} موجود است.
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-column gap-2 mt-1 text-xs" v-if="duplicateWarning.payments && duplicateWarning.payments.length">
|
||||||
|
<div
|
||||||
|
v-for="p in duplicateWarning.payments"
|
||||||
|
:key="p._id"
|
||||||
|
class="duplicate-item p-2 border-round flex flex-wrap align-items-center justify-content-between gap-2"
|
||||||
|
>
|
||||||
|
<div class="flex align-items-center gap-2 flex-wrap">
|
||||||
|
<span>کد صورتحساب: <strong>{{ p.uniqueCode || p._id }}</strong></span>
|
||||||
|
<span v-if="p.amount">مبلغ: <strong>{{ toPersianDigits((p.amount || 0).toLocaleString()) }} تومان</strong></span>
|
||||||
|
<span v-if="p.createdAt" class="text-color-secondary">تاریخ: {{ formatJalali(p.createdAt) }}</span>
|
||||||
|
</div>
|
||||||
|
<StatusTag v-if="p.status" :value="p.status" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-column gap-2">
|
<div class="flex flex-column gap-2">
|
||||||
@@ -316,7 +329,7 @@ import DatePicker from 'vue3-persian-datetime-picker';
|
|||||||
|
|
||||||
const { toPersianDigits, formatJalali, toGregorianIso, getTodayJalali } = usePersianDate();
|
const { toPersianDigits, formatJalali, toGregorianIso, getTodayJalali } = usePersianDate();
|
||||||
|
|
||||||
const { showSuccess, showError } = useToast();
|
const { showSuccess, showError, showWarn } = useToast();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
items,
|
items,
|
||||||
@@ -334,6 +347,7 @@ const usersList = ref([]);
|
|||||||
const classesList = ref([]);
|
const classesList = ref([]);
|
||||||
const isCreating = ref(false);
|
const isCreating = ref(false);
|
||||||
const duplicateWarning = ref(null);
|
const duplicateWarning = ref(null);
|
||||||
|
let lastDuplicateAlertKey = '';
|
||||||
|
|
||||||
const createForm = reactive({
|
const createForm = reactive({
|
||||||
user: null,
|
user: null,
|
||||||
@@ -418,24 +432,43 @@ const checkDuplicateInvoice = async (userIdVal = null) => {
|
|||||||
const targetUserId = extractId(userIdVal) || extractId(createForm.user);
|
const targetUserId = extractId(userIdVal) || extractId(createForm.user);
|
||||||
if (!targetUserId || !createForm.classes || !createForm.classes.length) {
|
if (!targetUserId || !createForm.classes || !createForm.classes.length) {
|
||||||
duplicateWarning.value = null;
|
duplicateWarning.value = null;
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const classIds = Array.isArray(createForm.classes)
|
const classIds = Array.isArray(createForm.classes)
|
||||||
? createForm.classes.map(extractId).filter(Boolean).join(',')
|
? createForm.classes.map(extractId).filter(Boolean).sort().join(',')
|
||||||
: (extractId(createForm.classes) || '');
|
: (extractId(createForm.classes) || '');
|
||||||
if (!classIds) {
|
if (!classIds) {
|
||||||
duplicateWarning.value = null;
|
duplicateWarning.value = null;
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkKey = `${targetUserId}_${classIds}`;
|
||||||
const res = await paymentApi.checkDuplicate({
|
const res = await paymentApi.checkDuplicate({
|
||||||
userId: targetUserId,
|
userId: targetUserId,
|
||||||
classes: classIds
|
classes: classIds
|
||||||
});
|
});
|
||||||
duplicateWarning.value = res.data?.data || res.data || res;
|
const result = res.data?.data || res.data || res;
|
||||||
|
duplicateWarning.value = result;
|
||||||
|
|
||||||
|
if (result && result.hasDuplicate) {
|
||||||
|
if (lastDuplicateAlertKey !== checkKey) {
|
||||||
|
lastDuplicateAlertKey = checkKey;
|
||||||
|
const studentObj = usersList.value.find((u) => String(u._id || u.id) === String(targetUserId));
|
||||||
|
const studentName = studentObj?.name || studentObj?.fullName || 'این دانشجو';
|
||||||
|
const codes = (result.payments || []).map((p) => p.uniqueCode || p._id).filter(Boolean).join('، ');
|
||||||
|
const codeSuffix = codes ? ` (کد: ${codes})` : '';
|
||||||
|
showWarn(`برای ${studentName} قبلاً در این کلاس صورتحساب ثبت شده است${codeSuffix}.`, 'اطلاعیه صورتحساب قبلی');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('checkDuplicateInvoice error:', err);
|
console.error('checkDuplicateInvoice error:', err);
|
||||||
duplicateWarning.value = null;
|
duplicateWarning.value = null;
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -446,6 +479,7 @@ watch(
|
|||||||
await checkDuplicateInvoice(newUser);
|
await checkDuplicateInvoice(newUser);
|
||||||
} else {
|
} else {
|
||||||
duplicateWarning.value = null;
|
duplicateWarning.value = null;
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
@@ -507,6 +541,7 @@ const resetCreateForm = () => {
|
|||||||
createForm.dueDate = getTodayJalali();
|
createForm.dueDate = getTodayJalali();
|
||||||
hasDiscount.value = false;
|
hasDiscount.value = false;
|
||||||
duplicateWarning.value = null;
|
duplicateWarning.value = null;
|
||||||
|
lastDuplicateAlertKey = '';
|
||||||
createNotify.sms = true;
|
createNotify.sms = true;
|
||||||
createNotify.email = true;
|
createNotify.email = true;
|
||||||
createNotify.bot = true;
|
createNotify.bot = true;
|
||||||
@@ -691,8 +726,12 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.duplicate-warning-box {
|
.duplicate-warning-box {
|
||||||
background: rgba(234, 179, 8, 0.15) !important;
|
background: rgba(245, 158, 11, 0.12) !important;
|
||||||
border: 1px solid rgba(234, 179, 8, 0.6) !important;
|
border: 1px solid rgba(245, 158, 11, 0.45) !important;
|
||||||
color: #fef08a !important;
|
}
|
||||||
|
|
||||||
|
.duplicate-item {
|
||||||
|
background: rgba(245, 158, 11, 0.08);
|
||||||
|
border: 1px dashed rgba(245, 158, 11, 0.35);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user