Files
khatibi/docs/visit_hold_entezar.md
T
2026-10-21 12:24:52 +03:30

32 KiB
Raw Blame History

«قرار بعدی» بازدید و صفحهٔ انتظار — توضیح کامل عملکرد

این سند رفتار نهایی صفحهٔ /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 یا وضعیت «انجام شده» ثبت نشود). اگر کاربر خودش تیک زده باشد، دست‌نخورده می‌ماند.
  • حالت ویرایش: تأیید گرفته نمی‌شود (کاربر فقط رکورد موجود را اصلاح می‌کند)؛ اما کادر هشدار برای هماهنگیِ انجام‌شده به‌صورت اطلاعاتی نمایش داده می‌شود.
  • باز شدن مجدد مودال: تیک، هشدار و وضعیت تأیید همیشه ریست می‌شوند.

بارگذاری مقدار ذخیره‌شده در حالت ویرایش

هر ردیف اقدام، مقدار فعلی Customer.visit_hold_until را به‌صورت ISO در صفت customer_hold نگه می‌دارد. هنگام کلیک روی ردیف (باز کردن فرم ویرایش)، تابع seedHoldPicker() آن را به تقویم شمسی و فیلد مخفی visitHoldUntil می‌نویسد.

باگی که این بخش رفع می‌کند: قبلاً فرم ویرایش مقدار را از دیتابیس نمی‌خواند. مقدار در سرور درست ذخیره می‌شد ولی چون فیلد هنگام باز شدن مجدد خالی نمایش داده می‌شد، کاربر فکر می‌کرد «ثبت نشده است».

ترتیب کار مهم است: seedHoldPicker() بعد از toggleCoordinationField() صدا زده می‌شود، چون برای انواع غیرهماهنگی، toggle فیلد را پاک می‌کند.

ایمنی ویرایش رکوردهای قدیمی

در حالت ویرایش، تغییر نوع اقدام به غیرهماهنگی، قرار دستی را پاک نمی‌کند؛ زیرا رکورد ویرایش‌شده ممکن است قدیمی‌تر از یک هماهنگیِ جدیدتر باشد و پاک‌کردن، قرار معتبر را از بین می‌برد. اعتبارسنجی manual_holds_map (شرط «آخرین فعالیت = هماهنگی») خودش قرار را بی‌اثر می‌کند.

در حالت ایجاد برعکس است: اقدام تازه آخرین فعالیت مشتری است، پس ثبت بازدید جدید قرار قبلی را پاک می‌کند.

۴.۳ راهنمای بازشونده (فقط «هماهنگی»)

زیر فیلد تاریخ پیگیری، یک دکمهٔ راهنما با آیکون ! قرار دارد که پنل توضیحات را باز/بسته می‌کند. محتوای پنل:

  • «این تاریخ چه کاری می‌کند؟» — تعریف ساده + اینکه تا پایان همان روز معتبر است.
  • «مثال» — سناریوی واقعی با تاریخ نمونه.
  • «نکات مهم» — خالی + تیک ⇒ ۱۴ روز، خالی + بدون تیک ⇒ بدون تعویق، فقط تمدید (نه کوتاه‌کردن)، حذف/لغو هماهنگی ⇒ بازگشت فوری، سقف ۶ ماه، تفاوت با «تاریخ بازدید پرسنل»، و توقف پیامک یادآوری در مدت تعویق.

رفتار:

رویداد نتیجه
انتخاب نوع = هماهنگی دکمهٔ راهنما ظاهر می‌شود (پنل بسته)
کلیک روی دکمه پنل باز/بسته می‌شود و aria-expanded به‌روز می‌شود
تغییر نوع به غیر هماهنگی فیلد و راهنما پنهان و پنل بسته می‌شود
باز شدن مجدد مودال (ایجاد یا ویرایش) پنل همیشه بسته شروع می‌شود

دسترسی‌پذیری: دکمه aria-expanded و aria-controls="holdHelpPanel" دارد و پنل با is-hidden کنترل می‌شود.

قواعد اعتبارسنجی (سرور)

  • تاریخ قرار نباید پیش از امروز باشد ⇒ پیام: «تاریخ قرار نمی‌تواند پیش از امروز باشد.»
  • حداکثر ۱۸۳ روز (≈۶ ماه) جلوتر ⇒ پیام: «قرار بعدی حداکثر می‌تواند ۱۸۳ روز (۶ ماه) جلوتر باشد.»
  • تاریخ «امروز» پذیرفته است و به ۲۳:۵۹:۵۹ همان روز تبدیل می‌شود (تا پایان روز معتبر).
  • اگر اعتبارسنجی رد شود، هیچ اقدامی ذخیره نمی‌شود.

قاعدهٔ «تمدید مجاز، کوتاه‌کردن ممنوع» (max)

قرار ذخیره‌شده همیشه بزرگ‌ترین تاریخِ ثبت‌شده است:

visit_hold_until = max(مقدار فعلی، تاریخ جدید)
وضعیت نتیجه
قرار فعلی ۱ آذر، کارشناس جدید ۸ آذر تمدید می‌شود به ۸ آذر ✅
قرار فعلی ۸ آذر، کارشناس جدید ۱ آذر تغییری نمی‌کند (۸ آذر می‌ماند)

