490 lines
28 KiB
Markdown
490 lines
28 KiB
Markdown
# «قرار بعدی» بازدید و صفحهٔ انتظار — توضیح کامل عملکرد
|
||
|
||
این سند رفتار نهایی صفحهٔ `/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)` — **همان تابعی که اسکنر پیامک
|
||
هم استفاده میکند**، پس صفحه و پیامک هرگز واگرا نمیشوند.
|
||
|
||
**چرا مرز زمانیِ دو منبع فرق دارد؟**
|
||
|
||
* در `scheduled`، `actionDateTime` یک **قرارِ آینده** است که کارشناس با تقویم
|
||
روزانه انتخاب میکند؛ پس «قرار امروز» باید تا پایان امروز معتبر بماند. با
|
||
مقایسهٔ لحظهای، بازدیدِ امروز بلافاصله پس از گذشتن ساعتش از فهرست ناپدید
|
||
میشد (باگی که گزارش شد).
|
||
* در `coordination`، `actionDateTime` **زمانِ تماس** است، نه قرار آینده. اگر آن
|
||
را هم تا پایان روز معتبر بگیریم، هر تماسِ امروز مشتری را تا آخر امروز از
|
||
گروه «قرار دستی» بیرون میبرد و سناریوی اصلی (تماس امروز + قرار دو هفته بعد)
|
||
بیاثر میشد.
|
||
|
||
اگر مشتری چند قرار داشته باشد، **زودترین** تاریخ ملاک نمایش است؛ اما برای
|
||
«بازگشت به فهرست» کافی است **آخرین** قرار منقضی شود.
|
||
|
||
---
|
||
|
||
## ۴. فرم ثبت اقدام (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. عدد «بدون قرار» در داشبورد همان لیست کاریِ فوری است.
|
||
|
||
|
||
|