feat: 三项改进 — require_report + 拜访联动 + 解绑移到弹窗

1. 用户管理页 UX 优化:
   - 表格内「解绑」按钮移入编辑弹窗(clearable 输入框)
   - 新增「需要填写周报」el-switch 开关
   - 新增「填报」列显示是否计入统计

2. require_report 字段 (DB + API + 联动):
   - users 表新增 require_report BOOLEAN DEFAULT TRUE
   - 调度器/仪表盘进度/立即检查 均排除 require_report=false
   - UserOut schema + UpdateUserRoleRequest 同步更新

3. 拜访联动客户 last_visit:
   - customers 表新增 last_visit_date / last_visit_manager_id
   - 创建拜访记录时自动更新对应客户的最后拜访信息
   - 亮灯表可据此精确判断客户拜访状态

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-26 16:36:27 +08:00
parent 989c41da8c
commit a580eefd14
9 changed files with 54 additions and 26 deletions
+3
View File
@@ -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,
}
+8
View File
@@ -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"]):
+10
View File
@@ -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")
+4 -2
View File
@@ -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")
+1
View File
@@ -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())
+1
View File
@@ -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}
+1 -1
View File
@@ -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]
+2 -2
View File
@@ -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()