first commit
This commit is contained in:
		
						commit
						b71ea45681
					
				
					 898 changed files with 138202 additions and 0 deletions
				
			
		
							
								
								
									
										293
									
								
								processes/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								processes/models.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,293 @@
 | 
			
		|||
from django.db import models
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from common.models import NameSlugModel
 | 
			
		||||
from simple_history.models import HistoricalRecords
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
 | 
			
		||||
User = get_user_model()
 | 
			
		||||
 | 
			
		||||
class Process(NameSlugModel):
 | 
			
		||||
    """مدل فرآیند اصلی"""
 | 
			
		||||
    description = models.TextField(verbose_name="توضیحات", blank=True)
 | 
			
		||||
    is_active = models.BooleanField(default=True, verbose_name="فعال")
 | 
			
		||||
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="ایجاد کننده")
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "فرآیند"
 | 
			
		||||
        verbose_name_plural = "فرآیندها"
 | 
			
		||||
        ordering = ['-created']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
class ProcessStep(NameSlugModel):
 | 
			
		||||
    """مدل مراحل فرآیند"""
 | 
			
		||||
    process = models.ForeignKey(Process, on_delete=models.CASCADE, related_name='steps', verbose_name="فرآیند")
 | 
			
		||||
    order = models.PositiveIntegerField(verbose_name="ترتیب")
 | 
			
		||||
    description = models.TextField(verbose_name="توضیحات", blank=True)
 | 
			
		||||
    is_required = models.BooleanField(default=True, verbose_name="اجباری")
 | 
			
		||||
    estimated_duration = models.PositiveIntegerField(verbose_name="مدت زمان تخمینی (روز)", null=True, blank=True)
 | 
			
		||||
    
 | 
			
		||||
    # فیلدهای جدید برای کنترل وابستگیها
 | 
			
		||||
    blocks_previous = models.BooleanField(
 | 
			
		||||
        default=False, 
 | 
			
		||||
        verbose_name="مسدود کننده مراحل قبلی",
 | 
			
		||||
        help_text="اگر فعال باشد، پس از تکمیل این مرحله، مراحل قبلی غیرقابل ویرایش میشوند"
 | 
			
		||||
    )
 | 
			
		||||
    can_go_back = models.BooleanField(
 | 
			
		||||
        default=True, 
 | 
			
		||||
        verbose_name="قابل بازگشت",
 | 
			
		||||
        help_text="آیا میتوان به مراحل قبلی بازگشت"
 | 
			
		||||
    )
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "مرحله فرآیند"
 | 
			
		||||
        verbose_name_plural = "مراحل فرآیند"
 | 
			
		||||
        ordering = ['process', 'order']
 | 
			
		||||
        unique_together = ['process', 'order']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.process.name} - {self.name}"
 | 
			
		||||
 | 
			
		||||
    def get_dependencies(self):
 | 
			
		||||
        """دریافت مراحل وابسته"""
 | 
			
		||||
        return StepDependency.objects.filter(dependent_step=self).values_list('dependency_step', flat=True)
 | 
			
		||||
 | 
			
		||||
    def get_dependents(self):
 | 
			
		||||
        """دریافت مراحلی که به این مرحله وابسته هستند"""
 | 
			
		||||
        return StepDependency.objects.filter(dependency_step=self).values_list('dependent_step', flat=True)
 | 
			
		||||
 | 
			
		||||
class StepDependency(models.Model):
 | 
			
		||||
    """مدل وابستگی بین مراحل"""
 | 
			
		||||
    dependent_step = models.ForeignKey(
 | 
			
		||||
        ProcessStep, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='dependencies',
 | 
			
		||||
        verbose_name="مرحله وابسته"
 | 
			
		||||
    )
 | 
			
		||||
    dependency_step = models.ForeignKey(
 | 
			
		||||
        ProcessStep, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='dependents',
 | 
			
		||||
        verbose_name="مرحله مورد نیاز"
 | 
			
		||||
    )
 | 
			
		||||
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ ایجاد")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "وابستگی مرحله"
 | 
			
		||||
        verbose_name_plural = "وابستگیهای مراحل"
 | 
			
		||||
        unique_together = ['dependent_step', 'dependency_step']
 | 
			
		||||
        ordering = ['dependent_step__order', 'dependency_step__order']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.dependent_step} ← {self.dependency_step}"
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        """اعتبارسنجی مدل"""
 | 
			
		||||
        if self.dependent_step == self.dependency_step:
 | 
			
		||||
            raise ValidationError("مرحله نمیتواند به خودش وابسته باشد")
 | 
			
		||||
        
 | 
			
		||||
        if self.dependent_step.process != self.dependency_step.process:
 | 
			
		||||
            raise ValidationError("مراحل باید از یک فرآیند باشند")
 | 
			
		||||
        
 | 
			
		||||
        if self.dependent_step.order <= self.dependency_step.order:
 | 
			
		||||
            raise ValidationError("مرحله وابسته باید بعد از مرحله مورد نیاز باشد")
 | 
			
		||||
 | 
			
		||||
