From 10835ef15caf9778338b1f15a9092f86d50ece6f Mon Sep 17 00:00:00 2001 From: v6ole Date: Sun, 12 Jul 2026 19:40:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B6=88=E9=99=A4=E5=90=8C=E8=AE=BF?= =?UTF-8?q?=E8=8D=89=E6=A1=88=E6=9C=BA=E5=88=B6=EF=BC=8C=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=97=B6=E9=80=9A=E8=BF=87=20companions=20?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 POST /visits 中的草案创建逻辑(不再创建空拜访记录) - 周报查询:manager 角色也显示同访的拜访记录 - 亮灯表覆盖:同访人也算客户拜访覆盖(UNION companions) - 填报进度:同访人也算今日已填报 - 清理已有 2 条空草案记录 Co-Authored-By: Claude --- backend/app/api/visits.py | 16 ------------- backend/app/services/dashboard.py | 18 +++++++++++---- backend/app/services/light_board.py | 36 ++++++++++++++++++----------- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/backend/app/api/visits.py b/backend/app/api/visits.py index 736707b..fe35321 100644 --- a/backend/app/api/visits.py +++ b/backend/app/api/visits.py @@ -194,22 +194,6 @@ async def create_visit( ) db.add(log) - # Create draft copies for companions - for companion_id in data.companions: - if companion_id != uuid.UUID(current_user["user_id"]): - draft = Visit( - customer_id=data.customer_id, - visit_date=parse_date(data.visit_date), - visit_method=data.visit_method, - time_range=data.time_range, - communication_content="", # Leave blank for companion to fill - customer_demand="", - companions=[], - photos=[], - manager_id=companion_id, - ) - db.add(draft) - await db.commit() await db.refresh(visit) return await _enrich_visit(visit, db) diff --git a/backend/app/services/dashboard.py b/backend/app/services/dashboard.py index 821c9e9..96e0387 100644 --- a/backend/app/services/dashboard.py +++ b/backend/app/services/dashboard.py @@ -121,10 +121,14 @@ async def get_reporting_progress(db: AsyncSession, reference_date: date | None = } if leave else None, }) - # Check today's reporting — visits OR daily notes - today_visits = await db.execute( - select(Visit.manager_id).where(Visit.visit_date == today) + # Check today's reporting — visits (direct + companions) OR daily notes + from sqlalchemy import union_all + today_direct = select(Visit.manager_id).where(Visit.visit_date == today) + today_companion = select(func.unnest(Visit.companions).label("manager_id")).where( + Visit.visit_date == today, Visit.companions.isnot(None) ) + today_combined = union_all(today_direct, today_companion).subquery() + today_visits = await db.execute(select(today_combined.c.manager_id)) today_notes = await db.execute( select(DailyNote.manager_id).where(DailyNote.note_date == today) ) @@ -162,9 +166,13 @@ async def get_weekly_report( # ── Visits ── visit_query = select(Visit).where(Visit.visit_date >= monday, Visit.visit_date <= sunday) if role == "manager": - visit_query = visit_query.where(Visit.manager_id == user_id) + visit_query = visit_query.where( + (Visit.manager_id == user_id) | (Visit.companions.any(user_id)) + ) elif filter_manager_id: - visit_query = visit_query.where(Visit.manager_id == filter_manager_id) + visit_query = visit_query.where( + (Visit.manager_id == filter_manager_id) | (Visit.companions.any(filter_manager_id)) + ) if filter_customer_id: visit_query = visit_query.where(Visit.customer_id == filter_customer_id) visit_query = visit_query.order_by(Visit.visit_date.desc()) diff --git a/backend/app/services/light_board.py b/backend/app/services/light_board.py index bc78c55..8b29c59 100644 --- a/backend/app/services/light_board.py +++ b/backend/app/services/light_board.py @@ -85,19 +85,34 @@ async def get_light_board( for m in visible_managers } - # ── All customers with their last visit per manager ── - # Subquery: latest visit date + method per (manager_id, customer_id) + # ── All customers with their last visit per manager (including companions) ── + # UNION direct + companions (unnested), then DISTINCT ON to get the latest per (manager, customer) + from sqlalchemy import union_all, distinct + direct = select( + Visit.manager_id, Visit.customer_id, Visit.visit_date, Visit.visit_method, + ) + companion = select( + func.unnest(Visit.companions).label("manager_id"), + Visit.customer_id, + Visit.visit_date, + Visit.visit_method, + ).where(Visit.companions.isnot(None)) + combined = union_all(direct, companion).subquery() + + # Get latest visit (date + method) per (manager_id, customer_id) using DISTINCT ON latest_visit = ( select( - Visit.manager_id, - Visit.customer_id, - func.max(Visit.visit_date).label("last_visit_date"), + combined.c.manager_id, + combined.c.customer_id, + combined.c.visit_date.label("last_visit_date"), + combined.c.visit_method, ) - .group_by(Visit.manager_id, Visit.customer_id) + .distinct(combined.c.manager_id, combined.c.customer_id) + .order_by(combined.c.manager_id, combined.c.customer_id, combined.c.visit_date.desc()) .subquery() ) - # Join: assignments → customers → latest_visit → visits (for method) + # Join: assignments → customers → latest_visit rows = await db.execute( select( CustomerAssignment.manager_id, @@ -108,18 +123,13 @@ async def get_light_board( Customer.in_use_services, Customer.monthly_fee, latest_visit.c.last_visit_date, - Visit.visit_method, + latest_visit.c.visit_method, ) .join(Customer, Customer.id == CustomerAssignment.customer_id) .outerjoin(latest_visit, and_( latest_visit.c.manager_id == CustomerAssignment.manager_id, latest_visit.c.customer_id == CustomerAssignment.customer_id, )) - .outerjoin(Visit, and_( - Visit.manager_id == CustomerAssignment.manager_id, - Visit.customer_id == CustomerAssignment.customer_id, - Visit.visit_date == latest_visit.c.last_visit_date, - )) .where(CustomerAssignment.role == "primary") .order_by(Customer.name) )