dingdanquanliucheng/backend/app/api/salesman.py
2026-06-19 23:03:14 +08:00

170 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
业务员工作台路由模块
提供业务员个人工作台的概览数据接口,包括:
- 订单概览(最近订单列表)
- 提醒概览(待处理提醒列表)
- 汇总统计(订单数量、提醒数量)
- 销售统计(日/周/月销售额统计)
URL 前缀:/api/salesman
权限要求salesman/admin 角色
"""
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, Query
from sqlalchemy import func
from sqlalchemy.orm import Session
from backend.app.api.deps import require_permissions, require_roles
from backend.app.db import get_db_session
from backend.app.core.cache import cache_delete_pattern, cache_get, cache_set
from backend.app.core.error_codes import ErrorCode
from backend.app.core.exceptions import AppException
from backend.app.models.business import SalesOrder
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(
session: Session = Depends(get_db_session), # 数据库会话
current_user: dict = Depends(require_roles("salesman", "admin")), # 当前登录用户仅salesman和admin可访问
_permission_user: dict = Depends(require_permissions("order:list")), # 权限校验:订单列表查看权限
) -> dict:
"""
获取业务员工作台概览数据
返回当前业务员的订单列表最近20条、提醒列表及汇总统计信息。
业务员只能查看自己的数据,管理员可查看所有。
返回值:
包含以下字段的字典:
- orders: 最近20条订单列表
- reminders: 提醒列表
- summary: 汇总数据order_count 订单总数、reminder_count 提醒总数)
权限要求salesman、admin 角色,需 order:list 权限
"""
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)
orders_result = order_service.list_orders(session, {"salesman_id": user_id})
orders = orders_result.get("list", [])
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": {
"order_count": orders_result.get("total", 0),
"reminder_count": len(reminders.get("list", [])) if isinstance(reminders, dict) else 0,
},
}
cache_set(cache_key, result, ttl=30)
return success_payload(result)
@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)