128 lines
8.3 KiB
Python
128 lines
8.3 KiB
Python
import uuid
|
|
import jdatetime
|
|
from django import forms
|
|
from django.db.models import Q
|
|
from django.utils import timezone
|
|
from apps.accounts.models import User
|
|
from . import validators
|
|
from .models import BankInstitution, CompanyBankAccount, PaymentNotice
|
|
|
|
|
|
class BankAccountForm(forms.ModelForm):
|
|
revision_token = forms.CharField(required=False, widget=forms.HiddenInput)
|
|
|
|
class Meta:
|
|
model = CompanyBankAccount
|
|
fields = ['revision_token', 'name', 'bank', 'holder', 'account_number', 'iban', 'card_number', 'requires_deposit_id', 'is_active', 'available_to_staff', 'available_to_customers', 'sort_order']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if self.instance.pk:
|
|
self.initial['revision_token'] = self.instance.updated_at.isoformat()
|
|
|
|
def clean_account_number(self):
|
|
return validators.bank_number(self.cleaned_data['account_number'])
|
|
|
|
def clean_card_number(self):
|
|
value = validators.bank_number(self.cleaned_data['card_number'])
|
|
if value and (len(value) != 16 or sum((int(n) * (2 if i % 2 == 0 else 1)) // 10 + (int(n) * (2 if i % 2 == 0 else 1)) % 10 for i, n in enumerate(value)) % 10):
|
|
raise forms.ValidationError('شماره کارت ۱۶ رقمی معتبر وارد کنید.')
|
|
return value
|
|
|
|
def clean_iban(self):
|
|
return validators.iban(self.cleaned_data['iban'])
|
|
|
|
def clean(self):
|
|
data = super().clean()
|
|
if self.instance.pk and data.get('revision_token') != self.instance.updated_at.isoformat():
|
|
raise forms.ValidationError('حساب از زمان باز کردن فرم تغییر کرده است؛ صفحه را تازه کنید و تغییرات را دوباره بررسی کنید.')
|
|
if not any(data.get(key) for key in ('account_number', 'iban', 'card_number')):
|
|
raise forms.ValidationError('حداقل شماره حساب، شبا یا کارت را وارد کنید.')
|
|
return data
|
|
|
|
|
|
class NoticeForm(forms.Form):
|
|
submission_key = forms.UUIDField(widget=forms.HiddenInput)
|
|
customer = forms.ModelChoiceField(label='مشتری', queryset=User.objects.none(), widget=forms.HiddenInput,
|
|
error_messages={'required': 'مشتری را از نتایج جستوجو انتخاب کنید.',
|
|
'invalid_choice': 'مشتری انتخابشده معتبر یا فعال نیست؛ دوباره جستوجو کنید.'})
|
|
account = forms.ModelChoiceField(label='حساب مقصد', queryset=CompanyBankAccount.objects.none())
|
|
subject = forms.CharField(label='موضوع', max_length=200)
|
|
amount = forms.CharField(label='مبلغ (ریال)')
|
|
deposit_id = forms.CharField(label='شناسه واریز', max_length=100, required=False)
|
|
invoice_number = forms.CharField(label='شماره فاکتور', max_length=100, required=False)
|
|
description = forms.CharField(label='توضیحات', required=False, widget=forms.Textarea)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.initial.setdefault('submission_key', uuid.uuid4())
|
|
self.fields['customer'].queryset = User.objects.filter(account_type='CUSTOMER', is_active=True).select_related('customer_profile')
|
|
self.fields['customer'].label_from_instance = lambda u: f'{getattr(u, "customer_profile", u)} — {u.local_mobile}'
|
|
self.fields['account'].queryset = CompanyBankAccount.objects.filter(is_active=True, available_to_staff=True).select_related('bank')
|
|
|
|
def clean_amount(self):
|
|
return validators.amount(self.cleaned_data['amount'])
|
|
|
|
def clean(self):
|
|
data = super().clean()
|
|
if data.get('account') and data['account'].requires_deposit_id and not data.get('deposit_id', '').strip():
|
|
self.add_error('deposit_id', 'این حساب شناسهدار است؛ شناسه واریز مشتری را حتماً وارد کنید.')
|
|
return data
|
|
|
|
|
|
class ReceiptForm(forms.Form):
|
|
receipt_mode = forms.ChoiceField(label='نوع واریز', choices=[('linked', 'واریز مرتبط (پیشنهادی)'), ('independent', 'واریز مستقل')], widget=forms.RadioSelect, initial='linked')
|
|
notice_id = forms.ModelChoiceField(label='اعلامیه واریز', queryset=PaymentNotice.objects.none(), required=False)
|
|
paid_amount = forms.CharField(label='مبلغ پرداختشده (ریال)')
|
|
destination_account = forms.ModelChoiceField(label='به کدام حساب ما واریز کردید؟', queryset=CompanyBankAccount.objects.none())
|
|
deposit_id = forms.CharField(label='شناسه واریز که با آن پرداخت انجام دادهاید (اختیاری)', max_length=100, required=False)
|
|
origin_bank = forms.ModelChoiceField(label='بانک مبدأ (اختیاری)', queryset=BankInstitution.objects.filter(is_active=True), required=False)
|
|
origin_other = forms.CharField(label='نام بانک دیگر (اختیاری)', max_length=100, required=False)
|
|
paid_day = forms.ChoiceField(label='روز', choices=[('', 'روز')] + [(str(i), str(i)) for i in range(1, 32)])
|
|
paid_month = forms.ChoiceField(label='ماه', choices=[('', 'ماه')] + list(zip(map(str, range(1, 13)), ['فروردین','اردیبهشت','خرداد','تیر','مرداد','شهریور','مهر','آبان','آذر','دی','بهمن','اسفند'])))
|
|
paid_year = forms.ChoiceField(label='سال')
|
|
receipt_image = forms.FileField(label='تصویر فیش', widget=forms.ClearableFileInput(attrs={'accept': 'image/*'}))
|
|
tracking_number = forms.CharField(label='شماره پیگیری (اختیاری)', max_length=100, required=False)
|
|
description = forms.CharField(label='توضیحات (اختیاری)', required=False, widget=forms.Textarea)
|
|
submission_key = forms.UUIDField(widget=forms.HiddenInput)
|
|
|
|
def __init__(self, *args, customer, notice_ids=None, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
notices = PaymentNotice.objects.filter(customer=customer, cancelled_at__isnull=True)
|
|
if notice_ids is not None:
|
|
notices = notices.filter(pk__in=notice_ids)
|
|
self.fields['notice_id'].queryset = notices
|
|
self.fields['notice_id'].label_from_instance = lambda n: f'{n.subject} — {n.amount:,} ریال — {n.invoice_number}'
|
|
accounts = Q(is_active=True, available_to_customers=True) | Q(is_active=True, paymentnotice__in=notices)
|
|
self.fields['destination_account'].queryset = CompanyBankAccount.objects.filter(accounts).distinct()
|
|
current = jdatetime.date.fromgregorian(date=timezone.localdate())
|
|
self.fields['paid_year'].choices = [('', 'سال')] + [(str(y), str(y)) for y in range(current.year, current.year - 11, -1)]
|
|
self.initial.setdefault('submission_key', uuid.uuid4())
|
|
self.initial.setdefault('paid_month', str(current.month))
|
|
self.initial.setdefault('paid_year', str(current.year))
|
|
|
|
def clean_paid_amount(self):
|
|
return validators.amount(self.cleaned_data['paid_amount'])
|
|
|
|
def clean_receipt_image(self):
|
|
upload = self.cleaned_data['receipt_image']
|
|
self.image_metadata = validators.image(upload)
|
|
return upload
|
|
|
|
def clean(self):
|
|
data = super().clean()
|
|
notice = data.get('notice_id') if data.get('receipt_mode') == 'linked' else None
|
|
data['notice_id'] = notice
|
|
if data.get('receipt_mode') == 'linked' and not notice:
|
|
self.add_error('notice_id', 'اعلامیه را انتخاب کنید یا واریز مستقل را بزنید.')
|
|
try:
|
|
data['paid_date'] = validators.paid_date(data.get('paid_day'), data.get('paid_month'), data.get('paid_year'))
|
|
if self.data.get('paid_date') and self.data['paid_date'] != data['paid_date'].isoformat():
|
|
raise forms.ValidationError('تاریخ ارسالی با تاریخ انتخابشده سازگار نیست؛ دوباره انتخاب کنید.')
|
|
except forms.ValidationError as error:
|
|
self.add_error('paid_day', error)
|
|
account = data.get('destination_account')
|
|
if account and not account.available_to_customers and (not notice or notice.account_id != account.pk):
|
|
self.add_error('destination_account', 'این حساب فقط برای اعلامیه مربوط به آن قابل انتخاب است.')
|
|
return data
|