diff --git a/backend/app/api/users.py b/backend/app/api/users.py index 96fde87..71a266b 100644 --- a/backend/app/api/users.py +++ b/backend/app/api/users.py @@ -15,6 +15,7 @@ router = APIRouter(prefix="/users", tags=["Users"]) class UpdateUserRoleRequest(BaseModel): role: str # manager / director / leader department: str = "" + require_report: bool = True @router.get("/", response_model=list[UserOut]) @@ -71,6 +72,7 @@ async def update_user_role( user.role = data.role if data.department: user.department = data.department + user.require_report = data.require_report await db.commit() await db.refresh(user) @@ -79,6 +81,7 @@ async def update_user_role( "name": user.name, "role": user.role, "department": user.department, + "require_report": user.require_report, } diff --git a/backend/app/api/visits.py b/backend/app/api/visits.py index a95162e..607c18d 100644 --- a/backend/app/api/visits.py +++ b/backend/app/api/visits.py @@ -143,6 +143,14 @@ async def create_visit( init_entry(visit, current_user["name"]) db.add(visit) + # Update customer's last visit tracking + cust_result = await db.execute(select(Customer).where(Customer.id == data.customer_id)) + customer = cust_result.scalar_one_or_none() + if customer: + customer.last_visit_date = parse_date(data.visit_date) + customer.last_visit_manager_id = uuid.UUID(current_user["user_id"]) + db.add(customer) + # Create draft copies for companions for companion_id in data.companions: if companion_id != uuid.UUID(current_user["user_id"]): diff --git a/backend/app/main.py b/backend/app/main.py index 49d7002..b4395b7 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -38,6 +38,16 @@ async def lifespan(app: FastAPI): await conn.run_sync(lambda c, t=tbl: c.exec_driver_sql( f"ALTER TABLE {t} ADD COLUMN IF NOT EXISTS edit_log JSONB DEFAULT '[]'" )) + # New columns for v0.3 + await conn.run_sync(lambda c: c.exec_driver_sql( + "ALTER TABLE users ADD COLUMN IF NOT EXISTS require_report BOOLEAN DEFAULT TRUE" + )) + await conn.run_sync(lambda c: c.exec_driver_sql( + "ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_date DATE" + )) + await conn.run_sync(lambda c: c.exec_driver_sql( + "ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_manager_id UUID" + )) # Start daily reporting scheduler (17:30 CST = 09:30 UTC) _scheduler.add_job(_scheduled_check, "cron", hour=17, minute=30, id="daily_check") diff --git a/backend/app/models/customer.py b/backend/app/models/customer.py index 16880d3..1e6a654 100644 --- a/backend/app/models/customer.py +++ b/backend/app/models/customer.py @@ -1,6 +1,6 @@ import uuid -from datetime import datetime -from sqlalchemy import String, Text, DateTime, func, ForeignKey +from datetime import date, datetime +from sqlalchemy import String, Text, Date, DateTime, Boolean, func, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID from app.database import Base @@ -19,6 +19,8 @@ class Customer(Base): created_by: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id")) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) + last_visit_date: Mapped[date | None] = mapped_column(Date, nullable=True) + last_visit_manager_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True) contacts: Mapped[list["CustomerContact"]] = relationship("CustomerContact", back_populates="customer", cascade="all, delete-orphan") assignments: Mapped[list["CustomerAssignment"]] = relationship("CustomerAssignment", back_populates="customer", cascade="all, delete-orphan") diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 8cafad7..aaa9613 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -15,4 +15,5 @@ class User(Base): role: Mapped[str] = mapped_column(String(30)) # manager / director / leader department: Mapped[str] = mapped_column(String(100), default="") wecom_userid: Mapped[str | None] = mapped_column(String(100), unique=True, nullable=True) + require_report: Mapped[bool] = mapped_column(default=True, server_default="true") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) diff --git a/backend/app/schemas/customer.py b/backend/app/schemas/customer.py index cfc8166..be6dcef 100644 --- a/backend/app/schemas/customer.py +++ b/backend/app/schemas/customer.py @@ -19,6 +19,7 @@ class UserOut(BaseModel): role: str department: str wecom_userid: Optional[str] = None + require_report: bool = True model_config = {"from_attributes": True} diff --git a/backend/app/services/dashboard.py b/backend/app/services/dashboard.py index 3c035d4..f2a8c52 100644 --- a/backend/app/services/dashboard.py +++ b/backend/app/services/dashboard.py @@ -55,7 +55,7 @@ async def get_reporting_progress(db: AsyncSession, reference_date: date | None = """Get per-manager reporting progress. Managers only see themselves.""" monday, sunday = get_week_range(reference_date) - managers_result = await db.execute(select(User).where(User.role == "manager")) + managers_result = await db.execute(select(User).where(User.role == "manager", User.require_report == True)) all_managers = managers_result.scalars().all() # Filter: managers only see themselves managers = all_managers if role in ("director", "leader") else [m for m in all_managers if str(m.id) == user_id] diff --git a/backend/app/services/scheduler.py b/backend/app/services/scheduler.py index 17e21ef..2525a6c 100644 --- a/backend/app/services/scheduler.py +++ b/backend/app/services/scheduler.py @@ -15,9 +15,9 @@ async def check_daily_reporting(db: AsyncSession) -> dict: if weekday >= 5: # Skip weekends return {"status": "weekend", "date": str(today)} - # Get all managers (stats include everyone) + # Get all managers who need to report result = await db.execute( - select(User).where(User.role == "manager") + select(User).where(User.role == "manager", User.require_report == True) ) managers = result.scalars().all() diff --git a/frontend/src/views/desktop/UserManage.vue b/frontend/src/views/desktop/UserManage.vue index 089e00c..4472ea8 100644 --- a/frontend/src/views/desktop/UserManage.vue +++ b/frontend/src/views/desktop/UserManage.vue @@ -1,6 +1,6 @@ - + - + + + + @@ -109,10 +106,14 @@ async function handleUnbind(row: any) { - + -
在企微后台 → 通讯录 → 成员详情 → 账号 中查看
+
企微后台 → 通讯录 → 成员详情 → 账号
+
+ + +
关闭后该用户不计入填报统计,不接收催办提醒
@@ -135,4 +136,6 @@ async function handleUnbind(row: any) { .page-rule { width: 32px; height: 3px; background: var(--gold); margin-top: 12px; } .wecom-id { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--sage); background: var(--c-bg-light, #f0ede5); padding: 2px 8px; border-radius: 4px; } .form-hint { font-size: 12px; color: var(--c-text-muted); margin-top: 4px; } +.tag-yes { font-size: 12px; color: var(--sage); } +.tag-no { font-size: 12px; color: var(--c-text-muted); }