class ProcessInstance(NameSlugModel):
 | 
			
		||||
    """مدل نمونه فرآیند (برای هر درخواست)"""
 | 
			
		||||
    process = models.ForeignKey(Process, on_delete=models.CASCADE, related_name='instances', verbose_name="فرآیند")
 | 
			
		||||
    requester = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="درخواست کننده")
 | 
			
		||||
    current_step = models.ForeignKey('ProcessStep', on_delete=models.SET_NULL, null=True, blank=True, verbose_name="مرحله فعلی")
 | 
			
		||||
    status = models.CharField(
 | 
			
		||||
        max_length=20,
 | 
			
		||||
        choices=[
 | 
			
		||||
            ('pending', 'در انتظار'),
 | 
			
		||||
            ('in_progress', 'در حال انجام'),
 | 
			
		||||
            ('completed', 'تکمیل شده'),
 | 
			
		||||
            ('cancelled', 'لغو شده'),
 | 
			
		||||
            ('rejected', 'رد شده'),
 | 
			
		||||
        ],
 | 
			
		||||
        default='pending',
 | 
			
		||||
        verbose_name="وضعیت"
 | 
			
		||||
    )
 | 
			
		||||
    started_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ شروع")
 | 
			
		||||
    completed_at = models.DateTimeField(null=True, blank=True, verbose_name="تاریخ تکمیل")
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "نمونه فرآیند"
 | 
			
		||||
        verbose_name_plural = "نمونههای فرآیند"
 | 
			
		||||
        ordering = ['-started_at']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.process.name} - {self.requester.get_full_name()}"
 | 
			
		||||
 | 
			
		||||
    def get_available_steps(self):
 | 
			
		||||
        """دریافت مراحل قابل دسترس"""
 | 
			
		||||
        available_steps = []
 | 
			
		||||
        for step in self.process.steps.all():
 | 
			
		||||
            if self.can_access_step(step):
 | 
			
		||||
                available_steps.append(step)
 | 
			
		||||
        return available_steps
 | 
			
		||||
 | 
			
		||||
    def can_access_step(self, step):
 | 
			
		||||
        """بررسی امکان دسترسی به مرحله"""
 | 
			
		||||
        # بررسی وابستگیها
 | 
			
		||||
        dependencies = step.get_dependencies()
 | 
			
		||||
        for dependency_id in dependencies:
 | 
			
		||||
            step_instance = self.step_instances.filter(step_id=dependency_id).first()
 | 
			
		||||
            if not step_instance or step_instance.status != 'completed':
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def can_edit_step(self, step):
 | 
			
		||||
        """بررسی امکان ویرایش مرحله"""
 | 
			
		||||
        # اگر مرحله مسدود کننده باشد و مراحل بعدی تکمیل شده باشند
 | 
			
		||||
        if step.blocks_previous:
 | 
			
		||||
            later_steps = self.step_instances.filter(
 | 
			
		||||
                step__order__gt=step.order,
 | 
			
		||||
                status='completed'
 | 
			
		||||
            )
 | 
			
		||||
            if later_steps.exists():
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
class StepInstance(models.Model):
 | 
			
		||||
    """مدل نمونه مرحله (برای هر مرحله در هر درخواست)"""
 | 
			
		||||
    process_instance = models.ForeignKey(ProcessInstance, on_delete=models.CASCADE, related_name='step_instances', verbose_name="نمونه فرآیند")
 | 
			
		||||
    step = models.ForeignKey(ProcessStep, on_delete=models.CASCADE, verbose_name="مرحله")
 | 
			
		||||
    assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="واگذار شده به")
 | 
			
		||||
    status = models.CharField(
 | 
			
		||||
        max_length=20,
 | 
			
		||||
        choices=[
 | 
			
		||||
            ('pending', 'در انتظار'),
 | 
			
		||||
            ('in_progress', 'در حال انجام'),
 | 
			
		||||
            ('completed', 'تکمیل شده'),
 | 
			
		||||
            ('skipped', 'رد شده'),
 | 
			
		||||
            ('blocked', 'مسدود شده'),
 | 
			
		||||
            ('rejected', 'رد شده و نیاز به اصلاح'),
 | 
			
		||||
        ],
 | 
			
		||||
        default='pending',
 | 
			
		||||
        verbose_name="وضعیت"
 | 
			
		||||
    )
 | 
			
		||||
    notes = models.TextField(verbose_name="یادداشتها", blank=True)
 | 
			
		||||
    started_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ شروع")
 | 
			
		||||
    completed_at = models.DateTimeField(null=True, blank=True, verbose_name="تاریخ تکمیل")
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "نمونه مرحله"
 | 
			
		||||
        verbose_name_plural = "نمونههای مرحله"
 | 
			
		||||
        ordering = ['process_instance', 'step__order']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.process_instance} - {self.step.name}"
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """ذخیره با اعتبارسنجی"""
 | 
			
		||||
        # بررسی وابستگیها
 | 
			
		||||
        if self.status == 'in_progress' or self.status == 'completed':
 | 
			
		||||
            if not self.process_instance.can_access_step(self.step):
 | 
			
		||||
                raise ValidationError("مراحل وابسته تکمیل نشدهاند")
 | 
			
		||||
        
 | 
			
		||||
        # بررسی امکان ویرایش
 | 
			
		||||
        if self.status == 'completed' and not self.process_instance.can_edit_step(self.step):
 | 
			
		||||
            raise ValidationError("این مرحله قابل ویرایش نیست")
 | 
			
		||||
        
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_status_display_with_color(self):
 | 
			
		||||
        """نمایش وضعیت با رنگ"""
 | 
			
		||||
        status_colors = {
 | 
			
		||||
            'pending': 'secondary',
 | 
			
		||||
            'in_progress': 'primary',
 | 
			
		||||
            'completed': 'success',
 | 
			
		||||
            'skipped': 'warning',
 | 
			
		||||
            'blocked': 'danger',
 | 
			
		||||
            'rejected': 'danger',
 | 
			
		||||
        }
 | 
			
		||||
        color = status_colors.get(self.status, 'secondary')
 | 
			
		||||
        return '<span class="badge bg-{}">{}</span>'.format(color, self.get_status_display())
 | 
			
		||||
 | 
			
		||||
    def get_rejection_count(self):
 | 
			
		||||
        """دریافت تعداد رد شدنها"""
 | 
			
		||||
        return self.rejections.count()
 | 
			
		||||
 | 
			
		||||
    def get_latest_rejection(self):
 | 
			
		||||
        """دریافت آخرین رد شدن"""
 | 
			
		||||
        return self.rejections.order_by('-created_at').first()
 | 
			
		||||
 | 
			
		||||
