improve bazdid 2
This commit is contained in:
1 parent
534a84f977
commit
f8d768a724
6 files changed
+374
-92
No files matched your search
+87
-29
@@ -338,24 +338,35 @@ SOURCE_SCHEDULED = "scheduled"
|
||||
SOURCE_COORDINATION = "coordination"
|
||||
|
||||
|
||||
def _future_actions(action_type, statuses, *, now_utc):
|
||||
"""اقدامهای آیندهٔ یک نوع با تاریخ مؤثر (تاریخ اقدام، وگرنه تاریخ اتمام)."""
|
||||
def _future_actions(action_type, statuses, *, now_utc, whole_day=False):
|
||||
"""اقدامهای آیندهٔ یک نوع با تاریخ مؤثر (تاریخ اقدام، وگرنه تاریخ اتمام).
|
||||
|
||||
``whole_day=True`` فقط برای «بازدید برنامهریزیشده» استفاده میشود: در آنجا
|
||||
``actionDateTime`` یک **قرارِ آینده** است که کاربر با تقویم روزانه انتخاب
|
||||
میکند، پس «قرار امروز» باید تا پایان امروز معتبر بماند. مقایسهٔ لحظهای
|
||||
باعث میشد بازدیدِ امروز بلافاصله پس از گذشتن ساعتش ناپدید شود.
|
||||
|
||||
برای «هماهنگی» عمداً ``whole_day=False`` است: ``actionDateTime`` آنجا
|
||||
**زمانِ تماس** است، نه قرار آینده؛ تماسِ گذشته نباید مشتری را تا آخر امروز
|
||||
پنهان کند (وگرنه گروه «قرار دستی» در سناریوی رایج خالی میشد).
|
||||
"""
|
||||
threshold = start_of_local_day(now_utc) if whole_day else now_utc
|
||||
return (
|
||||
Action.objects.filter(actionType=action_type, status__in=statuses)
|
||||
.filter(
|
||||
models.Q(actionDateTime__gt=now_utc)
|
||||
| models.Q(actionDateTime__isnull=True, finishDateTime__gt=now_utc)
|
||||
models.Q(actionDateTime__gte=threshold)
|
||||
| models.Q(actionDateTime__isnull=True, finishDateTime__gte=threshold)
|
||||
)
|
||||
.values_list("customer_id", "actionDateTime", "finishDateTime")
|
||||
)
|
||||
|
||||
|
||||
def _action_appointments(action_type, statuses, *, now=None):
|
||||
def _action_appointments(action_type, statuses, *, now=None, whole_day=False):
|
||||
"""نگاشت ``customer_id → زودترین تاریخِ آینده`` برای یک نوع اقدام."""
|
||||
now_utc = _now(now).astimezone(dt_timezone.utc)
|
||||
result = {}
|
||||
for customer_id, action_dt, finish_dt in _future_actions(
|
||||
action_type, statuses, now_utc=now_utc
|
||||
action_type, statuses, now_utc=now_utc, whole_day=whole_day
|
||||
).iterator():
|
||||
when = as_aware(action_dt) or as_aware(finish_dt)
|
||||
if when is None:
|
||||
@@ -367,9 +378,13 @@ def _action_appointments(action_type, statuses, *, now=None):
|
||||
|
||||
|
||||
def scheduled_visits_map(*, now=None):
|
||||
"""قرارهای «بازدید برنامهریزیشده»: ``bazdid + waite`` با تاریخ آینده."""
|
||||
"""قرارهای «بازدید برنامهریزیشده»: ``bazdid + waite`` با تاریخ آینده.
|
||||
|
||||
مقایسه در سطح **روز** است (``whole_day=True``): بازدیدی که برای «امروز»
|
||||
گذاشته شده تا پایان امروز معتبر میماند.
|
||||
"""
|
||||
return _action_appointments(
|
||||
Action.ActionType.BAZDID, (Action.Status.WAITE,), now=now,
|
||||
Action.ActionType.BAZDID, (Action.Status.WAITE,), now=now, whole_day=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -390,17 +405,19 @@ def coordination_holds_map(*, now=None):
|
||||
def manual_holds_map(*, now=None):
|
||||
"""قرارهای دستی (ستون ``visit_hold_until``) — **با اعتبارسنجی**.
|
||||
|
||||
قاعدهٔ قطعی: ستون بهتنهایی معتبر نیست. فقط وقتی اثر دارد که یک اقدام
|
||||
«هماهنگی» با وضعیت ``waite`` یا ``finish`` و **تاریخ آینده** برای همان
|
||||
مشتری وجود داشته باشد. بنابراین:
|
||||
قاعدهٔ قطعی: ستون بهتنهایی معتبر نیست. یک قرار دستی فقط وقتی اثر دارد که:
|
||||
|
||||
* حذف هماهنگی ⇒ قرار خودکار بیاثر میشود،
|
||||
* لغو هماهنگی (``cancell``) ⇒ بیاثر میشود،
|
||||
* هماهنگیِ گذشته ⇒ بیاثر میشود،
|
||||
* کهنهماندن ستون هیچ اثر مخربی ندارد.
|
||||
۱. تاریخش از حالا جلوتر باشد،
|
||||
۲. یک اقدام «هماهنگی» فعال (``waite``/``finish``) برای مشتری وجود داشته باشد،
|
||||
۳. و **آخرین فعالیتِ مشتری همان هماهنگی باشد** — اگر پس از هماهنگی، اقدام
|
||||
دیگری (بهویژه «بازدید») ثبت شده باشد، قرار باطل است.
|
||||
|
||||
مرجع هماهنگی: هر ``hamahangi`` فعالی با تاریخ آینده (نه لزوماً برابر با ستون)،
|
||||
چون کاربر میتواند هماهنگی را ویرایش کند و تاریخ دیگری بگذارد.
|
||||
بند ۳ همان چیزی است که «ثبت بازدید پس از هماهنگی» را خودکار بیاثر میکند؛
|
||||
بدون آن، قرارِ قدیمی تا تاریخش باقی میماند و مشتری در فهرست «با قرار»
|
||||
میماند، هرچند بازدید (یا برنامهٔ بازدید) جای هماهنگی را گرفته است.
|
||||
|
||||
نتیجه: حذف/لغو هماهنگی، یا هر اقدام جدیدتر از آن ⇒ قرار بیاثر میشود؛
|
||||
و کهنهماندن ستون هیچ اثر مخربی ندارد.
|
||||
"""
|
||||
now = _now(now)
|
||||
rows = (
|
||||
@@ -412,23 +429,44 @@ def manual_holds_map(*, now=None):
|
||||
if not holds:
|
||||
return {}
|
||||
|
||||
customer_ids = list(holds)
|
||||
|
||||
# مرجع اعتبار: وجود یک اقدام «هماهنگیِ فعال» برای همان مشتری (نه لزوماً با
|
||||
# تاریخ آینده). این نکته حیاتی است: سناریوی رایج «همین حالا تماس گرفتم و قرار
|
||||
# دو هفتهٔ بعد گذاشتم» تاریخ اقدام را «امروز» میذارد؛ اگر شرط آینده بودن تاریخ
|
||||
# اقدام را بگذاریم، همین مورد (که هدف اصلی این قابلیت است) بیاثر میشد.
|
||||
# پس فقط وضعیت مهم است: ``waite`` (در انتظار) یا ``finish`` (انجام شد).
|
||||
# ``cancell`` حساب نمیشود و حذف رکورد هم خودکار بیاثر میکند.
|
||||
active_coordination = set(
|
||||
# تاریخ آینده). سناریوی رایج «همین حالا تماس گرفتم و قرار دو هفتهٔ بعد
|
||||
# گذاشتم» تاریخ اقدام را «امروز» میذارد؛ اگر شرط آیندهبودن تاریخ اقدام را
|
||||
# بگذاریم، همین مورد (هدف اصلی این قابلیت) بیاثر میشد. پس فقط وضعیت مهم است.
|
||||
coordination_latest = dict(
|
||||
Action.objects.filter(
|
||||
customer_id__in=customer_ids,
|
||||
actionType=Action.ActionType.HAMAHANGI,
|
||||
status__in=(Action.Status.WAITE, Action.Status.FINISH),
|
||||
).values_list("customer_id", flat=True)
|
||||
)
|
||||
.values("customer_id")
|
||||
.annotate(last=Max("createdDT"))
|
||||
.values_list("customer_id", "last")
|
||||
)
|
||||
return {
|
||||
customer_id: until
|
||||
for customer_id, until in holds.items()
|
||||
if customer_id in active_coordination and until is not None
|
||||
}
|
||||
# آخرین فعالیتِ غیرهماهنگی (بازدید، نصب، تماس، …) برای هر مشتری.
|
||||
other_latest = dict(
|
||||
Action.objects.filter(customer_id__in=customer_ids)
|
||||
.exclude(actionType=Action.ActionType.HAMAHANGI)
|
||||
.values("customer_id")
|
||||
.annotate(last=Max("createdDT"))
|
||||
.values_list("customer_id", "last")
|
||||
)
|
||||
|
||||
valid = {}
|
||||
for customer_id, until in holds.items():
|
||||
if until is None:
|
||||
continue
|
||||
coordinated_at = coordination_latest.get(customer_id)
|
||||
if coordinated_at is None:
|
||||
continue
|
||||
activity_at = other_latest.get(customer_id)
|
||||
if activity_at is not None and as_aware(activity_at) > as_aware(coordinated_at):
|
||||
# پس از هماهنگی، اقدام دیگری ثبت شده ⇒ قرار منتفی است.
|
||||
continue
|
||||
valid[customer_id] = until
|
||||
return valid
|
||||
|
||||
|
||||
def appointment_sources_map(*, now=None):
|
||||
@@ -467,6 +505,26 @@ HOLD_DEFAULT_DAYS = 14
|
||||
HOLD_MAX_DAYS = 183 # ≈ ۶ ماه
|
||||
|
||||
|
||||
def start_of_local_day(moment):
|
||||
"""آغاز همان روز به وقت محلی (بهصورت aware).
|
||||
|
||||
قرارها با تقویم روزانه انتخاب میشوند؛ پس «قرار امروز» باید تا پایان امروز
|
||||
معتبر بماند. مقایسهٔ لحظهای باعث میشد قرارِ امروز بلافاصله پس از گذشتن
|
||||
ساعتِ آن، ناپدید شود.
|
||||
"""
|
||||
moment = as_aware(moment)
|
||||
if moment is None:
|
||||
return None
|
||||
local = moment.astimezone(timezone.get_current_timezone())
|
||||
start_of_day = datetime.combine(local.date(), time.min)
|
||||
try:
|
||||
return timezone.make_aware(start_of_day, timezone.get_current_timezone())
|
||||
except Exception:
|
||||
return timezone.make_aware(
|
||||
start_of_day, timezone.get_current_timezone(), is_dst=False,
|
||||
)
|
||||
|
||||
|
||||
def end_of_local_day(moment):
|
||||
"""پایان همان روز به وقت محلی (بهصورت aware).
|
||||
|
||||
|
||||
Reference in new issue
Block a user