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:
HHY
2026-09-20 12:27:19 -07:00
parent 3f871d7eeb
commit 453decf2e4
15 changed files with 233 additions and 29 deletions
+53
View File
@@ -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})
+3
View File
@@ -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)
+3
View File
@@ -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())