Add confirmation and summary
This commit is contained in:
		
							parent
							
								
									9b3973805e
								
							
						
					
					
						commit
						35799b7754
					
				
					 25 changed files with 1419 additions and 265 deletions
				
			
		| 
						 | 
				
			
			@ -4,6 +4,8 @@ from common.models import NameSlugModel, SluggedModel
 | 
			
		|||
from simple_history.models import HistoricalRecords
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from accounts.models import Role
 | 
			
		||||
from _helpers.utils import generate_unique_slug
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +48,9 @@ class ProcessStep(NameSlugModel):
 | 
			
		|||
    )
 | 
			
		||||
    history = HistoricalRecords()
 | 
			
		||||
 | 
			
		||||
    # Note: approver requirements are defined via StepApproverRequirement through model
 | 
			
		||||
    # See StepApproverRequirement below
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "مرحله فرآیند"
 | 
			
		||||
        verbose_name_plural = "مراحل فرآیند"
 | 
			
		||||
| 
						 | 
				
			
			@ -353,6 +358,26 @@ class StepInstance(models.Model):
 | 
			
		|||
        """دریافت آخرین رد شدن"""
 | 
			
		||||
        return self.rejections.order_by('-created_at').first()
 | 
			
		||||
 | 
			
		||||
    # -------- Multi-role approval helpers --------
 | 
			
		||||
    def required_roles(self):
 | 
			
		||||
        return [req.role for req in self.step.approver_requirements.select_related('role').all()]
 | 
			
		||||
 | 
			
		||||
    def approvals_by_role(self):
 | 
			
		||||
        decisions = {}
 | 
			
		||||
        for a in self.approvals.select_related('role').order_by('created_at'):
 | 
			
		||||
            decisions[a.role_id] = a.decision
 | 
			
		||||
        return decisions
 | 
			
		||||
 | 
			
		||||
    def is_fully_approved(self) -> bool:
 | 
			
		||||
        req_roles = self.required_roles()
 | 
			
		||||
        if not req_roles:
 | 
			
		||||
            return True
 | 
			
		||||
        role_to_decision = self.approvals_by_role()
 | 
			
		||||
        for r in req_roles:
 | 
			
		||||
            if role_to_decision.get(r.id) != 'approved':
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
class StepRejection(models.Model):
 | 
			
		||||
    """مدل رد شدن مرحله"""
 | 
			
		||||
    step_instance = models.ForeignKey(
 | 
			
		||||
| 
						 | 
				
			
			@ -424,3 +449,36 @@ class StepRevision(models.Model):
 | 
			
		|||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"بازبینی {self.step_instance} توسط {self.revised_by.get_full_name()}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StepApproverRequirement(models.Model):
 | 
			
		||||
    """Required approver roles for a step."""
 | 
			
		||||
    step = models.ForeignKey(ProcessStep, on_delete=models.CASCADE, related_name='approver_requirements', verbose_name="مرحله")
 | 
			
		||||
    role = models.ForeignKey(Role, on_delete=models.CASCADE, verbose_name="نقش تاییدکننده")
 | 
			
		||||
    required_count = models.PositiveIntegerField(default=1, verbose_name="تعداد موردنیاز")
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        unique_together = ('step', 'role')
 | 
			
		||||
        verbose_name = "نیازمندی تایید نقش"
 | 
			
		||||
        verbose_name_plural = "نیازمندیهای تایید نقش"
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.step} ← {self.role} (x{self.required_count})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StepApproval(models.Model):
 | 
			
		||||
    """Approvals per role for a concrete step instance."""
 | 
			
		||||
    step_instance = models.ForeignKey(StepInstance, on_delete=models.CASCADE, related_name='approvals', verbose_name="نمونه مرحله")
 | 
			
		||||
    role = models.ForeignKey(Role, on_delete=models.CASCADE, verbose_name="نقش")
 | 
			
		||||
    approved_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="تاییدکننده")
 | 
			
		||||
    decision = models.CharField(max_length=8, choices=[('approved', 'تایید'), ('rejected', 'رد')], verbose_name='نتیجه')
 | 
			
		||||
    reason = models.TextField(blank=True, verbose_name='علت (برای رد)')
 | 
			
		||||
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='تاریخ')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        unique_together = ('step_instance', 'role')
 | 
			
		||||
        verbose_name = 'تایید مرحله'
 | 
			
		||||
        verbose_name_plural = 'تاییدهای مرحله'
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.step_instance} - {self.role} - {self.decision}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue