feat: 亮灯表30天滚动窗口 + 相关人员文案统一 + 周报数据补全
- light_board.classify: 自然月 → 30/60天滚动窗口 (≤30天🟢, 31-60天🟡, 61+天🔴) - LightBoard.vue: 4处文案同步 (近30天已拜访 / 上次 / 个周期) - dashboard.get_weekly_report: 补全 companion_names_resolved + visitor_name + visitor_phone + edit_log - 导入/导出模板 + VisitForm + ManagerWorkspace: 同访人员 → 相关人员
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"""Customer light board — visit coverage matrix per manager.
|
||||
|
||||
Status:
|
||||
green — visited this month (亮灯)
|
||||
yellow — visited last month but not this month (临期)
|
||||
red — not visited in 2+ months, or never visited (灭灯+警示)
|
||||
Status (rolling 30-day window):
|
||||
green — visited within last 30 days (亮灯)
|
||||
yellow — visited 31-60 days ago (临期)
|
||||
red — visited 61+ days ago, or never visited (灭灯+警示)
|
||||
gray — customer has no assigned primary manager (未分配)
|
||||
"""
|
||||
|
||||
@@ -29,29 +29,26 @@ def month_end(ref: date) -> date:
|
||||
|
||||
|
||||
def classify(last_visit_date: date | None, ref: date) -> tuple[str, int]:
|
||||
"""Return (status, consecutive_missed_months)."""
|
||||
"""Return (status, consecutive_missed_periods).
|
||||
|
||||
30-day rolling window:
|
||||
green — visited within 30 days
|
||||
yellow — visited 31-60 days ago (临期)
|
||||
red — 61+ days ago, or never visited
|
||||
"""
|
||||
if last_visit_date is None:
|
||||
return ("red", 99) # never visited
|
||||
|
||||
this_month = month_start(ref)
|
||||
last_month = month_start(this_month - timedelta(days=1))
|
||||
days_since = (ref - last_visit_date).days
|
||||
|
||||
if last_visit_date >= this_month:
|
||||
if days_since <= 30:
|
||||
return ("green", 0)
|
||||
elif last_visit_date >= last_month:
|
||||
elif days_since <= 60:
|
||||
return ("yellow", 0)
|
||||
|
||||
# Count how many consecutive months missed
|
||||
cursor = month_start(ref)
|
||||
missed = 0
|
||||
while True:
|
||||
cursor = month_start(cursor - timedelta(days=1))
|
||||
if last_visit_date >= cursor:
|
||||
break
|
||||
missed += 1
|
||||
if missed > 24: # safety cap
|
||||
break
|
||||
return ("red", missed)
|
||||
# Each 30-day block beyond 60 days counts as one missed period
|
||||
periods = days_since // 30
|
||||
return ("red", min(periods, 99))
|
||||
|
||||
|
||||
async def get_light_board(
|
||||
|
||||
Reference in New Issue
Block a user