shafafiyat/processes/models.py
2025-08-10 07:44:23 +03:30

293 lines
12 KiB
Python

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()}"