2026-05-30 07:23:33 +08:00
|
|
|
|
"""
|
|
|
|
|
|
业务员工作台路由模块
|
|
|
|
|
|
|
|
|
|
|
|
提供业务员个人工作台的概览数据接口,包括:
|
|
|
|
|
|
- 订单概览(最近订单列表)
|
|
|
|
|
|
- 提醒概览(待处理提醒列表)
|
|
|
|
|
|
- 汇总统计(订单数量、提醒数量)
|
2026-06-19 23:03:14 +08:00
|
|
|
|
- 销售统计(日/周/月销售额统计)
|
2026-05-30 07:23:33 +08:00
|
|
|
|
|
|
|
|
|
|
URL 前缀:/api/salesman
|
|
|
|
|
|
权限要求:salesman/admin 角色
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-06-19 23:03:14 +08:00
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
|
|
|
from sqlalchemy import func
|
2026-05-21 17:51:35 +08:00
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
|
|
from backend.app.api.deps import require_permissions, require_roles
|
|
|
|
|
|
from backend.app.db import get_db_session
|
2026-05-31 11:17:56 +08:00
|
|
|
|
from backend.app.core.cache import cache_delete_pattern, cache_get, cache_set
|
2026-05-29 11:38:53 +08:00
|
|
|
|
from backend.app.core.error_codes import ErrorCode
|
|
|
|
|
|
from backend.app.core.exceptions import AppException
|
2026-06-19 23:03:14 +08:00
|
|
|
|
from backend.app.models.business import SalesOrder
|
2026-05-21 17:51:35 +08:00
|
|
|
|
from backend.app.schemas.common import success_payload
|
|
|
|
|
|
from backend.app.services.reminder_service import reminder_service
|
|
|
|
|
|
from backend.app.services.order_service import order_service
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/salesman", tags=["salesman"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/dashboard")
|
|
|
|
|
|
def salesman_dashboard(
|
2026-05-30 07:23:33 +08:00
|
|
|
|
session: Session = Depends(get_db_session), # 数据库会话
|
|
|
|
|
|
current_user: dict = Depends(require_roles("salesman", "admin")), # 当前登录用户,仅salesman和admin可访问
|
|
|
|
|
|
_permission_user: dict = Depends(require_permissions("order:list")), # 权限校验:订单列表查看权限
|
2026-05-21 17:51:35 +08:00
|
|
|
|
) -> dict:
|
2026-05-30 07:23:33 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取业务员工作台概览数据
|
|
|
|
|
|
|
|
|
|
|
|
返回当前业务员的订单列表(最近20条)、提醒列表及汇总统计信息。
|
|
|
|
|
|
业务员只能查看自己的数据,管理员可查看所有。
|
|
|
|
|
|
|
|
|
|
|
|
返回值:
|
|
|
|
|
|
包含以下字段的字典:
|
|
|
|
|
|
- orders: 最近20条订单列表
|
|
|
|
|
|
- reminders: 提醒列表
|
|
|
|
|
|
- summary: 汇总数据(order_count 订单总数、reminder_count 提醒总数)
|
|
|
|
|
|
|
|
|
|
|
|
权限要求:salesman、admin 角色,需 order:list 权限
|
|
|
|
|
|
"""
|
2026-05-31 11:17:56 +08:00
|
|
|
|
user_id = current_user.get("user_id")
|
|
|
|
|
|
cache_key = f"dashboard:{user_id}"
|
|
|
|
|
|
cached = cache_get(cache_key)
|
|
|
|
|
|
if cached is not None:
|
|
|
|
|
|
return success_payload(cached)
|
|
|
|
|
|
|
2026-06-19 23:03:14 +08:00
|
|
|
|
orders_result = order_service.list_orders(session, {"salesman_id": user_id})
|
|
|
|
|
|
orders = orders_result.get("list", [])
|
2026-05-31 11:17:56 +08:00
|
|
|
|
reminders = reminder_service.list_reminders(session, {"receiver_user_id": user_id})
|
|
|
|
|
|
result = {
|
|
|
|
|
|
"orders": orders[:20],
|
|
|
|
|
|
"reminders": reminders.get("list", []) if isinstance(reminders, dict) else [],
|
|
|
|
|
|
"summary": {
|
2026-06-19 23:03:14 +08:00
|
|
|
|
"order_count": orders_result.get("total", 0),
|
2026-05-31 11:17:56 +08:00
|
|
|
|
"reminder_count": len(reminders.get("list", [])) if isinstance(reminders, dict) else 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
cache_set(cache_key, result, ttl=30)
|
|
|
|
|
|
return success_payload(result)
|
2026-06-19 23:03:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/statistics")
|
|
|
|
|
|
def salesman_statistics(
|
|
|
|
|
|
period: str = Query(default="week", regex="^(day|week|month)$"),
|
|
|
|
|
|
session: Session = Depends(get_db_session),
|
|
|
|
|
|
current_user: dict = Depends(require_roles("salesman", "admin")),
|
|
|
|
|
|
_permission_user: dict = Depends(require_permissions("order:list")),
|
|
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取业务员销售统计数据
|
|
|
|
|
|
|
|
|
|
|
|
根据指定的时间周期(日/周/月)统计销售额、订单数、客单价等数据。
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
period: 统计周期,可选值:day(今日)、week(本周)、month(本月)
|
|
|
|
|
|
|
|
|
|
|
|
返回值:
|
|
|
|
|
|
包含以下字段的字典:
|
|
|
|
|
|
- total_sales: 总销售额
|
|
|
|
|
|
- order_count: 订单数
|
|
|
|
|
|
- avg_order_amount: 客单价
|
|
|
|
|
|
- daily_breakdown: 每日销售明细
|
|
|
|
|
|
- top_customers: TOP客户排名
|
|
|
|
|
|
|
|
|
|
|
|
权限要求:salesman、admin 角色,需 order:list 权限
|
|
|
|
|
|
"""
|
|
|
|
|
|
user_id = current_user.get("user_id")
|
|
|
|
|
|
cache_key = f"salesman:statistics:{user_id}:{period}"
|
|
|
|
|
|
cached = cache_get(cache_key)
|
|
|
|
|
|
if cached is not None:
|
|
|
|
|
|
return success_payload(cached)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算时间范围
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
if period == "day":
|
|
|
|
|
|
start_date = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
elif period == "week":
|
|
|
|
|
|
start_date = now - timedelta(days=now.weekday())
|
|
|
|
|
|
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
else: # month
|
|
|
|
|
|
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
|
|
|
|
|
|
# 查询订单数据
|
|
|
|
|
|
base_query = session.query(SalesOrder).filter(
|
|
|
|
|
|
SalesOrder.salesman_id == user_id,
|
|
|
|
|
|
SalesOrder.order_status.notin_(["canceled", "draft"]),
|
|
|
|
|
|
SalesOrder.created_at >= start_date,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 汇总统计
|
|
|
|
|
|
total_sales = base_query.with_entities(func.sum(SalesOrder.sale_price_total)).scalar() or 0
|
|
|
|
|
|
order_count = base_query.count()
|
|
|
|
|
|
avg_order_amount = total_sales / order_count if order_count > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
# 每日明细
|
|
|
|
|
|
daily_query = session.query(
|
|
|
|
|
|
func.date(SalesOrder.created_at).label("date"),
|
|
|
|
|
|
func.sum(SalesOrder.sale_price_total).label("sales"),
|
|
|
|
|
|
func.count(SalesOrder.id).label("count"),
|
|
|
|
|
|
).filter(
|
|
|
|
|
|
SalesOrder.salesman_id == user_id,
|
|
|
|
|
|
SalesOrder.order_status.notin_(["canceled", "draft"]),
|
|
|
|
|
|
SalesOrder.created_at >= start_date,
|
|
|
|
|
|
).group_by(func.date(SalesOrder.created_at)).order_by(func.date(SalesOrder.created_at))
|
|
|
|
|
|
|
|
|
|
|
|
daily_breakdown = [
|
|
|
|
|
|
{"date": str(row.date), "sales": float(row.sales or 0), "count": row.count}
|
|
|
|
|
|
for row in daily_query.all()
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# TOP客户
|
|
|
|
|
|
top_customers_query = session.query(
|
|
|
|
|
|
SalesOrder.customer_name,
|
|
|
|
|
|
func.sum(SalesOrder.sale_price_total).label("sales"),
|
|
|
|
|
|
func.count(SalesOrder.id).label("count"),
|
|
|
|
|
|
).filter(
|
|
|
|
|
|
SalesOrder.salesman_id == user_id,
|
|
|
|
|
|
SalesOrder.order_status.notin_(["canceled", "draft"]),
|
|
|
|
|
|
SalesOrder.created_at >= start_date,
|
|
|
|
|
|
).group_by(SalesOrder.customer_name).order_by(func.sum(SalesOrder.sale_price_total).desc()).limit(10)
|
|
|
|
|
|
|
|
|
|
|
|
top_customers = [
|
|
|
|
|
|
{"customer_name": row.customer_name, "sales": float(row.sales or 0), "count": row.count}
|
|
|
|
|
|
for row in top_customers_query.all()
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
result = {
|
|
|
|
|
|
"period": period,
|
|
|
|
|
|
"total_sales": round(float(total_sales), 2),
|
|
|
|
|
|
"order_count": order_count,
|
|
|
|
|
|
"avg_order_amount": round(float(avg_order_amount), 2),
|
|
|
|
|
|
"daily_breakdown": daily_breakdown,
|
|
|
|
|
|
"top_customers": top_customers,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cache_set(cache_key, result, ttl=60)
|
|
|
|
|
|
return success_payload(result)
|