Add qoute step.
This commit is contained in:
		
							parent
							
								
									b71ea45681
								
							
						
					
					
						commit
						6ff4740d04
					
				
					 30 changed files with 3362 additions and 376 deletions
				
			
		| 
						 | 
				
			
			@ -1,8 +1,11 @@
 | 
			
		|||
from django.db import models
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
from common.models import NameSlugModel
 | 
			
		||||
from common.models import NameSlugModel, SluggedModel
 | 
			
		||||
from simple_history.models import HistoricalRecords
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from _helpers.utils import generate_unique_slug
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
User = get_user_model()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,6 +24,7 @@ class Process(NameSlugModel):
 | 
			
		|||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProcessStep(NameSlugModel):
 | 
			
		||||
    """مدل مراحل فرآیند"""
 | 
			
		||||
    process = models.ForeignKey(Process, on_delete=models.CASCADE, related_name='steps', verbose_name="فرآیند")
 | 
			
		||||
| 
						 | 
				
			
			@ -95,35 +99,169 @@ class StepDependency(models.Model):
 | 
			
		|||
        if self.dependent_step.order <= self.dependency_step.order:
 | 
			
		||||
            raise ValidationError("مرحله وابسته باید بعد از مرحله مورد نیاز باشد")
 | 
			
		||||
 | 
			
		||||
class ProcessInstance(NameSlugModel):
 | 
			
		||||
 | 
			
		||||