منطق: اگر مشتری گفته «۸ آذر تماس بگیرید»، سیستم نباید ۱ آذر زنگ بزند. در مقابل، یک ثبت اشتباه نباید قرارِ طولانیِ قبلی را نابود کند.

برای عقب‌بردن قرار (مثلاً مشتری گفت زودتر بیایید): هماهنگی را حذف و دوباره با تاریخ جدید ثبت کنید.


۵. صفحهٔ انتظار: چهار گروه و فیلترها

تفکیک بر اساس نوعِ منبع است، نه «چند گروه ثابت». هر مشتری می‌تواند صفر تا سه منبع داشته باشد:

منبع تعریف فیلتر
manual ستون visit_hold_until (تعویق از فرم هماهنگی) ?view=manual
scheduled اقدام bazdid + waite با تاریخ آینده ?view=scheduled
coordination اقدام hamahangi (waite/finish) با تاریخ آینده ?view=coordination

باگ قبلی: coordination با scheduled در یک گروه جمع می‌شد؛ نتیجه اینکه مشتریانی که فقط «هماهنگی» داشتند در نمای «بازدید برنامه‌ریزی‌شده» دیده می‌شدند و برچسب «بازدید برنامه‌ریزی‌شده» می‌گرفتند. حالا هر منبع فیلتر و ستون خودش را دارد.

فیلترها (قابل بوکمارک و ترکیب با جست‌وجو)

URL نمایش شامل
/panel/entezar/ (بدون پارامتر) پیش‌فرض = بدون قرار (رفتار قبلی) بدون هیچ منبع
?view=uncoordinated بدون قرار بدون هیچ منبع
?view=manual قرار دستی هر مشتری با manual
?view=scheduled بازدید برنامه‌ریزی‌شده هر مشتری با scheduled
?view=coordination هماهنگی هر مشتری با coordination
?view=coordinated همهٔ دارای قرار اجتماع هر سه منبع
?view=all همهٔ معوق‌ها همه
مقدار نامعتبر برگشت به پیش‌فرض، بدون خطا بدون هیچ منبع

چون یک مشتری می‌تواند چند منبع داشته باشد، در چند فیلتر دیده می‌شود؛ اما در ?view=coordinated فقط یک‌بار شمرده می‌شود. به همین دلیل جمع سه کارت منبع می‌تواند از «با قرار» بیشتر باشد (صفحه این را در راهنمای بالای خود توضیح می‌دهد).

ستون‌های جدول

ستون توضیح
وضعیت قرار «بدون قرار» / «قرار دستی» / «بازدید برنامه» / «هماهنگی» / «N قرار»
قرار دستی تاریخ شمسی قرار دستی (یا —)
بازدید برنامه‌ریزی‌شده تاریخ شمسی اقدام بازدید با تاریخ آینده (یا —)
هماهنگی تاریخ شمسی اقدام هماهنگی با تاریخ آینده (یا —)
آخرین بازدید بدون تغییر نسبت به قبل

شمارندهٔ داشبورد

CacheTable('9mahbazdid') = بدون قرار (کارِ فوری). هر بار باز شدن صفحه به‌روز می‌شود. با تغییر تفکیک، این عدد تغییر نمی‌کند.


۶. تعامل با سایر بخش‌ها

بخش اثر
آخرین بازدید (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 زودترین تاریخ ✅

جمع: ۳۷۶ تست موفق در کل پروژه (۱۵ تست جدید مربوط به این قابلیت).


۸. استقرار

# ۱) بکاپ
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).
  • قاعدهٔ اعتبار قرار دستی (وجود هماهنگی فعال + آخرین فعالیت بودن).
  • visit_policy.py به‌طور کامل: next_appointment_map، scheduled_visits_map، coordination_holds_map — پس اسکنر پیامک، recalc_last_bazdid و چرخهٔ ۹ ماهه دقیقاً مثل قبل کار می‌کنند.
  • شمارندهٔ داشبورد (CacheTable('9mahbazdid') = بدون قرار).
  • لینک‌های قدیمی ?view=... و رفتار پیش‌فرض (نمای «بدون قرار»).

۱۰. پارامترهای URL

/panel/entezar/                                  نمای پیش‌فرض (بدون قرار، قدیمی‌ترین)
?view=all                                        همهٔ معوق‌ها
?view=coordinated                                با قرار (اجتماع سه منبع)
?view=manual                                     فقط قرار دستی
?view=scheduled                                  فقط بازدید برنامه‌ریزی‌شده
?view=coordination                               فقط هماهنگی
?sort=overdue                                    بیشترین تأخیر اول
?sort=newest                                     جدیدترین بازدید اول
?searchVal=رضایی&searchOn=name                   جست‌وجوی نام
?searchVal=0912&searchOn=phone                   جست‌وجوی تلفن
?view=all&sort=overdue&page=3                    ترکیب‌پذیر

مقدار نامعتبر view یا sort بی‌صدا به پیش‌فرض برمی‌گردد (هیچ خطای ۴۰۰/۵۰۰).

  1. پس از تاریخ قرار، مشتری خودکار در گروه A ظاهر می‌شود؛ نیازی به کار دستی نیست.
  2. عدد «بدون قرار» در داشبورد همان لیست کاریِ فوری است.