feat: add admin transaction edit UI on payment detail page
Wire create and update flows to admin transaction endpoints with status, amount, and date fields.
This commit is contained in:
@@ -8,5 +8,6 @@ export const paymentApi = {
|
|||||||
create: (data) => axiosInstance.post('/payments/admin/create', data),
|
create: (data) => axiosInstance.post('/payments/admin/create', data),
|
||||||
update: (id, data) => axiosInstance.put(`/payments/admin/update/${id}`, data),
|
update: (id, data) => axiosInstance.put(`/payments/admin/update/${id}`, data),
|
||||||
delete: (id) => axiosInstance.delete(`/payments/admin/delete/${id}`),
|
delete: (id) => axiosInstance.delete(`/payments/admin/delete/${id}`),
|
||||||
recordTransaction: (paymentId, data) => axiosInstance.post(`/payments/user/pay/${paymentId}`, data)
|
recordTransaction: (paymentId, data) => axiosInstance.post(`/payments/admin/transactions/${paymentId}`, data),
|
||||||
|
updateTransaction: (transactionId, data) => axiosInstance.put(`/payments/admin/transactions/${transactionId}`, data)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,6 +108,13 @@
|
|||||||
<span class="white-space-pre-wrap">{{ data.notes || '—' }}</span>
|
<span class="white-space-pre-wrap">{{ data.notes || '—' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
|
<Column header="عملیات" style="width: 5rem">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<PermissionGate permission="payments:update">
|
||||||
|
<Button icon="pi pi-pencil" text rounded severity="secondary" size="small" @click="openEditTransaction(data)" />
|
||||||
|
</PermissionGate>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -118,14 +125,26 @@
|
|||||||
<div class="flex flex-column gap-3 py-2">
|
<div class="flex flex-column gap-3 py-2">
|
||||||
<div class="flex flex-column gap-2">
|
<div class="flex flex-column gap-2">
|
||||||
<label class="font-semibold text-sm">مبلغ واریزی (تومان) *</label>
|
<label class="font-semibold text-sm">مبلغ واریزی (تومان) *</label>
|
||||||
<InputNumber v-model="trxForm.amount" class="w-full text-sm" suffix=" تومان" />
|
<InputNumber v-model="trxForm.amount" class="w-full text-sm" suffix=" تومان" :min="1" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-column gap-2">
|
<div class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">وضعیت تراکنش *</label>
|
||||||
|
<Dropdown v-model="trxForm.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
|
||||||
|
</div>
|
||||||
|
<div v-if="trxForm.status === 'paid'" class="flex flex-column gap-2">
|
||||||
<label class="font-semibold text-sm">روش پرداخت *</label>
|
<label class="font-semibold text-sm">روش پرداخت *</label>
|
||||||
<Dropdown v-model="trxForm.method" :options="methodOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
|
<Dropdown v-model="trxForm.method" :options="methodOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-column gap-2">
|
<div class="flex flex-column gap-2">
|
||||||
<label class="font-semibold text-sm">شماره فیش / پیگیری *</label>
|
<label class="font-semibold text-sm">تاریخ سررسید *</label>
|
||||||
|
<DatePicker v-model="trxForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
|
||||||
|
</div>
|
||||||
|
<div v-if="trxForm.status === 'paid'" class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">تاریخ پرداخت</label>
|
||||||
|
<DatePicker v-model="trxForm.date" class="w-full text-sm" placeholder="1403/01/01" />
|
||||||
|
</div>
|
||||||
|
<div v-if="trxForm.status === 'paid'" 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" />
|
<InputText v-model.trim="trxForm.receiptNumber" class="w-full text-sm" dir="ltr" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-column gap-2">
|
<div class="flex flex-column gap-2">
|
||||||
@@ -145,6 +164,51 @@
|
|||||||
<Button label="ثبت تراکنش" icon="pi pi-check" severity="success" :loading="isSubmittingTrx" @click="handleRecordTransaction" />
|
<Button label="ثبت تراکنش" icon="pi pi-check" severity="success" :loading="isSubmittingTrx" @click="handleRecordTransaction" />
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Edit Transaction Modal -->
|
||||||
|
<Dialog v-model:visible="showEditModal" 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="editForm.amount" class="w-full text-sm" suffix=" تومان" :min="0" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">وضعیت تراکنش *</label>
|
||||||
|
<Dropdown v-model="editForm.status" :options="statusOptions" optionLabel="label" optionValue="value" class="w-full text-sm" />
|
||||||
|
</div>
|
||||||
|
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">روش پرداخت</label>
|
||||||
|
<Dropdown v-model="editForm.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>
|
||||||
|
<DatePicker v-model="editForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
|
||||||
|
</div>
|
||||||
|
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">تاریخ پرداخت</label>
|
||||||
|
<DatePicker v-model="editForm.date" class="w-full text-sm" placeholder="1403/01/01" />
|
||||||
|
</div>
|
||||||
|
<div v-if="editForm.status === 'paid'" class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm">شماره فیش / پیگیری</label>
|
||||||
|
<InputText v-model.trim="editForm.receiptNumber" class="w-full text-sm" dir="ltr" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-column gap-2">
|
||||||
|
<label class="font-semibold text-sm" for="edit-trx-notes">یادداشت</label>
|
||||||
|
<Textarea
|
||||||
|
id="edit-trx-notes"
|
||||||
|
v-model="editForm.notes"
|
||||||
|
rows="3"
|
||||||
|
class="w-full text-sm"
|
||||||
|
maxlength="5000"
|
||||||
|
placeholder="یادداشت داخلی درباره این تراکنش…"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<Button label="انصراف" text severity="secondary" @click="showEditModal = false" />
|
||||||
|
<Button label="ذخیره تغییرات" icon="pi pi-check" severity="success" :loading="isSubmittingEdit" @click="handleUpdateTransaction" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -167,6 +231,7 @@ import Dropdown from 'primevue/select';
|
|||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
import InputNumber from 'primevue/inputnumber';
|
import InputNumber from 'primevue/inputnumber';
|
||||||
import Textarea from 'primevue/textarea';
|
import Textarea from 'primevue/textarea';
|
||||||
|
import DatePicker from 'vue3-persian-datetime-picker';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const paymentId = route.params.id;
|
const paymentId = route.params.id;
|
||||||
@@ -175,7 +240,10 @@ const { showSuccess, showError } = useToast();
|
|||||||
|
|
||||||
const payment = ref(null);
|
const payment = ref(null);
|
||||||
const showTransactionModal = ref(false);
|
const showTransactionModal = ref(false);
|
||||||
|
const showEditModal = ref(false);
|
||||||
const isSubmittingTrx = ref(false);
|
const isSubmittingTrx = ref(false);
|
||||||
|
const isSubmittingEdit = ref(false);
|
||||||
|
const editingTransactionId = ref(null);
|
||||||
|
|
||||||
const methodOptions = [
|
const methodOptions = [
|
||||||
{ label: 'کارت به کارت', value: 'card' },
|
{ label: 'کارت به کارت', value: 'card' },
|
||||||
@@ -183,10 +251,30 @@ const methodOptions = [
|
|||||||
{ label: 'نقدی / شبا', value: 'cash' }
|
{ label: 'نقدی / شبا', value: 'cash' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const trxForm = reactive({
|
const statusOptions = [
|
||||||
|
{ label: 'پرداخت شده', value: 'paid' },
|
||||||
|
{ label: 'در انتظار پرداخت', value: 'pending' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultTrxForm = () => ({
|
||||||
amount: 500000,
|
amount: 500000,
|
||||||
|
status: 'paid',
|
||||||
method: 'card',
|
method: 'card',
|
||||||
receiptNumber: '',
|
receiptNumber: '',
|
||||||
|
dueDate: '',
|
||||||
|
date: '',
|
||||||
|
notes: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const trxForm = reactive(defaultTrxForm());
|
||||||
|
|
||||||
|
const editForm = reactive({
|
||||||
|
amount: 0,
|
||||||
|
status: 'paid',
|
||||||
|
method: 'card',
|
||||||
|
receiptNumber: '',
|
||||||
|
dueDate: '',
|
||||||
|
date: '',
|
||||||
notes: ''
|
notes: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,15 +292,31 @@ const fetchPayment = async () => {
|
|||||||
|
|
||||||
const handleRecordTransaction = async () => {
|
const handleRecordTransaction = async () => {
|
||||||
if (!trxForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
|
if (!trxForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
|
||||||
if (!trxForm.receiptNumber) { showError('لطفا شماره فیش یا پیگیری را وارد کنید'); return; }
|
if (!trxForm.dueDate) { showError('لطفا تاریخ سررسید را وارد کنید'); return; }
|
||||||
|
if (trxForm.status === 'paid' && !trxForm.receiptNumber) {
|
||||||
|
showError('لطفا شماره فیش یا پیگیری را وارد کنید');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isSubmittingTrx.value = true;
|
isSubmittingTrx.value = true;
|
||||||
try {
|
try {
|
||||||
await paymentApi.recordTransaction(paymentId, trxForm);
|
const payload = {
|
||||||
|
amount: trxForm.amount,
|
||||||
|
status: trxForm.status,
|
||||||
|
dueDate: trxForm.dueDate,
|
||||||
|
notes: trxForm.notes || undefined
|
||||||
|
};
|
||||||
|
if (trxForm.status === 'paid') {
|
||||||
|
payload.method = trxForm.method;
|
||||||
|
payload.receiptNumber = trxForm.receiptNumber;
|
||||||
|
if (trxForm.date) payload.date = trxForm.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await paymentApi.recordTransaction(paymentId, payload);
|
||||||
|
payment.value = res.data || res;
|
||||||
showSuccess('تراکنش جدید با موفقیت ثبت شد');
|
showSuccess('تراکنش جدید با موفقیت ثبت شد');
|
||||||
showTransactionModal.value = false;
|
showTransactionModal.value = false;
|
||||||
trxForm.notes = '';
|
Object.assign(trxForm, defaultTrxForm());
|
||||||
fetchPayment();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError(err);
|
showError(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -220,6 +324,53 @@ const handleRecordTransaction = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openEditTransaction = (trx) => {
|
||||||
|
editingTransactionId.value = trx._id || trx.id;
|
||||||
|
editForm.amount = trx.amount || 0;
|
||||||
|
editForm.status = trx.status || (trx.date ? 'paid' : 'pending');
|
||||||
|
editForm.method = trx.method || 'card';
|
||||||
|
editForm.receiptNumber = trx.receiptNumber || '';
|
||||||
|
editForm.dueDate = trx.dueDate || '';
|
||||||
|
editForm.date = trx.date || '';
|
||||||
|
editForm.notes = trx.notes || '';
|
||||||
|
showEditModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateTransaction = async () => {
|
||||||
|
if (!editForm.amount && editForm.amount !== 0) {
|
||||||
|
showError('لطفا مبلغ را وارد کنید');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!editForm.dueDate) {
|
||||||
|
showError('لطفا تاریخ سررسید را وارد کنید');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmittingEdit.value = true;
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
amount: editForm.amount,
|
||||||
|
status: editForm.status,
|
||||||
|
dueDate: editForm.dueDate,
|
||||||
|
notes: editForm.notes
|
||||||
|
};
|
||||||
|
if (editForm.status === 'paid') {
|
||||||
|
payload.method = editForm.method;
|
||||||
|
payload.receiptNumber = editForm.receiptNumber;
|
||||||
|
payload.date = editForm.date || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await paymentApi.updateTransaction(editingTransactionId.value, payload);
|
||||||
|
payment.value = res.data || res;
|
||||||
|
showSuccess('تراکنش با موفقیت بهروزرسانی شد');
|
||||||
|
showEditModal.value = false;
|
||||||
|
} catch (err) {
|
||||||
|
showError(err);
|
||||||
|
} finally {
|
||||||
|
isSubmittingEdit.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchPayment();
|
fetchPayment();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user