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 ReceiptAlertConfigurationForm(forms.ModelForm): clear_bale_token = forms.BooleanField(label='پاک کردن توکن ذخیره‌شده', required=False) class Meta: from .models import PaymentReceiptAlertConfiguration model = PaymentReceiptAlertConfiguration fields = '__all__' widgets = {'bale_token': forms.PasswordInput(render_value=False)} help_texts = {'bale_token': 'برای حفظ توکن فعلی این فیلد را خالی بگذارید؛ برای جایگزینی، توکن تازه وارد کنید.'} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.initial['bale_token'] = '' def clean(self): data = super().clean() if data.get('clear_bale_token'): data['bale_token'] = '' elif not data.get('bale_token') and self.instance.pk: data['bale_token'] = self.instance.bale_token return data 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