class StepRejection(models.Model):
 | 
			
		||||
    """مدل رد شدن مرحله"""
 | 
			
		||||
    step_instance = models.ForeignKey(
 | 
			
		||||
        StepInstance, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='rejections',
 | 
			
		||||
        verbose_name="نمونه مرحله"
 | 
			
		||||
    )
 | 
			
		||||
    rejected_by = models.ForeignKey(
 | 
			
		||||
        User, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        verbose_name="رد کننده",
 | 
			
		||||
        related_name='step_rejections'
 | 
			
		||||
    )
 | 
			
		||||
    reason = models.TextField(verbose_name="دلیل رد شدن", help_text="توضیح کامل دلیل رد شدن")
 | 
			
		||||
    instructions = models.TextField(
 | 
			
		||||
        verbose_name="دستورالعملهای اصلاح", 
 | 
			
		||||
        help_text="دستورالعملهایی برای اصلاح مرحله",
 | 
			
		||||
        blank=True
 | 
			
		||||
    )
 | 
			
		||||
    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.rejected_by.get_full_name()}"
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """ذخیره با تغییر وضعیت مرحله"""
 | 
			
		||||
        # تغییر وضعیت مرحله به رد شده
 | 
			
		||||
        self.step_instance.status = 'rejected'
 | 
			
		||||
        self.step_instance.save()
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
class StepRevision(models.Model):
 | 
			
		||||
    """مدل بازبینی و اصلاح مرحله"""
 | 
			
		||||
    step_instance = models.ForeignKey(
 | 
			
		||||
        StepInstance, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='revisions',
 | 
			
		||||
        verbose_name="نمونه مرحله"
 | 
			
		||||
    )
 | 
			
		||||
    rejection = models.ForeignKey(
 | 
			
		||||
        StepRejection, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='revisions',
 | 
			
		||||
        verbose_name="رد شدن مربوطه"
 | 
			
		||||
    )
 | 
			
		||||
    revised_by = models.ForeignKey(
 | 
			
		||||
        User, 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        verbose_name="اصلاح کننده",
 | 
			
		||||
        related_name='step_revisions'
 | 
			
		||||
    )
 | 
			
		||||
    changes_description = models.TextField(
 | 
			
		||||
        verbose_name="توضیح تغییرات",
 | 
			
		||||
        help_text="توضیح تغییراتی که برای اصلاح انجام شده"
 | 
			
		||||
    )
 | 
			
		||||
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="تاریخ اصلاح")
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "بازبینی مرحله"
 | 
			
		||||
        verbose_name_plural = "بازبینیهای مراحل"
 | 
			
		||||
        ordering = ['-created_at']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"بازبینی {self.step_instance} توسط {self.revised_by.get_full_name()}"
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue