improve bazdid

This commit is contained in:
javad committed 2026-10-21 10:50:42 +03:30
1 parent 2aee85593f
commit 534a84f977
12 files changed
+2305 -339

No files matched your search

+468
View File
@@ -0,0 +1,468 @@
# «قرار بعدی» بازدید و صفحهٔ انتظار — توضیح کامل عملکرد
این سند رفتار نهایی صفحهٔ `/panel/entezar/`، فیلد «تا تاریخ پیگیری نشود» در فرم
اقدام، و قواعد محاسبه را توضیح می‌دهد. هدف: بعد از این تغییر، **هماهنگیِ انجام‌شده
هم** مشتری را تا تاریخ توافق از فهرست خارج کند و کاربر بتواند بازدیدهای
«هماهنگ‌شده» را از «هماهنگ‌نشده» جدا ببیند.
---
## ۱. صورت مسئله
پیش از این تغییر، فقط دو حالت مشتری را از فهرست خارج می‌کرد:
1. اقدام `bazdid` با وضعیت `waite` و تاریخ آینده (بازدید برنامه‌ریزی‌شده)
2. اقدام `hamahangi` با تاریخ **آینده** در فیلد تاریخ اقدام
اما سناریوی واقعی کارشناس این بود:
> «همین حالا با مشتری تماس گرفتم، توافق کردیم هفتهٔ بعد بیاییم، و تیک *انجام شد* را
> زدم.»
در این حالت، فیلد «تاریخ اقدام» روی **همین حالا** می‌نشیند، پس هیچ قرار آینده‌ای
وجود نداشت و مشتری در فهرست می‌ماند و کارشناس دیگری دوباره تماس می‌گرفت.
**ریشه:** «تاریخ تماس» و «تاریخ قرار بعدی» دو مفهوم متفاوت‌اند ولی در یک فیلد
(`actionDateTime`) جمع شده بودند.
---
## ۲. راه‌حل: ستون «قرار بعدی»
دو ستون به مدل `Customer` اضافه شد:
| ستون | نوع | معنا |
|---|---|---|
| `visit_hold_until` | `DateTimeField(null=True)` | تا **پایان** این روز پیگیری لازم نیست |
| `visit_hold_set_at` | `DateTimeField(null=True)` | زمان ثبت، برای حسابرسی |
### قاعدهٔ طلایی: ستون، «مرجع» نیست
> `visit_hold_until` هیچ اعتباری به‌تنهایی ندارد. فقط وقتی اثر دارد که برای همان
> مشتری یک اقدام **هماهنگیِ فعال** (`waite` یا `finish`) وجود داشته باشد.
نتیجهٔ این قاعده:
| رویداد | اثر روی قرار |
|---|---|
| حذف اقدام هماهنگی | قرار **خودکار بی‌اثر** می‌شود |
| لغو هماهنگی (`cancell`) | بی‌اثر می‌شود |
| ویرایش تاریخ هماهنگی | قرار با تاریخ جدید هم‌راستا می‌شود |
| کهنه ماندن ستون | **هیچ اثر مخربی ندارد** |
به همین دلیل دیگر خطر «کش کهنه» وجود ندارد: منبع حقیقت همیشه جدول `Action` است.
---
## ۳. سه منبع «قرار بعدی»
تصمیم «مشتری در فهرست هست یا نه» از اجتماع سه منبع گرفته می‌شود:
| منبع | تعریف | تاریخ قرار |
|---|---|---|
| `manual` | ستون `visit_hold_until` (با اعتبارسنجی بالا) | همان ستون (پایان روز) |
| `scheduled` | `bazdid + waite` با تاریخ آینده | `actionDateTime` |
| `coordination` | `hamahangi` با `waite` یا `finish` و تاریخ آینده | `actionDateTime` (یا `finishDateTime`) |
تابع واحد: `visit_policy.next_appointment_map(now)` — **همان تابعی که اسکنر پیامک
هم استفاده می‌کند**، پس صفحه و پیامک هرگز واگرا نمی‌شوند.
اگر مشتری چند قرار داشته باشد، **زودترین** تاریخ ملاک نمایش است؛ اما برای
«بازگشت به فهرست» کافی است **آخرین** قرار منقضی شود.
---
## ۴. فرم ثبت اقدام (UI)
در مودال «ایجاد وظیفه» (`customerActionInc.html`):
| مورد | رفتار |
|---|---|
| نمایش فیلد | فقط وقتی نوع اقدام = **هماهنگی** انتخاب شود |
| برچسب | «تا تاریخ پیگیری نشود» |
| کنترل | تقویم شمسی (همان `pDatepicker` پروژه) |
| پیش‌فرض | اگر خالی بماند و تیک «انجام شد» زده شود ⇒ **+۱۴ روز** |
| سقف | **۶ ماه** (اعتبارسنجی سروری) |
| حالت ویرایش | فیلد با نوع اقدام هم‌راستا می‌شود (نمایش/پنهان) |
نکتهٔ مهم: فیلد «تاریخ و ساعتی که کار باید انجام شود» (`dateTimeAnjam`) همچنان
«زمان تماس» است و دست نخورده باقی می‌ماند. دو مفهوم، دو فیلد.
### ۴.۱ چیدمان مودال (mobile first)
مودال به سه گام شماره‌دار تقسیم شده تا کاربر بداند چه چیزی را کجا وارد کند:
| گام | محتوا |
|---|---|
| ۱ | نوع اقدام + مسئول (توسط) — کنار هم در دسکتاپ، زیر هم در موبایل |
| ۲ | زمان‌بندی: تاریخ انجام، «تا تاریخ پیگیری نشود»، و راهنمای بازشونده |
| ۳ | توضیحات، تیک «انجام شده»، تاریخ اتمام و نتیجه |
نکات پیاده‌سازی:
* پیشوند همهٔ کلاس‌ها `ax-` و همهٔ انتخابگرها زیر `#ActionBox` است ⇒ هیچ استایلی
به سایر صفحات نشت نمی‌کند.
* روی موبایل: کارت تمام‌عرض (`calc(100% - 1rem)`)، بدنهٔ اسکرول‌شونده، هدف لمسی
حداقل ۲٫۶rem، و دکمه‌های پاصفحه تمام‌عرض.
* روی دسکتاپ (≥۷۶۸px): شبکهٔ دوستونه، عرض حداکثر ۴۴rem.
* سرصفحه نام مشتری را نشان می‌دهد تا کاربر بداند برای چه کسی وظیفه می‌سازد.
* **همان تقویم شمسی قبلی** استفاده می‌شود: `dateTimePickerAnjam`،
`dateTimePickerFinish` و `dateTimePickerHoldUntil` با `pDatepicker` و
`calendar.persian.locale = 'fa'`. هیچ کتابخانهٔ جدیدی اضافه نشده است.
* **نام کلاس فیلد باید با سلکتور JS یکی باشد.** هر فیلد تاریخ دو المان دارد:
ورودی نمایشی (با کلاس `dateTimePickerXxx`) و ورودی مخفیِ ارسال (با کلاس
`dateTimePickerXxx-alt`). اگر این نام‌ها هم‌خوانی نداشته باشند،
`pDatepicker` روی مجموعهٔ خالی اجرا می‌شود، `undefined` برمی‌گرداند و
`setDate()` با `TypeError` کل هندلر را می‌شکند — نتیجه: تقویم باز نمی‌شود و
بقیهٔ منطق (پنهان‌کردن فیلدها، برداشتن تیک خودکار) هم اجرا نمی‌شود.
دو تست اختصاصی
(`test_every_picker_selector_matches_markup` و
`test_every_alt_field_class_exists_in_markup`) همین را قفل کرده‌اند.
* `altField` با فرمت پیش‌فرض `unix` مقدار می‌دهد؛ بنابراین `visitHoldUntil`
یک timestamp میلی‌ثانیه‌ای است که `_parse_form_datetime` آن را می‌خواند.
* تقویم به `body` اضافه می‌شود (نه داخل مودال) ⇒ اسکرول بدنهٔ مودال آن را نمی‌برد.
### ۴.۲ هشدار زمان‌بندی و پیش‌فرض «انجام شده» (فقط هماهنگی)
چون در عمل «ثبت هماهنگی» یعنی «همین حالا با مشتری تماس گرفتم»، انتخاب نوع
**هماهنگی** به‌طور خودکار تیک «این کار انجام شده است» را می‌زند (و فیلدهای
تاریخ اتمام و نتیجه را نمایش می‌دهد). برای جلوگیری از ثبتِ ناخواسته، دو لایه
محافظ اضافه شده است:
**۱) هشدار همیشگی** — به‌محض تیک‌خوردن و تا وقتی نوع هماهنگی است، یک کادر
کهربایی زیر تیک نمایش داده می‌شود:
> آیا مطمئن هستی که هماهنگی را انجام دادی؟
> اگر هنوز هماهنگی نکردی، این تیک را بردار.
**۲) تأیید در لحظهٔ ثبت** — اگر کاربر روی «ثبت» بزند و نوع هماهنگی و تیک
«انجام شده» فعال باشد، ارسال متوقف می‌شود و همان کادر برجسته شده و دو دکمه
نشان می‌دهد:
| دکمه | نتیجه |
|---|---|
| بله، هماهنگی انجام شد | فرم ارسال می‌شود (هماهنگی با وضعیت `finish` ثبت می‌شود) |
| نه، تیک را بردار | تیک برداشته می‌شود، فیلدهای اتمام پنهان می‌شوند و ارسال نمی‌شود |
قواعد ایمنی:
* **فقط هماهنگی**: سایر انواع اقدام (بازدید، نصب، وصول، تماس…) هیچ تیک
خودکار و هیچ تأییدی ندارند و رفتارشان دقیقاً مثل قبل است.
* **تغییر نوع = برداشتن تیک خودکار**: اگر سیستم تیک زده باشد و کاربر نوع را
به غیر هماهنگی تغییر دهد، تیک برداشته می‌شود (تا ناخواسته `last_bazdid_dt`
یا وضعیت «انجام شده» ثبت نشود). اگر کاربر خودش تیک زده باشد، دست‌نخورده
می‌ماند.
* **حالت ویرایش**: تأیید گرفته نمی‌شود (کاربر فقط رکورد موجود را اصلاح می‌کند)؛
اما کادر هشدار برای هماهنگیِ انجام‌شده به‌صورت اطلاعاتی نمایش داده می‌شود.
* **باز شدن مجدد مودال**: تیک، هشدار و وضعیت تأیید همیشه ریست می‌شوند.
### ۴.۳ راهنمای بازشونده (فقط «هماهنگی»)
زیر فیلد تاریخ پیگیری، یک دکمهٔ راهنما با آیکون `!` قرار دارد که پنل توضیحات را
باز/بسته می‌کند. محتوای پنل:
* «این تاریخ چه کاری می‌کند؟» — تعریف ساده + اینکه تا پایان همان روز معتبر است.
* «مثال» — سناریوی واقعی با تاریخ نمونه.
* «نکات مهم» — خالی + تیک ⇒ ۱۴ روز، خالی + بدون تیک ⇒ بدون تعویق،
فقط تمدید (نه کوتاه‌کردن)، حذف/لغو هماهنگی ⇒ بازگشت فوری، سقف ۶ ماه،
تفاوت با «تاریخ بازدید پرسنل»، و توقف پیامک یادآوری در مدت تعویق.
رفتار:
| رویداد | نتیجه |
|---|---|
| انتخاب نوع = هماهنگی | دکمهٔ راهنما ظاهر می‌شود (پنل بسته) |
| کلیک روی دکمه | پنل باز/بسته می‌شود و `aria-expanded` به‌روز می‌شود |
| تغییر نوع به غیر هماهنگی | فیلد و راهنما پنهان و پنل بسته می‌شود |
| باز شدن مجدد مودال (ایجاد یا ویرایش) | پنل همیشه بسته شروع می‌شود |
دسترسی‌پذیری: دکمه `aria-expanded` و `aria-controls="holdHelpPanel"` دارد و
پنل با `is-hidden` کنترل می‌شود.
### قواعد اعتبارسنجی (سرور)
* تاریخ قرار نباید **پیش از امروز** باشد ⇒ پیام: «تاریخ قرار نمی‌تواند پیش از امروز باشد.»
* حداکثر **۱۸۳ روز** (≈۶ ماه) جلوتر ⇒ پیام: «قرار بعدی حداکثر می‌تواند ۱۸۳ روز (۶ ماه) جلوتر باشد.»
* تاریخ «امروز» پذیرفته است و به **۲۳:۵۹:۵۹ همان روز** تبدیل می‌شود (تا پایان روز معتبر).
* اگر اعتبارسنجی رد شود، **هیچ اقدامی ذخیره نمی‌شود**.
### قاعدهٔ «تمدید مجاز، کوتاه‌کردن ممنوع» (max)
قرار ذخیره‌شده همیشه **بزرگ‌ترین** تاریخِ ثبت‌شده است:
```
visit_hold_until = max(مقدار فعلی، تاریخ جدید)
```
| وضعیت | نتیجه |
|---|---|
| قرار فعلی ۱ آذر، کارشناس جدید ۸ آذر | **تمدید می‌شود به ۸ آذر** ✅ |
| قرار فعلی ۸ آذر، کارشناس جدید ۱ آذر | **تغییری نمی‌کند** (۸ آذر می‌ماند) |
منطق: اگر مشتری گفته «۸ آذر تماس بگیرید»، سیستم نباید ۱ آذر زنگ بزند. در مقابل،
یک ثبت اشتباه نباید قرارِ طولانیِ قبلی را نابود کند.
**برای عقب‌بردن قرار** (مثلاً مشتری گفت زودتر بیایید): هماهنگی را **حذف** و دوباره
با تاریخ جدید ثبت کنید.
---
## ۵. صفحهٔ انتظار: چهار گروه و فیلترها
هر مشتری معوق **دقیقاً در یکی** از این چهار گروه است:
| گروه | تعریف |
|---|---|
| **A** | معوق، بدون هیچ قرار |
| **B** | معوق، فقط قرار دستی (`manual`) |
| **C** | معوق، فقط بازدید برنامه‌ریزی‌شده یا هماهنگی در انتظار |
| **D** | معوق، هم قرار دستی و هم بازدید برنامه‌ریزی‌شده |
### فیلترها (قابل بوکمارک و ترکیب با جست‌وجو)
| URL | نمایش | شامل |
|---|---|---|
| `/panel/entezar/` (بدون پارامتر) | **پیش‌فرض = فقط A** (رفتار قبلی صفحه) | A |
| `?view=uncoordinated` | بدون قرار | A |
| `?view=manual` | قرار هماهنگی | B + D |
| `?view=scheduled` | بازدید برنامه‌ریزی‌شده | C + D |
| `?view=coordinated` | همهٔ دارای قرار | B + C + D |
| `?view=all` | همهٔ معوق‌ها | A + B + C + D |
| مقدار نامعتبر | برگشت به پیش‌فرض، **بدون خطا** | A |
### ستون‌های جدول
| ستون | توضیح |
|---|---|
| وضعیت قرار | برچسب «با قرار» / «بدون قرار» |
| قرار هماهنگی | تاریخ شمسی قرار دستی (یا —) |
| بازدید برنامه‌ریزی‌شده | تاریخ شمسی قرار `Action` (یا —) |
| آخرین بازدید | بدون تغییر نسبت به قبل |
### شمارندهٔ داشبورد
`CacheTable('9mahbazdid')` = **گروه A** (کارِ فوری = بدون قرار). هر بار باز شدن
صفحه به‌روز می‌شود.
---
## ۶. تعامل با سایر بخش‌ها
| بخش | اثر |
|---|---|
| **آخرین بازدید** (`last_visit_map`) | دست‌نخورده — فقط `bazdid + finish` |
| **چرخهٔ ۹ ماهه** (`cycle_anchor_map`) | دست‌نخورده — `bazdid` و `nasb` |
| **اسکنر پیامک** (`scan_visit`) | فقط یک منبع اضافه در `next_appointment_map` |
| **`recalc_last_bazdid`** | دست‌نخورده |
| **`last_bazdid_dt`** | بدون تغییر (نمایش/ترتیب) |
| **ایندکس `act_visit_idx`** | بدون تغییر |
قاعدهٔ طلایی حفظ شده است: **هماهنگی هرگز بازدید حساب نمی‌شود.**
---
## ۷. سناریوهای تست‌شده
| # | سناریو | نتیجهٔ انتظار | وضعیت |
|---|---|---|---|
| ۱ | هماهنگی با قرار «امروز» | تا پایان روز خارج از فهرست | ✅ |
| ۲ | هماهنگی finish بدون تاریخ قرار | پیش‌فرض ۱۴ روز | ✅ |
| ۳ | حذف اقدام هماهنگی | قرار بی‌اثر، مشتری برمی‌گردد | ✅ |
| ۴ | لغو هماهنگی (`cancell`) | قرار بی‌اثر | ✅ |
| ۵ | هماهنگی دوم با تاریخ کوچک‌تر | قرار قبلی حفظ می‌شود | ✅ |
| ۶ | هماهنگی دوم با تاریخ بزرگ‌تر | قرار تمدید می‌شود | ✅ |
| ۷ | قرار بیش از ۶ ماه | رد با پیام، هیچ ذخیره‌ای | ✅ |
| ۸ | قرار در گذشته | رد با پیام | ✅ |
| ۹ | بازدید واقعی بعد از قرار | مشتری از فهرست خارج می‌ماند | ✅ |
| ۱۰ | هماهنگی finish با تاریخ آینده (بدون ستون) | خارج از فهرست | ✅ |
| ۱۱ | تفکیک A/B/C/D و هر پنج فیلتر | اعداد و فهرست درست | ✅ |
| ۱۲ | `?view=nonsense` | پیش‌فرض، بدون خطا | ✅ |
| ۱۳ | اسکنر پیامک با قرار دستی | پیامک ارسال نمی‌شود | ✅ |
| ۱۴ | اجتماع منابع در `next_appointment_map` | زودترین تاریخ | ✅ |
جمع: **۳۷۶ تست موفق** در کل پروژه (۱۵ تست جدید مربوط به این قابلیت).
---
## ۸. استقرار
```bash
# ۱) بکاپ
python manage.py sqlite_db_backup # یا کپی فایل دیتابیس
# ۲) مهاجرت (دو ستون nullable — امن و سریع)
python manage.py migrate
# ۳) بررسی
python manage.py check
python manage.py test tests
```
### رفتار دادهٔ موجود
* همهٔ مشتریان با `visit_hold_until = NULL` شروع می‌کنند ⇒ **رفتار امروز دقیقاً حفظ می‌شود**.
* هماهنگی‌های قدیمی (۵ رکورد سال ۲۰۲۲) هیچ ستونی ندارند و فقط از مسیر `Action`
ارزیابی می‌شوند.
---
## ۹. نکات عملیاتی
1. **حذف هماهنگی** بهترین راه برای «لغو قرار» است.
2. اگر قرار اشتباه ثبت شد، هماهنگی را **حذف** و دوباره ثبت کنید (قرار جدید فقط
می‌تواند تمدید کند، نه آن را کوتاه‌تر کند).
---
# بخش دوم — امکانات جدید رابط کاربری صفحهٔ انتظار
این بخش قابلیت‌هایی را توضیح می‌دهد که برای «راحت دیدن اطلاعات و تصمیم درست
گرفتن» اضافه شده‌اند. طراحی **mobile first** است: در موبایل هر مشتری یک کارت
می‌شود و در دسکتاپ همان جدول قبلی؛ هیچ صفحه/منوی جدیدی اضافه نشده و شکل کلی
صفحه حفظ شده است.
## ۱. نوار آمار (بالای صفحه)
پنج کارت که هر کدام هم **عدد** را نشان می‌دهد و هم **لینک فیلتر** است:
| کارت | چه چیزی را نشان می‌دهد |
|---|---|
| کل معوق | همهٔ مشتریان عقب‌افتاده (با قرار و بدون قرار) |
| بدون قرار | کسانی که هیچ قراری ندارند — **کار عملیاتی امروز** |
| با قرار | قرار هماهنگی یا بازدید برنامه‌ریزی‌شده دارند |
| قرار هماهنگی | قرار دستی ثبت‌شده روی مشتری |
| بازدید برنامه‌ریزی‌شده | بازدید/هماهنگیِ در جریان از نوع `Action` |
کارت فعال با حاشیهٔ آبی مشخص می‌شود.
## ۲. تفکیک مدت تأخیر
زیر نوار آمار، تعداد مشتریان بر اساس **مدت عقب‌افتادگی** نمایش داده می‌شود:
- کمتر از ۳۰ روز
- ۳۰ تا ۹۰ روز
- ۹۰ تا ۱۸۰ روز
- بیش از ۶ ماه
**مبنای محاسبه:** «موعد بازدید» = آخرین بازدید معتبر + دورهٔ قالب (پیش‌فرض ۹ ماه).
برای مشتریِ بدون بازدید، مبنای مقایسه مقطع دوره است تا عدد بی‌معنا نشود.
یعنی «۳ ماه تأخیر» یعنی «۳ ماه از موعد بازدید گذشته»، نه «۳ ماه از خود بازدید».
## ۳. مرتب‌سازی
سه حالت اختیاری (پارامتر `sort` در URL، قابل بوکمارک):
| مقدار | ترتیب | کاربرد |
|---|---|---|
| `oldest` (پیش‌فرض) | قدیمی‌ترین بازدید اول | اولویت کارشناس |
| `overdue` | بیشترین تأخیر اول | ببینیم چه کسی از همه دیرتر است |
| `newest` | جدیدترین بازدید اول | بررسی موارد تازه‌معوق |
ترتیب بر مبنای **تاریخ مؤثر بازدید از `Action`** است، نه ستون
`last_bazdid_dt` (که ممکن است کهنه باشد). برای گروهِ بدون بازدید، مبنای
ترتیب مقطع دوره است.
## ۴. جست‌وجو (تکمیل‌شده)
پیش‌تر فقط جست‌وجو بر اساس **آدرس** ممکن بود. اکنون:
| فیلد | جست‌وجو در |
|---|---|
| نام و نام خانوادگی | `name_family` |
| تلفن | `phone1`، `phone2`، `phone3` |
| آدرس | `address` |
| کد مشتری | `idCode` |
نما و مرتب‌سازی هنگام جست‌وجو **حفظ می‌شوند** و دکمهٔ «حذف جستجو» برای برگشت
به فهرست کامل هست.
## ۵. چیدمان ستون‌ها (بازطراحی)
پیش‌تر ستون «ردیف» یک ستون کامل جدول را برای یک عدد دو رقمی می‌گرفت و دو ستون
با `min-width` ثابت (۱۹۰px و ۳۲۰px) تعیین عرض می‌کردند؛ نتیجه: بی‌تناسبی
فاصله‌ها و اسکرول افقی. تغییرات:
1. **ستون «ردیف» حذف شد.** شمارهٔ ردیف حالا یک **چیپ کوچک کنار نام** است
(`.ez-idx`) که هم فضا نمی‌خورد و هم برای پیدا کردن ردیف کافی است.
2. **عرض ستون‌ها با `<colgroup>` درصدی** تعیین می‌شود و جدول
`table-layout: fixed` است. بنابراین:
- مجموع عرض‌ها همیشه ۱۰۰٪ است،
- هیچ ستونی بی‌دلیل پهن نمی‌شود،
- اسکرول افقی در حالت جدول رخ نمی‌دهد.
3. **مقیاس فاصله یکسان:** همهٔ حاشیه‌ها مضرب ۰٫۲۵rem (۴px) هستند.
4. **سرستون چسبان (sticky)** هنگام اسکرول عمودی.
5. **اعداد هم‌عرض** (`tabular-nums`) تا ستون تاریخ‌ها لق نزند.
6. **تلفن با `direction: ltr`** تا ارقام در متن راست‌به‌چپ جابه‌جا نشوند.
7. **آدرس حداکثر دو خط** (`line-clamp`) با متن کامل در `title`.
8. **تأخیر به‌شکل چیپ رنگی** (خاکستری/کهربایی/قرمز/تیره) برای اسکن سریع.
| ستون | عرض | محتوا |
|---|---|---|
| نام و نام خانوادگی | ۲۳٪ | چیپ شماره + نام + کد مشتری |
| وضعیت قرار | ۱۳٪ | برچسب رنگی وضعیت |
| قرار هماهنگی | ۱۰٪ | تاریخ شمسی قرار دستی |
| بازدید برنامه‌ریزی‌شده | ۱۱٪ | تاریخ قرار از `Action` |
| آخرین بازدید | ۱۰٪ | تاریخ مؤثر واقعی |
| تأخیر | ۹٪ | چیپ تعداد روز |
| تلفن | ۱۱٪ | شمارهٔ اول (LTR) |
| آدرس | ۱۳٪ | دو خط + `title` |
**نقطهٔ شکست (breakpoint):** تا **۸۹۹px** حالت «کارت» و از **۹۰۰px** حالت
«جدول». این دو مرز باید همیشه یکی باشند؛ اگر نباشند، در بازه‌ای از عرض صفحه هر
دو حالت هم‌زمان اعمال می‌شوند و چیدمان می‌شکند. یک تست اختصاصی
(`test_card_and_table_breakpoints_agree`) همین را قفل کرده است.
## ۶. قابلیت‌های موبایل
- هر ردیف یک **کارت** می‌شود. نام مشتری سرِ کارت است (بدون برچسب) و بقیهٔ
مقادیر با برچسب ثابت ۶٫۵rem هم‌تراز می‌شوند.
- برچسب «وضعیت» حذف می‌شود چون خودِ برچسب رنگی گویاست.
- دکمهٔ **«تماس»** (`tel:`) و **«ثبت اقدام»** به‌صورت دو دکمهٔ تمام‌عرض.
- شماره‌های دوم و سوم در موبایل مخفی‌اند (برای تماس، شمارهٔ اول کافی است).
## ۷. نمای خالی
اگر در نما/جست‌وجوی جاری موردی نباشد، پیام «موردی در این نما پیدا نشد»
نمایش داده می‌شود (به‌جای جدولِ خالی و گمراه‌کننده).
## ۸. کارایی
| سنجه | مقدار |
|---|---|
| تعداد کوئری در بارگذاری صفحه | **۱۹** |
| رندر کامل صفحه روی دادهٔ واقعی (۳٬۲۴۳ معوق) | ~۱ ثانیه |
نکتهٔ پیاده‌سازی: دورهٔ قالب **یک بار** خوانده می‌شود و به حلقهٔ محاسبهٔ تأخیر
پاس داده می‌شود (`months=period_months`). اگر این پارامتر پاس نشود، برای هر
مشتری یک کوئری اضافه زده می‌شود (باگ N+1 که در تست‌ها کشف و رفع شد).
## ۹. آنچه تغییر نکرد
- منطق «چه کسی عقب‌افتاده است» (همان `last_visit_map` + `due_cut`).
- قاعدهٔ اعتبار قرار دستی (وجود هماهنگی فعال).
- اسکنر پیامک، `recalc_last_bazdid` و چرخهٔ ۹ ماهه.
- لینک‌های قدیمی `?view=...` و رفتار پیش‌فرض (نمای «بدون قرار»).
## ۱۰. پارامترهای URL
```
/panel/entezar/ نمای پیش‌فرض (بدون قرار، قدیمی‌ترین)
?view=all همهٔ معوق‌ها
?view=coordinated با قرار
?view=manual فقط قرار هماهنگی
?view=scheduled فقط بازدید برنامه‌ریزی‌شده
?sort=overdue بیشترین تأخیر اول
?sort=newest جدیدترین بازدید اول
?searchVal=رضایی&searchOn=name جست‌وجوی نام
?searchVal=0912&searchOn=phone جست‌وجوی تلفن
?view=all&sort=overdue&page=3 ترکیب‌پذیر
```
مقدار نامعتبر `view` یا `sort` بی‌صدا به پیش‌فرض برمی‌گردد (هیچ خطای ۴۰۰/۵۰۰).
3. پس از تاریخ قرار، مشتری **خودکار** در گروه A ظاهر می‌شود؛ نیازی به کار دستی نیست.
4. عدد «بدون قرار» در داشبورد همان لیست کاریِ فوری است.