diff --git a/.gitignore b/.gitignore index 77e02cd..38eb404 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ *.pyc __pycache__/ local_settings.py -# *.sqlite3 -# db.sqlite3 +#*.sqlite3 +#db.sqlite3 db.sqlite3-journal media #static diff --git a/_base/settings.py b/_base/settings.py index 8261409..6283ccf 100644 --- a/_base/settings.py +++ b/_base/settings.py @@ -157,15 +157,15 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' JAZZMIN_SETTINGS = { # title of the window (Will default to current_admin_site.site_title if absent or None) - "site_title": "سامانه شفافیت", + "site_title": "کنتور پلاس", # Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None) - "site_header": "سامانه شفافیت", + "site_header": "کنتور پلاس", # Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None) - "site_brand": "سامانه شفافیت", + "site_brand": "کنتور پلاس", # Welcome text on the login screen - "welcome_sign": "به سامانه شفافیت خوش آمدید", + "welcome_sign": "به کنتور پلاس خوش آمدید", # Copyright on the footer - "copyright": "سامانه شفافیت", + "copyright": "کنتور پلاس", # Logo to use for your site, must be present in static files, used for brand on top left # "site_logo": "../static/dist/img/iconlogo.png", # Relative paths to custom CSS/JS scripts (must be present in static files) @@ -173,3 +173,6 @@ JAZZMIN_SETTINGS = { "custom_js": None, } + +# VAT / Value Added Tax percent (e.g., 0.09 for 9%) +VAT_RATE = 0.1 \ No newline at end of file diff --git a/_helpers/utils.py b/_helpers/utils.py index e4ee804..92f24b4 100644 --- a/_helpers/utils.py +++ b/_helpers/utils.py @@ -144,7 +144,7 @@ def persian_converter2(time): def persian_converter3(time): - time = time + datetime.timedelta(days=1) + time = time time_to_str = "{},{},{}".format(time.year, time.month, time.day) time_to_tuple = jalali.Gregorian(time_to_str).persian_tuple() time_to_list = list(time_to_tuple) @@ -192,4 +192,122 @@ def normalize_size(size: int) -> str: return f"{int(size_mb)} MB" if size_mb.is_integer() else f"{size_mb:.1f} MB" else: size_gb = size / (1024 * 1024 * 1024) - return f"{int(size_gb)} GB" if size_gb.is_integer() else f"{size_gb:.1f} GB" \ No newline at end of file + return f"{int(size_gb)} GB" if size_gb.is_integer() else f"{size_gb:.1f} GB" + + +def number_to_persian_words(number): + """ + تبدیل عدد به حروف فارسی + مثال: 12345 -> دوازده هزار و سیصد و چهل و پنج + """ + try: + # تبدیل به عدد صحیح (در صورت نیاز) + from decimal import Decimal + if isinstance(number, Decimal): + number = int(number) + elif isinstance(number, float): + number = int(number) + elif isinstance(number, str): + number = int(float(number.replace(',', ''))) + + if number == 0: + return "صفر" + + if number < 0: + return "منفی " + number_to_persian_words(abs(number)) + + # اعداد یک رقمی + ones = [ + "", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" + ] + + # اعداد ده تا نوزده + teens = [ + "ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", + "شانزده", "هفده", "هجده", "نوزده" + ] + + # اعداد بیست تا نود + tens = [ + "", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" + ] + + # اعداد صد تا نهصد + hundreds = [ + "", "یکصد", "دویست", "سیصد", "چهارصد", "پانصد", + "ششصد", "هفتصد", "هشتصد", "نهصد" + ] + + # مراتب بزرگتر + scale = [ + "", "هزار", "میلیون", "میلیارد", "بیلیون", "بیلیارد" + ] + + def convert_group(num): + """تبدیل گروه سه رقمی به حروف""" + if num == 0: + return "" + + result = [] + + # صدها + h = num // 100 + if h > 0: + result.append(hundreds[h]) + + # دهگان و یکان + remainder = num % 100 + + if remainder >= 10 and remainder < 20: + # اعداد 10 تا 19 + result.append(teens[remainder - 10]) + else: + # دهگان + t = remainder // 10 + if t > 0: + result.append(tens[t]) + + # یکان + o = remainder % 10 + if o > 0: + result.append(ones[o]) + + return " و ".join(result) + + # تقسیم عدد به گروه‌های سه رقمی + groups = [] + scale_index = 0 + + while number > 0: + group = number % 1000 + if group != 0: + group_text = convert_group(group) + if scale_index > 0: + group_text += " " + scale[scale_index] + groups.append(group_text) + + number //= 1000 + scale_index += 1 + + # معکوس کردن و ترکیب گروه‌ها + groups.reverse() + result = " و ".join(groups) + + return result + + except Exception: + return "" + + +def amount_to_persian_words(amount): + """ + تبدیل مبلغ به حروف فارسی با واحد ریال + مثال: 12345 -> دوازده هزار و سیصد و چهل و پنج ریال + """ + try: + words = number_to_persian_words(amount) + if words: + return words + " ریال" + return "" + except Exception: + return "" \ No newline at end of file diff --git a/accounts/admin.py b/accounts/admin.py index d741357..7ffe250 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -16,6 +16,8 @@ class ProfileAdmin(admin.ModelAdmin): list_display = [ "user", "fullname", + "user_type_display", + "company_name", "pic_tag", "roles_str", "affairs", @@ -25,8 +27,52 @@ class ProfileAdmin(admin.ModelAdmin): "is_active", "jcreated", ] - search_fields = ['user__username', 'user__first_name', 'user__last_name', 'user__phone_number'] - list_filter = ['user', 'roles', 'affairs', 'county', 'broker'] + search_fields = [ + 'user__username', + 'user__first_name', + 'user__last_name', + 'user__phone_number', + 'company_name', + 'company_national_id', + 'national_code' + ] + list_filter = [ + 'user_type', + 'user', + 'roles', + 'affairs', + 'county', + 'broker', + 'is_completed', + 'is_active' + ] + fieldsets = ( + ('اطلاعات کاربری', { + 'fields': ('user', 'user_type', 'pic', 'roles') + }), + ('اطلاعات شخصی - حقیقی', { + 'fields': ('national_code', 'address', 'phone_number_1', 'phone_number_2'), + 'classes': ('collapse',), + }), + ('اطلاعات شرکت - حقوقی', { + 'fields': ('company_name', 'company_national_id'), + 'classes': ('collapse',), + }), + ('اطلاعات بانکی', { + 'fields': ('card_number', 'account_number', 'bank_name'), + 'classes': ('collapse',), + }), + ('اطلاعات سازمانی', { + 'fields': ('affairs', 'county', 'broker', 'owner'), + }), + ('وضعیت', { + 'fields': ('is_completed', 'is_active'), + }), + ('تاریخ‌ها', { + 'fields': ('created', 'updated'), + 'classes': ('collapse',), + }), + ) date_hierarchy = 'created' ordering = ['-created'] readonly_fields = ['created', 'updated'] diff --git a/accounts/forms.py b/accounts/forms.py index 76beb31..eca6ffc 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -2,7 +2,7 @@ from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm from .models import Profile, Role -from common.consts import UserRoles +from common.consts import UserRoles, USER_TYPE_CHOICES User = get_user_model() @@ -28,13 +28,19 @@ class CustomerForm(forms.ModelForm): class Meta: model = Profile fields = [ - 'phone_number_1', 'phone_number_2', 'national_code', + 'user_type', 'phone_number_1', 'phone_number_2', 'national_code', + 'company_name', 'company_national_id', 'address', 'card_number', 'account_number', 'bank_name' ] widgets = { + 'user_type': forms.Select(attrs={ + 'class': 'form-control', + 'id': 'user-type-select' + }), 'phone_number_1': forms.TextInput(attrs={ 'class': 'form-control', - 'placeholder': '09123456789' + 'placeholder': '09123456789', + 'required': True }), 'phone_number_2': forms.TextInput(attrs={ 'class': 'form-control', @@ -46,30 +52,46 @@ class CustomerForm(forms.ModelForm): 'maxlength': '10', 'required': 'required' }), + 'company_name': forms.TextInput(attrs={ + 'class': 'form-control company-field', + 'placeholder': 'نام شرکت' + }), + 'company_national_id': forms.TextInput(attrs={ + 'class': 'form-control company-field', + 'placeholder': 'شناسه ملی شرکت', + 'maxlength': '11' + }), 'address': forms.Textarea(attrs={ 'class': 'form-control', 'placeholder': 'آدرس کامل', - 'rows': '3' + 'rows': '3', + 'required': True }), 'card_number': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'شماره کارت بانکی', - 'maxlength': '16' + 'maxlength': '16', + 'required': True }), 'account_number': forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'شماره حساب بانکی', - 'maxlength': '20' + 'maxlength': '20', + 'required': True }), 'bank_name': forms.Select(attrs={ 'class': 'form-control', 'placeholder': 'نام بانک', + 'required': True }), } labels = { + 'user_type': 'نوع کاربر', 'phone_number_1': 'تلفن ۱', 'phone_number_2': 'تلفن ۲', 'national_code': 'کد ملی', + 'company_name': 'نام شرکت', + 'company_national_id': 'شناسه ملی شرکت', 'address': 'آدرس', 'card_number': 'شماره کارت', 'account_number': 'شماره حساب', @@ -89,6 +111,21 @@ class CustomerForm(forms.ModelForm): raise forms.ValidationError('این کد ملی قبلاً استفاده شده است.') return national_code + def clean(self): + cleaned_data = super().clean() + user_type = cleaned_data.get('user_type') + company_name = cleaned_data.get('company_name') + company_national_id = cleaned_data.get('company_national_id') + + # If user type is legal, company fields are required + if user_type == 'legal': + if not company_name: + self.add_error('company_name', 'برای کاربران حقوقی نام شرکت الزامی است.') + if not company_national_id: + self.add_error('company_national_id', 'برای کاربران حقوقی شناسه ملی شرکت الزامی است.') + + return cleaned_data + def save(self, commit=True): def _compute_completed(cleaned): try: @@ -100,7 +137,15 @@ class CustomerForm(forms.ModelForm): bank_ok = bool(cleaned.get('bank_name')) card_ok = bool((cleaned.get('card_number') or '').strip()) acc_ok = bool((cleaned.get('account_number') or '').strip()) - return all([first_ok, last_ok, nc_ok, phone_ok, addr_ok, bank_ok, card_ok, acc_ok]) + + # Check user type specific requirements + user_type = cleaned.get('user_type', 'individual') + if user_type == 'legal': + company_name_ok = bool((cleaned.get('company_name') or '').strip()) + company_id_ok = bool((cleaned.get('company_national_id') or '').strip()) + return all([first_ok, last_ok, nc_ok, phone_ok, addr_ok, bank_ok, card_ok, acc_ok, company_name_ok, company_id_ok]) + else: + return all([first_ok, last_ok, nc_ok, phone_ok, addr_ok, bank_ok, card_ok, acc_ok]) except Exception: return False # Check if this is an update (instance exists) @@ -125,9 +170,12 @@ class CustomerForm(forms.ModelForm): profile.is_completed = _compute_completed({ 'first_name': user.first_name, 'last_name': user.last_name, + 'user_type': self.cleaned_data.get('user_type'), 'national_code': self.cleaned_data.get('national_code'), 'phone_number_1': self.cleaned_data.get('phone_number_1'), 'phone_number_2': self.cleaned_data.get('phone_number_2'), + 'company_name': self.cleaned_data.get('company_name'), + 'company_national_id': self.cleaned_data.get('company_national_id'), 'address': self.cleaned_data.get('address'), 'bank_name': self.cleaned_data.get('bank_name'), 'card_number': self.cleaned_data.get('card_number'), @@ -171,9 +219,12 @@ class CustomerForm(forms.ModelForm): profile.is_completed = _compute_completed({ 'first_name': user.first_name, 'last_name': user.last_name, + 'user_type': self.cleaned_data.get('user_type'), 'national_code': self.cleaned_data.get('national_code'), 'phone_number_1': self.cleaned_data.get('phone_number_1'), 'phone_number_2': self.cleaned_data.get('phone_number_2'), + 'company_name': self.cleaned_data.get('company_name'), + 'company_national_id': self.cleaned_data.get('company_national_id'), 'address': self.cleaned_data.get('address'), 'bank_name': self.cleaned_data.get('bank_name'), 'card_number': self.cleaned_data.get('card_number'), diff --git a/accounts/migrations/0007_historicalprofile_company_name_and_more.py b/accounts/migrations/0007_historicalprofile_company_name_and_more.py new file mode 100644 index 0000000..0d2edc1 --- /dev/null +++ b/accounts/migrations/0007_historicalprofile_company_name_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 5.2.4 on 2025-09-21 07:37 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0006_company_card_holder_name'), + ] + + operations = [ + migrations.AddField( + model_name='historicalprofile', + name='company_name', + field=models.CharField(blank=True, help_text='فقط برای کاربران حقوقی الزامی است', max_length=255, null=True, verbose_name='نام شرکت'), + ), + migrations.AddField( + model_name='historicalprofile', + name='company_national_id', + field=models.CharField(blank=True, help_text='فقط برای کاربران حقوقی الزامی است', max_length=11, null=True, validators=[django.core.validators.RegexValidator(code='invalid_company_national_id', message='شناسه ملی باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شناسه ملی شرکت'), + ), + migrations.AddField( + model_name='historicalprofile', + name='user_type', + field=models.CharField(choices=[('individual', 'حقیقی'), ('legal', 'حقوقی')], default='individual', max_length=20, verbose_name='نوع کاربر'), + ), + migrations.AddField( + model_name='profile', + name='company_name', + field=models.CharField(blank=True, help_text='فقط برای کاربران حقوقی الزامی است', max_length=255, null=True, verbose_name='نام شرکت'), + ), + migrations.AddField( + model_name='profile', + name='company_national_id', + field=models.CharField(blank=True, help_text='فقط برای کاربران حقوقی الزامی است', max_length=11, null=True, validators=[django.core.validators.RegexValidator(code='invalid_company_national_id', message='شناسه ملی باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شناسه ملی شرکت'), + ), + migrations.AddField( + model_name='profile', + name='user_type', + field=models.CharField(choices=[('individual', 'حقیقی'), ('legal', 'حقوقی')], default='individual', max_length=20, verbose_name='نوع کاربر'), + ), + ] diff --git a/accounts/migrations/0008_alter_historicalprofile_phone_number_1_and_more.py b/accounts/migrations/0008_alter_historicalprofile_phone_number_1_and_more.py new file mode 100644 index 0000000..a005ff8 --- /dev/null +++ b/accounts/migrations/0008_alter_historicalprofile_phone_number_1_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.4 on 2025-10-02 09:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0007_historicalprofile_company_name_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalprofile', + name='phone_number_1', + field=models.CharField(default=1, max_length=11, verbose_name='شماره تماس ۱'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='phone_number_1', + field=models.CharField(default=1, max_length=11, verbose_name='شماره تماس ۱'), + preserve_default=False, + ), + ] diff --git a/accounts/migrations/0009_alter_historicalprofile_account_number_and_more.py b/accounts/migrations/0009_alter_historicalprofile_account_number_and_more.py new file mode 100644 index 0000000..b9e015a --- /dev/null +++ b/accounts/migrations/0009_alter_historicalprofile_account_number_and_more.py @@ -0,0 +1,74 @@ +# Generated by Django 5.2.4 on 2025-10-04 10:36 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0008_alter_historicalprofile_phone_number_1_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalprofile', + name='account_number', + field=models.CharField(default=1, max_length=20, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب'), + preserve_default=False, + ), + migrations.AlterField( + model_name='historicalprofile', + name='address', + field=models.TextField(default=1, verbose_name='آدرس'), + preserve_default=False, + ), + migrations.AlterField( + model_name='historicalprofile', + name='bank_name', + field=models.CharField(choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], default=1, max_length=255, verbose_name='نام بانک'), + preserve_default=False, + ), + migrations.AlterField( + model_name='historicalprofile', + name='card_number', + field=models.CharField(default=1, max_length=16, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت'), + preserve_default=False, + ), + migrations.AlterField( + model_name='historicalprofile', + name='national_code', + field=models.CharField(default=1, max_length=10, validators=[django.core.validators.RegexValidator(code='invalid_national_code', message='کد ملی باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='کد ملی'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='account_number', + field=models.CharField(default=1, max_length=20, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='address', + field=models.TextField(default=1, verbose_name='آدرس'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='bank_name', + field=models.CharField(choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], default=1, max_length=255, verbose_name='نام بانک'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='card_number', + field=models.CharField(default=1, max_length=16, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='national_code', + field=models.CharField(default=1, max_length=10, validators=[django.core.validators.RegexValidator(code='invalid_national_code', message='کد ملی باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='کد ملی'), + preserve_default=False, + ), + ] diff --git a/accounts/models.py b/accounts/models.py index 937c329..b896581 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -4,7 +4,7 @@ from django.utils.html import format_html from django.core.validators import RegexValidator from simple_history.models import HistoricalRecords from common.models import TagModel, BaseModel, NameSlugModel -from common.consts import UserRoles, BANK_CHOICES +from common.consts import UserRoles, BANK_CHOICES, USER_TYPE_CHOICES from locations.models import Affairs, Broker, County @@ -27,9 +27,7 @@ class Profile(BaseModel): ) national_code = models.CharField( max_length=10, - null=True, verbose_name="کد ملی", - blank=True, validators=[ RegexValidator( regex=r'^\d+$', @@ -39,15 +37,11 @@ class Profile(BaseModel): ] ) address = models.TextField( - null=True, verbose_name="آدرس", - blank=True ) card_number = models.CharField( max_length=16, - null=True, verbose_name="شماره کارت", - blank=True, validators=[ RegexValidator( regex=r'^\d+$', @@ -58,9 +52,7 @@ class Profile(BaseModel): ) account_number = models.CharField( max_length=20, - null=True, verbose_name="شماره حساب", - blank=True, validators=[ RegexValidator( regex=r'^\d+$', @@ -72,15 +64,11 @@ class Profile(BaseModel): bank_name = models.CharField( max_length=255, choices=BANK_CHOICES, - null=True, verbose_name="نام بانک", - blank=True ) phone_number_1 = models.CharField( max_length=11, - null=True, verbose_name="شماره تماس ۱", - blank=True ) phone_number_2 = models.CharField( max_length=11, @@ -88,6 +76,33 @@ class Profile(BaseModel): verbose_name="شماره تماس ۲", blank=True ) + user_type = models.CharField( + max_length=20, + choices=USER_TYPE_CHOICES, + default='individual', + verbose_name="نوع کاربر" + ) + company_national_id = models.CharField( + max_length=11, + null=True, + verbose_name="شناسه ملی شرکت", + blank=True, + validators=[ + RegexValidator( + regex=r'^\d+$', + message='شناسه ملی باید فقط شامل اعداد باشد.', + code='invalid_company_national_id' + ) + ], + help_text="فقط برای کاربران حقوقی الزامی است" + ) + company_name = models.CharField( + max_length=255, + null=True, + verbose_name="نام شرکت", + blank=True, + help_text="فقط برای کاربران حقوقی الزامی است" + ) pic = models.ImageField( upload_to="profile_images", @@ -179,6 +194,23 @@ class Profile(BaseModel): pic_tag.short_description = "تصویر" + def is_legal_entity(self): + return self.user_type == 'legal' + + def is_individual(self): + return self.user_type == 'individual' + + def get_display_name(self): + """Returns appropriate display name based on user type""" + if self.is_legal_entity() and self.company_name: + return self.company_name + return self.user.get_full_name() or str(self.user) + + def user_type_display(self): + return dict(USER_TYPE_CHOICES).get(self.user_type, self.user_type) + + user_type_display.short_description = "نوع کاربر" + class Company(NameSlugModel): logo = models.ImageField( diff --git a/accounts/templates/accounts/customer_list.html b/accounts/templates/accounts/customer_list.html index 1b7e103..c1e2c0d 100644 --- a/accounts/templates/accounts/customer_list.html +++ b/accounts/templates/accounts/customer_list.html @@ -61,6 +61,7 @@ ردیف کاربر + نوع کاربر کد ملی تلفن آدرس @@ -100,6 +101,27 @@ + + {% if customer.user_type == 'legal' %} + + حقوقی + +
+ {% if customer.company_name %} + {{ customer.company_name|truncatechars:25 }} + {% endif %} + {% if customer.company_national_id %} + + {{ customer.company_national_id }} + + {% endif %} +
+ {% else %} + + حقیقی + + {% endif %} + {{ customer.national_code|default:"کد ملی ثبت نشده" }}
@@ -205,6 +227,16 @@ +
+ +
+ + {{ form.user_type }} +
+ {% if form.user_type.errors %} +
{{ form.user_type.errors.0 }}
+ {% endif %} +
@@ -261,6 +293,29 @@ {% endif %}
+ + + + +
@@ -313,6 +368,165 @@
+ + +