feat(payments): automatically notify admin and show existing invoice details when selecting student

This commit is contained in:
2026-08-23 16:55:56 +03:30
parent 966b066358
commit 7cb2b957a7
+51 -12
View File
@@ -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>