Files
khatibi/recalc_last_bazdid.md
2026-10-11 19:04:20 +03:30

6.8 KiB

بازمحاسبهٔ «آخرین بازدید» مشتریان

چرا؟

فیلد Customer.last_bazdid_dt یعنی «آخرین بازدیدی که برای مشتری انجام شده» و صفحهٔ /panel/entezar/ بر پایهٔ آن مشتریانِ معوق (بیش از N ماه بازدید نشده) را لیست می‌کند.

قاعدهٔ درست (منبع حقیقت): این مقدار باید برابر بزرگ‌ترین finishDateTime میان اقدام‌های actionType='bazdid' و status='finish' همان مشتری باشد — دقیقاً همان چیزی که ویوهای customerNewAction و customerEditAction می‌نویسند:

if newAction.status == 'finish' and newAction.actionType == 'bazdid':
    customer_obj.last_bazdid_dt = newAction.finishDateTime
    customer_obj.save()

پس اگر رکوردی از راهی غیر از این ویوها وارد شود (اسکریپت، ایمپورت، ویرایش دستی دیتابیس، اجرای دستی gt_esh و…)، این فیلد عقب می‌ماند و مشتریِ واقعاً بازدیدشده، اشتباهاً «معوق» شمرده می‌شود. این دستور آن واگرایی (drift) را از روی جدول Action ترمیم می‌کند.

دستور idempotent است؛ اجرای دوباره‌اش هیچ تغییری ایجاد نمی‌کند.

دستور

نام دستور منیج‌منت: recalc_last_bazdid مسیر فایل: admin_panel/management/commands/recalc_last_bazdid.py

۱) اول «شبیه‌سازی» — بدون هیچ تغییری

فقط گزارش می‌دهد چه چیزی تغییر می‌کند. قبل از هر اجرای واقعی این را بزنید.

از WSL:

.venv/Scripts/python.exe manage.py recalc_last_bazdid --dry-run

از Windows:

.venv\Scripts\python.exe manage.py recalc_last_bazdid --dry-run

۲) اجرای واقعی

.venv/Scripts/python.exe manage.py recalc_last_bazdid

گزینه‌ها

گزینه پیش‌فرض کار
--dry-run خاموش فقط گزارش؛ هیچ رکوردی ذخیره نمی‌شود. اول این را بزنید.
--clear-orphans خاموش مشتریانی که مقدار دارند ولی هیچ اقدام بازدیدِ تمام‌شده‌ای پشتشان نیست را هم NULL می‌کند. به‌طور پیش‌فرض دست‌نخورده می‌مانند. فقط اگر مطمئنید این ستون آلوده است.
--batch-size 500 اندازهٔ دستهٔ خواندن مشتریان در هر گام.

دقیقاً چه می‌کند؟

برای هر مشتری، MAX(finishDateTime) اقدام‌های بازدیدِ تمام‌شده محاسبه می‌شود، سپس:

وضعیت مشتری اقدام دستور
بازدید دارد و مقدار فعلی برابر MAX است دست نمی‌زند (از قبل درست)
بازدید دارد و مقدار فعلی کوچک‌تر یا NULL است به MAX به‌روز می‌کند — از لیست معوق‌ها خارج می‌شود (جدیدتر شد)
بازدید دارد و مقدار فعلی بزرگ‌تر از MAX است به MAX عقب می‌برد (عقب‌تر شد)
بازدید ندارد و مقدار NULL است کاری ندارد (طبیعی)
بازدید ندارد ولی مقداری دارد دست نمی‌زند — مگر --clear-orphans بدهید (خالی شد)

نوشتن با Customer.objects.filter(pk=…).update(last_bazdid_dt=…) داخل یک transaction.atomic() انجام می‌شود؛ یعنی فقط همین یک ستون لمس می‌شود و هیچ save() کاملی که فیلدهای دیگر (auto_now و…) را تغییر دهد اجرا نمی‌شود.

نمونهٔ خروجی

بازمحاسبهٔ آخرین بازدید مشتریان آغاز شد.

گزارش بازمحاسبهٔ آخرین بازدید:
  بررسی‌شده (کل مشتریان):        10,426
  دارای بازدیدِ تمام‌شده:       9,151
  از قبل درست:                  9,084
  نیازمند اصلاح:                67
    → جدیدتر شد (خروج از معوق):  67
    → عقب‌تر شد:                 0
  بدون اقدام ولی دارای مقدار (دست‌نخورده): 0
  NULL و بدون اقدام (طبیعی):    1,275

انجام شد: 67 رکورد به‌روزرسانی شد.

اجرا روی production

اول از دیتابیس بکاپ بگیرید، سپس:

python manage.py recalc_last_bazdid --dry-run
python manage.py recalc_last_bazdid

دستور تراکنشی است و در صورت خطا، هیچ تغییری کامیت نمی‌شود.

بررسی صحت داده (SQL، پیش از اجرا)

اگر می‌خواهید بدون دستور، وضعیت را بسنجید:

-- مشتریانی که مقدارشان با آخرین بازدید واقعی اختلاف دارد (تعداد «نیازمند اصلاح»)
SELECT COUNT(*) FROM admin_panel_customer cu
JOIN (SELECT customer_id, MAX(finishDateTime) mx FROM admin_panel_action
      WHERE actionType='bazdid' AND status='finish' GROUP BY customer_id) a
  ON a.customer_id = cu.id
WHERE cu.last_bazdid_dt IS NULL OR cu.last_bazdid_dt <> a.mx;

-- مشتریانی که بازدید تمام‌شده دارند ولی مقدارشان NULL است (بدترین حالت)
SELECT COUNT(*) FROM admin_panel_customer cu
WHERE cu.last_bazdid_dt IS NULL
  AND EXISTS (SELECT 1 FROM admin_panel_action a
              WHERE a.customer_id=cu.id AND a.actionType='bazdid' AND a.status='finish');

-- تعداد سالم (مقدار دارد)
SELECT COUNT(*) FROM admin_panel_customer WHERE last_bazdid_dt IS NOT NULL;

نکته‌های مهم

  • مقادیر NULL مشتریانی که هرگز بازدید تمام‌شده نداشته‌اند، طبیعی است و دستور آن‌ها را پر نمی‌کند (اگر پر کند، یعنی داده ساختن — که غلط است).
  • دستور هیچ فاکتور، سند، اقدام یا مشتری نمی‌سازد و حذف نمی‌کند؛ فقط یک ستون را هم‌راستا می‌کند.
  • صفحهٔ /panel/entezar/ یک کش در CacheTable(key='9mahbazdid') نگه می‌دارد؛ عددِ روی صفحه ممکن است یک اجرا عقب باشد. هر بار باز شدن صفحه آن را به‌روز می‌کند.
  • منبع حقیقت همیشه جدول Action است، نه این ستون. در صورت شک، همان کوئری‌های بخش «بررسی صحت داده» ملاک هستند.