1234 lines
83 KiB
HTML
1234 lines
83 KiB
HTML
<!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&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 > 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&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 < 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 <= now else finish_dt
|
||
if eff and eff.date() >= 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/<backup>.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>
|