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