feat: v0.9 样板数据完整性验证/解析加固/报表统计
- 双源确认样板库完整性: search接口与画图编辑器for_ht目录均为254条, 0遗漏 - 全量体检: 6套样板含7个畸形段(缺ps/pe)导致解析报错 前后端重放/算料统一加固过滤, 254样板0异常/1046窗格 - 报表统计模块(/chart): 近N天订单数金额按日柱图+8项总量+状态分布 - 样板库官方/我的样板双Tab(is_default过滤) - 使用指南接入帮助文章接口(种子8篇), seed分类树幂等修复 - 全接口回归18/18通过
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""报表统计路由(对齐原站 /chart 报表)。"""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from huamenchuang.core.deps import CurrentUser, DbSession
|
||||
from huamenchuang.core.response import ok
|
||||
from huamenchuang.models.business import AfterSale, MeasureOrder, Quotation
|
||||
from huamenchuang.models.contract import Contract, Customer
|
||||
from huamenchuang.models.order import Order
|
||||
|
||||
router = APIRouter(prefix="/reports", tags=["报表"])
|
||||
|
||||
|
||||
@router.get("/summary")
|
||||
async def summary(user: CurrentUser, db: DbSession, days: int = 14):
|
||||
"""经营汇总:近 N 天订单数/金额按日 + 各实体总数。"""
|
||||
since = datetime.utcnow() - timedelta(days=days)
|
||||
cid = user.company_id
|
||||
|
||||
# 按日订单数与金额
|
||||
day_rows = (await db.execute(
|
||||
select(func.date(Order.created_at), func.count(), func.sum(Order.money))
|
||||
.where(Order.company_id == cid, Order.created_at >= since)
|
||||
.group_by(func.date(Order.created_at))
|
||||
)).all()
|
||||
by_day = [{"date": str(d), "count": c, "money": float(m or 0)} for d, c, m in day_rows]
|
||||
|
||||
async def count(model, extra=None):
|
||||
conds = [model.company_id == cid]
|
||||
if extra is not None:
|
||||
conds.append(extra)
|
||||
return (await db.execute(select(func.count()).select_from(model).where(*conds))).scalar_one()
|
||||
|
||||
totals = {
|
||||
"orders": await count(Order),
|
||||
"intentions": await count(Order, Order.status == 0),
|
||||
"finished": await count(Order, Order.status == 3),
|
||||
"quotations": await count(Quotation),
|
||||
"customers": await count(Customer),
|
||||
"contracts": await count(Contract),
|
||||
"measures": await count(MeasureOrder),
|
||||
"after_sales": await count(AfterSale, AfterSale.status != 2),
|
||||
}
|
||||
|
||||
# 订单状态分布
|
||||
status_rows = (await db.execute(
|
||||
select(Order.status, func.count()).where(Order.company_id == cid).group_by(Order.status)
|
||||
)).all()
|
||||
status_dist = [{"status": s, "count": c} for s, c in status_rows]
|
||||
|
||||
return ok(data={"by_day": by_day, "totals": totals, "status_dist": status_dist, "days": days})
|
||||
@@ -10,6 +10,7 @@ from huamenchuang.api import (
|
||||
orders,
|
||||
platform,
|
||||
quote,
|
||||
reports,
|
||||
warehouse,
|
||||
windoor,
|
||||
)
|
||||
@@ -25,3 +26,5 @@ api_router.include_router(quote.router)
|
||||
api_router.include_router(warehouse.router)
|
||||
api_router.include_router(business.router)
|
||||
api_router.include_router(platform.router)
|
||||
api_router.include_router(reports.router)
|
||||
api_router.include_router(platform.router)
|
||||
|
||||
@@ -32,6 +32,7 @@ async def search_templates(
|
||||
db: DbSession, user: CurrentUser,
|
||||
name: str = Query(default="", max_length=64),
|
||||
catelog_id: int | None = None,
|
||||
is_default: int | None = Query(default=None, description="1官方样板 0我的样板"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=50, ge=1, le=200),
|
||||
):
|
||||
@@ -41,6 +42,8 @@ async def search_templates(
|
||||
conds.append(WindoorTemplate.name.like(f"%{name}%"))
|
||||
if catelog_id is not None:
|
||||
conds.append(WindoorTemplate.catelog_id == catelog_id)
|
||||
if is_default is not None:
|
||||
conds.append(WindoorTemplate.is_default == is_default)
|
||||
stmt = (select(WindoorTemplate).where(*conds)
|
||||
.order_by(WindoorTemplate.id).offset((page - 1) * page_size).limit(page_size))
|
||||
items = list((await db.execute(stmt)).scalars())
|
||||
|
||||
Reference in New Issue
Block a user