improve bazdid 4

This commit is contained in:
javad committed 2026-10-21 16:34:13 +03:30
1 parent 8fd881e0ea
commit 825f2a5973
13 files changed
+585 -15

No files matched your search

+86
View File
@@ -936,3 +936,89 @@ def normalize_finish_datetime(value, *, now=None):
"""تاریخ اتمام: اگر خالی بود، زمان ثبت قرار می‌گیرد (نه ``None``)."""
value = as_aware(value)
return value if value is not None else _now(now)
# ---------------------------------------------------------------------------
# حذف (soft-delete) یک اقدام
# ---------------------------------------------------------------------------
class ActionDeleteError(Exception):
"""حذف مجاز نیست؛ پیام برای نمایش به کاربر."""
def cancel_pending_sms_for_action(customer, *, now=None):
"""لغو پیامک‌های **ارسال‌نشدهٔ** «بازدید ثبت شد» همان مشتری.
چرا با ``action`` پیدا نمی‌کنیم؟ چون ``SMSQueue`` هیچ ارجاعی به رکورد اقدام
ندارد (فقط ``dedup_key`` متنی). برای جلوگیری از ارسال پیامکِ بازدیدی که
دیگر وجود ندارد، پیامک‌های معلق همان مشتری با نوع ``visit_created`` لغو
می‌شوند. پیامک‌های **ارسال‌شده** دست نمی‌خورند (سابقه است).
خروجی: تعداد رکوردهای لغوشده.
"""
# ایمپورت محلی: sms.models در زمان ایمپورت این ماژول ممکن است آماده نباشد.
from .models import SMSQueue, SMSType
if customer is None or customer.pk is None:
return 0
now = _now(now)
pending = (SMSQueue.Status.PENDING, SMSQueue.Status.APPROVED)
return (
SMSQueue.objects.filter(
customer_id=customer.pk,
sms_type=SMSType.VISIT_CREATED,
status__in=pending,
)
.exclude(status=SMSQueue.Status.SENT)
.update(status=SMSQueue.Status.CANCELLED, updated_at=now)
)
def soft_delete_action(action, *, user=None, now=None):
"""«حذف» یک اقدام: soft-delete با ``status='cancell'`` + حسابرسی.
چرا ``cancell`` و نه ستون ``is_deleted``؟ چون **تمام** محاسبات
(:func:`last_visit_map`، :func:`cycle_anchor_map`،
:func:`appointment_sources_map`، :func:`followups_on_date`،
:func:`overdue_followups`) از قبل فقط ``waite``/``finish`` را می‌بینند؛ پس
همین یک مقدار، اقدام را از همهٔ خروجی‌ها حذف می‌کند — بدون Manager سفارشی،
بدون تغییر ایندکس پوشا و بدون دست‌زدن به ۳۱ کوئری.
پیامدهای جانبی که همین‌جا انجام می‌شوند:
۱. ``last_bazdid_dt`` بازمحاسبه می‌شود (کش کهنه نماند).
۲. قرار دستی اگر دیگر مرجعی نداشته باشد، خودکار بی‌اثر می‌شود
(قاعدهٔ «ستون، مرجع نیست»).
۳. پیامک‌های **ارسال‌نشده**ی همان مشتری لغو می‌شوند.
ممنوعیت: اقدامی که نظرسنجی ثبت‌شده دارد (``nazarDT`` پر است) حذف نمی‌شود؛
زیرا هیچ آرشیو/کپی‌ای وجود ندارد و گزارش رضایت‌سنجی برای همیشه تغییر می‌کرد.
"""
if action is None or action.pk is None:
raise ActionDeleteError("اقدام معتبر نیست.")
if action.status == Action.Status.CANCELL and action.deleted_at is not None:
raise ActionDeleteError("این اقدام قبلاً حذف شده است.")
if action.nazarDT is not None:
raise ActionDeleteError(
"این اقدام دارای نظرسنجی ثبت‌شده است و قابل حذف نیست. "
"برای کنار گذاشتن آن، از ویرایش و تغییر وضعیت استفاده کنید."
)
now = _now(now)
customer = action.customer
with transaction.atomic():
Action.objects.filter(pk=action.pk).update(
status=Action.Status.CANCELL,
deleted_at=now,
deleted_by=user if getattr(user, "pk", None) else None,
)
action.status = Action.Status.CANCELL
action.deleted_at = now
if customer is not None:
refresh_last_bazdid(customer, reason=REASON_CANCEL, user=user, now=now)
cancelled = cancel_pending_sms_for_action(customer, now=now)
else:
cancelled = 0
return cancelled