feat: add messaging toggles, password reset UI, and payment discounts
Add notification channel toggles to settings, password reset SMS action on user form, and discount/payable amount display on payment views.
This commit is contained in:
@@ -27,7 +27,15 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted text-xs block mb-1">مبلغ کل صورتحساب</span>
|
||||
<span class="font-bold text-color text-xl">{{ toPersianDigits(payment.amount?.toLocaleString()) }} تومان</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>
|
||||
<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>
|
||||
@@ -35,12 +43,16 @@
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted text-xs block mb-1">باقیمانده</span>
|
||||
<span class="font-bold text-red-500 text-lg">{{ toPersianDigits((payment.amount - (payment.paidAmount || 0)).toLocaleString()) }} تومان</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>
|
||||
@@ -76,6 +88,11 @@
|
||||
{{ formatJalali(data.createdAt || 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>
|
||||
@@ -96,6 +113,16 @@
|
||||
<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"
|
||||
placeholder="یادداشت داخلی درباره این تراکنش…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="انصراف" text severity="secondary" @click="showTransactionModal = false" />
|
||||
@@ -106,11 +133,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
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';
|
||||
@@ -122,6 +150,7 @@ 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;
|
||||
@@ -141,9 +170,13 @@ const methodOptions = [
|
||||
const trxForm = reactive({
|
||||
amount: 500000,
|
||||
method: 'card',
|
||||
receiptNumber: ''
|
||||
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);
|
||||
@@ -162,6 +195,7 @@ const handleRecordTransaction = async () => {
|
||||
await paymentApi.recordTransaction(paymentId, trxForm);
|
||||
showSuccess('تراکنش جدید با موفقیت ثبت شد');
|
||||
showTransactionModal.value = false;
|
||||
trxForm.notes = '';
|
||||
fetchPayment();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
|
||||
<Column field="amount" header="مبلغ کل (تومان)" sortable>
|
||||
<template #body="{ data }">
|
||||
{{ toPersianDigits((data.amount || 0).toLocaleString()) }} تومان
|
||||
<div>{{ toPersianDigits(getPayableAmount(data).toLocaleString()) }} تومان</div>
|
||||
<small v-if="data.discount" class="text-muted text-xs">
|
||||
تخفیف {{ toPersianDigits((data.discount || 0).toLocaleString()) }} تومان
|
||||
</small>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
@@ -77,7 +80,7 @@
|
||||
</DataTableWrapper>
|
||||
|
||||
<!-- Create Payment Modal -->
|
||||
<Dialog v-model:visible="showCreateModal" header="ایجاد صورتحساب جدید" modal :style="{ width: '480px' }">
|
||||
<Dialog v-model:visible="showCreateModal" header="ایجاد صورتحساب جدید" modal :style="{ width: '520px' }">
|
||||
<div class="flex flex-column gap-3 py-2">
|
||||
<div class="flex flex-column gap-2">
|
||||
<label class="font-semibold text-sm">انتخاب یک یا چند کلاس *</label>
|
||||
@@ -112,8 +115,28 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column gap-2">
|
||||
<label class="font-semibold text-sm">مبلغ کل صورتحساب (تومان) *</label>
|
||||
<InputNumber v-model="createForm.amount" class="w-full text-sm" suffix=" تومان" />
|
||||
<label class="font-semibold text-sm" for="invoice-amount">مبلغ کل صورتحساب (تومان) *</label>
|
||||
<InputNumber inputId="invoice-amount" v-model="createForm.amount" class="w-full text-sm" suffix=" تومان" :min="0" />
|
||||
</div>
|
||||
|
||||
<div class="flex align-items-center gap-2">
|
||||
<Checkbox v-model="hasDiscount" binary inputId="invoice-discount" />
|
||||
<label for="invoice-discount" class="font-semibold text-sm cursor-pointer">تخفیف ؟</label>
|
||||
</div>
|
||||
|
||||
<div v-if="hasDiscount" class="flex flex-column gap-2">
|
||||
<label class="font-semibold text-sm" for="invoice-discount-amount">مبلغ تخفیف (تومان)</label>
|
||||
<InputNumber
|
||||
inputId="invoice-discount-amount"
|
||||
v-model="createForm.discount"
|
||||
class="w-full text-sm"
|
||||
suffix=" تومان"
|
||||
:min="0"
|
||||
:max="createForm.amount || 0"
|
||||
/>
|
||||
<small class="font-semibold text-color">
|
||||
مبلغ نهایی: {{ toPersianDigits(createPayableAmount.toLocaleString()) }} تومان
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column gap-2">
|
||||
@@ -121,6 +144,17 @@
|
||||
<DatePicker v-model="createForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column gap-2">
|
||||
<label class="font-semibold text-sm" for="invoice-notes">یادداشت</label>
|
||||
<Textarea
|
||||
id="invoice-notes"
|
||||
v-model="createForm.notes"
|
||||
rows="3"
|
||||
class="w-full text-sm"
|
||||
placeholder="یادداشت داخلی درباره این صورتحساب…"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<NotifyChannelsField :notify="createNotify" />
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -151,6 +185,7 @@ import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
|
||||
import PermissionGate from '@/components/common/PermissionGate.vue';
|
||||
import StatusTag from '@/components/common/StatusTag.vue';
|
||||
import NotifyChannelsField from '@/components/common/NotifyChannelsField.vue';
|
||||
import { getPayableAmount } from '@/utils/paymentAmount';
|
||||
import Button from 'primevue/button';
|
||||
import Column from 'primevue/column';
|
||||
import Tag from 'primevue/tag';
|
||||
@@ -158,6 +193,8 @@ import Dialog from 'primevue/dialog';
|
||||
import Dropdown from 'primevue/select';
|
||||
import MultiSelect from 'primevue/multiselect';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Checkbox from 'primevue/checkbox';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import DatePicker from 'vue3-persian-datetime-picker';
|
||||
|
||||
const { toPersianDigits, formatJalali } = usePersianDate();
|
||||
@@ -183,9 +220,16 @@ const createForm = reactive({
|
||||
user: null,
|
||||
classes: [],
|
||||
amount: 0,
|
||||
discount: 0,
|
||||
notes: '',
|
||||
dueDate: ''
|
||||
});
|
||||
const hasDiscount = ref(false);
|
||||
const createNotify = reactive({ sms: true, email: true, bot: true });
|
||||
const createPayableAmount = computed(() => getPayableAmount({
|
||||
amount: createForm.amount,
|
||||
discount: hasDiscount.value ? createForm.discount : 0
|
||||
}));
|
||||
|
||||
const deleteDialogVisible = ref(false);
|
||||
const selectedPayment = ref(null);
|
||||
@@ -241,7 +285,10 @@ const resetCreateForm = () => {
|
||||
createForm.user = null;
|
||||
createForm.classes = [];
|
||||
createForm.amount = 0;
|
||||
createForm.discount = 0;
|
||||
createForm.notes = '';
|
||||
createForm.dueDate = '';
|
||||
hasDiscount.value = false;
|
||||
createNotify.sms = true;
|
||||
createNotify.email = true;
|
||||
createNotify.bot = true;
|
||||
@@ -275,9 +322,18 @@ const handleCreatePayment = async () => {
|
||||
if (!createForm.amount) { showError('لطفا مبلغ را وارد کنید'); return; }
|
||||
if (!createForm.dueDate) { showError('لطفا تاریخ سررسید را وارد کنید'); return; }
|
||||
|
||||
if (hasDiscount.value && (createForm.discount || 0) > createForm.amount) {
|
||||
showError('مبلغ تخفیف نمیتواند بیشتر از مبلغ کل باشد');
|
||||
return;
|
||||
}
|
||||
|
||||
isCreating.value = true;
|
||||
try {
|
||||
await paymentApi.create({ ...createForm, notify: { ...createNotify } });
|
||||
await paymentApi.create({
|
||||
...createForm,
|
||||
discount: hasDiscount.value ? (createForm.discount || 0) : 0,
|
||||
notify: { ...createNotify }
|
||||
});
|
||||
showSuccess('صورتحساب جدید با موفقیت ایجاد شد');
|
||||
showCreateModal.value = false;
|
||||
loadData();
|
||||
|
||||
Reference in New Issue
Block a user