clean up proccess and req_list app.
This commit is contained in:
		
							parent
							
								
									35799b7754
								
							
						
					
					
						commit
						6f3ce51ab9
					
				
					 26 changed files with 287 additions and 744 deletions
				
			
		| 
						 | 
					@ -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.core.validators
 | 
				
			||||||
import django.db.models.deletion
 | 
					import django.db.models.deletion
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,27 @@ class Migration(migrations.Migration):
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    operations = [
 | 
					    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(
 | 
					        migrations.CreateModel(
 | 
				
			||||||
            name='HistoricalProfile',
 | 
					            name='HistoricalProfile',
 | 
				
			||||||
            fields=[
 | 
					            fields=[
 | 
				
			||||||
| 
						 | 
					@ -30,6 +51,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
                ('address', models.TextField(blank=True, null=True, verbose_name='آدرس')),
 | 
					                ('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='شماره کارت')),
 | 
					                ('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='شماره حساب')),
 | 
					                ('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_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='شماره تماس ۲')),
 | 
					                ('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='تصویر')),
 | 
					                ('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='آدرس')),
 | 
					                ('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='شماره کارت')),
 | 
					                ('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='شماره حساب')),
 | 
					                ('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_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='شماره تماس ۲')),
 | 
					                ('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='تصویر')),
 | 
					                ('pic', models.ImageField(default='../static/sample_images/profile.jpg', upload_to='profile_images', verbose_name='تصویر')),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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ها',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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='نام بانک'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					import django.db.models.deletion
 | 
				
			||||||
from django.db import migrations, models
 | 
					from django.db import migrations, models
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
    initial = True
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies = [
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('accounts', '0001_initial'),
 | 
				
			||||||
        ('processes', '0001_initial'),
 | 
					        ('processes', '0001_initial'),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,10 +24,8 @@ class Migration(migrations.Migration):
 | 
				
			||||||
                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
					                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
				
			||||||
                ('title', models.CharField(max_length=200, verbose_name='عنوان')),
 | 
					                ('title', models.CharField(max_length=200, verbose_name='عنوان')),
 | 
				
			||||||
                ('body', models.TextField(verbose_name='متن قالب (با جایگزین\u200cها)')),
 | 
					                ('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='فعال')),
 | 
					                ('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={
 | 
					            options={
 | 
				
			||||||
                'verbose_name': 'قالب گواهی',
 | 
					                'verbose_name': 'قالب گواهی',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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='شرکت صادر کننده'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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 django.db.models.deletion
 | 
				
			||||||
import simple_history.models
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.db import migrations, models
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +10,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
    initial = True
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies = [
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('accounts', '0001_initial'),
 | 
				
			||||||
        ('processes', '0001_initial'),
 | 
					        ('processes', '0001_initial'),
 | 
				
			||||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
					        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='اسلاگ')),
 | 
					                ('slug', models.SlugField(max_length=100, unique=True, verbose_name='اسلاگ')),
 | 
				
			||||||
                ('name', models.CharField(max_length=100, verbose_name='نام')),
 | 
					                ('name', models.CharField(max_length=100, verbose_name='نام')),
 | 
				
			||||||
                ('body', models.TextField(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', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.company', verbose_name='شرکت')),
 | 
				
			||||||
                ('company_signature', models.ImageField(blank=True, null=True, upload_to='contracts/signatures/%Y/%m/%d/', verbose_name='امضای شرکت')),
 | 
					 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            options={
 | 
					            options={
 | 
				
			||||||
                'verbose_name': 'قالب قرارداد',
 | 
					                'verbose_name': 'قالب قرارداد',
 | 
				
			||||||
| 
						 | 
					@ -58,61 +57,4 @@ class Migration(migrations.Migration):
 | 
				
			||||||
                'ordering': ['-created'],
 | 
					                '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),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -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
 | 
					import django.db.models.deletion
 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
    initial = True
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies = [
 | 
					    dependencies = [
 | 
				
			||||||
        ('invoices', '0002_historicalpayment_receipt_image_and_more'),
 | 
					        ('invoices', '0001_initial'),
 | 
				
			||||||
        ('processes', '0001_initial'),
 | 
					        ('processes', '0001_initial'),
 | 
				
			||||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
					        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_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')),
 | 
					                ('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='توضیحات')),
 | 
					                ('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='اختصاص')),
 | 
					                ('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='ایجادکننده')),
 | 
					                ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='ایجادکننده')),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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='تاریخ تایید'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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 django.db.models.deletion
 | 
				
			||||||
import simple_history.models
 | 
					import simple_history.models
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
                ('name', models.CharField(max_length=100, verbose_name='نام')),
 | 
					                ('name', models.CharField(max_length=100, verbose_name='نام')),
 | 
				
			||||||
                ('description', models.TextField(blank=True, verbose_name='توضیحات')),
 | 
					                ('description', models.TextField(blank=True, verbose_name='توضیحات')),
 | 
				
			||||||
                ('unit_price', models.DecimalField(decimal_places=2, max_digits=15, 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فرض')),
 | 
					                ('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فاکتورها')),
 | 
					                ('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)),
 | 
					                ('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='حذف شده')),
 | 
					                ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
 | 
				
			||||||
                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
					                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
				
			||||||
                ('amount', models.DecimalField(decimal_places=2, max_digits=15, 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='روش پرداخت')),
 | 
					                ('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='تاریخ پرداخت')),
 | 
					                ('payment_date', models.DateField(verbose_name='تاریخ پرداخت')),
 | 
				
			||||||
                ('notes', models.TextField(blank=True, verbose_name='یادداشت\u200cها')),
 | 
					                ('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_id', models.AutoField(primary_key=True, serialize=False)),
 | 
				
			||||||
                ('history_date', models.DateTimeField(db_index=True)),
 | 
					                ('history_date', models.DateTimeField(db_index=True)),
 | 
				
			||||||
                ('history_change_reason', models.CharField(max_length=100, null=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='نام')),
 | 
					                ('name', models.CharField(max_length=100, verbose_name='نام')),
 | 
				
			||||||
                ('description', models.TextField(blank=True, verbose_name='توضیحات')),
 | 
					                ('description', models.TextField(blank=True, verbose_name='توضیحات')),
 | 
				
			||||||
                ('unit_price', models.DecimalField(decimal_places=2, max_digits=15, 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فرض')),
 | 
					                ('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فاکتورها')),
 | 
					                ('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='ایجاد کننده')),
 | 
					                ('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='حذف شده')),
 | 
					                ('is_deleted', models.BooleanField(default=False, verbose_name='حذف شده')),
 | 
				
			||||||
                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
					                ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='تاریخ حذف')),
 | 
				
			||||||
                ('amount', models.DecimalField(decimal_places=2, max_digits=15, 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='روش پرداخت')),
 | 
					                ('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='تاریخ پرداخت')),
 | 
					                ('payment_date', models.DateField(verbose_name='تاریخ پرداخت')),
 | 
				
			||||||
                ('notes', models.TextField(blank=True, verbose_name='یادداشت\u200cها')),
 | 
					                ('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='ثبت کننده')),
 | 
					                ('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='فاکتور')),
 | 
					                ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='invoices.invoice', verbose_name='فاکتور')),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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='تصویر فیش'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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='شماره مرجع'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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='نوع تراکنش'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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='نوع ویژه'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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 django.db.models.deletion
 | 
				
			||||||
from django.db import migrations, models
 | 
					from django.db import migrations, models
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ from django.contrib import admin
 | 
				
			||||||
from simple_history.admin import SimpleHistoryAdmin
 | 
					from simple_history.admin import SimpleHistoryAdmin
 | 
				
			||||||
from django.utils.html import format_html
 | 
					from django.utils.html import format_html
 | 
				
			||||||
from django.utils.safestring import mark_safe
 | 
					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)
 | 
					@admin.register(Process)
 | 
				
			||||||
class ProcessAdmin(SimpleHistoryAdmin):
 | 
					class ProcessAdmin(SimpleHistoryAdmin):
 | 
				
			||||||
| 
						 | 
					@ -168,18 +168,6 @@ class StepRejectionAdmin(SimpleHistoryAdmin):
 | 
				
			||||||
        return obj.reason[:50] + "..." if len(obj.reason) > 50 else obj.reason
 | 
					        return obj.reason[:50] + "..." if len(obj.reason) > 50 else obj.reason
 | 
				
			||||||
    reason_short.short_description = "دلیل رد شدن"
 | 
					    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)
 | 
					@admin.register(StepApproverRequirement)
 | 
				
			||||||
class StepApproverRequirementAdmin(admin.ModelAdmin):
 | 
					class StepApproverRequirementAdmin(admin.ModelAdmin):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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})
 | 
					 | 
				
			||||||
        } 
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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 django.db.models.deletion
 | 
				
			||||||
import simple_history.models
 | 
					import simple_history.models
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ class Migration(migrations.Migration):
 | 
				
			||||||
    initial = True
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dependencies = [
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('accounts', '0001_initial'),
 | 
				
			||||||
        ('wells', '0001_initial'),
 | 
					        ('wells', '0001_initial'),
 | 
				
			||||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
					        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -231,42 +232,17 @@ class Migration(migrations.Migration):
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        migrations.CreateModel(
 | 
					        migrations.CreateModel(
 | 
				
			||||||
            name='HistoricalStepRevision',
 | 
					            name='StepApproverRequirement',
 | 
				
			||||||
            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',
 | 
					 | 
				
			||||||
            fields=[
 | 
					            fields=[
 | 
				
			||||||
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
					                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
                ('changes_description', models.TextField(help_text='توضیح تغییراتی که برای اصلاح انجام شده', verbose_name='توضیح تغییرات')),
 | 
					                ('required_count', models.PositiveIntegerField(default=1, verbose_name='تعداد موردنیاز')),
 | 
				
			||||||
                ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='تاریخ اصلاح')),
 | 
					                ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.role', verbose_name='نقش تاییدکننده')),
 | 
				
			||||||
                ('rejection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='processes.steprejection', verbose_name='رد شدن مربوطه')),
 | 
					                ('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='approver_requirements', to='processes.processstep', 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='نمونه مرحله')),
 | 
					 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            options={
 | 
					            options={
 | 
				
			||||||
                'verbose_name': 'بازبینی مرحله',
 | 
					                'verbose_name': 'نیازمندی تایید نقش',
 | 
				
			||||||
                'verbose_name_plural': 'بازبینی\u200cهای مراحل',
 | 
					                'verbose_name_plural': 'نیازمندی\u200cهای تایید نقش',
 | 
				
			||||||
                'ordering': ['-created_at'],
 | 
					                'unique_together': {('step', 'role')},
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        migrations.CreateModel(
 | 
					        migrations.CreateModel(
 | 
				
			||||||
| 
						 | 
					@ -284,4 +260,21 @@ class Migration(migrations.Migration):
 | 
				
			||||||
                'unique_together': {('dependent_step', 'dependency_step')},
 | 
					                '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')},
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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')},
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
| 
						 | 
					@ -68,6 +68,7 @@ class ProcessStep(NameSlugModel):
 | 
				
			||||||
        """دریافت مراحلی که به این مرحله وابسته هستند"""
 | 
					        """دریافت مراحلی که به این مرحله وابسته هستند"""
 | 
				
			||||||
        return StepDependency.objects.filter(dependency_step=self).values_list('dependent_step', flat=True)
 | 
					        return StepDependency.objects.filter(dependency_step=self).values_list('dependent_step', flat=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StepDependency(models.Model):
 | 
					class StepDependency(models.Model):
 | 
				
			||||||
    """مدل وابستگی بین مراحل"""
 | 
					    """مدل وابستگی بین مراحل"""
 | 
				
			||||||
    dependent_step = models.ForeignKey(
 | 
					    dependent_step = models.ForeignKey(
 | 
				
			||||||
| 
						 | 
					@ -295,6 +296,7 @@ class ProcessInstance(SluggedModel):
 | 
				
			||||||
                return False
 | 
					                return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StepInstance(models.Model):
 | 
					class StepInstance(models.Model):
 | 
				
			||||||
    """مدل نمونه مرحله (برای هر مرحله در هر درخواست)"""
 | 
					    """مدل نمونه مرحله (برای هر مرحله در هر درخواست)"""
 | 
				
			||||||
    process_instance = models.ForeignKey(ProcessInstance, on_delete=models.CASCADE, related_name='step_instances', verbose_name="نمونه فرآیند")
 | 
					    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 False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StepRejection(models.Model):
 | 
					class StepRejection(models.Model):
 | 
				
			||||||
    """مدل رد شدن مرحله"""
 | 
					    """مدل رد شدن مرحله"""
 | 
				
			||||||
    step_instance = models.ForeignKey(
 | 
					    step_instance = models.ForeignKey(
 | 
				
			||||||
| 
						 | 
					@ -415,41 +418,6 @@ class StepRejection(models.Model):
 | 
				
			||||||
        self.step_instance.save()
 | 
					        self.step_instance.save()
 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					        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):
 | 
					class StepApproverRequirement(models.Model):
 | 
				
			||||||
    """Required approver roles for a step."""
 | 
					    """Required approver roles for a step."""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,17 +28,113 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
					<div class="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="d-flex align-items-center justify-content-between mb-3">
 | 
					  <div class="row py-3 mb-4 card-header flex-column flex-md-row pb-0">
 | 
				
			||||||
    <h4 class="mb-0">درخواستها</h4>
 | 
					    <div class="d-md-flex justify-content-between align-items-center dt-layout-start col-md-auto me-auto mt-0">
 | 
				
			||||||
 | 
					      <h5 class="card-title mb-0 text-md-start text-center fw-bold">لیست درخواستها</h5>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="d-md-flex justify-content-between align-items-center dt-layout-end col-md-auto ms-auto mt-0">
 | 
				
			||||||
 | 
					      <div class="dt-buttons btn-group flex-wrap mb-0">
 | 
				
			||||||
 | 
					        <div class="btn-group">
 | 
				
			||||||
 | 
					          <button class="btn buttons-collection btn-label-primary dropdown-toggle me-4 d-none" type="button">
 | 
				
			||||||
 | 
					            <span>
 | 
				
			||||||
 | 
					              <span class="d-flex align-items-center gap-2">
 | 
				
			||||||
 | 
					                <i class="icon-base bx bx-export me-sm-1"></i>
 | 
				
			||||||
 | 
					                <span class="d-none d-sm-inline-block">خروجی</span>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
          <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#requestModal">
 | 
					          <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#requestModal">
 | 
				
			||||||
      <i class="bx bx-plus"></i>
 | 
					            <i class="bx bx-plus me-1"></i>
 | 
				
			||||||
            درخواست جدید
 | 
					            درخواست جدید
 | 
				
			||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- Summary Cards -->
 | 
				
			||||||
 | 
					  <div class="row g-4 mb-4">
 | 
				
			||||||
 | 
					    <div class="col-sm-6 col-xl-3">
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="d-flex align-items-start justify-content-between">
 | 
				
			||||||
 | 
					            <div class="content-left">
 | 
				
			||||||
 | 
					              <span>کل درخواستها</span>
 | 
				
			||||||
 | 
					              <div class="d-flex align-items-end mt-2">
 | 
				
			||||||
 | 
					                <h4 class="mb-0 me-2">{{ total_count }}</h4>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="avatar">
 | 
				
			||||||
 | 
					              <span class="avatar-initial rounded bg-label-primary">
 | 
				
			||||||
 | 
					                <i class="bx bx-list-ul bx-sm"></i>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="col-sm-6 col-xl-3">
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="d-flex align-items-start justify-content-between">
 | 
				
			||||||
 | 
					            <div class="content-left">
 | 
				
			||||||
 | 
					              <span>تکمیلشده</span>
 | 
				
			||||||
 | 
					              <div class="d-flex align-items-end mt-2">
 | 
				
			||||||
 | 
					                <h4 class="mb-0 me-2">{{ completed_count }}</h4>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="avatar">
 | 
				
			||||||
 | 
					              <span class="avatar-initial rounded bg-label-success">
 | 
				
			||||||
 | 
					                <i class="bx bx-badge-check bx-sm"></i>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="col-sm-6 col-xl-3">
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="d-flex align-items-start justify-content-between">
 | 
				
			||||||
 | 
					            <div class="content-left">
 | 
				
			||||||
 | 
					              <span>در حال انجام</span>
 | 
				
			||||||
 | 
					              <div class="d-flex align-items-end mt-2">
 | 
				
			||||||
 | 
					                <h4 class="mb-0 me-2">{{ in_progress_count }}</h4>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="avatar">
 | 
				
			||||||
 | 
					              <span class="avatar-initial rounded bg-label-info">
 | 
				
			||||||
 | 
					                <i class="bx bx-loader-circle bx-sm"></i>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="col-sm-6 col-xl-3">
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="d-flex align-items-start justify-content-between">
 | 
				
			||||||
 | 
					            <div class="content-left">
 | 
				
			||||||
 | 
					              <span>در انتظار</span>
 | 
				
			||||||
 | 
					              <div class="d-flex align-items-end mt-2">
 | 
				
			||||||
 | 
					                <h4 class="mb-0 me-2">{{ pending_count }}</h4>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="avatar">
 | 
				
			||||||
 | 
					              <span class="avatar-initial rounded bg-label-warning">
 | 
				
			||||||
 | 
					                <i class="bx bx-time bx-sm"></i>
 | 
				
			||||||
 | 
					              </span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="card">
 | 
					  <div class="card">
 | 
				
			||||||
    <div class="table-responsive">
 | 
					    <div class="card-datatable table-responsive">
 | 
				
			||||||
      <table id="requestsTable" class="table table-striped">
 | 
					      <table id="requests-table" class="datatables-basic table border-top">
 | 
				
			||||||
        <thead>
 | 
					        <thead>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
            <th>شناسه</th>
 | 
					            <th>شناسه</th>
 | 
				
			||||||
| 
						 | 
					@ -46,8 +142,8 @@
 | 
				
			||||||
            <th>مرحله فعلی</th>
 | 
					            <th>مرحله فعلی</th>
 | 
				
			||||||
            <th>شماره اشتراک آب</th>
 | 
					            <th>شماره اشتراک آب</th>
 | 
				
			||||||
            <th>نماینده</th>
 | 
					            <th>نماینده</th>
 | 
				
			||||||
            <th>درخواستکننده</th>
 | 
					            <th>استان</th>
 | 
				
			||||||
            <th>اولویت</th>
 | 
					            <th>امور</th>
 | 
				
			||||||
            <th>وضعیت</th>
 | 
					            <th>وضعیت</th>
 | 
				
			||||||
            <th>تاریخ ایجاد</th>
 | 
					            <th>تاریخ ایجاد</th>
 | 
				
			||||||
            <th>عملیات</th>
 | 
					            <th>عملیات</th>
 | 
				
			||||||
| 
						 | 
					@ -61,9 +157,9 @@
 | 
				
			||||||
            <td class="text-primary">{{ inst.current_step.name|default:"--" }}</td>
 | 
					            <td class="text-primary">{{ inst.current_step.name|default:"--" }}</td>
 | 
				
			||||||
            <td>{{ inst.well.water_subscription_number }}</td>
 | 
					            <td>{{ inst.well.water_subscription_number }}</td>
 | 
				
			||||||
            <td>{% if inst.representative %}{{ inst.representative.get_full_name }}{% else %}-{% endif %}</td>
 | 
					            <td>{% if inst.representative %}{{ inst.representative.get_full_name }}{% else %}-{% endif %}</td>
 | 
				
			||||||
            <td>{% if inst.requester %}{{ inst.requester.get_full_name }}{% else %}-{% endif %}</td>
 | 
					            <td>{% if inst.well and inst.well.county %}{{ inst.well.county }}{% else %}-{% endif %}</td>
 | 
				
			||||||
            <td>{{ inst.get_priority_display }}</td>
 | 
					            <td>{% if inst.well and inst.well.affairs %}{{ inst.well.affairs }}{% else %}-{% endif %}</td>
 | 
				
			||||||
            <td>{{ inst.get_status_display }}</td>
 | 
					            <td>{{ inst.get_status_display_with_color|safe }}</td>
 | 
				
			||||||
            <td>{{ inst.jcreated }}</td>
 | 
					            <td>{{ inst.jcreated }}</td>
 | 
				
			||||||
            <td>
 | 
					            <td>
 | 
				
			||||||
              <div class="d-inline-block">
 | 
					              <div class="d-inline-block">
 | 
				
			||||||
| 
						 | 
					@ -126,7 +222,7 @@
 | 
				
			||||||
              <div class="col-sm-12">
 | 
					              <div class="col-sm-12">
 | 
				
			||||||
                <label class="form-label">شماره اشتراک آب</label>
 | 
					                <label class="form-label">شماره اشتراک آب</label>
 | 
				
			||||||
                <div class="input-group">
 | 
					                <div class="input-group">
 | 
				
			||||||
                  <input type="text" class="form-control" id="req_water_sub" placeholder="مثال: 12345" required>
 | 
					                  <input type="text" class="form-control" id="req_water_sub" name="water_subscription_number" data-field="water_subscription_number" placeholder="مثال: 12345" required>
 | 
				
			||||||
                  <button class="btn btn-outline-secondary" type="button" id="btnLookupWell">
 | 
					                  <button class="btn btn-outline-secondary" type="button" id="btnLookupWell">
 | 
				
			||||||
                    بررسی/افزودن چاه
 | 
					                    بررسی/افزودن چاه
 | 
				
			||||||
                  </button>
 | 
					                  </button>
 | 
				
			||||||
| 
						 | 
					@ -217,7 +313,7 @@
 | 
				
			||||||
              <div class="col-sm-12">
 | 
					              <div class="col-sm-12">
 | 
				
			||||||
                <label class="form-label">کد ملی نماینده</label>
 | 
					                <label class="form-label">کد ملی نماینده</label>
 | 
				
			||||||
                <div class="input-group">
 | 
					                <div class="input-group">
 | 
				
			||||||
                  <input type="text" class="form-control" id="rep_national_code" placeholder="مثال: 0012345678">
 | 
					                  <input type="text" class="form-control" id="rep_national_code" data-field="national_code" placeholder="مثال: 0012345678" maxlength="10" inputmode="numeric" pattern="\d*">
 | 
				
			||||||
                  <button class="btn btn-outline-secondary" type="button" id="btnLookupRep">
 | 
					                  <button class="btn btn-outline-secondary" type="button" id="btnLookupRep">
 | 
				
			||||||
                    بررسی/افزودن نماینده
 | 
					                    بررسی/افزودن نماینده
 | 
				
			||||||
                  </button>
 | 
					                  </button>
 | 
				
			||||||
| 
						 | 
					@ -268,7 +364,7 @@
 | 
				
			||||||
              <hr class="mt-3 border border-dashed">
 | 
					              <hr class="mt-3 border border-dashed">
 | 
				
			||||||
              <div class="col-sm-12">
 | 
					              <div class="col-sm-12">
 | 
				
			||||||
                <label class="form-label">توضیحات</label>
 | 
					                <label class="form-label">توضیحات</label>
 | 
				
			||||||
                <textarea class="form-control" rows="3" id="req_description"></textarea>
 | 
					                <textarea class="form-control" rows="3" id="req_description" name="description"></textarea>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </form>
 | 
					          </form>
 | 
				
			||||||
| 
						 | 
					@ -361,19 +457,13 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $(function() {
 | 
					  $(function() {
 | 
				
			||||||
    // if ($.fn.DataTable) {
 | 
					    // Initialize DataTable similar to customer_list
 | 
				
			||||||
    //   try {
 | 
					    $('#requests-table').DataTable({
 | 
				
			||||||
    //     $('#requestsTable').DataTable({
 | 
					      pageLength: 10,
 | 
				
			||||||
    //       pageLength: 10,
 | 
					      lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "همه"]],
 | 
				
			||||||
    //       order: [[0, 'desc']]
 | 
					      order: [[0, 'desc']],
 | 
				
			||||||
    //     });
 | 
					      responsive: true,
 | 
				
			||||||
    //   } catch (e) {
 | 
					    });
 | 
				
			||||||
    //     console.error('DataTable init failed', e);
 | 
					 | 
				
			||||||
    //   }
 | 
					 | 
				
			||||||
    // } else {
 | 
					 | 
				
			||||||
    //   console.warn('DataTables library not loaded');
 | 
					 | 
				
			||||||
    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let currentWellId = null;
 | 
					    let currentWellId = null;
 | 
				
			||||||
    let currentRepId = null;
 | 
					    let currentRepId = null;
 | 
				
			||||||
    let wellChecked = false;
 | 
					    let wellChecked = false;
 | 
				
			||||||
| 
						 | 
					@ -399,7 +489,7 @@
 | 
				
			||||||
      if (!$el.length) return false;
 | 
					      if (!$el.length) return false;
 | 
				
			||||||
      $el.addClass('is-invalid');
 | 
					      $el.addClass('is-invalid');
 | 
				
			||||||
      const $feedback = $('<div class="invalid-feedback inline-error"></div>').text(message);
 | 
					      const $feedback = $('<div class="invalid-feedback inline-error"></div>').text(message);
 | 
				
			||||||
      const $grp = $el.closest('.input-group');
 | 
					      const $grp = $el.closest('.input-group, .form-group, .mb-3');
 | 
				
			||||||
      if ($grp.length) {
 | 
					      if ($grp.length) {
 | 
				
			||||||
        $feedback.insertAfter($grp);
 | 
					        $feedback.insertAfter($grp);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
| 
						 | 
					@ -408,60 +498,49 @@
 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function mapWellFieldToSelector(field) {
 | 
					    // Generic field resolution with small exception map
 | 
				
			||||||
      switch (field) {
 | 
					    const exceptionMap = {
 | 
				
			||||||
        case 'water_subscription_number': return '#req_water_sub';
 | 
					      water_subscription_number: '#req_water_sub',
 | 
				
			||||||
        case 'electricity_subscription_number': return '#id_electricity_subscription_number';
 | 
					      national_code: '#rep_national_code',
 | 
				
			||||||
        case 'water_meter_serial_number': return '#id_water_meter_serial_number';
 | 
					      representative: '#rep_national_code'
 | 
				
			||||||
        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;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function mapCustomerFieldToSelector(field) {
 | 
					    function findFieldSelector(field, context) {
 | 
				
			||||||
      switch (field) {
 | 
					      const $ctx = context ? $(context) : $('#requestModal');
 | 
				
			||||||
        case 'national_code': return $('#id_national_code').length ? '#id_national_code' : '#rep_national_code';
 | 
					      let $el = $ctx.find(`#id_${field}`).first();
 | 
				
			||||||
        case 'first_name': return '#id_first_name';
 | 
					      if ($el.length) return $el;
 | 
				
			||||||
        case 'last_name': return '#id_last_name';
 | 
					      $el = $ctx.find(`[name="${field}"]`).first();
 | 
				
			||||||
        case 'phone_number_1': return '#id_phone_number_1';
 | 
					      if ($el.length) return $el;
 | 
				
			||||||
        case 'phone_number_2': return '#id_phone_number_2';
 | 
					      $el = $ctx.find(`[data-field="${field}"]`).first();
 | 
				
			||||||
        case 'card_number': return '#id_card_number';
 | 
					      if ($el.length) return $el;
 | 
				
			||||||
        case 'account_number': return '#id_account_number';
 | 
					      const ex = exceptionMap[field];
 | 
				
			||||||
        case 'bank_name': return '#id_bank_name';
 | 
					      return ex ? $(ex) : $();
 | 
				
			||||||
        case 'address': return '#id_address';
 | 
					 | 
				
			||||||
        default: return '#id_' + field;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showInlineErrors(errors) {
 | 
					    function showInlineErrors(errors) {
 | 
				
			||||||
      if (!errors) return;
 | 
					      if (!errors) return;
 | 
				
			||||||
      let nonFieldWell = '';
 | 
					      let nonFieldWell = '';
 | 
				
			||||||
      let nonFieldCustomer = '';
 | 
					      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) {
 | 
					      if (errors.well) {
 | 
				
			||||||
        for (const key in errors.well) {
 | 
					        for (const key in errors.well) {
 | 
				
			||||||
          const msgs = Array.isArray(errors.well[key]) ? errors.well[key] : [errors.well[key]];
 | 
					          const msgs = Array.isArray(errors.well[key]) ? errors.well[key] : [errors.well[key]];
 | 
				
			||||||
          if (key === '__all__' || key === 'non_field_errors') { nonFieldWell = msgs.join('، '); continue; }
 | 
					          if (key === '__all__' || key === 'non_field_errors') { nonFieldWell = msgs.join('، '); continue; }
 | 
				
			||||||
          const sel = mapWellFieldToSelector(key);
 | 
					          applyErrorTo(findFieldSelector(key, '#wellFormBlock'), msgs[0]);
 | 
				
			||||||
          applyErrorTo(sel, msgs[0]);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (errors.customer) {
 | 
					      if (errors.customer) {
 | 
				
			||||||
        for (const key in errors.customer) {
 | 
					        for (const key in errors.customer) {
 | 
				
			||||||
          const msgs = Array.isArray(errors.customer[key]) ? errors.customer[key] : [errors.customer[key]];
 | 
					          const msgs = Array.isArray(errors.customer[key]) ? errors.customer[key] : [errors.customer[key]];
 | 
				
			||||||
          if (key === '__all__' || key === 'non_field_errors') { nonFieldCustomer = msgs.join('، '); continue; }
 | 
					          if (key === '__all__' || key === 'non_field_errors') { nonFieldCustomer = msgs.join('، '); continue; }
 | 
				
			||||||
          const sel = mapCustomerFieldToSelector(key);
 | 
					          applyErrorTo(findFieldSelector(key, '#repNewFields'), msgs[0]);
 | 
				
			||||||
          applyErrorTo(sel, msgs[0]);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (nonFieldWell) setStatus('#wellStatus', nonFieldWell, 'danger');
 | 
					      if (nonFieldWell) setStatus('#wellStatus', nonFieldWell, 'danger');
 | 
				
			||||||
| 
						 | 
					@ -536,7 +615,7 @@
 | 
				
			||||||
              $('#remove-file').val('false');
 | 
					              $('#remove-file').val('false');
 | 
				
			||||||
            // Initialize Persian Date Picker after well form is shown
 | 
					            // Initialize Persian Date Picker after well form is shown
 | 
				
			||||||
            setTimeout(initPersianDatePicker, 100);
 | 
					            setTimeout(initPersianDatePicker, 100);
 | 
				
			||||||
            setStatus('#wellStatus', 'چاه یافت نشد. با ذخیره، ایجاد خواهد شد.', 'danger');
 | 
					            setStatus('#wellStatus', 'چاه یافت نشد. اطلاعات چاه را وارد کنید.', 'danger');
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .fail(function(){ setStatus('#wellStatus', 'خطا در بررسی چاه', 'danger'); });
 | 
					        .fail(function(){ setStatus('#wellStatus', 'خطا در بررسی چاه', 'danger'); });
 | 
				
			||||||
| 
						 | 
					@ -594,46 +673,26 @@
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('#btnSaveRequest').on('click', function(){
 | 
					    $('#btnSaveRequest').on('click', function(){
 | 
				
			||||||
      const formData = new FormData();
 | 
					      clearInlineErrors();
 | 
				
			||||||
      formData.append('csrfmiddlewaretoken', $('input[name=csrfmiddlewaretoken]').val());
 | 
					      // Use form's native FormData - much cleaner!
 | 
				
			||||||
      formData.append('process', $('#req_process').val());
 | 
					      const formData = new FormData(document.getElementById('requestForm'));
 | 
				
			||||||
      formData.append('description', $('#req_description').val());
 | 
					      
 | 
				
			||||||
      formData.append('water_subscription_number', $('#req_water_sub').val().trim());
 | 
					      // Add custom fields that aren't in the form
 | 
				
			||||||
      if (currentWellId) formData.append('well_id', currentWellId);
 | 
					      if (currentWellId) formData.append('well_id', currentWellId);
 | 
				
			||||||
      if (currentRepId) formData.append('representative_id', currentRepId);
 | 
					      if (currentRepId) formData.append('representative_id', currentRepId);
 | 
				
			||||||
      // Send fields using CustomerForm names if visible
 | 
					      
 | 
				
			||||||
      const ncField = $('#id_national_code').length ? $('#id_national_code').val() : '';
 | 
					      // Handle special national_code logic (prefer visible field)
 | 
				
			||||||
      formData.append('national_code', (ncField || $('#rep_national_code').val().trim()));
 | 
					      const ncField = $('#id_national_code').val();
 | 
				
			||||||
      formData.append('first_name', $('#id_first_name').val() || '');
 | 
					      if (ncField) {
 | 
				
			||||||
      formData.append('last_name', $('#id_last_name').val() || '');
 | 
					        formData.set('national_code', ncField);
 | 
				
			||||||
      formData.append('phone_number_1', $('#id_phone_number_1').val() || '');
 | 
					      } else {
 | 
				
			||||||
      formData.append('phone_number_2', $('#id_phone_number_2').val() || '');
 | 
					        formData.set('national_code', $('#rep_national_code').val().trim());
 | 
				
			||||||
      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 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('در حال ذخیره...');
 | 
					      const $btn = $(this).prop('disabled', true).text('در حال ذخیره...');
 | 
				
			||||||
| 
						 | 
					@ -652,6 +711,10 @@
 | 
				
			||||||
            setTimeout(function(){ location.reload(); }, 1200);
 | 
					            setTimeout(function(){ location.reload(); }, 1200);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 | 
					          clearInlineErrors();
 | 
				
			||||||
 | 
					          if (resp.errors) {
 | 
				
			||||||
 | 
					            showInlineErrors(resp.errors);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          const msg = buildErrorMessage(resp);
 | 
					          const msg = buildErrorMessage(resp);
 | 
				
			||||||
          showToast(msg, 'danger');
 | 
					          showToast(msg, 'danger');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -659,6 +722,10 @@
 | 
				
			||||||
        let msg = 'خطا در ذخیره';
 | 
					        let msg = 'خطا در ذخیره';
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          const resp = JSON.parse(xhr.responseText);
 | 
					          const resp = JSON.parse(xhr.responseText);
 | 
				
			||||||
 | 
					          clearInlineErrors();
 | 
				
			||||||
 | 
					          if (resp && resp.errors) {
 | 
				
			||||||
 | 
					            showInlineErrors(resp.errors);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          msg = buildErrorMessage(resp) || msg;
 | 
					          msg = buildErrorMessage(resp) || msg;
 | 
				
			||||||
        } catch(e) {}
 | 
					        } catch(e) {}
 | 
				
			||||||
        showToast(msg, 'danger');
 | 
					        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(){
 | 
					    $('#requestModal').on('hidden.bs.modal', function(){
 | 
				
			||||||
      $('#requestForm')[0].reset();
 | 
					      $('#requestForm')[0].reset();
 | 
				
			||||||
      $('#wellFormBlock').hide();
 | 
					      $('#wellFormBlock').hide();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,10 +16,4 @@ urlpatterns = [
 | 
				
			||||||
    path('instance/<int:instance_id>/step/<int:step_id>/', views.step_detail, name='step_detail'),
 | 
					    path('instance/<int:instance_id>/step/<int:step_id>/', views.step_detail, name='step_detail'),
 | 
				
			||||||
    path('instance/<int:instance_id>/summary/', views.instance_summary, name='instance_summary'),
 | 
					    path('instance/<int:instance_id>/summary/', views.instance_summary, name='instance_summary'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Legacy process views
 | 
					 | 
				
			||||||
    path('', views.process_list, name='process_list'),
 | 
					 | 
				
			||||||
    path('<int:process_id>/', views.process_detail, name='process_detail'),
 | 
					 | 
				
			||||||
    path('<int:process_id>/start/', views.start_process, name='start_process'),
 | 
					 | 
				
			||||||
    path('instance/<int:instance_id>/', views.instance_detail, name='instance_detail'),
 | 
					 | 
				
			||||||
    path('my-processes/', views.my_processes, name='my_processes'),
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
from django.shortcuts import render, get_object_or_404, redirect
 | 
					from django.shortcuts import render, get_object_or_404, redirect
 | 
				
			||||||
from django.urls import reverse
 | 
					from django.urls import reverse
 | 
				
			||||||
from django.utils import timezone
 | 
					
 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
from django.contrib.auth.decorators import login_required
 | 
					from django.contrib.auth.decorators import login_required
 | 
				
			||||||
from django.contrib import messages
 | 
					from django.contrib import messages
 | 
				
			||||||
from django.http import JsonResponse
 | 
					from django.http import JsonResponse
 | 
				
			||||||
| 
						 | 
					@ -11,41 +10,32 @@ from django.contrib.auth import get_user_model
 | 
				
			||||||
from .models import Process, ProcessInstance, StepInstance
 | 
					from .models import Process, ProcessInstance, StepInstance
 | 
				
			||||||
from wells.models import Well
 | 
					from wells.models import Well
 | 
				
			||||||
from accounts.models import Profile
 | 
					from accounts.models import Profile
 | 
				
			||||||
from .forms import ProcessInstanceForm
 | 
					 | 
				
			||||||
from accounts.forms import CustomerForm
 | 
					from accounts.forms import CustomerForm
 | 
				
			||||||
from wells.forms import WellForm
 | 
					from wells.forms import WellForm
 | 
				
			||||||
from wells.models import WaterMeterManufacturer
 | 
					from wells.models import WaterMeterManufacturer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					 | 
				
			||||||
def process_list(request):
 | 
					 | 
				
			||||||
    """نمایش لیست فرآیندهای فعال"""
 | 
					 | 
				
			||||||
    processes = Process.objects.filter(is_active=True)
 | 
					 | 
				
			||||||
    return render(request, 'processes/process_list.html', {
 | 
					 | 
				
			||||||
        'processes': processes
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@login_required
 | 
					 | 
				
			||||||
def process_detail(request, process_id):
 | 
					 | 
				
			||||||
    """نمایش جزئیات فرآیند"""
 | 
					 | 
				
			||||||
    process = get_object_or_404(Process, id=process_id, is_active=True)
 | 
					 | 
				
			||||||
    return render(request, 'processes/process_detail.html', {
 | 
					 | 
				
			||||||
        'process': process
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def request_list(request):
 | 
					def request_list(request):
 | 
				
			||||||
    """نمایش لیست درخواستها با جدول و مدال ایجاد"""
 | 
					    """نمایش لیست درخواستها با جدول و مدال ایجاد"""
 | 
				
			||||||
    instances = ProcessInstance.objects.select_related('well', 'representative', 'requester').filter(is_deleted=False).order_by('-created')
 | 
					    instances = ProcessInstance.objects.select_related('well', 'representative', 'requester').filter(is_deleted=False).order_by('-created')
 | 
				
			||||||
    processes = Process.objects.filter(is_active=True)
 | 
					    processes = Process.objects.filter(is_active=True)
 | 
				
			||||||
    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
					    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
				
			||||||
 | 
					    # Summary stats for header cards
 | 
				
			||||||
 | 
					    total_count = instances.count()
 | 
				
			||||||
 | 
					    completed_count = instances.filter(status='completed').count()
 | 
				
			||||||
 | 
					    in_progress_count = instances.filter(status='in_progress').count()
 | 
				
			||||||
 | 
					    pending_count = instances.filter(status='pending').count()
 | 
				
			||||||
    return render(request, 'processes/request_list.html', {
 | 
					    return render(request, 'processes/request_list.html', {
 | 
				
			||||||
        'instances': instances,
 | 
					        'instances': instances,
 | 
				
			||||||
        'customer_form': CustomerForm(),
 | 
					        'customer_form': CustomerForm(),
 | 
				
			||||||
        'well_form': WellForm(),
 | 
					        'well_form': WellForm(),
 | 
				
			||||||
        'processes': processes,
 | 
					        'processes': processes,
 | 
				
			||||||
        'manufacturers': manufacturers
 | 
					        'manufacturers': manufacturers,
 | 
				
			||||||
 | 
					        'total_count': total_count,
 | 
				
			||||||
 | 
					        'completed_count': completed_count,
 | 
				
			||||||
 | 
					        'in_progress_count': in_progress_count,
 | 
				
			||||||
 | 
					        'pending_count': pending_count,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -127,23 +117,12 @@ def create_request_with_entities(request):
 | 
				
			||||||
    well_id = request.POST.get('well_id')  # optional if existing
 | 
					    well_id = request.POST.get('well_id')  # optional if existing
 | 
				
			||||||
    # Representative fields
 | 
					    # Representative fields
 | 
				
			||||||
    representative_id = request.POST.get('representative_id')
 | 
					    representative_id = request.POST.get('representative_id')
 | 
				
			||||||
    # Prefer plain CustomerForm keys; fallback to representative_* keys
 | 
					 | 
				
			||||||
    representative_national_code = request.POST.get('national_code') or request.POST.get('representative_national_code')
 | 
					 | 
				
			||||||
    representative_first_name = request.POST.get('first_name') or request.POST.get('representative_first_name')
 | 
					 | 
				
			||||||
    representative_last_name = request.POST.get('last_name') or request.POST.get('representative_last_name')
 | 
					 | 
				
			||||||
    representative_username = request.POST.get('username') or request.POST.get('representative_username')
 | 
					 | 
				
			||||||
    representative_phone_number_1 = request.POST.get('phone_number_1') or request.POST.get('representative_phone_number_1')
 | 
					 | 
				
			||||||
    representative_phone_number_2 = request.POST.get('phone_number_2') or request.POST.get('representative_phone_number_2')
 | 
					 | 
				
			||||||
    representative_card_number = request.POST.get('card_number') or request.POST.get('representative_card_number')
 | 
					 | 
				
			||||||
    representative_account_number = request.POST.get('account_number') or request.POST.get('representative_account_number')
 | 
					 | 
				
			||||||
    representative_bank_name = request.POST.get('bank_name') or request.POST.get('representative_bank_name')
 | 
					 | 
				
			||||||
    representative_address = request.POST.get('address') or request.POST.get('representative_address')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not process_id:
 | 
					    if not process_id:
 | 
				
			||||||
        return JsonResponse({'ok': False, 'errors': {'request': {'process': ['فرآیند الزامی است']}}}, status=400)
 | 
					        return JsonResponse({'ok': False, 'errors': {'request': {'process': ['فرآیند الزامی است']}}}, status=400)
 | 
				
			||||||
    if not water_subscription_number:
 | 
					    if not water_subscription_number:
 | 
				
			||||||
        return JsonResponse({'ok': False, 'errors': {'well': {'water_subscription_number': ['شماره اشتراک آب الزامی است']}}}, status=400)
 | 
					        return JsonResponse({'ok': False, 'errors': {'well': {'water_subscription_number': ['شماره اشتراک آب الزامی است']}}}, status=400)
 | 
				
			||||||
    if not representative_id and not representative_national_code:
 | 
					    if not representative_id and not request.POST.get('national_code'):
 | 
				
			||||||
        return JsonResponse({'ok': False, 'errors': {'customer': {'national_code': ['کد ملی نماینده را وارد کنید یا دکمه بررسی/افزودن نماینده را بزنید']}}}, status=400)
 | 
					        return JsonResponse({'ok': False, 'errors': {'customer': {'national_code': ['کد ملی نماینده را وارد کنید یا دکمه بررسی/افزودن نماینده را بزنید']}}}, status=400)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    representative_user = None
 | 
					    representative_user = None
 | 
				
			||||||
| 
						 | 
					@ -152,52 +131,20 @@ def create_request_with_entities(request):
 | 
				
			||||||
        representative_profile = Profile.objects.select_related('user').filter(user_id=representative_id).first()
 | 
					        representative_profile = Profile.objects.select_related('user').filter(user_id=representative_id).first()
 | 
				
			||||||
        if not representative_profile:
 | 
					        if not representative_profile:
 | 
				
			||||||
            return JsonResponse({'ok': False, 'errors': {'customer': {'__all__': ['نماینده انتخابشده یافت نشد']}}}, status=400)
 | 
					            return JsonResponse({'ok': False, 'errors': {'customer': {'__all__': ['نماینده انتخابشده یافت نشد']}}}, status=400)
 | 
				
			||||||
 | 
					        # Use CustomerForm with request.POST data, merging with existing values
 | 
				
			||||||
 | 
					        customer_form = CustomerForm(request.POST, instance=representative_profile)
 | 
				
			||||||
 | 
					        customer_form.request = request
 | 
				
			||||||
 | 
					        if not customer_form.is_valid():
 | 
				
			||||||
 | 
					            return JsonResponse({'ok': False, 'errors': {'customer': customer_form.errors}}, status=400)
 | 
				
			||||||
 | 
					        representative_profile = customer_form.save()
 | 
				
			||||||
        representative_user = representative_profile.user
 | 
					        representative_user = representative_profile.user
 | 
				
			||||||
        # Optionally update if fields provided
 | 
					 | 
				
			||||||
        changed = False
 | 
					 | 
				
			||||||
        if representative_first_name:
 | 
					 | 
				
			||||||
            representative_user.first_name = representative_first_name
 | 
					 | 
				
			||||||
            changed = True
 | 
					 | 
				
			||||||
        if representative_last_name:
 | 
					 | 
				
			||||||
            representative_user.last_name = representative_last_name
 | 
					 | 
				
			||||||
            changed = True
 | 
					 | 
				
			||||||
        if representative_username:
 | 
					 | 
				
			||||||
            representative_user.username = representative_username
 | 
					 | 
				
			||||||
            changed = True
 | 
					 | 
				
			||||||
        if changed:
 | 
					 | 
				
			||||||
            representative_user.save()
 | 
					 | 
				
			||||||
        if representative_national_code:
 | 
					 | 
				
			||||||
            representative_profile.national_code = representative_national_code
 | 
					 | 
				
			||||||
        if representative_phone_number_1 is not None:
 | 
					 | 
				
			||||||
            representative_profile.phone_number_1 = representative_phone_number_1
 | 
					 | 
				
			||||||
        if representative_phone_number_2 is not None:
 | 
					 | 
				
			||||||
            representative_profile.phone_number_2 = representative_phone_number_2
 | 
					 | 
				
			||||||
        if representative_card_number is not None:
 | 
					 | 
				
			||||||
            representative_profile.card_number = representative_card_number
 | 
					 | 
				
			||||||
        if representative_account_number is not None:
 | 
					 | 
				
			||||||
            representative_profile.account_number = representative_account_number
 | 
					 | 
				
			||||||
        if representative_bank_name is not None:
 | 
					 | 
				
			||||||
            representative_profile.bank_name = representative_bank_name
 | 
					 | 
				
			||||||
        if representative_address is not None:
 | 
					 | 
				
			||||||
            representative_profile.address = representative_address
 | 
					 | 
				
			||||||
        representative_profile.save()
 | 
					 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        # Use CustomerForm to validate/create/update representative profile by national code
 | 
					        # Use CustomerForm to validate/create/update representative profile by national code
 | 
				
			||||||
        profile_instance = None
 | 
					        profile_instance = None
 | 
				
			||||||
        if representative_national_code:
 | 
					        national_code = request.POST.get('national_code')
 | 
				
			||||||
            profile_instance = Profile.objects.filter(national_code=representative_national_code).first()
 | 
					        if national_code:
 | 
				
			||||||
        customer_data = {
 | 
					            profile_instance = Profile.objects.filter(national_code=national_code).first()
 | 
				
			||||||
            'first_name': representative_first_name or '',
 | 
					        customer_form = CustomerForm(request.POST, instance=profile_instance)
 | 
				
			||||||
            'last_name': representative_last_name or '',
 | 
					 | 
				
			||||||
            'phone_number_1': representative_phone_number_1 or '',
 | 
					 | 
				
			||||||
            'phone_number_2': representative_phone_number_2 or '',
 | 
					 | 
				
			||||||
            'national_code': representative_national_code or '',
 | 
					 | 
				
			||||||
            'address': representative_address or '',
 | 
					 | 
				
			||||||
            'card_number': representative_card_number or '',
 | 
					 | 
				
			||||||
            'account_number': representative_account_number or '',
 | 
					 | 
				
			||||||
            'bank_name': representative_bank_name or '',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        customer_form = CustomerForm(customer_data, instance=profile_instance)
 | 
					 | 
				
			||||||
        customer_form.request = request
 | 
					        customer_form.request = request
 | 
				
			||||||
        if not customer_form.is_valid():
 | 
					        if not customer_form.is_valid():
 | 
				
			||||||
            return JsonResponse({'ok': False, 'errors': {'customer': customer_form.errors}}, status=400)
 | 
					            return JsonResponse({'ok': False, 'errors': {'customer': customer_form.errors}}, status=400)
 | 
				
			||||||
| 
						 | 
					@ -292,62 +239,24 @@ def create_request_with_entities(request):
 | 
				
			||||||
    redirect_url = reverse('processes:instance_steps', args=[instance.id])
 | 
					    redirect_url = reverse('processes:instance_steps', args=[instance.id])
 | 
				
			||||||
    return JsonResponse({'ok': True, 'instance_id': instance.id, 'redirect': redirect_url})
 | 
					    return JsonResponse({'ok': True, 'instance_id': instance.id, 'redirect': redirect_url})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@require_POST
 | 
					@require_POST
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_request(request, instance_id):
 | 
					def delete_request(request, instance_id):
 | 
				
			||||||
    """حذف درخواست"""
 | 
					    """حذف درخواست"""
 | 
				
			||||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
					    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
				
			||||||
    code = instance.code
 | 
					    code = instance.code
 | 
				
			||||||
 | 
					    if instance.status == 'completed':
 | 
				
			||||||
 | 
					        return JsonResponse({
 | 
				
			||||||
 | 
					            'success': False,
 | 
				
			||||||
 | 
					            'message': 'درخواست تکمیل شده نمیتواند حذف شود'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    instance.delete()
 | 
					    instance.delete()
 | 
				
			||||||
    return JsonResponse({
 | 
					    return JsonResponse({
 | 
				
			||||||
        'success': True,
 | 
					        'success': True,
 | 
				
			||||||
        'message': f'درخواست {code} با موفقیت حذف شد'
 | 
					        'message': f'درخواست {code} با موفقیت حذف شد'
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					 | 
				
			||||||
def start_process(request, process_id):
 | 
					 | 
				
			||||||
    """شروع فرآیند جدید"""
 | 
					 | 
				
			||||||
    process = get_object_or_404(Process, id=process_id, is_active=True)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if request.method == 'POST':
 | 
					 | 
				
			||||||
        form = ProcessInstanceForm(request.POST)
 | 
					 | 
				
			||||||
        if form.is_valid():
 | 
					 | 
				
			||||||
            instance = form.save(commit=False)
 | 
					 | 
				
			||||||
            instance.process = process
 | 
					 | 
				
			||||||
            instance.requester = request.user
 | 
					 | 
				
			||||||
            instance.save()
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            # ایجاد نمونههای مرحله
 | 
					 | 
				
			||||||
            for step in process.steps.all():
 | 
					 | 
				
			||||||
                StepInstance.objects.create(
 | 
					 | 
				
			||||||
                    process_instance=instance,
 | 
					 | 
				
			||||||
                    step=step
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            # تنظیم مرحله اول به عنوان مرحله فعلی
 | 
					 | 
				
			||||||
            first_step = process.steps.first()
 | 
					 | 
				
			||||||
            if first_step:
 | 
					 | 
				
			||||||
                instance.current_step = first_step
 | 
					 | 
				
			||||||
                instance.status = 'in_progress'
 | 
					 | 
				
			||||||
                instance.save()
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            messages.success(request, f'فرآیند {process.name} با موفقیت شروع شد.')
 | 
					 | 
				
			||||||
            return redirect('processes:instance_detail', instance_id=instance.id)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        form = ProcessInstanceForm()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return render(request, 'processes/start_process.html', {
 | 
					 | 
				
			||||||
        'process': process,
 | 
					 | 
				
			||||||
        'form': form
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@login_required
 | 
					 | 
				
			||||||
def instance_detail(request, instance_id):
 | 
					 | 
				
			||||||
    """نمایش جزئیات نمونه فرآیند"""
 | 
					 | 
				
			||||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
					 | 
				
			||||||
    return render(request, 'processes/instance_detail.html', {
 | 
					 | 
				
			||||||
        'instance': instance
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def step_detail(request, instance_id, step_id):
 | 
					def step_detail(request, instance_id, step_id):
 | 
				
			||||||
| 
						 | 
					@ -409,6 +318,7 @@ def step_detail(request, instance_id, step_id):
 | 
				
			||||||
        'next_step': next_step,
 | 
					        'next_step': next_step,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required 
 | 
					@login_required 
 | 
				
			||||||
def instance_steps(request, instance_id):
 | 
					def instance_steps(request, instance_id):
 | 
				
			||||||
    """هدایت به مرحله فعلی instance"""
 | 
					    """هدایت به مرحله فعلی instance"""
 | 
				
			||||||
| 
						 | 
					@ -430,6 +340,7 @@ def instance_steps(request, instance_id):
 | 
				
			||||||
        return redirect('processes:instance_summary', instance_id=instance.id)
 | 
					        return redirect('processes:instance_summary', instance_id=instance.id)
 | 
				
			||||||
    return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id)
 | 
					    return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def instance_summary(request, instance_id):
 | 
					def instance_summary(request, instance_id):
 | 
				
			||||||
    """نمای خلاصهٔ فقطخواندنی برای درخواستهای تکمیلشده."""
 | 
					    """نمای خلاصهٔ فقطخواندنی برای درخواستهای تکمیلشده."""
 | 
				
			||||||
| 
						 | 
					@ -462,14 +373,3 @@ def instance_summary(request, instance_id):
 | 
				
			||||||
        'certificate': certificate,
 | 
					        'certificate': certificate,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@login_required
 | 
					 | 
				
			||||||
def my_processes(request):
 | 
					 | 
				
			||||||
    """نمایش فرآیندهای کاربر"""
 | 
					 | 
				
			||||||
    my_instances = ProcessInstance.objects.filter(requester=request.user)
 | 
					 | 
				
			||||||
    assigned_steps = StepInstance.objects.filter(assigned_to=request.user, status='in_progress')
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    return render(request, 'processes/my_processes.html', {
 | 
					 | 
				
			||||||
        'my_instances': my_instances,
 | 
					 | 
				
			||||||
        'assigned_steps': assigned_steps
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 django.db.models.deletion
 | 
				
			||||||
import simple_history.models
 | 
					import simple_history.models
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue