diff --git a/backend/app/api/visits.py b/backend/app/api/visits.py index d38ad12..001388c 100644 --- a/backend/app/api/visits.py +++ b/backend/app/api/visits.py @@ -161,17 +161,11 @@ async def create_visit( db.add(customer) # Auto-complete matching work plans for this customer - from app.models.work_plan import WorkPlan - plans_result = await db.execute( - select(WorkPlan).where( - WorkPlan.customer_id == data.customer_id, - WorkPlan.status == "计划中", - WorkPlan.plan_date <= parse_date(data.visit_date), - ) + from app.services.visits import auto_complete_work_plans + await auto_complete_work_plans( + db, data.customer_id, parse_date(data.visit_date), + current_user["name"], "拜访自动完成", ) - for plan in plans_result.scalars().all(): - plan.status = "已完成" - append_entry(plan, current_user["name"], [{"field": "status", "from": "计划中", "to": "已完成", "reason": "拜访自动完成"}]) # Create draft copies for companions for companion_id in data.companions: @@ -237,6 +231,15 @@ async def update_visit( await db.commit() await db.refresh(visit) + + # Auto-complete matching work plans after visit update + from app.services.visits import auto_complete_work_plans + await auto_complete_work_plans( + db, visit.customer_id, visit.visit_date, + current_user["name"], "拜访更新自动完成", + ) + await db.commit() + return await _enrich_visit(visit, db) diff --git a/backend/app/services/excel_import.py b/backend/app/services/excel_import.py index 5cad36b..646c7d5 100644 --- a/backend/app/services/excel_import.py +++ b/backend/app/services/excel_import.py @@ -203,6 +203,13 @@ async def import_from_excel(db: AsyncSession, file_bytes: bytes, manager_id: uui db.add(visit) stats["visits"] += 1 + # Auto-complete matching work plans + from app.services.visits import auto_complete_work_plans + await auto_complete_work_plans( + db, customer_id, visit_date, + mgr_name, "旧周报导入自动完成", + ) + # Update customer's last_visit_date for light board cust = await db.get(Customer, customer_id) if cust and (not cust.last_visit_date or visit_date > cust.last_visit_date): diff --git a/backend/app/services/scheduler.py b/backend/app/services/scheduler.py index 33e192c..98d8374 100644 --- a/backend/app/services/scheduler.py +++ b/backend/app/services/scheduler.py @@ -168,9 +168,33 @@ async def check_overdue_plans(db: AsyncSession) -> dict: await wecom_client.send_text_message([user.wecom_userid], content) + # ── Auto-cancel overdue plans that have no matching visit ── + from app.utils.edit_log import append_entry as append_edit_log + auto_cancelled = 0 + for plan in overdue: + has_visit = await db.execute( + select(Visit).where( + Visit.customer_id == plan.customer_id, + Visit.visit_date >= plan.plan_date, + ) + ) + if not has_visit.scalar(): + plan.status = "已取消" + append_edit_log(plan, "系统", [{ + "field": "status", + "from": "计划中", + "to": "已取消", + "reason": "逾期自动取消", + }]) + auto_cancelled += 1 + + if auto_cancelled: + await db.commit() + return { "status": "ok", "date": str(today), "overdue": len(overdue), + "auto_cancelled": auto_cancelled, "managers_affected": len(by_manager), } diff --git a/backend/app/services/visits.py b/backend/app/services/visits.py new file mode 100644 index 0000000..9808483 --- /dev/null +++ b/backend/app/services/visits.py @@ -0,0 +1,40 @@ +"""Visit service — shared logic for visit creation, update, and import.""" + +from uuid import UUID +from datetime import date +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from app.models.work_plan import WorkPlan +from app.utils.edit_log import append_entry + + +async def auto_complete_work_plans( + db: AsyncSession, + customer_id: UUID, + visit_date: date, + editor_name: str, + reason: str = "拜访自动完成", +) -> int: + """Auto-complete matching work plans when a visit is created/updated/imported. + + Matches by: same customer_id + status=="计划中" + plan_date <= visit_date. + Returns the number of plans completed. + """ + plans_result = await db.execute( + select(WorkPlan).where( + WorkPlan.customer_id == customer_id, + WorkPlan.status == "计划中", + WorkPlan.plan_date <= visit_date, + ) + ) + count = 0 + for plan in plans_result.scalars().all(): + plan.status = "已完成" + append_entry(plan, editor_name, [{ + "field": "status", + "from": "计划中", + "to": "已完成", + "reason": reason, + }]) + count += 1 + return count