class ProcessInstance(SluggedModel):
 | 
			
		||||
    code = models.CharField(
 | 
			
		||||
        max_length=5,
 | 
			
		||||
        unique=True,
 | 
			
		||||
        verbose_name="کد درخواست",
 | 
			
		||||
        help_text="کد ۵ رقمی یکتا برای هر درخواست"
 | 
			
		||||
    )
 | 
			
		||||
    """مدل نمونه فرآیند (برای هر درخواست)"""
 | 
			
		||||
    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="مرحله فعلی")
 | 
			
		||||
 | 
			
		||||
    PRIORITY_CHOICES = [
 | 
			
		||||
 | 
			
		||||
        ('low', 'کم'),
 | 
			
		||||
        ('medium', 'متوسط'),
 | 
			
		||||
        ('high', 'زیاد'),
 | 
			
		||||
        ('urgent', 'فوری'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    STATUS_CHOICES = [
 | 
			
		||||
        ('pending', 'در انتظار'),
 | 
			
		||||
        ('in_progress', 'در حال انجام'),
 | 
			
		||||
        ('completed', 'تکمیل شده'),
 | 
			
		||||
        ('cancelled', 'لغو شده'),
 | 
			
		||||
        ('rejected', 'رد شده'),
 | 
			
		||||
    ]
 | 
			
		||||
    
 | 
			
		||||
    description = models.TextField(
 | 
			
		||||
        verbose_name="توضیحات درخواست", 
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    process = models.ForeignKey(
 | 
			
		||||
        Process,
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
        related_name='instances',
 | 
			
		||||
        verbose_name="فرآیند",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    well = models.ForeignKey(
 | 
			
		||||
        'wells.Well', 
 | 
			
		||||
        on_delete=models.CASCADE, 
 | 
			
		||||
        related_name='process_instances',
 | 
			
		||||
        verbose_name="چاه",
 | 
			
		||||
    )
 | 
			
		||||
    representative = models.ForeignKey(
 | 
			
		||||
        User, 
 | 
			
		||||
        on_delete=models.SET_NULL, 
 | 
			
		||||
        related_name='representative_instances',
 | 
			
		||||
        verbose_name="نماینده چاه",
 | 
			
		||||
        null=True,
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    requester = models.ForeignKey(
 | 
			
		||||
        User,
 | 
			
		||||
        on_delete=models.SET_NULL,
 | 
			
		||||
        verbose_name="درخواست کننده",
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True
 | 
			
		||||
    )
 | 
			
		||||
    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', 'رد شده'),
 | 
			
		||||
        ],
 | 
			
		||||
        choices=STATUS_CHOICES,
 | 
			
		||||
        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()
 | 
			
		||||
    
 | 
			
		||||
    priority = models.CharField(
 | 
			
		||||
        max_length=20,
 | 
			
		||||
        choices=PRIORITY_CHOICES,
 | 
			
		||||
        default='medium',
 | 
			
		||||
        verbose_name="اولویت"
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    completed_at = models.DateTimeField(
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        verbose_name="تاریخ تکمیل"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = "نمونه فرآیند"
 | 
			
		||||
        verbose_name_plural = "نمونههای فرآیند"
 | 
			
		||||
        ordering = ['-started_at']
 | 
			
		||||
        verbose_name = "درخواست"
 | 
			
		||||
        verbose_name_plural = "درخواستها"
 | 
			
		||||
        ordering = ['-created']
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        if self.well:
 | 
			
		||||
            return f"{self.process.name} - {self.well.water_subscription_number}"
 | 
			
		||||
        return f"{self.process.name} - {self.requester.get_full_name()}"
 | 
			
		||||
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        """اعتبارسنجی مدل"""
 | 
			
		||||
        if self.well and self.representative and self.well.representative != self.representative:
 | 
			
		||||
            raise ValidationError("نماینده درخواست باید همان نماینده ثبت شده در چاه باشد")
 | 
			
		||||
        
 | 
			
		||||
        if self.well and self.representative and self.requester == self.representative:
 | 
			
		||||
            raise ValidationError("درخواست کننده نمیتواند نماینده چاه باشد")
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        # Generate unique 5-digit numeric code if missing
 | 
			
		||||
        if not self.code:
 | 
			
		||||
            # Try a few times to avoid rare collisions
 | 
			
		||||
            for _ in range(10):
 | 
			
		||||
                candidate = f"{random.randint(10000, 99999)}"
 | 
			
		||||
                if not ProcessInstance.objects.filter(code=candidate).exists():
 | 
			
		||||
                    self.code = candidate
 | 
			
		||||
                    break
 | 
			
		||||
            # As a fallback if collision persists (very unlikely)
 | 
			
		||||
            if not self.code:
 | 
			
		||||
                self.code = f"{random.randint(10000, 99999)}"
 | 
			
		||||
 | 
			
		||||
        if not self.slug:
 | 
			
		||||
            slug_text = f"{self.process.name}-{self.well.water_subscription_number if self.well else 'unknown'}-{timezone.now().strftime('%Y%m%d')}"
 | 
			
		||||
            self.slug = generate_unique_slug(slug_text)
 | 
			
		||||
        
 | 
			
		||||
        if self.status == 'completed' and not self.completed_at:
 | 
			
		||||
            self.completed_at = timezone.now()
 | 
			
		||||
        
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_status_display_with_color(self):
 | 
			
		||||
        """نمایش وضعیت با رنگ"""
 | 
			
		||||
        status_colors = {
 | 
			
		||||
            'pending': 'info',
 | 
			
		||||
            'in_progress': 'primary',
 | 
			
		||||
            'completed': 'success',
 | 
			
		||||
            'rejected': 'danger',
 | 
			
		||||
            'cancelled': 'warning',
 | 
			
		||||
        }
 | 
			
		||||
        color = status_colors.get(self.status, 'secondary')
 | 
			
		||||
        return '<span class="badge bg-{}">{}</span>'.format(color, self.get_status_display())
 | 
			
		||||
 | 
			
		||||
    def get_priority_display_with_color(self):
 | 
			
		||||
        """نمایش اولویت با رنگ"""
 | 
			
		||||
        priority_colors = {
 | 
			
		||||
            'low': 'success',
 | 
			
		||||
            'medium': 'info',
 | 
			
		||||
            'high': 'warning',
 | 
			
		||||
            'urgent': 'danger',
 | 
			
		||||
        }
 | 
			
		||||
        color = priority_colors.get(self.priority, 'secondary')
 | 
			
		||||
        return '<span class="badge bg-{}">{}</span>'.format(color, self.get_priority_display())
 | 
			
		||||
 | 
			
		||||
    def can_edit(self, user):
 | 
			
		||||
        """بررسی امکان ویرایش درخواست"""
 | 
			
		||||
        if self.status == 'pending' and self.requester == user:
 | 
			
		||||
            return True
 | 
			
		||||
        
 | 
			
		||||
        if self.representative == user and self.status in ['pending']:
 | 
			
		||||
            return True
 | 
			
		||||
        
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def get_available_steps(self):
 | 
			
		||||
        """دریافت مراحل قابل دسترس"""
 | 
			
		||||
        available_steps = []
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +272,6 @@ class ProcessInstance(NameSlugModel):
 | 
			
		|||
 | 
			
		||||
    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()
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +281,6 @@ class ProcessInstance(NameSlugModel):
 | 
			
		|||
 | 
			
		||||
    def can_edit_step(self, step):
 | 
			
		||||
        """بررسی امکان ویرایش مرحله"""
 | 
			
		||||
        # اگر مرحله مسدود کننده باشد و مراحل بعدی تکمیل شده باشند
 | 
			
		||||
        if step.blocks_previous:
 | 
			
		||||
            later_steps = self.step_instances.filter(
 | 
			
		||||
                step__order__gt=step.order,
 | 
			
		||||
| 
						 | 
				
			
			@ -187,12 +323,10 @@ class StepInstance(models.Model):
 | 
			
		|||
 | 
			
		||||
    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("این مرحله قابل ویرایش نیست")
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +386,6 @@ class StepRejection(models.Model):
 | 
			
		|||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """ذخیره با تغییر وضعیت مرحله"""
 | 
			
		||||
        # تغییر وضعیت مرحله به رد شده
 | 
			
		||||
        self.step_instance.status = 'rejected'
 | 
			
		||||
        self.step_instance.save()
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue