Files
khatibi/docs/entezar_action_fields_report.html
T
2026-10-22 14:01:08 +03:30

1234 lines
83 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>گزارش رفتاری: اقدام، هماهنگی، قرار و صفحهٔ انتظار</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;700;900&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['Vazirmatn', 'system-ui', 'sans-serif'] },
colors: {
ink: '#0b1220',
panel: '#111a2e',
card: '#16213a',
line: '#22304d',
brand: '#38bdf8',
ok: '#34d399',
warn: '#fbbf24',
bad: '#f87171',
coord: '#a78bfa'
}
}
}
}
</script>
<style>
html { scroll-behavior: smooth; overflow-x: hidden; }
body { background: radial-gradient(1200px 600px at 80% -10%, #1e3a5f55, transparent), #0b1220; overflow-x: hidden; }
.code { direction: ltr; text-align: left; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* جداکردن جهت متن کد از متن فارسی اطراف؛ وگرنه «?view=today&missed=1» جابه‌جا دیده می‌شود */
code { unicode-bidi: isolate; }
pre { unicode-bidi: isolate; }
.glow { box-shadow: 0 0 0 1px #22304d, 0 20px 50px -20px #000; }
.arrow { position: relative; }
.arrow::after {
content: ""; position: absolute; right: 50%; bottom: -10px; height: 10px; width: 2px;
background: #38bdf8; transform: translateX(50%);
}
.arrow:last-child::after { display: none; }
table.report tbody tr:hover { background: #1b2741; }
.bar { transition: width .8s cubic-bezier(.2,.8,.2,1); }
details > summary { cursor: pointer; list-style: none; }
details > summary::-webkit-details-marker { display: none; }
/* روی موبایل، سلول‌های nowrap جدول را به شکستن خط وادار می‌کنیم تا صفحه افقی سرریز نکند */
@media (max-width: 640px) {
table.report th, table.report td { white-space: normal !important; }
table.report { font-size: 11px; }
}
@media print {
body { background: #fff; color: #000; }
nav, .no-print { display: none !important; }
.glow { box-shadow: none; border: 1px solid #ccc; }
}
</style>
<!-- متن جایگزین CDN: اگر cdn.tailwindcss.com در دسترس نباشد، همین کلاس‌ها کار می‌کنند -->
<style id="fallback-css">
*, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: #22304d; }
body { margin: 0; line-height: 1.6; font-family: Vazirmatn, system-ui, sans-serif; }
h1,h2,h3,h4,p,ol,ul,pre { margin: 0; } table { border-collapse: collapse; width: 100%; }
th, td { text-align: right; } a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; cursor: pointer; }
/* متن */
.text-white{color:#fff}.text-slate-200{color:#e2e8f0}.text-slate-300{color:#cbd5e1}
.text-slate-400{color:#94a3b8}.text-slate-500{color:#64748b}.text-slate-600{color:#475569}
.text-brand{color:#38bdf8}.text-sky-300{color:#7dd3fc}
.text-violet-200{color:#ddd6fe}.text-violet-300{color:#c4b5fd}.text-violet-300\/70{color:#c4b5fdb3}
.text-violet-100\/90{color:#ede9fee6}
.text-emerald-200{color:#a7f3d0}.text-emerald-300{color:#6ee7b7}
.text-rose-100{color:#ffe4e6}.text-rose-100\/90{color:#ffe4e6e6}
.text-rose-200{color:#fecdd3}.text-rose-300{color:#fda4af}.text-rose-300\/70{color:#fda4afb3}
.text-amber-100{color:#fef3c7}.text-amber-100\/90{color:#fef3c7e6}
.text-amber-200{color:#fde68a}.text-amber-300{color:#fcd34d}.text-amber-300\/70{color:#fcd34db3}
/* پس‌زمینه */
.bg-ink,.bg-ink\/60,.bg-ink\/80,.bg-ink\/90{background-color:#0b1220}
.bg-ink\/60{background-color:#0b122099}.bg-ink\/80{background-color:#0b1220cc}.bg-ink\/90{background-color:#0b1220e6}
.bg-panel,.bg-panel\/60{background-color:#111a2e}.bg-panel\/60{background-color:#111a2e99}
.bg-card,.bg-card\/50,.bg-card\/60,.bg-card\/70{background-color:#16213a}
.bg-card\/50{background-color:#16213a80}.bg-card\/60{background-color:#16213a99}.bg-card\/70{background-color:#16213ab3}
.bg-brand{background-color:#38bdf8}.bg-brand\/5{background-color:#38bdf80d}
.bg-brand\/10{background-color:#38bdf81a}.bg-brand\/15{background-color:#38bdf826}
.bg-violet-400,.bg-violet-400\/5,.bg-violet-400\/10,.bg-violet-400\/20,.bg-violet-500\/20{background-color:#a78bfa}
.bg-violet-400\/5{background-color:#a78bfa0d}.bg-violet-400\/10{background-color:#a78bfa1a}
.bg-violet-400\/20,.bg-violet-500\/20{background-color:#a78bfa33}
.bg-emerald-400{background-color:#34d399}.bg-emerald-400\/5{background-color:#34d3990d}
.bg-emerald-400\/10{background-color:#34d3991a}.bg-emerald-400\/15{background-color:#34d39926}
.bg-emerald-500\/5{background-color:#10b9810d}.bg-emerald-500\/10{background-color:#10b9811a}
.bg-emerald-500\/15{background-color:#10b98126}.bg-emerald-500\/20{background-color:#10b98133}
.bg-rose-400{background-color:#fb7185}.bg-rose-400\/10{background-color:#fb71851a}
.bg-rose-500\/5{background-color:#f43f5e0d}.bg-rose-500\/10{background-color:#f43f5e1a}
.bg-rose-500\/15{background-color:#f43f5e26}.bg-rose-500\/20{background-color:#f43f5e33}
.bg-amber-400{background-color:#fbbf24}.bg-amber-400\/5{background-color:#fbbf240d}
.bg-amber-400\/10{background-color:#fbbf241a}.bg-amber-400\/15,.bg-amber-400\/20{background-color:#fbbf2433}
.bg-sky-400\/5{background-color:#38bdf80d}.bg-sky-400\/15{background-color:#38bdf826}
.bg-\[\#0d1424\]{background-color:#0d1424}
.bg-gradient-to-l{background-image:linear-gradient(to left,#38bdf8,#a78bfa)}
/* حاشیه */
.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}
.border-brand\/30,.border-brand\/40{border-color:#38bdf84d}
.border-emerald-400\/25,.border-emerald-400\/30,.border-emerald-400\/40,.border-emerald-500\/30,.border-emerald-500\/40{border-color:#34d3994d}
.border-rose-400\/25,.border-rose-400\/30,.border-rose-400\/40,.border-rose-500\/30,.border-rose-500\/40{border-color:#fb71854d}
.border-amber-400\/20,.border-amber-400\/25,.border-amber-400\/30{border-color:#fbbf2440}
.border-sky-400\/25,.border-sky-400\/30{border-color:#38bdf840}
.border-violet-400\/30,.border-violet-400\/40{border-color:#a78bfa4d}
.divide-y > * + *{border-top-width:1px;border-color:#22304d}
/* شعاع */
.rounded{border-radius:.25rem}.rounded-md{border-radius:.375rem}.rounded-lg{border-radius:.5rem}
.rounded-xl{border-radius:.75rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}
/* چیدمان */
.flex{display:flex}.grid{display:grid}.hidden{display:none}.block{display:block}
.flex-wrap{flex-wrap:wrap}.flex-1{flex:1 1 0%}
.items-center{align-items:center}.items-start{align-items:flex-start}.items-baseline{align-items:baseline}
.justify-between{justify-content:space-between}.place-items-center{place-items:center}
.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}
.space-y-2 > * + *{margin-top:.5rem}.space-y-3 > * + *{margin-top:.75rem}
.space-y-4 > * + *{margin-top:1rem}
.mx-auto{margin-left:auto;margin-right:auto}
.max-w-3xl{max-width:48rem}.max-w-6xl{max-width:72rem}.w-full{width:100%}
.w-7{width:1.75rem}.h-7{height:1.75rem}.w-40{width:10rem}.w-0{width:0}
.h-0\.5{height:2px}.h-2{height:.5rem}.h-3{height:.75rem}.h-14{height:3.5rem}
.sticky{position:sticky}.top-0{top:0}.z-40{z-index:40}.relative{position:relative}
.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}
.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}
.text-right{text-align:right}.uppercase{text-transform:uppercase}
.tracking-wide{letter-spacing:.025em}.antialiased{-webkit-font-smoothing:antialiased}
.backdrop-blur{backdrop-filter:blur(8px)}
/* فاصله */
.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}
.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}
.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}
.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}
.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}
.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}
.py-8{padding-top:2rem;padding-bottom:2rem}.pt-3{padding-top:.75rem}.pt-12{padding-top:3rem}
.pb-2{padding-bottom:.5rem}.pb-8{padding-bottom:2rem}.pb-24{padding-bottom:6rem}
.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}
.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-16{margin-top:4rem}
.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}
.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.scroll-mt-20{scroll-margin-top:5rem}
/* تایپوگرافی */
.text-\[10px\]{font-size:10px;line-height:1.5}.text-\[11px\]{font-size:11px;line-height:1.6}
.text-\[11\.5px\]{font-size:11.5px;line-height:1.7}.text-\[12px\]{font-size:12px;line-height:1.7}
.text-xs{font-size:.75rem;line-height:1.6}.text-sm{font-size:.875rem;line-height:1.7}
.text-lg{font-size:1.125rem}.text-xl{font-size:1.25rem;line-height:1.5}
.text-3xl{font-size:1.875rem;line-height:1.2}
.font-medium{font-weight:500}.font-bold{font-weight:700}.font-black{font-weight:900}
.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}
.leading-7{line-height:1.75rem}.leading-tight{line-height:1.25}
.font-sans{font-family:Vazirmatn,system-ui,sans-serif}
.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
/* بریک‌پوینت‌های استفاده‌شده */
@media (min-width:640px){
.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.sm\:p-6{padding:1.5rem}.sm\:text-4xl{font-size:2.25rem;line-height:1.15}
.sm\:text-base{font-size:1rem}
}
@media (min-width:768px){
.md\:flex{display:flex}.md\:hidden{display:none}
.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (min-width:1024px){
.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}
.lg\:col-span-2{grid-column:span 2 / span 2}.lg\:col-span-3{grid-column:span 3 / span 3}
}
/* hover ساده */
a:hover.hover\:bg-card{background-color:#16213a}
a:hover.hover\:text-white{color:#fff}
a.hover\:bg-emerald-500\/10:hover{background-color:#10b9811a}
a.hover\:bg-rose-500\/10:hover{background-color:#f43f5e1a}
</style>
</head>
<body class="font-sans text-slate-200 antialiased">
<!-- ======================= NAV ======================= -->
<nav class="sticky top-0 z-40 border-b border-line bg-ink/90 backdrop-blur no-print">
<div class="mx-auto max-w-6xl px-4">
<div class="flex h-14 items-center justify-between gap-4">
<a href="#top" class="flex items-center gap-2 font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-brand">◈</span>
<span class="text-sm sm:text-base">گزارش رفتاری سیستم</span>
</a>
<div class="hidden items-center gap-1 text-xs md:flex">
<a href="#summary" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">خلاصه</a>
<a href="#flow" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">نقشهٔ داده</a>
<a href="#field1" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">تاریخ انجام</a>
<a href="#field2" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">تا تاریخ پیگیری</a>
<a href="#sms" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">پیامک</a>
<a href="#today" class="rounded-lg px-2 py-1.5 text-slate-300 hover:bg-card hover:text-white">صفحهٔ امروز</a>
<a href="#bug" class="rounded-lg px-2 py-1.5 text-rose-300 hover:bg-rose-500/10">باگ missed</a>
<a href="#fix" class="rounded-lg px-2 py-1.5 text-emerald-300 hover:bg-emerald-500/10">پیشنهاد رفع</a>
</div>
<button onclick="window.print()" class="rounded-lg border border-line px-3 py-1.5 text-xs text-slate-300 hover:bg-card no-print">چاپ / PDF</button>
</div>
<div class="flex gap-1 overflow-x-auto pb-2 text-[11px] md:hidden">
<a href="#summary" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300 bg-card">خلاصه</a>
<a href="#flow" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">نقشه</a>
<a href="#field1" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">تاریخ انجام</a>
<a href="#field2" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">تعویق</a>
<a href="#sms" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">پیامک</a>
<a href="#done" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">تیک انجام شد</a>
<a href="#today" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">امروز</a>
<a href="#bug" class="whitespace-nowrap rounded-lg px-2 py-1 text-rose-300">باگ</a>
<a href="#fix" class="whitespace-nowrap rounded-lg px-2 py-1 text-emerald-300">رفع</a>
<a href="#repro" class="whitespace-nowrap rounded-lg px-2 py-1 text-slate-300">بازتولید</a>
</div>
</div>
<div id="progress" class="h-0.5 w-0 bg-gradient-to-l from-brand to-coord"></div>
</nav>
<!-- ======================= HERO ======================= -->
<header id="top" class="mx-auto max-w-6xl px-4 pb-8 pt-12">
<div class="flex flex-wrap items-center gap-2 text-xs">
<span class="rounded-full border border-brand/40 bg-brand/10 px-3 py-1 text-brand">Django 3.2 · SQLite</span>
<span class="rounded-full border border-line bg-card px-3 py-1 text-slate-300">۳۲ شبیه‌سازی سبز</span>
<span class="rounded-full border border-line bg-card px-3 py-1 text-slate-300">ممیزی روی کپی دیتابیس واقعی</span>
<span class="rounded-full border border-emerald-500/40 bg-emerald-500/10 px-3 py-1 text-emerald-300">کدِ تولیدی بدون تغییر</span>
</div>
<h1 class="mt-5 text-3xl font-black leading-tight text-white sm:text-4xl">
دو فیلد تاریخِ «اقدام»، صفحهٔ <span class="text-brand">انتظار</span> و پیامک‌های بازدید
</h1>
<p class="mt-4 max-w-3xl text-sm leading-7 text-slate-300">
تحلیل کامل رفتار <code class="rounded bg-card px-1 text-brand">Action</code> نوع «هماهنگی» و «بازدید»،
اثرشان بر <code class="rounded bg-card px-1 text-brand">/panel/entezar/?view=today</code> و
<code class="rounded bg-card px-1 text-rose-300">?view=today&amp;missed=1</code>،
به‌همراه اعداد واقعی، خط زمانی نمونه‌ها و ریشهٔ باگ.
</p>
<div class="mt-6 grid gap-3 text-xs text-slate-400 sm:grid-cols-3">
<div class="rounded-xl border border-line bg-card/60 p-3">
<div class="text-slate-500">دیتابیس ممیزی</div>
<div class="mt-1 font-mono text-[11px] text-slate-300 code">khatibi92669266_…_20261021_223009.sqlite3</div>
</div>
<div class="rounded-xl border border-line bg-card/60 p-3">
<div class="text-slate-500">مصنوعات</div>
<div class="mt-1 leading-6 text-slate-300">audit script · simulation tests · md report</div>
</div>
<div class="rounded-xl border border-line bg-card/60 p-3">
<div class="text-slate-500">داده</div>
<div class="mt-1 text-slate-300">۱۰٬۸۲۲ مشتری · ۲٬۵۴۱ هماهنگی فعال · ۵۰٬۰۹۰ بازدید انجام‌شده</div>
</div>
</div>
</header>
<!-- ======================= SUMMARY ======================= -->
<main class="mx-auto max-w-6xl px-4 pb-24">
<section id="summary" class="scroll-mt-20">
<h2 class="mb-4 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۰</span>
پاسخ کوتاه
</h2>
<div class="grid gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-line bg-card/70 p-5 glow">
<div class="flex items-start justify-between gap-3">
<h3 class="font-bold text-white">«تاریخ و ساعتی که کار باید انجام شود»</h3>
<span class="rounded-md bg-brand/15 px-2 py-1 text-[11px] text-brand code">actionDateTime</span>
</div>
<p class="mt-3 text-sm leading-7 text-slate-300">
زمانِ انجام وظیفه. بسته به نوع اقدام سه معنا دارد:
برای <b class="text-sky-300">بازدید در انتظار</b> = زمان قرار بازدید،
برای <b class="text-violet-300">هماهنگی</b> = زمان تماس،
برای بقیه = زمان وظیفه. <b>۸ مصرف‌کننده</b> دارد.
</p>
<div class="mt-3 flex flex-wrap gap-1 text-[11px]">
<span class="rounded bg-panel px-2 py-1">لیست وظایف</span>
<span class="rounded bg-panel px-2 py-1">آخرین بازدید</span>
<span class="rounded bg-panel px-2 py-1">لنگر SMS</span>
<span class="rounded bg-panel px-2 py-1">قرار scheduled</span>
<span class="rounded bg-panel px-2 py-1">قرار coordination</span>
<span class="rounded bg-panel px-2 py-1">فهرست روز</span>
<span class="rounded bg-panel px-2 py-1">missed</span>
<span class="rounded bg-panel px-2 py-1">متن visit_created</span>
</div>
</div>
<div class="rounded-2xl border border-line bg-card/70 p-5 glow">
<div class="flex items-start justify-between gap-3">
<h3 class="font-bold text-white">«تا تاریخ پیگیری نشود»</h3>
<span class="rounded-md bg-amber-400/15 px-2 py-1 text-[11px] text-amber-300 code">visit_hold_until</span>
</div>
<p class="mt-3 text-sm leading-7 text-slate-300">
قرارِ بعدی مشتری روی خودِ <code class="text-amber-300">Customer</code>، پایانِ همان روز.
فقط برای «هماهنگی» نوشته می‌شود، پیش‌فرض <b>۱۴ روز</b>، سقف <b>۱۸۳ روز</b>، فقط رو به جلو.
<b>۳ مصرف‌کننده</b> دارد و اعتبارش وابسته به «آخرین فعالیت = هماهنگی» است.
</p>
<div class="mt-3 flex flex-wrap gap-1 text-[11px]">
<span class="rounded bg-panel px-2 py-1">برچسب/ستون «قرار دستی»</span>
<span class="rounded bg-panel px-2 py-1">فهرست پیگیری همان روز</span>
<span class="rounded bg-panel px-2 py-1">قطع یادآوری SMS</span>
</div>
</div>
</div>
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-xs text-slate-400">تیک «انجام شد» در هماهنگی</div>
<div class="mt-2 text-sm leading-6 text-slate-200">
<code class="text-emerald-300">finish</code> + تاریخ اتمام (خالی ⇒ الان) +
تعویقِ واردشده؛ اگر خالی باشد <b class="text-amber-300">+۱۴ روز</b>
</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-xs text-slate-400">بدون تیک</div>
<div class="mt-2 text-sm leading-6 text-slate-200">
<code class="text-sky-300">waite</code> و بدون پیش‌فرض؛ فقط اگر تاریخ تعویق پر شود قرار ثبت می‌شود
</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-xs text-slate-400">«۲ قرار» یعنی</div>
<div class="mt-2 text-sm leading-6 text-slate-200">
بیش از یک <b>منبعِ</b> قرار (دستی / بازدید برنامه / هماهنگی) — نه لزوماً دو توافق
</div>
</div>
<div class="rounded-xl border border-rose-500/40 bg-rose-500/10 p-4">
<div class="text-xs text-rose-300">باگ missed</div>
<div class="mt-2 text-sm leading-6 text-rose-100">
۸۸٪ ردیف‌ها بازدید انجام‌شده دارند؛ ۳۸ ردیف قرارِ معتبر آینده دارند
</div>
</div>
</div>
</section>
<!-- ======================= FLOW ======================= -->
<section id="flow" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۱</span>
نقشهٔ داده: از فرم تا تصمیم
</h2>
<p class="mb-5 text-sm text-slate-400">هر فیلد کجا ذخیره می‌شود و کدام تابع آن را می‌خواند.</p>
<div class="rounded-2xl border border-line bg-card/60 p-4 sm:p-6">
<div class="grid gap-4 lg:grid-cols-3">
<div class="rounded-xl border border-line bg-panel p-3">
<div class="mb-2 text-[11px] uppercase tracking-wide text-slate-500">۱. فرم مودال</div>
<div class="space-y-2 text-[12px]">
<div class="rounded-lg bg-ink px-2 py-1.5 text-slate-200">تاریخ و ساعتی که کار باید انجام شود</div>
<div class="rounded-lg bg-ink px-2 py-1.5 text-slate-200">تا تاریخ پیگیری نشود</div>
<div class="rounded-lg bg-ink px-2 py-1.5 text-slate-200">تیک «این کار انجام شده است»</div>
<div class="rounded-lg bg-ink px-2 py-1.5 text-slate-200">تاریخ و ساعت اتمام کار</div>
</div>
<div class="mt-3 break-all rounded bg-ink px-2 py-1 text-[10px] text-slate-500 code">customerActionInc.html</div>
</div>
<div class="rounded-xl border border-line bg-panel p-3">
<div class="mb-2 text-[11px] uppercase tracking-wide text-slate-500">۲. نرمال‌سازی و اعتبارسنجی</div>
<div class="space-y-2 text-[12px] text-slate-300">
<div class="rounded-lg bg-ink px-2 py-1.5"><span class="text-brand code">_parse_form_datetime</span><div class="text-[10px] text-slate-500">ms → datetime aware</div></div>
<div class="rounded-lg bg-ink px-2 py-1.5"><span class="text-amber-300 code">normalize_hold_until</span><div class="text-[10px] text-slate-500">گذشته ✗ · بیش از ۱۸۳ روز ✗ · پایان روز</div></div>
<div class="rounded-lg bg-ink px-2 py-1.5"><span class="text-emerald-300 code">normalize_finish_datetime</span><div class="text-[10px] text-slate-500">خالی ⇒ now</div></div>
<div class="rounded-lg bg-ink px-2 py-1.5"><span class="text-slate-300 code">validate_action_payload</span><div class="text-[10px] text-slate-500">بازدید انجام‌شده: حداکثر +۳۰ روز</div></div>
</div>
<div class="mt-3 break-all rounded bg-ink px-2 py-1 text-[10px] text-slate-500 code">views/actions.py:84-146</div>
</div>
<div class="rounded-xl border border-line bg-panel p-3">
<div class="mb-2 text-[11px] uppercase tracking-wide text-slate-500">۳. دیتابیس</div>
<div class="space-y-2 text-[12px]">
<div class="rounded-lg border border-brand/30 bg-brand/5 px-2 py-1.5">
<div class="text-brand code">Action.actionDateTime</div>
<div class="text-[10px] text-slate-500">زمان انجام / تماس / قرار</div>
</div>
<div class="rounded-lg border border-amber-400/30 bg-amber-400/5 px-2 py-1.5">
<div class="text-amber-300 code">Customer.visit_hold_until</div>
<div class="text-[10px] text-slate-500">قرار بعدی (پایان روز)</div>
</div>
<div class="rounded-lg border border-emerald-400/30 bg-emerald-400/5 px-2 py-1.5">
<div class="text-emerald-300 code">status / finishDateTime</div>
<div class="text-[10px] text-slate-500 code">waite / finish / cancell</div>
</div>
<div class="rounded-lg border border-line bg-ink px-2 py-1.5">
<div class="text-slate-300 code">createdDT</div>
<div class="text-[10px] text-slate-500">ترتیب ثبت (قاعدهٔ آخرین فعالیت)</div>
</div>
</div>
</div>
</div>
<div class="mt-5 rounded-xl border border-line bg-ink/60 p-3">
<div class="mb-2 text-[11px] uppercase tracking-wide text-slate-500">۴. مصرف‌کننده‌های نهایی</div>
<div class="grid gap-2 text-[12px] sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-lg border border-line bg-panel px-3 py-2">
<div class="text-slate-200 code">visit_policy</div>
<div class="mt-1 text-[10px] leading-5 text-slate-500">last_visit_map · cycle_anchor_map · appointment_sources_map · followups_on_date · overdue_followups</div>
</div>
<div class="rounded-lg border border-line bg-panel px-3 py-2">
<div class="text-slate-200 code">views/customers.py</div>
<div class="mt-1 text-[10px] leading-5 text-slate-500">entezar + appointment_rows + counts</div>
</div>
<div class="rounded-lg border border-line bg-panel px-3 py-2">
<div class="text-slate-200 code">sms/scanner.py</div>
<div class="mt-1 text-[10px] leading-5 text-slate-500">scan_visit → enqueue(visit)</div>
</div>
<div class="rounded-lg border border-line bg-panel px-3 py-2">
<div class="text-slate-200 code">sms/triggers.py</div>
<div class="mt-1 text-[10px] leading-5 text-slate-500">notify_visit_scheduled → visit_created</div>
</div>
</div>
</div>
</div>
</section>
<!-- ======================= FIELD 1 ======================= -->
<section id="field1" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۲</span>
فیلد «تاریخ و ساعتی که کار باید انجام شود»
</h2>
<p class="mb-5 text-sm text-slate-400">
یک ستون، سه معنا — تفکیک آن در <code class="text-brand">visit_policy._future_actions</code> صریح پیاده شده است.
</p>
<div class="grid gap-3 md:grid-cols-3">
<div class="rounded-xl border border-sky-400/30 bg-sky-400/5 p-4">
<div class="text-xs text-sky-300">بازدید + در انتظار</div>
<div class="mt-2 text-sm font-bold text-white">زمان قرار بازدید</div>
<div class="mt-2 text-[11px] leading-5 text-slate-400">مرز: <b class="text-slate-200">پایان روز</b> — قرار امروز تا ۲۳:۵۹ معتبر می‌ماند.</div>
<div class="mt-2 text-[10px] text-slate-500 code">whole_day=True</div>
</div>
<div class="rounded-xl border border-violet-400/30 bg-violet-400/5 p-4">
<div class="text-xs text-violet-300">هماهنگی (waite / finish)</div>
<div class="mt-2 text-sm font-bold text-white">زمان تماس</div>
<div class="mt-2 text-[11px] leading-5 text-slate-400">مرز: <b class="text-slate-200">لحظه‌ای</b> — تماسِ امروز نباید مشتری را تا آخر امروز پنهان کند.</div>
<div class="mt-2 text-[10px] text-slate-500 code">whole_day=False</div>
</div>
<div class="rounded-xl border border-emerald-400/30 bg-emerald-400/5 p-4">
<div class="text-xs text-emerald-300">بازدید + انجام‌شده</div>
<div class="mt-2 text-sm font-bold text-white">تاریخ مؤثر بازدید</div>
<div class="mt-2 text-[11px] leading-5 text-slate-400">اگر از حالا جلوتر نباشد، مبنای «آخرین بازدید»؛ وگرنه تاریخ اتمام.</div>
<div class="mt-2 text-[10px] text-slate-500 code">effective_visit_dt</div>
</div>
</div>
<div class="mt-5 overflow-hidden rounded-2xl border border-line">
<div class="overflow-x-auto">
<table class="report w-full text-right text-[12px]">
<thead class="bg-panel text-slate-400">
<tr>
<th class="whitespace-nowrap px-3 py-2 font-medium">#</th>
<th class="px-3 py-2 font-medium">مصرف‌کننده</th>
<th class="px-3 py-2 font-medium">تابع / فایل</th>
<th class="px-3 py-2 font-medium">اثر</th>
</tr>
</thead>
<tbody class="divide-y divide-line bg-card/50 text-slate-300">
<tr>
<td class="px-3 py-2 text-slate-500">۱</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">لیست وظایف</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">ActionListToday · ActionListUserToday</td>
<td class="px-3 py-2">امروز ۰۰:۰۰ تا ۲۳:۵۹ + وضعیت باز ⇒ «وظایف امروز»؛ قبل از امروز ⇒ «عقب‌افتاده»</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۲</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">آخرین بازدید</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">last_visit_map</td>
<td class="px-3 py-2">فقط <span class="text-emerald-300">bazdid + finish</span>؛ «هماهنگی» هرگز وارد نمی‌شود</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۳</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">لنگر چرخه و SMS</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">cycle_anchor_map → scan_visit</td>
<td class="px-3 py-2">آخرین بازدید (یا تاریخ نصب) + دورهٔ ۹ ماه ⇒ موعد یادآوری دوره‌ای</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۴</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">قرار بازدید برنامه</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">scheduled_visits_map</td>
<td class="px-3 py-2"><span class="text-sky-300">bazdid + waite</span> با تاریخ ≥ آغاز امروز ⇒ خروج از «تعیین‌تکلیف‌نشده» و قطع یادآوری</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۵</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">قرار هماهنگی</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">coordination_holds_map</td>
<td class="px-3 py-2"><span class="text-violet-300">hamahangi</span> با تاریخ ≥ همین لحظه ⇒ همان اثر با مرز لحظه‌ای</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۶</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">فهرست پیگیری روز</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">followups_on_date</td>
<td class="px-3 py-2">هماهنگی همان روز (یا تاریخ اتمام اگر تاریخ اقدام خالی باشد) + قرار دستی همان روز</td>
</tr>
<tr class="bg-rose-500/5">
<td class="px-3 py-2 text-slate-500">۷</td>
<td class="whitespace-nowrap px-3 py-2 text-rose-200">فهرست عقب‌افتاده‌ها</td>
<td class="px-3 py-2 text-[11px] text-rose-300/70 code">overdue_followups</td>
<td class="px-3 py-2">آخرین هماهنگی با تاریخ قبل از امروز ⇒ «موعد ازدست‌رفته» (منشأ باگ)</td>
</tr>
<tr>
<td class="px-3 py-2 text-slate-500">۸</td>
<td class="whitespace-nowrap px-3 py-2 text-slate-200">متن پیامک ثبت بازدید</td>
<td class="px-3 py-2 text-[11px] text-slate-400 code">notify_visit_scheduled</td>
<td class="px-3 py-2">متغیر <code class="text-brand">{date}</code> از همین فیلد پر می‌شود (تبدیل شمسی)</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="mt-3 rounded-xl border border-line bg-panel/60 px-4 py-3 text-[12px] leading-6 text-slate-400">
<b class="text-slate-200">اعتبارسنجی:</b>
فقط برای <span class="text-emerald-300">bazdid + finish</span> سقف «۳۰ روز آینده» اعمال می‌شود
(<code class="text-slate-300">FINISH_MAX_FUTURE_DAYS</code>). برای هماهنگی هیچ سقفی وجود ندارد.
</div>
</section>
<!-- ======================= FIELD 2 ======================= -->
<section id="field2" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-amber-400/15 text-sm text-amber-300">۳</span>
فیلد «تا تاریخ پیگیری نشود»
<span class="rounded bg-amber-400/10 px-2 py-0.5 text-[11px] text-amber-300 code">visit_hold_until</span>
</h2>
<p class="mb-5 text-sm text-slate-400">قرار دستی؛ ستون هست، ولی <b class="text-slate-200">مرجع نیست</b>.</p>
<div class="grid gap-4 lg:grid-cols-5">
<div class="lg:col-span-3 overflow-hidden rounded-2xl border border-line">
<table class="report w-full text-right text-[12px]">
<thead class="bg-panel text-slate-400">
<tr>
<th class="px-3 py-2 font-medium">قاعده</th>
<th class="px-3 py-2 font-medium">پیاده‌سازی</th>
<th class="px-3 py-2 font-medium">اثر</th>
</tr>
</thead>
<tbody class="divide-y divide-line bg-card/50 text-slate-300">
<tr><td class="whitespace-nowrap px-3 py-2">فقط هماهنگی</td><td class="px-3 py-2 text-[11px] text-slate-400 code">_apply_visit_hold</td><td class="px-3 py-2">برای بازدید/نصب/وصول بی‌اثر</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">پایانِ روز</td><td class="px-3 py-2 text-[11px] text-slate-400 code">end_of_local_day</td><td class="px-3 py-2">معتبر تا ۲۳:۵۹:۵۹ همان روز</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">خالی + تیک انجام شد</td><td class="px-3 py-2 text-[11px] text-slate-400 code">default_hold_until()</td><td class="px-3 py-2"><b class="text-amber-300">+۱۴ روز</b> خودکار</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">خالی + بدون تیک</td><td class="px-3 py-2 text-[11px] text-slate-400 code">—</td><td class="px-3 py-2">هیچ نوشتنی؛ مشتری در فهرست می‌ماند</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">تاریخ گذشته</td><td class="px-3 py-2 text-[11px] text-slate-400 code">normalize_hold_until</td><td class="px-3 py-2 text-rose-300">رد با پیام فارسی</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">بیش از ۱۸۳ روز</td><td class="px-3 py-2 text-[11px] text-slate-400 code">HOLD_MAX_DAYS</td><td class="px-3 py-2 text-rose-300">رد (جلوگیری از تعویق بی‌پایان)</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">فقط رو به جلو</td><td class="px-3 py-2 text-[11px] text-slate-400 code">set_visit_hold</td><td class="px-3 py-2">قرار دورتر با درخواست کوتاه‌تر عقب نمی‌رود</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">ثبت اقدام غیرهماهنگی (ایجاد)</td><td class="px-3 py-2 text-[11px] text-slate-400 code">clear_visit_hold</td><td class="px-3 py-2">ستون پاک می‌شود</td></tr>
<tr><td class="whitespace-nowrap px-3 py-2">ویرایش اقدام</td><td class="px-3 py-2 text-[11px] text-slate-400 code">is_new=False</td><td class="px-3 py-2">عمداً دست نمی‌زند</td></tr>
</tbody>
</table>
</div>
<div class="lg:col-span-2 rounded-2xl border border-amber-400/25 bg-amber-400/5 p-4">
<div class="text-xs text-amber-300">قاعدهٔ اعتبار — سه شرط هم‌زمان</div>
<ol class="mt-3 space-y-2 text-[12px] leading-6 text-slate-300">
<li class="flex gap-2"><span class="text-amber-300">۱</span> تاریخ از حالا جلوتر باشد</li>
<li class="flex gap-2"><span class="text-amber-300">۲</span> یک اقدام <b>هماهنگی فعال</b> (<code>waite</code>/<code>finish</code>) وجود داشته باشد</li>
<li class="flex gap-2"><span class="text-amber-300">۳</span> <b>آخرین فعالیتِ مشتری همان هماهنگی باشد</b> — هر اقدام بعدی (به‌ویژه بازدید) قرار را باطل می‌کند</li>
</ol>
<div class="mt-4 border-t border-amber-400/20 pt-3 text-[11px] leading-5 text-slate-400">
پیاده‌سازی: <code class="text-slate-300">_valid_manual_holds</code> — ستون پر، ولی «مرده» ممکن است.
</div>
</div>
</div>
<div class="mt-4 grid gap-3 sm:grid-cols-3">
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-3xl font-black text-white">۵۴</div>
<div class="mt-1 text-xs text-slate-400">مشتری با ستون پرشده</div>
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4">
<div class="text-3xl font-black text-emerald-300">۵۱</div>
<div class="mt-1 text-xs text-slate-400">قرارِ آیندهٔ معتبر</div>
</div>
<div class="rounded-xl border border-rose-500/30 bg-rose-500/5 p-4">
<div class="text-3xl font-black text-rose-300">۳</div>
<div class="mt-1 text-xs text-slate-400">بی‌اعتبار (۱ بدون هماهنگی · ۲ با اقدام بعدی)</div>
</div>
</div>
<div class="mt-4 overflow-hidden rounded-2xl border border-line">
<div class="border-b border-line bg-panel px-4 py-2 text-[11px] text-slate-400">مصرف‌کننده‌ها</div>
<div class="divide-y divide-line bg-card/50 text-[12px]">
<div class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-40 text-slate-200">منبع <code class="text-emerald-300">manual</code></span>
<span class="text-[11px] text-slate-500 code">manual_holds_map → appointment_sources_map</span>
<span class="flex-1 text-slate-300">خروج از «تعیین‌تکلیف‌نشده» + فیلتر <code>?view=manual</code></span>
</div>
<div class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-40 text-slate-200">فهرست پیگیری روز</span>
<span class="text-[11px] text-slate-500 code">followups_on_date</span>
<span class="flex-1 text-slate-300">دقیقاً در همان روز (حتی گذشته) در <code>?view=today</code></span>
</div>
<div class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-40 text-slate-200">قطع یادآوری SMS</span>
<span class="text-[11px] text-slate-500 code">next_appointment_map → scan_visit</span>
<span class="flex-1 text-slate-300">تا پایان آن روز هیچ پیامک دوره‌ای <code>visit</code> صف نمی‌شود</span>
</div>
<div class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-40 text-slate-200">نمایش و ویرایش</span>
<span class="text-[11px] text-slate-500 code">entezar.html · customerActionInc.html</span>
<span class="flex-1 text-slate-300">برچسب/ستون «قرار دستی» + بارگذاری مقدار در مودال ویرایش</span>
</div>
</div>
</div>
</section>
<!-- ======================= SMS ======================= -->
<section id="sms" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۴</span>
اثر دو فیلد بر پیامک‌ها
</h2>
<p class="mb-5 text-sm text-slate-400">دو نوع پیامکِ بازدید، دو منطق کاملاً متفاوت.</p>
<div class="grid gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-sky-400/25 bg-sky-400/5 p-4">
<div class="flex items-center justify-between">
<div class="font-bold text-white">یادآوری دوره‌ای بازدید</div>
<span class="rounded bg-sky-400/15 px-2 py-0.5 text-[11px] text-sky-300 code">visit</span>
</div>
<p class="mt-2 text-[12px] leading-6 text-slate-300">
اسکنر وقتی موعدِ «آخرین بازدید + ۹ ماه» رسید صف می‌کند.
تصمیم‌گیر: <code class="text-sky-300">cycle_anchor_map</code> + <code class="text-sky-300">next_appointment_map</code>.
</p>
<div class="mt-3 rounded-lg bg-ink px-3 py-2 text-[11px] leading-6 text-slate-400">
<b class="text-slate-200">هر دو فیلد اثر دارند:</b>
فیلد اول به‌واسطهٔ لنگر چرخه، فیلد دوم به‌واسطهٔ قرار.
</div>
</div>
<div class="rounded-2xl border border-emerald-400/25 bg-emerald-400/5 p-4">
<div class="flex items-center justify-between">
<div class="font-bold text-white">ثبت بازدید</div>
<span class="rounded bg-emerald-400/15 px-2 py-0.5 text-[11px] text-emerald-300 code">visit_created</span>
</div>
<p class="mt-2 text-[12px] leading-6 text-slate-300">
بلافاصله پس از ثبت اقدام <b>بازدید</b> ساخته می‌شود؛ متن آن
<code class="text-emerald-300">{date}</code> را از <code class="text-brand">actionDateTime</code> می‌گیرد.
</p>
<div class="mt-3 rounded-lg bg-ink px-3 py-2 text-[11px] leading-6 text-slate-400">
<b class="text-slate-200">فیلد تعویق هیچ اثری ندارد:</b>
این پیامک از مسیر <code>next_appointment_map</code> عبور نمی‌کند.
</div>
</div>
</div>
<div class="mt-5 rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 text-[11px] uppercase tracking-wide text-slate-500">جریان تصمیم scan_visit</div>
<div class="flex flex-wrap items-center gap-2 text-[11px]">
<span class="rounded-lg border border-line bg-panel px-3 py-2">مشتریان فعال</span>
<span class="text-brand">→</span>
<span class="rounded-lg border border-line bg-panel px-3 py-2">قرار بعدی دارد؟</span>
<span class="rounded-lg border border-rose-400/30 bg-rose-400/10 px-3 py-2 text-rose-200">بله ⇒ توقف</span>
<span class="text-brand">→</span>
<span class="rounded-lg border border-line bg-panel px-3 py-2">لنگر = آخرین بازدید یا نصب</span>
<span class="text-brand">→</span>
<span class="rounded-lg border border-line bg-panel px-3 py-2">موعدِ ۹/۱۸/۲۷ ماه رسیده؟</span>
<span class="text-brand">→</span>
<span class="rounded-lg border border-emerald-400/30 bg-emerald-400/10 px-3 py-2 text-emerald-200">صف با کلید دوره‌ای</span>
</div>
<p class="mt-3 text-[11px] leading-6 text-slate-500">
«قرار بعدی» اجتماع سه منبع است (دستی / بازدید برنامه‌ریزی‌شده / هماهنگی)؛ پس صفحه و پیامک هرگز دو جواب نمی‌دهند.
کلید یکتاسازی <code class="text-slate-400">visit:{customer}:{YYYY-MM}</code> است؛ حذف از صف فقط همان دوره را کنسل می‌کند.
</p>
</div>
<div class="mt-5 overflow-hidden rounded-2xl border border-line">
<div class="overflow-x-auto">
<table class="report w-full text-right text-[12px]">
<thead class="bg-panel text-slate-400">
<tr>
<th class="px-3 py-2 font-medium">سناریو</th>
<th class="px-3 py-2 font-medium">یادآوری <code>visit</code></th>
<th class="px-3 py-2 font-medium">پیامک <code>visit_created</code></th>
</tr>
</thead>
<tbody class="divide-y divide-line bg-card/50 text-slate-300">
<tr><td class="px-3 py-2">مشتری عقب‌افتاده، هیچ اقدامی</td><td class="px-3 py-2"><span class="rounded bg-emerald-500/15 px-2 py-0.5 text-emerald-300">ارسال</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr><td class="px-3 py-2">«تا تاریخ پیگیری نشود» = +۱۰ روز</td><td class="px-3 py-2"><span class="rounded bg-rose-500/15 px-2 py-0.5 text-rose-300">قطع</span> <span class="text-[11px] text-slate-500">تا پایان روز دهم</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr><td class="px-3 py-2">ستون پر، ولی <b>بدون</b> اقدام هماهنگی</td><td class="px-3 py-2"><span class="rounded bg-emerald-500/15 px-2 py-0.5 text-emerald-300">ارسال</span> <span class="text-[11px] text-slate-500">ستون بی‌اثر</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr><td class="px-3 py-2">بازدید در انتظار با تاریخ +۵ روز</td><td class="px-3 py-2"><span class="rounded bg-rose-500/15 px-2 py-0.5 text-rose-300">قطع</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr><td class="px-3 py-2">هماهنگی با تاریخ +۵ روز</td><td class="px-3 py-2"><span class="rounded bg-rose-500/15 px-2 py-0.5 text-rose-300">قطع</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr><td class="px-3 py-2">هماهنگی انجام‌شده با تاریخ گذشته و بدون تعویق</td><td class="px-3 py-2"><span class="rounded bg-emerald-500/15 px-2 py-0.5 text-emerald-300">ارسال</span></td><td class="px-3 py-2 text-slate-500">—</td></tr>
<tr class="bg-emerald-500/5"><td class="px-3 py-2">ثبت اقدام <b>بازدید</b> (هر وضعیتی)</td><td class="px-3 py-2 text-slate-500">—</td><td class="px-3 py-2"><span class="rounded bg-emerald-500/15 px-2 py-0.5 text-emerald-300">ارسال</span> <span class="text-[11px] text-slate-500">{date} = تاریخ انجام</span></td></tr>
<tr><td class="px-3 py-2">ثبت اقدام <b>هماهنگی</b></td><td class="px-3 py-2 text-slate-500">—</td><td class="px-3 py-2"><span class="rounded bg-rose-500/15 px-2 py-0.5 text-rose-300">نه</span></td></tr>
<tr class="bg-amber-400/5"><td class="px-3 py-2">قرار دستی فعال + ثبت بازدید</td><td class="px-3 py-2 text-slate-500">—</td><td class="px-3 py-2"><span class="rounded bg-emerald-500/15 px-2 py-0.5 text-emerald-300">ارسال</span> <span class="text-[11px] text-slate-500">فیلد دوم مانع نیست</span></td></tr>
</tbody>
</table>
</div>
</div>
<p class="mt-3 text-[11px] leading-6 text-slate-500">
همهٔ سطرها با <code class="text-slate-400">scan_visit</code> و <code class="text-slate-400">notify_visit_scheduled</code> واقعی شبیه‌سازی شده‌اند
(<code class="text-slate-400">tests/test_entezar_action_simulation.py</code>).
</p>
</section>
<!-- ======================= DONE CHECKBOX ======================= -->
<section id="done" class="mt-16 scroll-mt-20">
<h2 class="mb-4 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-emerald-400/15 text-sm text-emerald-300">۵</span>
تیک «این کار انجام شده است» در هماهنگی
</h2>
<div class="overflow-hidden rounded-2xl border border-line">
<div class="overflow-x-auto">
<table class="report w-full text-right text-[12px]">
<thead class="bg-panel text-slate-400">
<tr>
<th class="px-3 py-2 font-medium">فرم</th>
<th class="px-3 py-2 font-medium">status</th>
<th class="px-3 py-2 font-medium">finishDateTime</th>
<th class="px-3 py-2 font-medium">visit_hold_until</th>
<th class="px-3 py-2 font-medium">اثر</th>
</tr>
</thead>
<tbody class="divide-y divide-line bg-card/50 text-slate-300">
<tr class="bg-emerald-500/5"><td class="px-3 py-2">تیک + تاریخ تعویق پر</td><td class="px-3 py-2 text-emerald-300">finish</td><td class="px-3 py-2">خالی ⇒ الان</td><td class="px-3 py-2">همان تاریخ</td><td class="px-3 py-2">پنهان تا آن روز؛ همان روز در نمای امروز برمی‌گردد</td></tr>
<tr class="bg-emerald-500/5"><td class="px-3 py-2">تیک + تاریخ تعویق خالی</td><td class="px-3 py-2 text-emerald-300">finish</td><td class="px-3 py-2">الان</td><td class="px-3 py-2 text-amber-300">+۱۴ روز</td><td class="px-3 py-2">همان؛ تعویقِ خودکار</td></tr>
<tr><td class="px-3 py-2">بدون تیک + تاریخ تعویق پر</td><td class="px-3 py-2 text-sky-300">waite</td><td class="px-3 py-2 text-slate-500">NULL</td><td class="px-3 py-2">همان تاریخ</td><td class="px-3 py-2">همان</td></tr>
<tr><td class="px-3 py-2">بدون تیک + تاریخ خالی</td><td class="px-3 py-2 text-sky-300">waite</td><td class="px-3 py-2 text-slate-500">NULL</td><td class="px-3 py-2 text-slate-500">بدون تغییر</td><td class="px-3 py-2">مشتری در فهرست می‌ماند</td></tr>
</tbody>
</table>
</div>
</div>
<div class="mt-4 grid gap-3 md:grid-cols-3">
<div class="rounded-xl border border-line bg-panel p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">تأیید دو مرحله‌ای UI</b><br>
برای هماهنگیِ تیک‌خورده، پیش از submit هشدار «آیا هماهنگی را انجام دادی؟» نمایش داده می‌شود؛
«نه» تیک را سمت کلاینت برمی‌دارد.
</div>
<div class="rounded-xl border border-line bg-panel p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">اثر بر «آخرین بازدید»</b><br>
هیچ. هماهنگی هرگز وارد <code>last_visit_map</code> نمی‌شود؛ تیک هم آن را تغییر نمی‌دهد.
</div>
<div class="rounded-xl border border-amber-400/30 bg-amber-400/5 p-4 text-[12px] leading-6 text-amber-100">
<b>ریسک رفتاری</b><br>
«انجام شد» بدون تاریخ تعویق همیشه ۱۴ روز پنهان می‌کند؛ اگر کارشناس فقط می‌خواست تماس را لاگ کند،
این پیش‌فرض ناخواسته است.
</div>
</div>
</section>
<!-- ======================= TODAY ======================= -->
<section id="today" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۶</span>
صفحهٔ <code class="text-brand">?view=today</code> — برچسب‌ها و اعداد
</h2>
<p class="mb-5 text-sm text-slate-400">«۲ قرار» و «قرار دستی» دقیقاً از کجا می‌آیند.</p>
<div class="grid gap-4 lg:grid-cols-2">
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 text-[11px] uppercase tracking-wide text-slate-500">منطق برچسب وضعیت</div>
<div class="space-y-2">
<div class="flex items-center justify-between rounded-lg bg-panel px-3 py-2 text-[12px]">
<span class="rounded bg-violet-500/20 px-2 py-0.5 text-violet-200">۲ قرار</span>
<span class="text-slate-400">source_count &gt; 1</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-panel px-3 py-2 text-[12px]">
<span class="rounded bg-amber-400/20 px-2 py-0.5 text-amber-200">قرار دستی</span>
<span class="text-slate-400">manual</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-panel px-3 py-2 text-[12px]">
<span class="rounded bg-emerald-500/20 px-2 py-0.5 text-emerald-200">بازدید برنامه</span>
<span class="text-slate-400">scheduled</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-panel px-3 py-2 text-[12px]">
<span class="rounded bg-violet-500/20 px-2 py-0.5 text-violet-200">هماهنگی</span>
<span class="text-slate-400">coordination</span>
</div>
<div class="flex items-center justify-between rounded-lg bg-panel px-3 py-2 text-[12px]">
<span class="rounded bg-rose-500/20 px-2 py-0.5 text-rose-200">بدون قرار</span>
<span class="text-slate-400">هیچ منبعی</span>
</div>
</div>
<p class="mt-3 text-[11px] leading-6 text-slate-500">
اولویت: چندقراره ← دستی ← بازدید ← هماهنگی ← بدون قرار.
یک هماهنگیِ انجام‌شده با تاریخ تعویق، هم‌زمان «هماهنگی» و «قرار دستی» تولید می‌کند.
</p>
</div>
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 text-[11px] uppercase tracking-wide text-slate-500">ترکیب منابع در دادهٔ واقعی</div>
<div class="space-y-3 text-[12px]">
<div>
<div class="mb-1 flex justify-between"><span class="text-slate-300">۱ منبع</span><span class="text-slate-400">۲٬۲۶۷</span></div>
<div class="h-2 rounded bg-ink"><div class="bar h-2 rounded bg-brand" style="width:100%"></div></div>
</div>
<div>
<div class="mb-1 flex justify-between"><span class="text-slate-300">۲ منبع</span><span class="text-slate-400">۱۶</span></div>
<div class="h-2 rounded bg-ink"><div class="bar h-2 rounded bg-violet-400" style="width:14%"></div></div>
</div>
<div class="rounded-lg bg-panel px-3 py-2 text-[11px] leading-6 text-slate-400">
ترکیب دو‌منبعی‌ها: <b class="text-violet-200">manual + coordination</b> = ۱۲
(۱۱ مورد تاریخ متفاوت، ۱ مورد هم‌روز) · <b class="text-emerald-200">scheduled + coordination</b> = ۴
</div>
<div class="rounded-lg border border-line bg-ink px-3 py-2 text-[11px] leading-6 text-slate-400">
<b class="text-slate-200">نمونهٔ #2888:</b> دستی ۲۰۲۶-۱۱-۱۱ و هماهنگی ۲۰۲۶-۱۱-۱۱ —
همان یک توافق، دو منبع ⇒ برچسب «۲ قرار».
</div>
</div>
</div>
</div>
<div class="mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-3xl font-black text-white">۱۳</div>
<div class="mt-1 text-xs text-slate-400">ردیف نمای امروز</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-3xl font-black text-white">۱۲</div>
<div class="mt-1 text-xs text-slate-400">دارای بازدید انجام‌شده (۹۲٪)</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-3xl font-black text-white">۴</div>
<div class="mt-1 text-xs text-slate-400">قرار دستیِ آینده هم دارند</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-3xl font-black text-white">۰</div>
<div class="mt-1 text-xs text-slate-400">آخرین بازدید در/بعد از روز موعد</div>
</div>
</div>
<div class="mt-4 rounded-xl border border-line bg-panel/60 px-4 py-3 text-[12px] leading-6 text-slate-400">
<b class="text-slate-200">مشکل این نما:</b>
«موعدِ تماس» با «موعدِ پیگیری» مخلوط شده است؛ تماسِ انجام‌شدهٔ امروز هم ردیف می‌سازد،
و حذف‌کنندهٔ ردیف فقط بازدیدِ <code>waite</code> است — پس بازدیدِ امروزِ انجام‌شده چیزی را حذف نمی‌کند.
</div>
</section>
<!-- ======================= BUG ======================= -->
<section id="bug" class="mt-16 scroll-mt-20">
<div class="rounded-2xl border border-rose-500/30 bg-rose-500/5 p-5">
<div class="flex flex-wrap items-start justify-between gap-3">
<h2 class="flex flex-wrap items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-rose-500/20 text-sm text-rose-200">۷</span>
باگ <code class="text-rose-300">?view=today&amp;missed=1</code>
</h2>
<span class="rounded-full border border-emerald-400/40 bg-emerald-500/10 px-3 py-1 text-[11px] text-emerald-300">رفع شد · ۱۱۲ → ۴۲ ردیف</span>
</div>
<p class="mt-2 text-sm leading-7 text-rose-100">
فهرست «نیازمند پیگیری از روزهای قبل» پر از مشتریانی بود که بازدیدشان انجام شده
(یا قرارِ معتبر آینده داشتند). تحلیل زیر وضعیت زمان کشف است؛ رفع در بخش ۸.
</p>
</div>
<div class="mt-5 grid gap-4 lg:grid-cols-2">
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 flex items-baseline justify-between">
<div class="text-[11px] uppercase tracking-wide text-slate-500">۱۱۲ ردیف — علتِ حضور</div>
<div class="text-[11px] text-slate-500">کپی دیتابیس ۲۰۲۶-۱۰-۲۱</div>
</div>
<div class="space-y-3 text-[12px]">
<div>
<div class="mb-1 flex justify-between"><span class="text-slate-300">بازدید انجام‌شده، ولی قدیمی‌تر از هماهنگی</span><span class="text-slate-400">۶۷ · ۵۹.۸٪</span></div>
<div class="h-3 rounded bg-ink"><div class="bar h-3 rounded bg-amber-400" style="width:59.8%"></div></div>
</div>
<div>
<div class="mb-1 flex justify-between"><span class="text-slate-300">بازدید انجام‌شده <b>پس از</b> ثبت هماهنگی</span><span class="text-slate-400">۳۲ · ۲۸.۶٪</span></div>
<div class="h-3 rounded bg-ink"><div class="bar h-3 rounded bg-rose-400" style="width:28.6%"></div></div>
</div>
<div>
<div class="mb-1 flex justify-between"><span class="text-slate-300">بدون هیچ بازدید انجام‌شده</span><span class="text-slate-400">۱۳ · ۱۱.۶٪</span></div>
<div class="h-3 rounded bg-ink"><div class="bar h-3 rounded bg-emerald-400" style="width:11.6%"></div></div>
</div>
</div>
<div class="mt-4 border-t border-line pt-3 text-[11px] leading-6 text-slate-400">
فقط <b class="text-emerald-300">۱۳ ردیف</b> واقعاً نیازمند پیگیری‌اند؛
<b class="text-rose-300">۹۹ ردیف (۸۸.۴٪)</b> حداقل یک بازدید انجام‌شده دارند.
</div>
</div>
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 text-[11px] uppercase tracking-wide text-slate-500">سنجه‌های تکمیلی</div>
<div class="grid grid-cols-2 gap-2 text-[12px]">
<div class="rounded-lg bg-panel px-3 py-2"><div class="text-xl font-bold text-white">۳۲</div><div class="text-[11px] text-slate-400">بازدید بعد از هماهنگی (createdDT)</div></div>
<div class="rounded-lg bg-panel px-3 py-2"><div class="text-xl font-bold text-white">۲۴</div><div class="text-[11px] text-slate-400">آخرین بازدید بعد از موعد هماهنگی</div></div>
<div class="rounded-lg bg-panel px-3 py-2"><div class="text-xl font-bold text-white">۹۹</div><div class="text-[11px] text-slate-400">آخرین هماهنگی با وضعیت finish</div></div>
<div class="rounded-lg bg-panel px-3 py-2"><div class="text-xl font-bold text-white">۱۳</div><div class="text-[11px] text-slate-400">آخرین هماهنگی با وضعیت waite</div></div>
<div class="rounded-lg bg-rose-500/10 px-3 py-2"><div class="text-xl font-bold text-rose-200">۶۱</div><div class="text-[11px] text-slate-400">finish بدون قرار دستی فعال</div></div>
<div class="rounded-lg bg-rose-500/10 px-3 py-2"><div class="text-xl font-bold text-rose-200">۳۸</div><div class="text-[11px] text-slate-400">قرار دستیِ معتبر آینده + حضور در missed</div></div>
</div>
<div class="mt-3 rounded-lg border border-rose-400/25 bg-ink px-3 py-2 text-[11px] leading-6 text-rose-100">
<b>تناقض:</b> همان مشتری در <code>appointment_sources_map</code> «قرار معتبر» دارد
(از تعیین‌تکلیف‌نشده خارج است) و هم‌زمان در فهرست قرمز «عقب‌افتاده» شمرده می‌شود.
</div>
</div>
</div>
<h3 class="mb-3 mt-8 text-lg font-bold text-white">ریشه‌ها — سه خطای هم‌راستا</h3>
<div class="grid gap-3 md:grid-cols-3">
<div class="rounded-2xl border border-rose-500/30 bg-rose-500/5 p-4">
<div class="flex items-center gap-2"><span class="rounded bg-rose-500/20 px-2 py-0.5 text-[11px] text-rose-200 code">R1</span><b class="text-white">بازدید finish دیده نمی‌شود</b></div>
<p class="mt-2 text-[12px] leading-6 text-rose-100/90">
حذف از فهرست فقط بر اساس اقدام <code>bazdid + waite</code> انجام می‌شود.
بازدیدی که انجام و بسته شده (<code>finish</code>) هیچ اثری در حذف ندارد.
</p>
<div class="mt-2 text-[10px] text-rose-300/70 code">_pending_visit_customer_ids</div>
</div>
<div class="rounded-2xl border border-amber-400/30 bg-amber-400/5 p-4">
<div class="flex items-center gap-2"><span class="rounded bg-amber-400/20 px-2 py-0.5 text-[11px] text-amber-200 code">R2</span><b class="text-white">قرار دستی نادیده گرفته می‌شود</b></div>
<p class="mt-2 text-[12px] leading-6 text-amber-100/90">
<code>overdue_followups</code> اصلاً ستون <code>visit_hold_until</code> را نمی‌خواند؛
پس مشتری با قرارِ آیندهٔ معتبر هم در فهرست «عقب‌افتاده» می‌ماند.
</p>
<div class="mt-2 text-[10px] text-amber-300/70 code">overdue_followups</div>
</div>
<div class="rounded-2xl border border-violet-400/30 bg-violet-400/5 p-4">
<div class="flex items-center gap-2"><span class="rounded bg-violet-400/20 px-2 py-0.5 text-[11px] text-violet-200 code">R3</span><b class="text-white">زمان تماس جای موعد نشست</b></div>
<p class="mt-2 text-[12px] leading-6 text-violet-100/90">
برای هماهنگیِ انجام‌شده، <code>actionDateTime</code> «زمان تماس» است نه «قرار بعدی»؛
ولی فهرست آن را موعد می‌گیرد ⇒ هر تماسِ تمام‌شده تا ابد عقب‌افتاده می‌ماند.
</p>
<div class="mt-2 text-[10px] text-violet-300/70 code">whole_day=False</div>
</div>
</div>
<div class="mt-5 rounded-2xl border border-line bg-[#0d1424] p-4">
<div class="mb-2 flex items-center justify-between text-[11px] text-slate-500">
<span>visit_policy.py — تصمیم فعلی «عقب‌افتاده»</span>
<span class="code">:590-644</span>
</div>
<pre class="code overflow-x-auto text-[11.5px] leading-6 text-slate-300"><code><span class="text-slate-500"># آخرین موعد هماهنگی هر مشتری</span>
when = as_aware(action_dt) <span class="text-rose-300">or</span> as_aware(finish_dt) <span class="text-rose-300"># ← finish هم با actionDateTime</span>
latest[cid] = max(day)
overdue = {cid: day for cid, day in latest.items() <span class="text-rose-300">if day &lt; today</span>}
<span class="text-slate-500"># تنها حذف‌کننده: بازدیدهای «در انتظار»</span>
pending_visits = _pending_visit_customer_ids(since=...)
<span class="text-slate-500"># ↑ داخلش فقط:</span>
actionType=<span class="text-emerald-300">'bazdid'</span>, status=<span class="text-rose-300">'waite'</span> <span class="text-rose-300"># ← finish هرگز</span>
</code></pre>
</div>
<h3 class="mb-3 mt-8 text-lg font-bold text-white">دو نمونهٔ واقعی با خط زمانی</h3>
<div class="space-y-4">
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 flex flex-wrap items-baseline justify-between gap-2">
<div class="text-sm font-bold text-white">#236 — خانم مرضیه جمالی</div>
<div class="text-[11px] text-rose-300">هنوز در missed</div>
</div>
<div class="flex flex-wrap items-center gap-2 text-[11px]">
<div class="rounded-lg border border-violet-400/40 bg-violet-400/10 px-3 py-2">
<div class="text-violet-200">هماهنگی · finish</div>
<div class="text-[10px] text-slate-400">ثبت ۱۵ اکتبر · تماس ۱۷ اکتبر</div>
</div>
<span class="text-slate-600">→</span>
<div class="rounded-lg border border-emerald-400/40 bg-emerald-400/10 px-3 py-2">
<div class="text-emerald-200">بازدید · finish</div>
<div class="text-[10px] text-slate-400">۱۸ اکتبر (ثبت ۱۷ اکتبر)</div>
</div>
<span class="text-slate-600">→</span>
<div class="rounded-lg border border-rose-400/40 bg-rose-400/10 px-3 py-2">
<div class="text-rose-200">missed</div>
<div class="text-[10px] text-slate-400">بازدید انجام‌شده نادیده گرفته شد</div>
</div>
</div>
</div>
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="mb-3 flex flex-wrap items-baseline justify-between gap-2">
<div class="text-sm font-bold text-white">#1431 — آقای حمید جمالی</div>
<div class="text-[11px] text-rose-300">هنوز در missed</div>
</div>
<div class="flex flex-wrap items-center gap-2 text-[11px]">
<div class="rounded-lg border border-violet-400/40 bg-violet-400/10 px-3 py-2">
<div class="text-violet-200">هماهنگی · finish</div>
<div class="text-[10px] text-slate-400">تماس ۱۶ اکتبر</div>
</div>
<span class="text-slate-600">→</span>
<div class="rounded-lg border border-emerald-400/40 bg-emerald-400/10 px-3 py-2">
<div class="text-emerald-200">بازدید · finish</div>
<div class="text-[10px] text-slate-400">۱۸ اکتبر</div>
</div>
<span class="text-slate-600">→</span>
<div class="rounded-lg border border-emerald-400/40 bg-emerald-400/10 px-3 py-2">
<div class="text-emerald-200">بازدید · finish</div>
<div class="text-[10px] text-slate-400">۲۰ اکتبر</div>
</div>
<span class="text-slate-600">→</span>
<div class="rounded-lg border border-rose-400/40 bg-rose-400/10 px-3 py-2">
<div class="text-rose-200">missed</div>
<div class="text-[10px] text-slate-400">دو بازدید انجام‌شده، بی‌اثر</div>
</div>
</div>
</div>
</div>
<div class="mt-4 rounded-xl border border-line bg-panel/60 px-4 py-3 text-[11px] leading-6 text-slate-400">
سناریوی «تماس گرفتم و فردا بازدید می‌کنم» (بازدید <b>در انتظار</b>) درست کار می‌کند؛
آنچه خراب است سناریوی «تماس گرفتم، بازدید انجام و ثبت شد» است.
</div>
</section>
<!-- ======================= FIX ======================= -->
<section id="fix" class="mt-16 scroll-mt-20">
<h2 class="mb-2 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-emerald-400/15 text-sm text-emerald-300">۸</span>
جمع‌بندی و رفع‌های اعمال‌شده
</h2>
<p class="mb-5 text-sm text-slate-400">
هر مورد پیاده شد، با تست قفل شد و روی دادهٔ واقعی سنجیده شد.
<span class="text-emerald-300">۲۶۹ + ۱۹۹ تست قبلی سبز ماند · ۴۶ تست شبیه‌سازی/رگرسیون OK</span>
</p>
<div class="overflow-hidden rounded-2xl border border-line">
<div class="overflow-x-auto">
<table class="report w-full text-right text-[12px]">
<thead class="bg-panel text-slate-400">
<tr>
<th class="px-3 py-2 font-medium">#</th>
<th class="px-3 py-2 font-medium">نوع</th>
<th class="px-3 py-2 font-medium">باگ</th>
<th class="px-3 py-2 font-medium">رفع</th>
</tr>
</thead>
<tbody class="divide-y divide-line bg-card/50 text-slate-300">
<tr><td class="px-3 py-2 text-slate-500">B1</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار</td><td class="px-3 py-2">فهرست missed پر از مشتریِ بازدیدشده (۹۹ از ۱۱۲)</td><td class="px-3 py-2">بازدید <code>finish</code> با تاریخ ≥ موعد ⇒ حذف</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B2</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار</td><td class="px-3 py-2">۳۸ ردیف هم‌زمان «قرار معتبر» و «عقب‌افتاده»</td><td class="px-3 py-2">قرار دستیِ معتبر ⇒ حذف از فهرست</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B3</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار</td><td class="px-3 py-2">نمای امروز بعد از بازدیدِ همان روز ردیف را نگه می‌داشت</td><td class="px-3 py-2">بازدید ≥ موعد ⇒ حذف (فقط امروز/آینده)</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B4</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار</td><td class="px-3 py-2">سند وصول یا تماس، قرار را بی‌صدا باطل می‌کرد</td><td class="px-3 py-2">فقط <code>bazdid</code>/<code>nasb</code> قرار را باطل می‌کنند</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B5</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار</td><td class="px-3 py-2">«۲ قرار» برای یک توافق (هماهنگی + تعویقِ همان روز)</td><td class="px-3 py-2">شمارش روزهای یکتا</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B6</td><td class="px-3 py-2 whitespace-nowrap text-sky-300">فنی</td><td class="px-3 py-2">مرز «وظایف امروز» با رشتهٔ naive و وابسته به تایم‌زون</td><td class="px-3 py-2"><code>_local_day_range()</code> aware + پوشش DST</td></tr>
<tr><td class="px-3 py-2 text-slate-500">B7</td><td class="px-3 py-2 whitespace-nowrap text-sky-300">فنی</td><td class="px-3 py-2">هر GET صفحهٔ انتظار در کش می‌نوشت</td><td class="px-3 py-2">فقط در صورت تغییر + <code>update_fields</code></td></tr>
<tr><td class="px-3 py-2 text-slate-500">B8</td><td class="px-3 py-2 whitespace-nowrap text-sky-300">فنی/تست</td><td class="px-3 py-2">یک تست به ساعت اجرا وابسته بود</td><td class="px-3 py-2">مستقل از ساعت</td></tr>
<tr class="bg-emerald-500/5"><td class="px-3 py-2 text-slate-500">B9</td><td class="px-3 py-2 whitespace-nowrap text-rose-300">کسب‌وکار/UX</td><td class="px-3 py-2">تاریخ عقب‌ترِ «تا تاریخ پیگیری نشود» بی‌صدا ذخیره نمی‌شد</td><td class="px-3 py-2">هشدار با هر دو تاریخ + تأیید صریح کاربر (<code>force_hold</code>)</td></tr>
</tbody>
</table>
</div>
</div>
<div class="mt-4 grid gap-3 sm:grid-cols-3">
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-xs text-slate-400">missed قبل</div>
<div class="mt-1 text-3xl font-black text-rose-300">۱۱۲</div>
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4">
<div class="text-xs text-slate-400">missed بعد</div>
<div class="mt-1 text-3xl font-black text-emerald-300">۴۲</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-xs text-slate-400">ردیف متناقض (قرار + عقب‌افتاده)</div>
<div class="mt-1 text-3xl font-black text-white">۰</div>
</div>
</div>
<div class="grid gap-3 md:grid-cols-2">
<div class="rounded-2xl border border-line bg-card/60 p-4">
<div class="text-[11px] uppercase tracking-wide text-slate-500">تناقض ساختاری</div>
<div class="mt-2 space-y-2 text-[12px]">
<div class="rounded-lg bg-panel px-3 py-2">
<span class="text-slate-400">«تعیین‌تکلیف‌نشده»:</span>
<span class="text-slate-200">اجتماع سه منبع قرار آینده + اعتبار قرار دستی</span>
</div>
<div class="rounded-lg bg-rose-500/10 px-3 py-2">
<span class="text-rose-300">«عقب‌افتاده»:</span>
<span class="text-rose-100">فقط hamahangi + فقط بازدید waite</span>
</div>
</div>
<p class="mt-3 text-[12px] leading-6 text-slate-300">رفع باگ = هم‌ترازکردن لایهٔ دوم با لایهٔ اول.</p>
</div>
<div class="rounded-2xl border border-line bg-[#0d1424] p-4">
<div class="mb-2 text-[11px] text-slate-500">پیاده‌شده — overdue_followups (B1 + B2)</div>
<pre class="code overflow-x-auto text-[11px] leading-6 text-emerald-200"><code>for customer_id, action_dt, finish_dt in Action.objects.filter(
actionType=BAZDID, status=FINISH,
).values_list(...):
eff = action_dt if action_dt &lt;= now else finish_dt
if eff and eff.date() &gt;= overdue.get(customer_id, today):
overdue.pop(customer_id, None)
for customer_id in manual_holds_map(now=now):
overdue.pop(customer_id, None)</code></pre>
</div>
</div>
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">B3 · followups_on_date</b><br>
قاعدهٔ «بازدید انجام‌شده از همان روز موعد به بعد» اعمال شد؛ ردیفِ امروز پس از
بازدید حذف می‌شود (روزهای گذشته دست‌نخورده می‌مانند).
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">B4 · ابطال قرار دستی</b><br>
فقط <code>bazdid</code>/<code>nasb</code> قرار را باطل می‌کنند؛ سند وصول و تماس
دیگر قرارِ توافق‌شده را نمی‌کشند.
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">B5 · برچسب «N قرار»</b><br>
بر اساس <b>روزهای یکتا</b> حساب می‌شود؛ هماهنگی + تعویقِ همان روز = «۱ قرار».
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">B7 · نوشتن در GET</b><br>
کش فقط وقتی مقدارش تغییر کند نوشته می‌شود (<code>update_fields</code>).
</div>
<div class="rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">B9 · بازخورد شفاف عقب‌بردن قرار</b><br>
اگر تاریخ «تا تاریخ پیگیری نشود» از قرار فعلی عقب‌تر باشد، به‌جای سکوت:
اعلان کوتاه «این تاریخ از قرار فعلی عقب‌تر است» + «قرار فعلی: …» و تیک
«تاریخ قرار را عقب ببر» (فیلد <code>force_hold</code>) — فقط در
<b>ویرایش</b>. در «ایجاد وظیفهٔ تازه» تیک وجود ندارد و راهنمای «وظیفه را
ویرایش کنید» نشان داده می‌شود، تا قولِ ثبت‌شدهٔ قبلی ناخواسته کوتاه نشود.
بدون جاوااسکریپت هم سرور همان دلیل را روی صفحه می‌نویسد.
</div>
<div class="rounded-xl border border-line bg-panel p-4 text-[12px] leading-6 text-slate-300">
<b class="text-white">باقی‌مانده (تصمیم محصولی)</b><br>
موعدِ هماهنگیِ <code>finish</code> همچنان «زمان تماس» است؛ بعد از انقضای تعویق،
اگر بازدیدی ثبت نشود، مشتری دوباره «نیازمند پیگیری» می‌شود — این رفتار عمدی است
تا مشتریِ بی‌قرار از فهرست کار ناپدید نشود.
</div>
<div class="rounded-xl border border-amber-400/30 bg-amber-400/5 p-4 text-[12px] leading-6 text-amber-100">
<b>ریسک</b><br>
هر تغییر در <code>overdue_followups</code> روی شمارندهٔ قرمز و هشدارهای صفحه اثر می‌گذارد؛
تست «عدد هشدار = تعداد ردیف» باید حفظ شود.
</div>
</div>
<div class="mt-4 rounded-xl border border-line bg-panel/60 px-4 py-3 text-[11px] leading-6 text-slate-400">
<b class="text-slate-200">نکته:</b> <code>scan_visit</code> از <code>next_appointment_map</code> می‌خواند،
نه از <code>overdue_followups</code>؛ پس اصلاح فهرست، پیامک‌ها را جابه‌جا نمی‌کند.
</div>
</section>
<!-- ======================= REPRO ======================= -->
<section id="repro" class="mt-16 scroll-mt-20">
<h2 class="mb-4 flex items-center gap-2 text-xl font-bold text-white">
<span class="grid h-7 w-7 place-items-center rounded-lg bg-brand/15 text-sm text-brand">۹</span>
بازتولید و اعتبارسنجی
</h2>
<div class="grid gap-4 lg:grid-cols-2">
<div class="rounded-2xl border border-line bg-[#0d1424] p-4">
<div class="mb-2 text-[11px] text-slate-500">ممیزی فقط-خواندنی روی کپی دیتابیس</div>
<pre class="code overflow-x-auto text-[11.5px] leading-6 text-slate-300"><code>.venv/Scripts/python.exe scripts/entezar_action_audit.py
.venv/Scripts/python.exe scripts/entezar_action_audit.py --samples 10
.venv/Scripts/python.exe scripts/entezar_action_audit.py --customer 236
.venv/Scripts/python.exe scripts/entezar_action_audit.py --db data9/&lt;backup&gt;.sqlite3</code></pre>
<p class="mt-2 text-[11px] leading-6 text-slate-500">
فایل sqlite + <code>-wal</code>/<code>-shm</code> در پوشهٔ موقت کپی می‌شود؛ دیتابیس اصلی دست‌نخورده می‌ماند.
</p>
</div>
<div class="rounded-2xl border border-line bg-[#0d1424] p-4">
<div class="mb-2 text-[11px] text-slate-500">شبیه‌سازی‌ها</div>
<pre class="code overflow-x-auto text-[11.5px] leading-6 text-slate-300"><code>.venv/Scripts/python.exe manage.py test \
tests.test_entezar_action_simulation -v 2</code></pre>
<div class="mt-3 flex flex-wrap items-center gap-2">
<span class="rounded-lg bg-emerald-500/15 px-3 py-1 text-[12px] text-emerald-300">۳۲ تست · OK</span>
<span class="text-[11px] text-slate-500">Django 3.2.25 · SQLite حافظه‌ای</span>
</div>
</div>
</div>
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-sm font-bold text-white">۹</div>
<div class="mt-1 text-[11px] leading-5 text-slate-400">تست فرم: نگاشت فیلدها، ۱۴ روز، گذشته/۶ ماه، فقط رو به جلو</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-sm font-bold text-white">۶</div>
<div class="mt-1 text-[11px] leading-5 text-slate-400">تست تصمیم انتظار: «۲ قرار»، قرار دستی، ابطال با بازدید</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-sm font-bold text-white">۶</div>
<div class="mt-1 text-[11px] leading-5 text-slate-400">تست بازتولید باگ missed (رفتار فعلی قفل شده)</div>
</div>
<div class="rounded-xl border border-line bg-panel p-4">
<div class="text-sm font-bold text-white">۱۱</div>
<div class="mt-1 text-[11px] leading-5 text-slate-400">تست SMS: قطع/بازگشت، بی‌اثری ستون، متن visit_created</div>
</div>
</div>
<div class="mt-4 rounded-xl border border-amber-400/25 bg-amber-400/5 px-4 py-3 text-[11px] leading-6 text-amber-100">
<b>محدودیت‌ها:</b> اعداد از یک نسخهٔ پشتیبان (۲۰۲۶-۱۰-۲۱) هستند · «بازدید بعد از هماهنگی» با
<code>createdDT</code> سنجیده شده (تنها ستون تضمین‌کنندهٔ ترتیب ثبت) · تاریخ‌های شبیه‌سازی نسبی‌اند
تا تست به روزِ اجرا وابسته نباشد.
</div>
</section>
</main>
<footer class="border-t border-line bg-ink/80 py-8">
<div class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-3 px-4 text-[11px] text-slate-500">
<div>
مصنوعات: <code class="text-slate-400">docs/entezar_action_fields_report.md</code> ·
<code class="text-slate-400">tests/test_entezar_action_simulation.py</code> ·
<code class="text-slate-400">scripts/entezar_action_audit.py</code>
</div>
<div>کد تولیدی بدون تغییر · گزارش تولیدشده از کد و دادهٔ واقعی</div>
</div>
</footer>
<script>
// نوار پیشرفت مطالعه
const bar = document.getElementById('progress');
const onScroll = () => {
const h = document.documentElement;
const max = h.scrollHeight - h.clientHeight;
bar.style.width = (max > 0 ? (h.scrollTop / max) * 100 : 0) + '%';
};
document.addEventListener('scroll', onScroll, { passive: true });
onScroll();
// هایلایت بخش فعال در ناوبری
const links = [...document.querySelectorAll('nav a[href^="#"]')];
const sections = links
.map(a => document.querySelector(a.getAttribute('href')))
.filter(Boolean);
const io = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
links.forEach(a => a.classList.remove('bg-card', 'text-white'));
const active = links.find(a => a.getAttribute('href') === '#' + entry.target.id);
if (active) active.classList.add('bg-card', 'text-white');
});
}, { rootMargin: '-30% 0px -60% 0px' });
sections.forEach(s => io.observe(s));
// انیمیشن نوارها بعد از بارگذاری
window.addEventListener('load', () => {
document.querySelectorAll('.bar').forEach(el => {
const w = el.style.width; el.style.width = '0';
requestAnimationFrame(() => { setTimeout(() => { el.style.width = w; }, 60); });
});
});
</script>
</body>
</html>