improve bazdid 4
This commit is contained in:
1 parent
8fd881e0ea
commit
825f2a5973
13 files changed
+585
-15
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user