diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index de431d4..7b35fb7 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-14 09:02 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.core.validators import django.db.models.deletion @@ -17,6 +17,27 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Company', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')), + ('updated', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')), + ('is_active', models.BooleanField(default=True, verbose_name='فعال')), + ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), + ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), + ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')), + ('name', models.CharField(max_length=100, verbose_name='نام')), + ('logo', models.ImageField(blank=True, null=True, upload_to='companies/logos', verbose_name='لوگوی شرکت')), + ('signature', models.ImageField(blank=True, null=True, upload_to='companies/signatures', verbose_name='امضای شرکت')), + ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')), + ('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس')), + ], + options={ + 'verbose_name': 'شرکت', + 'verbose_name_plural': 'شرکت\u200cها', + }, + ), migrations.CreateModel( name='HistoricalProfile', fields=[ @@ -30,6 +51,7 @@ class Migration(migrations.Migration): ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')), ('card_number', models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت')), ('account_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب')), + ('bank_name', models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک')), ('phone_number_1', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۱')), ('phone_number_2', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۲')), ('pic', models.TextField(default='../static/sample_images/profile.jpg', max_length=100, verbose_name='تصویر')), @@ -84,6 +106,7 @@ class Migration(migrations.Migration): ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')), ('card_number', models.CharField(blank=True, max_length=16, null=True, validators=[django.core.validators.RegexValidator(code='invalid_card_number', message='شماره کارت باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره کارت')), ('account_number', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator(code='invalid_account_number', message='شماره حساب باید فقط شامل اعداد باشد.', regex='^\\d+$')], verbose_name='شماره حساب')), + ('bank_name', models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک')), ('phone_number_1', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۱')), ('phone_number_2', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس ۲')), ('pic', models.ImageField(default='../static/sample_images/profile.jpg', upload_to='profile_images', verbose_name='تصویر')), diff --git a/accounts/migrations/0002_company.py b/accounts/migrations/0002_company.py deleted file mode 100644 index c944cdf..0000000 --- a/accounts/migrations/0002_company.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-21 06:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Company', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ ایجاد')), - ('updated', models.DateTimeField(auto_now=True, verbose_name='تاریخ بروزرسانی')), - ('is_active', models.BooleanField(default=True, verbose_name='فعال')), - ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), - ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), - ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')), - ('name', models.CharField(max_length=100, verbose_name='نام')), - ('logo', models.ImageField(blank=True, null=True, upload_to='companies/logos', verbose_name='لوگوی شرکت')), - ('signature', models.ImageField(blank=True, null=True, upload_to='companies/signatures', verbose_name='امضای شرکت')), - ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')), - ('phone', models.CharField(blank=True, max_length=11, null=True, verbose_name='شماره تماس')), - ], - options={ - 'verbose_name': 'شرکت', - 'verbose_name_plural': 'شرکت\u200cها', - }, - ), - ] diff --git a/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py b/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py deleted file mode 100644 index 6becfec..0000000 --- a/accounts/migrations/0003_historicalprofile_bank_name_profile_bank_name.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-21 07:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0002_company'), - ] - - operations = [ - migrations.AddField( - model_name='historicalprofile', - name='bank_name', - field=models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک'), - ), - migrations.AddField( - model_name='profile', - name='bank_name', - field=models.CharField(blank=True, choices=[('mellat', 'بانک ملت'), ('saman', 'بانک سامان'), ('parsian', 'بانک پارسیان'), ('sina', 'بانک سینا'), ('tejarat', 'بانک تجارت'), ('tosee', 'بانک توسعه'), ('iran_zamin', 'بانک ایران زمین'), ('meli', 'بانک ملی'), ('saderat', 'بانک توسعه صادرات'), ('iran_zamin', 'بانک ایران زمین'), ('refah', 'بانک رفاه'), ('eghtesad_novin', 'بانک اقتصاد نوین'), ('pasargad', 'بانک پاسارگاد'), ('other', 'سایر')], max_length=255, null=True, verbose_name='نام بانک'), - ), - ] diff --git a/certificates/migrations/0001_initial.py b/certificates/migrations/0001_initial.py index 83533bd..b5753ec 100644 --- a/certificates/migrations/0001_initial.py +++ b/certificates/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-22 09:58 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion from django.db import migrations, models @@ -9,6 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('accounts', '0001_initial'), ('processes', '0001_initial'), ] @@ -23,10 +24,8 @@ class Migration(migrations.Migration): ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), ('title', models.CharField(max_length=200, verbose_name='عنوان')), ('body', models.TextField(verbose_name='متن قالب (با جایگزین\u200cها)')), - ('company_logo', models.ImageField(blank=True, null=True, upload_to='certificates/logos/%Y/%m/%d/', verbose_name='لوگو')), - ('company_name', models.CharField(blank=True, max_length=200, verbose_name='نام شرکت')), - ('company_seal_signature', models.ImageField(blank=True, null=True, upload_to='certificates/seals/%Y/%m/%d/', verbose_name='مهر و امضا')), ('is_active', models.BooleanField(default=True, verbose_name='فعال')), + ('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت صادر کننده')), ], options={ 'verbose_name': 'قالب گواهی', diff --git a/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py b/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py deleted file mode 100644 index e929c5a..0000000 --- a/certificates/migrations/0002_remove_certificatetemplate_company_logo_and_more.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-22 10:05 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0003_historicalprofile_bank_name_profile_bank_name'), - ('certificates', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='certificatetemplate', - name='company_logo', - ), - migrations.RemoveField( - model_name='certificatetemplate', - name='company_name', - ), - migrations.RemoveField( - model_name='certificatetemplate', - name='company_seal_signature', - ), - migrations.AddField( - model_name='certificatetemplate', - name='company', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت صادر کننده'), - ), - ] diff --git a/contracts/migrations/0001_initial.py b/contracts/migrations/0001_initial.py index 8f9f4b7..25acea6 100644 --- a/contracts/migrations/0001_initial.py +++ b/contracts/migrations/0001_initial.py @@ -1,7 +1,6 @@ -# Generated by Django 5.2.4 on 2025-08-21 06:00 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion -import simple_history.models from django.conf import settings from django.db import migrations, models @@ -11,6 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('accounts', '0001_initial'), ('processes', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -28,8 +28,7 @@ class Migration(migrations.Migration): ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')), ('name', models.CharField(max_length=100, verbose_name='نام')), ('body', models.TextField(verbose_name='متن قرارداد')), - ('company_logo', models.ImageField(blank=True, null=True, upload_to='contracts/logos/%Y/%m/%d/', verbose_name='لوگوی شرکت')), - ('company_signature', models.ImageField(blank=True, null=True, upload_to='contracts/signatures/%Y/%m/%d/', verbose_name='امضای شرکت')), + ('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت')), ], options={ 'verbose_name': 'قالب قرارداد', @@ -58,61 +57,4 @@ class Migration(migrations.Migration): 'ordering': ['-created'], }, ), - migrations.CreateModel( - name='HistoricalContractInstance', - fields=[ - ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ ایجاد')), - ('updated', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ بروزرسانی')), - ('is_active', models.BooleanField(default=True, verbose_name='فعال')), - ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), - ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), - ('rendered_body', models.TextField(verbose_name='متن نهایی قرارداد')), - ('approved', models.BooleanField(default=False, verbose_name='تایید شده')), - ('approved_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید')), - ('history_id', models.AutoField(primary_key=True, serialize=False)), - ('history_date', models.DateTimeField(db_index=True)), - ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('created_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='ایجاد کننده')), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ('process_instance', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='processes.processinstance', verbose_name='نمونه فرآیند')), - ('template', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='contracts.contracttemplate', verbose_name='قالب مورد استفاده')), - ], - options={ - 'verbose_name': 'historical قرارداد', - 'verbose_name_plural': 'historical قراردادها', - 'ordering': ('-history_date', '-history_id'), - 'get_latest_by': ('history_date', 'history_id'), - }, - bases=(simple_history.models.HistoricalChanges, models.Model), - ), - migrations.CreateModel( - name='HistoricalContractTemplate', - fields=[ - ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), - ('created', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ ایجاد')), - ('updated', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ بروزرسانی')), - ('is_active', models.BooleanField(default=True, verbose_name='فعال')), - ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), - ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), - ('slug', models.SlugField(max_length=100, verbose_name='اسلاگ')), - ('name', models.CharField(max_length=100, verbose_name='نام')), - ('body', models.TextField(verbose_name='متن قرارداد')), - ('company_logo', models.TextField(blank=True, max_length=100, null=True, verbose_name='لوگوی شرکت')), - ('company_signature', models.TextField(blank=True, max_length=100, null=True, verbose_name='امضای شرکت')), - ('history_id', models.AutoField(primary_key=True, serialize=False)), - ('history_date', models.DateTimeField(db_index=True)), - ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'historical قالب قرارداد', - 'verbose_name_plural': 'historical قالب\u200cهای قرارداد', - 'ordering': ('-history_date', '-history_id'), - 'get_latest_by': ('history_date', 'history_id'), - }, - bases=(simple_history.models.HistoricalChanges, models.Model), - ), ] diff --git a/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py b/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py deleted file mode 100644 index 60d434d..0000000 --- a/contracts/migrations/0002_remove_historicalcontracttemplate_history_user_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-21 06:33 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0002_company'), - ('contracts', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicalcontracttemplate', - name='history_user', - ), - migrations.RemoveField( - model_name='contracttemplate', - name='company_logo', - ), - migrations.RemoveField( - model_name='contracttemplate', - name='company_signature', - ), - migrations.AddField( - model_name='contracttemplate', - name='company', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت'), - ), - migrations.DeleteModel( - name='HistoricalContractInstance', - ), - migrations.DeleteModel( - name='HistoricalContractTemplate', - ), - ] diff --git a/db.sqlite3 b/db.sqlite3 index d0474e8..d816e90 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/installations/migrations/0001_initial.py b/installations/migrations/0001_initial.py index 05cedcd..41f02f5 100644 --- a/installations/migrations/0001_initial.py +++ b/installations/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-21 08:25 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion from django.conf import settings @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('invoices', '0002_historicalpayment_receipt_image_and_more'), + ('invoices', '0001_initial'), ('processes', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -53,6 +53,8 @@ class Migration(migrations.Migration): ('utm_x', models.DecimalField(blank=True, decimal_places=6, max_digits=10, null=True, verbose_name='UTM X')), ('utm_y', models.DecimalField(blank=True, decimal_places=6, max_digits=10, null=True, verbose_name='UTM Y')), ('description', models.TextField(blank=True, verbose_name='توضیحات')), + ('approved', models.BooleanField(default=False, verbose_name='تایید شده')), + ('approved_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید')), ('assignment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports', to='installations.installationassignment', verbose_name='اختصاص')), ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='ایجادکننده')), ], diff --git a/installations/migrations/0002_installationreport_approved_and_more.py b/installations/migrations/0002_installationreport_approved_and_more.py deleted file mode 100644 index d5df3c8..0000000 --- a/installations/migrations/0002_installationreport_approved_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-21 09:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('installations', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='installationreport', - name='approved', - field=models.BooleanField(default=False, verbose_name='تایید شده'), - ), - migrations.AddField( - model_name='installationreport', - name='approved_at', - field=models.DateTimeField(blank=True, null=True, verbose_name='تاریخ تایید'), - ), - ] diff --git a/invoices/migrations/0001_initial.py b/invoices/migrations/0001_initial.py index abf23d3..1714b64 100644 --- a/invoices/migrations/0001_initial.py +++ b/invoices/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-14 09:02 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion import simple_history.models @@ -29,6 +29,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=100, verbose_name='نام')), ('description', models.TextField(blank=True, verbose_name='توضیحات')), ('unit_price', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='قیمت واحد')), + ('is_special', models.BooleanField(default=False, verbose_name='ویژه برای فاکتور نهایی')), ('default_quantity', models.PositiveIntegerField(default=1, verbose_name='تعداد پیش\u200cفرض')), ('is_default_in_quotes', models.BooleanField(default=False, help_text='این آیتم به صورت پیش\u200cفرض در همه پیش\u200cفاکتورها قرار می\u200cگیرد', verbose_name='پیش\u200cفرض در پیش\u200cفاکتورها')), ('history_id', models.AutoField(primary_key=True, serialize=False)), @@ -121,10 +122,12 @@ class Migration(migrations.Migration): ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), ('amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='مبلغ پرداخت')), + ('direction', models.CharField(choices=[('in', 'دریافتی'), ('out', 'پرداختی')], default='in', max_length=3, verbose_name='نوع تراکنش')), ('payment_method', models.CharField(choices=[('cash', 'نقدی'), ('bank_transfer', 'انتقال بانکی'), ('check', 'چک'), ('card', 'کارت بانکی'), ('other', 'سایر')], default='cash', max_length=20, verbose_name='روش پرداخت')), - ('reference_number', models.CharField(blank=True, max_length=100, verbose_name='شماره مرجع')), + ('reference_number', models.CharField(blank=True, db_index=True, max_length=100, verbose_name='شماره مرجع')), ('payment_date', models.DateField(verbose_name='تاریخ پرداخت')), ('notes', models.TextField(blank=True, verbose_name='یادداشت\u200cها')), + ('receipt_image', models.TextField(blank=True, max_length=100, null=True, verbose_name='تصویر فیش')), ('history_id', models.AutoField(primary_key=True, serialize=False)), ('history_date', models.DateTimeField(db_index=True)), ('history_change_reason', models.CharField(max_length=100, null=True)), @@ -154,6 +157,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=100, verbose_name='نام')), ('description', models.TextField(blank=True, verbose_name='توضیحات')), ('unit_price', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='قیمت واحد')), + ('is_special', models.BooleanField(default=False, verbose_name='ویژه برای فاکتور نهایی')), ('default_quantity', models.PositiveIntegerField(default=1, verbose_name='تعداد پیش\u200cفرض')), ('is_default_in_quotes', models.BooleanField(default=False, help_text='این آیتم به صورت پیش\u200cفرض در همه پیش\u200cفاکتورها قرار می\u200cگیرد', verbose_name='پیش\u200cفرض در پیش\u200cفاکتورها')), ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='ایجاد کننده')), @@ -225,10 +229,12 @@ class Migration(migrations.Migration): ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')), ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')), ('amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='مبلغ پرداخت')), + ('direction', models.CharField(choices=[('in', 'دریافتی'), ('out', 'پرداختی')], default='in', max_length=3, verbose_name='نوع تراکنش')), ('payment_method', models.CharField(choices=[('cash', 'نقدی'), ('bank_transfer', 'انتقال بانکی'), ('check', 'چک'), ('card', 'کارت بانکی'), ('other', 'سایر')], default='cash', max_length=20, verbose_name='روش پرداخت')), - ('reference_number', models.CharField(blank=True, max_length=100, verbose_name='شماره مرجع')), + ('reference_number', models.CharField(blank=True, max_length=100, unique=True, verbose_name='شماره مرجع')), ('payment_date', models.DateField(verbose_name='تاریخ پرداخت')), ('notes', models.TextField(blank=True, verbose_name='یادداشت\u200cها')), + ('receipt_image', models.ImageField(blank=True, null=True, upload_to='payments/%Y/%m/%d/', verbose_name='تصویر فیش')), ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='ثبت کننده')), ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='invoices.invoice', verbose_name='فاکتور')), ], diff --git a/invoices/migrations/0002_historicalpayment_receipt_image_and_more.py b/invoices/migrations/0002_historicalpayment_receipt_image_and_more.py deleted file mode 100644 index d8de4a0..0000000 --- a/invoices/migrations/0002_historicalpayment_receipt_image_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-16 04:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='historicalpayment', - name='receipt_image', - field=models.TextField(blank=True, max_length=100, null=True, verbose_name='تصویر فیش'), - ), - migrations.AddField( - model_name='payment', - name='receipt_image', - field=models.ImageField(blank=True, null=True, upload_to='payments/%Y/%m/%d/', verbose_name='تصویر فیش'), - ), - ] diff --git a/invoices/migrations/0003_alter_historicalpayment_reference_number_and_more.py b/invoices/migrations/0003_alter_historicalpayment_reference_number_and_more.py deleted file mode 100644 index d84cc14..0000000 --- a/invoices/migrations/0003_alter_historicalpayment_reference_number_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-21 18:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0002_historicalpayment_receipt_image_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='historicalpayment', - name='reference_number', - field=models.CharField(blank=True, db_index=True, max_length=100, verbose_name='شماره مرجع'), - ), - migrations.AlterField( - model_name='payment', - name='reference_number', - field=models.CharField(blank=True, max_length=100, unique=True, verbose_name='شماره مرجع'), - ), - ] diff --git a/invoices/migrations/0004_historicalpayment_direction_payment_direction.py b/invoices/migrations/0004_historicalpayment_direction_payment_direction.py deleted file mode 100644 index e3416cc..0000000 --- a/invoices/migrations/0004_historicalpayment_direction_payment_direction.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-22 08:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0003_alter_historicalpayment_reference_number_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='historicalpayment', - name='direction', - field=models.CharField(choices=[('in', 'دریافتی'), ('out', 'پرداختی')], default='in', max_length=3, verbose_name='نوع تراکنش'), - ), - migrations.AddField( - model_name='payment', - name='direction', - field=models.CharField(choices=[('in', 'دریافتی'), ('out', 'پرداختی')], default='in', max_length=3, verbose_name='نوع تراکنش'), - ), - ] diff --git a/invoices/migrations/0005_historicalitem_is_special_and_more.py b/invoices/migrations/0005_historicalitem_is_special_and_more.py deleted file mode 100644 index 7524867..0000000 --- a/invoices/migrations/0005_historicalitem_is_special_and_more.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-22 08:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0004_historicalpayment_direction_payment_direction'), - ] - - operations = [ - migrations.AddField( - model_name='historicalitem', - name='is_special', - field=models.BooleanField(default=False, verbose_name='ویژه برای فاکتور نهایی'), - ), - migrations.AddField( - model_name='historicalitem', - name='special_kind', - field=models.CharField(blank=True, choices=[('repair', 'تعمیر'), ('replace', 'تعویض')], max_length=10, verbose_name='نوع ویژه'), - ), - migrations.AddField( - model_name='item', - name='is_special', - field=models.BooleanField(default=False, verbose_name='ویژه برای فاکتور نهایی'), - ), - migrations.AddField( - model_name='item', - name='special_kind', - field=models.CharField(blank=True, choices=[('repair', 'تعمیر'), ('replace', 'تعویض')], max_length=10, verbose_name='نوع ویژه'), - ), - ] diff --git a/invoices/migrations/0006_remove_historicalitem_special_kind_and_more.py b/invoices/migrations/0006_remove_historicalitem_special_kind_and_more.py deleted file mode 100644 index 5abdd01..0000000 --- a/invoices/migrations/0006_remove_historicalitem_special_kind_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.2.4 on 2025-08-22 08:59 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('invoices', '0005_historicalitem_is_special_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='historicalitem', - name='special_kind', - ), - migrations.RemoveField( - model_name='item', - name='special_kind', - ), - ] diff --git a/locations/migrations/0001_initial.py b/locations/migrations/0001_initial.py index def46a0..db3cdac 100644 --- a/locations/migrations/0001_initial.py +++ b/locations/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-14 09:02 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion from django.db import migrations, models diff --git a/processes/admin.py b/processes/admin.py index c8ad3ac..0cbe11f 100644 --- a/processes/admin.py +++ b/processes/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin from django.utils.html import format_html from django.utils.safestring import mark_safe -from .models import Process, ProcessStep, ProcessInstance, StepInstance, StepDependency, StepRejection, StepRevision, StepApproverRequirement, StepApproval +from .models import Process, ProcessStep, ProcessInstance, StepInstance, StepDependency, StepRejection, StepApproverRequirement, StepApproval @admin.register(Process) class ProcessAdmin(SimpleHistoryAdmin): @@ -168,18 +168,6 @@ class StepRejectionAdmin(SimpleHistoryAdmin): return obj.reason[:50] + "..." if len(obj.reason) > 50 else obj.reason reason_short.short_description = "دلیل رد شدن" -@admin.register(StepRevision) -class StepRevisionAdmin(SimpleHistoryAdmin): - list_display = ['step_instance', 'rejection', 'revised_by', 'changes_short', 'created_at'] - list_filter = ['revised_by', 'created_at', 'step_instance__step__process'] - search_fields = ['step_instance__step__name', 'revised_by__username', 'changes_description'] - readonly_fields = ['created_at'] - ordering = ['-created_at'] - - def changes_short(self, obj): - return obj.changes_description[:50] + "..." if len(obj.changes_description) > 50 else obj.changes_description - changes_short.short_description = "تغییرات" - @admin.register(StepApproverRequirement) class StepApproverRequirementAdmin(admin.ModelAdmin): diff --git a/processes/forms.py b/processes/forms.py index 534c475..e69de29 100644 --- a/processes/forms.py +++ b/processes/forms.py @@ -1,26 +0,0 @@ -from django import forms -from .models import ProcessInstance, StepInstance - -class ProcessInstanceForm(forms.ModelForm): - class Meta: - model = ProcessInstance - fields = ['description', 'process', 'well', 'representative', 'requester', 'priority', 'status', 'current_step'] - widgets = { - 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), - 'process': forms.Select(attrs={'class': 'form-control'}), - 'well': forms.Select(attrs={'class': 'form-control'}), - 'representative': forms.Select(attrs={'class': 'form-control'}), - 'requester': forms.Select(attrs={'class': 'form-control'}), - 'priority': forms.Select(attrs={'class': 'form-control'}), - 'status': forms.Select(attrs={'class': 'form-control'}), - 'current_step': forms.Select(attrs={'class': 'form-control'}), - } - -class StepInstanceForm(forms.ModelForm): - class Meta: - model = StepInstance - fields = ['status', 'notes'] - widgets = { - 'status': forms.Select(attrs={'class': 'form-control'}), - 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}) - } \ No newline at end of file diff --git a/processes/migrations/0001_initial.py b/processes/migrations/0001_initial.py index 8675e70..4e90bf7 100644 --- a/processes/migrations/0001_initial.py +++ b/processes/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.4 on 2025-08-14 09:02 +# Generated by Django 5.2.4 on 2025-09-07 07:35 import django.db.models.deletion import simple_history.models @@ -11,6 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('accounts', '0001_initial'), ('wells', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -231,42 +232,17 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='HistoricalStepRevision', - fields=[ - ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), - ('changes_description', models.TextField(help_text='توضیح تغییراتی که برای اصلاح انجام شده', verbose_name='توضیح تغییرات')), - ('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='تاریخ اصلاح')), - ('history_id', models.AutoField(primary_key=True, serialize=False)), - ('history_date', models.DateTimeField(db_index=True)), - ('history_change_reason', models.CharField(max_length=100, null=True)), - ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), - ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), - ('revised_by', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='اصلاح کننده')), - ('step_instance', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='processes.stepinstance', verbose_name='نمونه مرحله')), - ('rejection', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='processes.steprejection', verbose_name='رد شدن مربوطه')), - ], - options={ - 'verbose_name': 'historical بازبینی مرحله', - 'verbose_name_plural': 'historical بازبینی\u200cهای مراحل', - 'ordering': ('-history_date', '-history_id'), - 'get_latest_by': ('history_date', 'history_id'), - }, - bases=(simple_history.models.HistoricalChanges, models.Model), - ), - migrations.CreateModel( - name='StepRevision', + name='StepApproverRequirement', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('changes_description', models.TextField(help_text='توضیح تغییراتی که برای اصلاح انجام شده', verbose_name='توضیح تغییرات')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ اصلاح')), - ('rejection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='processes.steprejection', verbose_name='رد شدن مربوطه')), - ('revised_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='step_revisions', to=settings.AUTH_USER_MODEL, verbose_name='اصلاح کننده')), - ('step_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='processes.stepinstance', verbose_name='نمونه مرحله')), + ('required_count', models.PositiveIntegerField(default=1, verbose_name='تعداد موردنیاز')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش تاییدکننده')), + ('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approver_requirements', to='processes.processstep', verbose_name='مرحله')), ], options={ - 'verbose_name': 'بازبینی مرحله', - 'verbose_name_plural': 'بازبینی\u200cهای مراحل', - 'ordering': ['-created_at'], + 'verbose_name': 'نیازمندی تایید نقش', + 'verbose_name_plural': 'نیازمندی\u200cهای تایید نقش', + 'unique_together': {('step', 'role')}, }, ), migrations.CreateModel( @@ -284,4 +260,21 @@ class Migration(migrations.Migration): 'unique_together': {('dependent_step', 'dependency_step')}, }, ), + migrations.CreateModel( + name='StepApproval', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('decision', models.CharField(choices=[('approved', 'تایید'), ('rejected', 'رد')], max_length=8, verbose_name='نتیجه')), + ('reason', models.TextField(blank=True, verbose_name='علت (برای رد)')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ')), + ('approved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='تاییدکننده')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش')), + ('step_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approvals', to='processes.stepinstance', verbose_name='نمونه مرحله')), + ], + options={ + 'verbose_name': 'تایید مرحله', + 'verbose_name_plural': 'تاییدهای مرحله', + 'unique_together': {('step_instance', 'role')}, + }, + ), ] diff --git a/processes/migrations/0002_stepapproval_stepapproverrequirement.py b/processes/migrations/0002_stepapproval_stepapproverrequirement.py deleted file mode 100644 index 6a771b1..0000000 --- a/processes/migrations/0002_stepapproval_stepapproverrequirement.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 5.2.4 on 2025-09-01 10:33 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounts', '0003_historicalprofile_bank_name_profile_bank_name'), - ('processes', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='StepApproval', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('decision', models.CharField(choices=[('approved', 'تایید'), ('rejected', 'رد')], max_length=8, verbose_name='نتیجه')), - ('reason', models.TextField(blank=True, verbose_name='علت (برای رد)')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ')), - ('approved_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='تاییدکننده')), - ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش')), - ('step_instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approvals', to='processes.stepinstance', verbose_name='نمونه مرحله')), - ], - options={ - 'verbose_name': 'تایید مرحله', - 'verbose_name_plural': 'تاییدهای مرحله', - 'unique_together': {('step_instance', 'role')}, - }, - ), - migrations.CreateModel( - name='StepApproverRequirement', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('required_count', models.PositiveIntegerField(default=1, verbose_name='تعداد موردنیاز')), - ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش تاییدکننده')), - ('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approver_requirements', to='processes.processstep', verbose_name='مرحله')), - ], - options={ - 'verbose_name': 'نیازمندی تایید نقش', - 'verbose_name_plural': 'نیازمندی\u200cهای تایید نقش', - 'unique_together': {('step', 'role')}, - }, - ), - ] diff --git a/processes/models.py b/processes/models.py index 604bb7b..0a119db 100644 --- a/processes/models.py +++ b/processes/models.py @@ -68,6 +68,7 @@ class ProcessStep(NameSlugModel): """دریافت مراحلی که به این مرحله وابسته هستند""" return StepDependency.objects.filter(dependency_step=self).values_list('dependent_step', flat=True) + class StepDependency(models.Model): """مدل وابستگی بین مراحل""" dependent_step = models.ForeignKey( @@ -295,6 +296,7 @@ class ProcessInstance(SluggedModel): return False return True + class StepInstance(models.Model): """مدل نمونه مرحله (برای هر مرحله در هر درخواست)""" process_instance = models.ForeignKey(ProcessInstance, on_delete=models.CASCADE, related_name='step_instances', verbose_name="نمونه فرآیند") @@ -378,6 +380,7 @@ class StepInstance(models.Model): return False return True + class StepRejection(models.Model): """مدل رد شدن مرحله""" step_instance = models.ForeignKey( @@ -415,41 +418,6 @@ class StepRejection(models.Model): self.step_instance.save() super().save(*args, **kwargs) -class StepRevision(models.Model): - """مدل بازبینی و اصلاح مرحله""" - step_instance = models.ForeignKey( - StepInstance, - on_delete=models.CASCADE, - related_name='revisions', - verbose_name="نمونه مرحله" - ) - rejection = models.ForeignKey( - StepRejection, - on_delete=models.CASCADE, - related_name='revisions', - verbose_name="رد شدن مربوطه" - ) - revised_by = models.ForeignKey( - User, - on_delete=models.CASCADE, - verbose_name="اصلاح کننده", - related_name='step_revisions' - ) - changes_description = models.TextField( - verbose_name="توضیح تغییرات", - help_text="توضیح تغییراتی که برای اصلاح انجام شده" - ) - created_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ اصلاح") - history = HistoricalRecords() - - class Meta: - verbose_name = "بازبینی مرحله" - verbose_name_plural = "بازبینیهای مراحل" - ordering = ['-created_at'] - - def __str__(self): - return f"بازبینی {self.step_instance} توسط {self.revised_by.get_full_name()}" - class StepApproverRequirement(models.Model): """Required approver roles for a step.""" diff --git a/processes/templates/processes/request_list.html b/processes/templates/processes/request_list.html index 6ca6d95..5a0331f 100644 --- a/processes/templates/processes/request_list.html +++ b/processes/templates/processes/request_list.html @@ -28,17 +28,113 @@
شناسه | @@ -46,8 +142,8 @@مرحله فعلی | شماره اشتراک آب | نماینده | -درخواستکننده | -اولویت | +استان | +امور | وضعیت | تاریخ ایجاد | عملیات | @@ -61,9 +157,9 @@{{ inst.current_step.name|default:"--" }} | {{ inst.well.water_subscription_number }} | {% if inst.representative %}{{ inst.representative.get_full_name }}{% else %}-{% endif %} | -{% if inst.requester %}{{ inst.requester.get_full_name }}{% else %}-{% endif %} | -{{ inst.get_priority_display }} | -{{ inst.get_status_display }} | +{% if inst.well and inst.well.county %}{{ inst.well.county }}{% else %}-{% endif %} | +{% if inst.well and inst.well.affairs %}{{ inst.well.affairs }}{% else %}-{% endif %} | +{{ inst.get_status_display_with_color|safe }} | {{ inst.jcreated }} |
@@ -126,7 +222,7 @@
-
+
@@ -217,7 +313,7 @@
-
+
@@ -268,7 +364,7 @@
@@ -361,19 +457,13 @@
$(function() {
- // if ($.fn.DataTable) {
- // try {
- // $('#requestsTable').DataTable({
- // pageLength: 10,
- // order: [[0, 'desc']]
- // });
- // } catch (e) {
- // console.error('DataTable init failed', e);
- // }
- // } else {
- // console.warn('DataTables library not loaded');
- // }
-
+ // Initialize DataTable similar to customer_list
+ $('#requests-table').DataTable({
+ pageLength: 10,
+ lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "همه"]],
+ order: [[0, 'desc']],
+ responsive: true,
+ });
let currentWellId = null;
let currentRepId = null;
let wellChecked = false;
@@ -399,7 +489,7 @@
if (!$el.length) return false;
$el.addClass('is-invalid');
const $feedback = $('').text(message);
- const $grp = $el.closest('.input-group');
+ const $grp = $el.closest('.input-group, .form-group, .mb-3');
if ($grp.length) {
$feedback.insertAfter($grp);
} else {
@@ -408,60 +498,49 @@
return true;
}
- function mapWellFieldToSelector(field) {
- switch (field) {
- case 'water_subscription_number': return '#req_water_sub';
- case 'electricity_subscription_number': return '#id_electricity_subscription_number';
- case 'water_meter_serial_number': return '#id_water_meter_serial_number';
- case 'water_meter_old_serial_number': return '#id_water_meter_old_serial_number';
- case 'water_meter_manufacturer': return '#id_water_meter_manufacturer';
- case 'new_manufacturer': return '#id_new_manufacturer';
- case 'utm_x': return '#id_utm_x';
- case 'utm_y': return '#id_utm_y';
- case 'utm_zone': return '#id_utm_zone';
- case 'utm_hemisphere': return '#id_utm_hemisphere';
- case 'well_power': return '#id_well_power';
- case 'reference_letter_number': return '#id_reference_letter_number';
- case 'reference_letter_date': return '#id_reference_letter_date';
- case 'representative_letter_file': return '#id_representative_letter_file';
- case 'representative': return '#rep_national_code';
- default: return '#id_' + field;
- }
- }
+ // Generic field resolution with small exception map
+ const exceptionMap = {
+ water_subscription_number: '#req_water_sub',
+ national_code: '#rep_national_code',
+ representative: '#rep_national_code'
+ };
- function mapCustomerFieldToSelector(field) {
- switch (field) {
- case 'national_code': return $('#id_national_code').length ? '#id_national_code' : '#rep_national_code';
- case 'first_name': return '#id_first_name';
- case 'last_name': return '#id_last_name';
- case 'phone_number_1': return '#id_phone_number_1';
- case 'phone_number_2': return '#id_phone_number_2';
- case 'card_number': return '#id_card_number';
- case 'account_number': return '#id_account_number';
- case 'bank_name': return '#id_bank_name';
- case 'address': return '#id_address';
- default: return '#id_' + field;
- }
+ function findFieldSelector(field, context) {
+ const $ctx = context ? $(context) : $('#requestModal');
+ let $el = $ctx.find(`#id_${field}`).first();
+ if ($el.length) return $el;
+ $el = $ctx.find(`[name="${field}"]`).first();
+ if ($el.length) return $el;
+ $el = $ctx.find(`[data-field="${field}"]`).first();
+ if ($el.length) return $el;
+ const ex = exceptionMap[field];
+ return ex ? $(ex) : $();
}
function showInlineErrors(errors) {
if (!errors) return;
let nonFieldWell = '';
let nonFieldCustomer = '';
+ // Request-level errors (e.g., process)
+ if (errors.request) {
+ for (const key in errors.request) {
+ const msgs = Array.isArray(errors.request[key]) ? errors.request[key] : [errors.request[key]];
+ if (key === '__all__' || key === 'non_field_errors') { continue; }
+ applyErrorTo(findFieldSelector(key, '#requestForm'), msgs[0]);
+ }
+ }
if (errors.well) {
for (const key in errors.well) {
const msgs = Array.isArray(errors.well[key]) ? errors.well[key] : [errors.well[key]];
if (key === '__all__' || key === 'non_field_errors') { nonFieldWell = msgs.join('، '); continue; }
- const sel = mapWellFieldToSelector(key);
- applyErrorTo(sel, msgs[0]);
+ applyErrorTo(findFieldSelector(key, '#wellFormBlock'), msgs[0]);
}
}
if (errors.customer) {
for (const key in errors.customer) {
const msgs = Array.isArray(errors.customer[key]) ? errors.customer[key] : [errors.customer[key]];
if (key === '__all__' || key === 'non_field_errors') { nonFieldCustomer = msgs.join('، '); continue; }
- const sel = mapCustomerFieldToSelector(key);
- applyErrorTo(sel, msgs[0]);
+ applyErrorTo(findFieldSelector(key, '#repNewFields'), msgs[0]);
}
}
if (nonFieldWell) setStatus('#wellStatus', nonFieldWell, 'danger');
@@ -536,7 +615,7 @@
$('#remove-file').val('false');
// Initialize Persian Date Picker after well form is shown
setTimeout(initPersianDatePicker, 100);
- setStatus('#wellStatus', 'چاه یافت نشد. با ذخیره، ایجاد خواهد شد.', 'danger');
+ setStatus('#wellStatus', 'چاه یافت نشد. اطلاعات چاه را وارد کنید.', 'danger');
}
})
.fail(function(){ setStatus('#wellStatus', 'خطا در بررسی چاه', 'danger'); });
@@ -594,46 +673,26 @@
});
$('#btnSaveRequest').on('click', function(){
- const formData = new FormData();
- formData.append('csrfmiddlewaretoken', $('input[name=csrfmiddlewaretoken]').val());
- formData.append('process', $('#req_process').val());
- formData.append('description', $('#req_description').val());
- formData.append('water_subscription_number', $('#req_water_sub').val().trim());
+ clearInlineErrors();
+ // Use form's native FormData - much cleaner!
+ const formData = new FormData(document.getElementById('requestForm'));
+
+ // Add custom fields that aren't in the form
if (currentWellId) formData.append('well_id', currentWellId);
if (currentRepId) formData.append('representative_id', currentRepId);
- // Send fields using CustomerForm names if visible
- const ncField = $('#id_national_code').length ? $('#id_national_code').val() : '';
- formData.append('national_code', (ncField || $('#rep_national_code').val().trim()));
- formData.append('first_name', $('#id_first_name').val() || '');
- formData.append('last_name', $('#id_last_name').val() || '');
- formData.append('phone_number_1', $('#id_phone_number_1').val() || '');
- formData.append('phone_number_2', $('#id_phone_number_2').val() || '');
- formData.append('card_number', $('#id_card_number').val() || '');
- formData.append('account_number', $('#id_account_number').val() || '');
- formData.append('address', $('#id_address').val() || '');
- formData.append('bank_name', $('#id_bank_name').val() || '');
- // Include WellForm fields so edits are saved
- if ($('#wellFormBlock').is(':visible')) {
- formData.append('electricity_subscription_number', $('#id_electricity_subscription_number').val() || '');
- formData.append('water_meter_serial_number', $('#id_water_meter_serial_number').val() || '');
- formData.append('water_meter_old_serial_number', $('#id_water_meter_old_serial_number').val() || '');
- formData.append('water_meter_manufacturer', $('#id_water_meter_manufacturer').is(':visible') ? ($('#id_water_meter_manufacturer').val() || '') : '');
- formData.append('new_manufacturer', $('#id_new_manufacturer').is(':visible') ? ($('#id_new_manufacturer').val() || '') : '');
- formData.append('utm_x', $('#id_utm_x').val() || '');
- formData.append('utm_y', $('#id_utm_y').val() || '');
- formData.append('utm_zone', $('#id_utm_zone').val() || '');
- formData.append('utm_hemisphere', $('#id_utm_hemisphere').val() || '');
- formData.append('well_power', $('#id_well_power').val() || '');
- formData.append('reference_letter_number', $('#id_reference_letter_number').val() || '');
- // Use gregorian date if available, otherwise use the field value
- const gregorianDate = $('#id_reference_letter_date').attr('data-gregorian');
- formData.append('reference_letter_date', gregorianDate || $('#id_reference_letter_date').val() || '');
- // Remove flag
- formData.append('remove_file', $('#remove-file').val() || 'false');
- const repFile = document.getElementById('id_representative_letter_file');
- if (repFile && repFile.files && repFile.files[0]) {
- formData.append('representative_letter_file', repFile.files[0]);
- }
+
+ // Handle special national_code logic (prefer visible field)
+ const ncField = $('#id_national_code').val();
+ if (ncField) {
+ formData.set('national_code', ncField);
+ } else {
+ formData.set('national_code', $('#rep_national_code').val().trim());
+ }
+
+ // Handle Persian date conversion
+ const gregorianDate = $('#id_reference_letter_date').attr('data-gregorian');
+ if (gregorianDate) {
+ formData.set('reference_letter_date', gregorianDate);
}
const $btn = $(this).prop('disabled', true).text('در حال ذخیره...');
@@ -652,6 +711,10 @@
setTimeout(function(){ location.reload(); }, 1200);
}
} else {
+ clearInlineErrors();
+ if (resp.errors) {
+ showInlineErrors(resp.errors);
+ }
const msg = buildErrorMessage(resp);
showToast(msg, 'danger');
}
@@ -659,6 +722,10 @@
let msg = 'خطا در ذخیره';
try {
const resp = JSON.parse(xhr.responseText);
+ clearInlineErrors();
+ if (resp && resp.errors) {
+ showInlineErrors(resp.errors);
+ }
msg = buildErrorMessage(resp) || msg;
} catch(e) {}
showToast(msg, 'danger');
@@ -715,6 +782,14 @@
}
});
+ // Enforce digit-only and max length for national code input
+ $('#rep_national_code').on('input', function() {
+ const cleaned = (this.value || '').replace(/\D/g, '').slice(0, 10);
+ if (this.value !== cleaned) {
+ this.value = cleaned;
+ }
+ });
+
$('#requestModal').on('hidden.bs.modal', function(){
$('#requestForm')[0].reset();
$('#wellFormBlock').hide();
diff --git a/processes/urls.py b/processes/urls.py
index fc2c424..a58ebab 100644
--- a/processes/urls.py
+++ b/processes/urls.py
@@ -16,10 +16,4 @@ urlpatterns = [
path('instance/
-
+
|
---|