feat: 三项联动 — 计划过期提醒 + 拜访自动关闭 + 批量制定

1. 计划过期自动提醒:
   - scheduler.py 新增 check_overdue_plans()
   - 每日 9:00 检查过期计划(plan_date < today, status=计划中)
   - 按经理汇总 → 企微推送提醒消息

2. 拜访后自动关闭计划:
   - create_visit 后自动将匹配的计划标记为「已完成」
   - 条件: customer_id 匹配 + status=计划中 + plan_date <= visit_date

3. 亮灯表批量制定:
   - 红灯/黄灯卡片右上角 ☐ 复选框
   - 勾选后出现「批量制定计划」按钮
   - 弹窗统一设置日期+内容 → 批量 POST

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-27 12:28:36 +08:00
parent 0e25201ede
commit b343970ecc
4 changed files with 165 additions and 4 deletions
+61
View File
@@ -4,6 +4,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.models.visit import Visit
from app.models.daily_note import DailyNote
from app.models.user import User
from app.models.work_plan import WorkPlan
from app.models.customer import Customer
from app.services.wecom import wecom_client
from app.utils.timezone import today_cst
@@ -85,3 +87,62 @@ async def check_daily_reporting(db: AsyncSession) -> dict:
"reported": len(reported_map),
"not_reported": len(not_reported),
}
async def check_overdue_plans(db: AsyncSession) -> dict:
"""Check for overdue work plans and remind managers (runs at 9:00 AM)."""
today = today_cst()
if today.weekday() >= 5:
return {"status": "weekend", "date": str(today)}
result = await db.execute(
select(WorkPlan).where(
WorkPlan.status == "计划中",
WorkPlan.plan_date < today,
).order_by(WorkPlan.manager_id, WorkPlan.plan_date)
)
overdue = result.scalars().all()
if not overdue:
return {"status": "ok", "date": str(today), "overdue": 0}
# Group by manager
by_manager: dict[str, list] = {}
for p in overdue:
mid = str(p.manager_id)
by_manager.setdefault(mid, []).append(p)
users_result = await db.execute(
select(User).where(User.id.in_([uid for uid in by_manager.keys()]))
)
user_map = {str(u.id): u for u in users_result.scalars().all()}
for mid, plans in by_manager.items():
user = user_map.get(mid)
if not user or not user.wecom_userid:
continue
names = "".join(f"{p.customer_id}" for p in plans[:5])
# Get customer names
cust_result = await db.execute(
select(Customer.name).where(Customer.id.in_([p.customer_id for p in plans[:5]]))
)
cust_names = [r[0] for r in cust_result.all()]
plan_lines = "".join(f"- {n} (计划 {p.plan_date})\n" for p, n in zip(plans[:5], cust_names))
content = (
f"📅 拜访计划过期提醒\n\n"
f"以下 {len(plans)} 个拜访计划已过期,请尽快安排拜访:\n"
f"{plan_lines}"
)
if len(plans) > 5:
content += f"... 还有 {len(plans) - 5} 个过期计划\n"
content += f"\n👉 查看详情:https://qj.dhdx.fun/light-board"
await wecom_client.send_text_message([user.wecom_userid], content)
return {
"status": "ok",
"date": str(today),
"overdue": len(overdue),
"managers_affected": len(by_manager),
}