Files
gameno-dashboard/src/views/payments/PaymentDetailView.vue
T
kavehhn 45a1c94774 feat: show transaction due dates on invoices
Surface paid vs pending installments and imported transaction counts after moving transactions off the payment document.
2026-08-16 07:32:27 +03:30

227 lines
10 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- /src/views/payments/PaymentDetailView.vue -->
<template>
<div class="payment-detail-view" v-if="payment">
<PageHeader :title="$t('payments.paymentDetail')" :subtitle="`کد صورتحساب: ${payment._id || payment.id}`">
<Button label="بازگشت" icon="pi pi-arrow-left" text severity="secondary" @click="$router.push('/payments')" />
</PageHeader>
<div class="grid mb-4">
<!-- Summary Card -->
<div class="col-12 lg:col-4">
<div class="surface-card p-4 border-round border-1 border-color shadow-sm mb-4">
<h3 class="text-lg font-bold mb-3">خلاصه صورتحساب</h3>
<div class="flex flex-column gap-3">
<div>
<span class="text-muted text-xs block mb-1">نام کاربر / دانشجو</span>
<span class="font-bold text-color text-base">{{ payment.user?.name || payment.userName || 'کاربر' }}</span>
</div>
<div v-if="payment.classes && payment.classes.length">
<span class="text-muted text-xs block mb-1">کلاسهای مربوطه</span>
<div class="flex flex-wrap gap-1 mt-1">
<Tag v-for="c in payment.classes" :key="c._id || c" :value="c.name || 'کلاس'" severity="info" class="text-xs" />
</div>
</div>
<div v-else-if="payment.course">
<span class="text-muted text-xs block mb-1">دوره آموزشی</span>
<span class="font-semibold text-color text-sm">{{ payment.course?.title }}</span>
</div>
<div>
<span class="text-muted text-xs block mb-1">مبلغ کل صورتحساب</span>
<span class="font-bold text-color text-xl">{{ toPersianDigits((payment.amount || 0).toLocaleString()) }} تومان</span>
</div>
<div v-if="payment.discount">
<span class="text-muted text-xs block mb-1">تخفیف</span>
<span class="font-semibold text-orange-500 text-lg">{{ toPersianDigits((payment.discount || 0).toLocaleString()) }} تومان</span>
</div>
<div v-if="payment.discount">
<span class="text-muted text-xs block mb-1">مبلغ قابل پرداخت</span>
<span class="font-bold text-color text-xl">{{ toPersianDigits(payableAmount.toLocaleString()) }} تومان</span>
</div>
<div>
<span class="text-muted text-xs block mb-1">مبلغ کل دریافتی</span>
<span class="font-bold text-green-600 text-lg">{{ toPersianDigits((payment.paidAmount || 0).toLocaleString()) }} تومان</span>
</div>
<div>
<span class="text-muted text-xs block mb-1">باقیمانده</span>
<span class="font-bold text-red-500 text-lg">{{ toPersianDigits(remainingAmount.toLocaleString()) }} تومان</span>
</div>
<div>
<span class="text-muted text-xs block mb-1">وضعیت پرداخت</span>
<StatusTag :status="payment.status" type="payment" />
</div>
<div>
<span class="text-muted text-xs block mb-1">یادداشت</span>
<p class="text-sm text-color m-0 white-space-pre-wrap line-height-3">{{ payment.notes || '—' }}</p>
</div>
</div>
</div>
</div>
<!-- Transaction History & Form -->
<div class="col-12 lg:col-8">
<div class="surface-card p-4 border-round border-1 border-color shadow-sm mb-4">
<div class="flex align-items-center justify-content-between mb-3">
<h3 class="text-lg font-bold m-0">تاریخچه تراکنشهای دریافتی</h3>
<PermissionGate permission="payments:update">
<Button label="ثبت تراکنش جدید" icon="pi pi-plus" size="small" severity="success" @click="showTransactionModal = true" />
</PermissionGate>
</div>
<DataTable :value="payment.transactions || []" class="p-datatable-sm text-sm">
<Column field="amount" header="مبلغ تراکنش">
<template #body="{ data }">
{{ toPersianDigits(data.amount?.toLocaleString()) }} تومان
</template>
</Column>
<Column field="status" header="وضعیت">
<template #body="{ data }">
<StatusTag :status="data.status || (data.date ? 'paid' : 'pending')" type="payment" />
</template>
</Column>
<Column field="method" header="روش پرداخت">
<template #body="{ data }">
<Tag
v-if="data.method"
:value="data.method === 'online' ? 'درگاه آنلاین' : (data.method === 'card' ? 'کارت به کارت' : 'نقدی')"
severity="info"
/>
<span v-else class="text-muted"></span>
</template>
</Column>
<Column field="receiptNumber" header="شماره فیش / پیگیری">
<template #body="{ data }">
<span dir="ltr">{{ toPersianDigits(data.receiptNumber || '-') }}</span>
</template>
</Column>
<Column field="dueDate" header="سررسید">
<template #body="{ data }">
{{ formatJalali(data.dueDate) }}
</template>
</Column>
<Column field="date" header="تاریخ پرداخت">
<template #body="{ data }">
{{ data.date ? formatJalali(data.date) : '—' }}
</template>
</Column>
<Column field="notes" header="یادداشت">
<template #body="{ data }">
<span class="white-space-pre-wrap">{{ data.notes || '—' }}</span>
</template>
</Column>
</DataTable>
</div>
</div>
</div>
<!-- Record New Transaction Modal -->
<Dialog v-model:visible="showTransactionModal" header="ثبت تراکنش جدید" modal :style="{ width: '450px' }">
<div class="flex flex-column gap-3 py-2">
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm">مبلغ واریزی (تومان) *</label>
<InputNumber v-model="trxForm.amount" class="w-full text-sm" suffix=" تومان" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm">روش پرداخت *</label>
<Dropdown v-model="trxForm.method" :options="methodOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm">شماره فیش / پیگیری *</label>
<InputText v-model.trim="trxForm.receiptNumber" class="w-full text-sm" dir="ltr" />
</div>
<div class="flex flex-column gap-2">
<label class="font-semibold text-sm" for="trx-notes">یادداشت</label>
<Textarea
id="trx-notes"
v-model="trxForm.notes"
rows="3"
class="w-full text-sm"
maxlength="5000"
placeholder="یادداشت داخلی درباره این تراکنش…"
/>
</div>
</div>
<template #footer>
<Button label="انصراف" text severity="secondary" @click="showTransactionModal = false" />
<Button label="ثبت تراکنش" icon="pi pi-check" severity="success" :loading="isSubmittingTrx" @click="handleRecordTransaction" />
</template>
</Dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { paymentApi } from '@/api/paymentApi';
import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import { getPayableAmount } from '@/utils/paymentAmount';
import PageHeader from '@/components/common/PageHeader.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import Button from 'primevue/button';
import Tag from 'primevue/tag';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import Dialog from 'primevue/dialog';
import Dropdown from 'primevue/select';
import InputText from 'primevue/inputtext';
import InputNumber from 'primevue/inputnumber';
import Textarea from 'primevue/textarea';
const route = useRoute();
const paymentId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate();
const { showSuccess, showError } = useToast();
const payment = ref(null);
const showTransactionModal = ref(false);
const isSubmittingTrx = ref(false);
const methodOptions = [
{ label: 'کارت به کارت', value: 'card' },
{ label: 'درگاه آنلاین', value: 'online' },
{ label: 'نقدی / شبا', value: 'cash' }
];
const trxForm = reactive({
amount: 500000,
method: 'card',
receiptNumber: '',
notes: ''
});
const payableAmount = computed(() => getPayableAmount(payment.value || {}));
const remainingAmount = computed(() => Math.max(0, payableAmount.value - (payment.value?.paidAmount || 0)));
const fetchPayment = async () => {
try {
const res = await paymentApi.getOne(paymentId);
payment.value = res.data || res;
} catch (err) {
showError(err);
}
};
const handleRecordTransaction = async () => {
if (!trxForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
if (!trxForm.receiptNumber) { showError('لطفا شماره فیش یا پیگیری را وارد کنید'); return; }
isSubmittingTrx.value = true;
try {
await paymentApi.recordTransaction(paymentId, trxForm);
showSuccess('تراکنش جدید با موفقیت ثبت شد');
showTransactionModal.value = false;
trxForm.notes = '';
fetchPayment();
} catch (err) {
showError(err);
} finally {
isSubmittingTrx.value = false;
}
};
onMounted(() => {
fetchPayment();
});
</script>