Files
khatibi/admin_panel/models/actions.py
T
2026-10-21 16:34:13 +03:30

104 lines
5.1 KiB
Python

from django.db import models
from django.contrib.auth.models import User
class VisitQueue(models.Model):
customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
dateTime = models.DateTimeField()
statusList = [
('waiting', 'waiting'),
('finish', 'finish'),
('cancell', 'cancell'),
]
status = models.CharField(max_length=10, choices=statusList, default='waiting')
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
GradeList = [
('3', '3'),
('2', '2'),
('1', '1'),
]
grade = models.CharField(max_length=1, choices=GradeList)
grade_desc = models.CharField(max_length=200)
class Action(models.Model):
"""اقدام/وظیفهٔ CRM مرتبط با مشتری.
``actionType`` فقط این شش مقدار را می‌پذیرد؛ همین شش مقدار در فرم‌های پنل
(``customerActionInc.html``) و در تمام مقایسه‌های کد (``== 'bazdid'``،
``exclude(actionType='vosol')`` و …) استفاده می‌شوند.
``status`` نیز همان سه مقدارِ به‌کاررفته در کد و قالب‌ها را می‌پذیرد.
املای ``'waite'`` عمداً حفظ شده است: ۲٫۷۱۹ رکورد موجودِ دیتابیس و تمام
مقایسه‌های کد/قالب (``== 'waite'``، ``if filter_status == 'waite'``) با
همین املا کار می‌کنند و تغییرش داده را از موتور جست‌وجو جدا می‌کند.
"""
class ActionType(models.TextChoices):
BAZDID = 'bazdid', 'بازدید'
NASB = 'nasb', 'نصب'
VOSOL = 'vosol', 'وصول مطالبات'
HAMAHANGI = 'hamahangi', 'هماهنگی'
IN_CALL = 'inCall', 'تماس ورودی'
OUT_CALL = 'outCall', 'تماس خروجی'
class Status(models.TextChoices):
# املا عیناً مطابق داده و کد فعلی است (نه 'wait').
WAITE = 'waite', 'در انتظار (باز)'
FINISH = 'finish', 'انجام شده'
CANCELL = 'cancell', 'لغو شده'
actionType = models.CharField(
max_length=50, null=True, blank=True, choices=ActionType.choices,
)
forUser = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
customer = models.ForeignKey('Customer', related_name='actionrlt', on_delete=models.CASCADE, null=True, blank=True)
createdDT = models.DateTimeField(auto_now_add=True)
changeDT = models.DateTimeField(auto_now=True)
actionDateTime = models.DateTimeField(null=True, blank=True)
finishDateTime = models.DateTimeField(null=True, blank=True)
desc = models.CharField(max_length=500, null=True, blank=True)
result = models.CharField(max_length=500, null=True, blank=True)
status = models.CharField(
max_length=50, null=True, blank=True, choices=Status.choices,
)
nazar = models.CharField(max_length=50, null=True, blank=True, default=None)
nextNazarDT = models.DateTimeField(null=True, blank=True, default=None)
nazarDT = models.DateTimeField(null=True, blank=True, default=None)
grade = models.IntegerField(null=True, blank=True, default=None)
# --- حسابرسی «حذف» (soft-delete از طریق status='cancell') ---
# چرا ستون جدا؟ چون ``cancell`` از قبل معنای «لغو برنامه» را دارد و در گزارش‌ها
# هم استفاده می‌شود. با این دو ستون می‌توانیم «حذف توسط کاربر» را از «لغو
# عادی» تفکیک کنیم، بدون آنکه منطق محاسبات (که فقط waite/finish را می‌بیند)
# دست بخورد.
deleted_at = models.DateTimeField(
null=True, blank=True, default=None, verbose_name="زمان حذف",
help_text="اگر پر باشد، این اقدام توسط کاربر حذف شده است (status=cancell).",
)
deleted_by = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True,
related_name="deleted_actions", verbose_name="حذف‌کننده",
)
@property
def is_deleted(self):
"""آیا این اقدام با «حذف» کنار گذاشته شده است؟ (نه لغو عادی)"""
return self.deleted_at is not None
class Meta:
# ایندکس **پوشا (covering)** برای محاسبهٔ «آخرین بازدید»: هر سه ستونِ لازم
# برای فیلتر و تجمیع داخل خودِ ایندکس است، پس دیتابیس به جدول اصلی مراجعه
# نمی‌کند. اندازه‌گیری روی دادهٔ واقعی: ۱۶۴ms → ۲۹ms.
# ستون‌های actionType/status/customer_id برای فیلتر و group، و دو ستون
# تاریخ برای محاسبهٔ «تاریخ مؤثر» در همان ایندکس.
indexes = [
models.Index(
fields=[
'actionType', 'status', 'customer_id',
'actionDateTime', 'finishDateTime',
],
name='act_visit_idx',
),
]