feat: P0-3 打印模板体系(区块化模板CRUD+打印中心重构)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""打印模板路由:订单打印模板 CRUD(对齐目标站 order_format 语义)。"""
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
|
||||
from huamenchuang.core.deps import CurrentUser, DbSession
|
||||
from huamenchuang.core.response import fail, ok
|
||||
from huamenchuang.models.extend import OrderFormat
|
||||
|
||||
router = APIRouter(prefix="/order-formats", tags=["打印模板"])
|
||||
|
||||
BLOCK_TYPES = ("title", "info", "image", "table", "sign")
|
||||
TABLE_KINDS = ("process", "bom")
|
||||
|
||||
|
||||
class FormatBody(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=64)
|
||||
order_type: int = Field(default=0, ge=0, le=1)
|
||||
blocks: list[dict] = Field(default_factory=list, description="模板区块列表")
|
||||
is_default: int = Field(default=0, ge=0, le=1)
|
||||
|
||||
|
||||
def _validate_blocks(blocks: list[dict]) -> str | None:
|
||||
for b in blocks:
|
||||
if b.get("type") not in BLOCK_TYPES:
|
||||
return f"不支持的区块类型: {b.get('type')}"
|
||||
if b.get("type") == "table" and b.get("kind") not in TABLE_KINDS:
|
||||
return "table 区块需要 kind: process/bom"
|
||||
return None
|
||||
|
||||
|
||||
def _to_out(f: OrderFormat) -> dict:
|
||||
return {"id": f.id, "name": f.name, "order_type": f.order_type,
|
||||
"blocks": json.loads(f.blocks or "[]"), "is_default": f.is_default, "sort": f.sort}
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_formats(user: CurrentUser, db: DbSession):
|
||||
stmt = (select(OrderFormat).where(OrderFormat.company_id == user.company_id)
|
||||
.order_by(OrderFormat.sort, OrderFormat.id))
|
||||
rows = list((await db.execute(stmt)).scalars())
|
||||
return ok(data=[_to_out(r) for r in rows])
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_format(body: FormatBody, user: CurrentUser, db: DbSession):
|
||||
err = _validate_blocks(body.blocks)
|
||||
if err:
|
||||
return fail(err)
|
||||
fmt = OrderFormat(company_id=user.company_id,
|
||||
name=body.name, order_type=body.order_type,
|
||||
blocks=json.dumps(body.blocks, ensure_ascii=False),
|
||||
is_default=body.is_default)
|
||||
if body.is_default: # 唯一默认
|
||||
for other in (await db.execute(select(OrderFormat).where(
|
||||
OrderFormat.company_id == user.company_id))).scalars():
|
||||
other.is_default = 0
|
||||
db.add(fmt)
|
||||
await db.flush()
|
||||
return ok(data=_to_out(fmt), message="模板已创建")
|
||||
|
||||
|
||||
@router.put("/{format_id}")
|
||||
async def update_format(format_id: int, body: FormatBody, user: CurrentUser, db: DbSession):
|
||||
fmt = await db.get(OrderFormat, format_id)
|
||||
if fmt is None or fmt.company_id != user.company_id:
|
||||
return fail("模板不存在")
|
||||
err = _validate_blocks(body.blocks)
|
||||
if err:
|
||||
return fail(err)
|
||||
fmt.name = body.name
|
||||
fmt.order_type = body.order_type
|
||||
fmt.blocks = json.dumps(body.blocks, ensure_ascii=False)
|
||||
if body.is_default:
|
||||
for other in (await db.execute(select(OrderFormat).where(
|
||||
OrderFormat.company_id == user.company_id))).scalars():
|
||||
other.is_default = 0
|
||||
fmt.is_default = 1
|
||||
else:
|
||||
fmt.is_default = 0
|
||||
return ok(data=_to_out(fmt), message="模板已更新")
|
||||
|
||||
|
||||
@router.delete("/{format_id}")
|
||||
async def delete_format(format_id: int, user: CurrentUser, db: DbSession):
|
||||
fmt = await db.get(OrderFormat, format_id)
|
||||
if fmt is None or fmt.company_id != user.company_id:
|
||||
return fail("模板不存在")
|
||||
await db.delete(fmt)
|
||||
return ok(message="模板已删除")
|
||||
@@ -7,6 +7,7 @@ from huamenchuang.api import (
|
||||
contracts,
|
||||
customers,
|
||||
optimize,
|
||||
order_format,
|
||||
orders,
|
||||
platform,
|
||||
quote,
|
||||
@@ -25,6 +26,7 @@ api_router.include_router(windoor.router)
|
||||
api_router.include_router(optimize.router)
|
||||
api_router.include_router(quote.router)
|
||||
api_router.include_router(script.router)
|
||||
api_router.include_router(order_format.router)
|
||||
api_router.include_router(warehouse.router)
|
||||
api_router.include_router(business.router)
|
||||
api_router.include_router(platform.router)
|
||||
|
||||
@@ -19,6 +19,7 @@ from huamenchuang.models.extend import (
|
||||
Article,
|
||||
CompanyParam,
|
||||
Device,
|
||||
OrderFormat,
|
||||
PayOrder,
|
||||
SmsCode,
|
||||
)
|
||||
@@ -37,6 +38,7 @@ __all__ = [
|
||||
"Device",
|
||||
"MeasureOrder",
|
||||
"Order",
|
||||
"OrderFormat",
|
||||
"OrderLog",
|
||||
"OrderProcess",
|
||||
"PayOrder",
|
||||
|
||||
@@ -68,6 +68,22 @@ class Article(Base):
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
class OrderFormat(Base):
|
||||
"""订单打印模板(对齐目标站 order_format:可自定义模板区块与默认模板)。"""
|
||||
|
||||
__tablename__ = "order_formats"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
company_id: Mapped[int] = mapped_column(default=1, index=True)
|
||||
name: Mapped[str] = mapped_column(String(64))
|
||||
order_type: Mapped[int] = mapped_column(default=0) # 0门窗订单 1阳光房订单
|
||||
# 模板区块 JSON: [{type: title|info|image|table|sign, text?, fields?, kind?}]
|
||||
blocks: Mapped[str] = mapped_column(Text, default="[]")
|
||||
is_default: Mapped[int] = mapped_column(default=0)
|
||||
sort: Mapped[int] = mapped_column(default=0)
|
||||
created_at: Mapped[datetime.datetime] = mapped_column(DateTime, default=datetime.datetime.utcnow)
|
||||
|
||||
|
||||
class SmsCode(Base):
|
||||
"""短信验证码(provider 适配层:未配置短信签名时走 mock 通道)。"""
|
||||
|
||||
|
||||
@@ -117,6 +117,35 @@ async def seed() -> None:
|
||||
await db.commit()
|
||||
print("价格脚本就绪")
|
||||
|
||||
# 7) 打印模板(幂等:按名称去重)
|
||||
async with SessionLocal() as db:
|
||||
from huamenchuang.models.extend import OrderFormat
|
||||
FORMATS = [
|
||||
("标准生产订单", [
|
||||
{"type": "title", "text": "生产订单"},
|
||||
{"type": "info", "fields": ["order_no", "customer_name", "phone", "address", "count", "money"]},
|
||||
{"type": "image"},
|
||||
{"type": "table", "kind": "process"},
|
||||
{"type": "sign"},
|
||||
], 1),
|
||||
("生产订单+BOM下料单", [
|
||||
{"type": "title", "text": "生产订单"},
|
||||
{"type": "info", "fields": ["order_no", "customer_name", "phone", "address",
|
||||
"count", "area", "money", "created_at"]},
|
||||
{"type": "image"},
|
||||
{"type": "table", "kind": "bom"},
|
||||
{"type": "table", "kind": "process"},
|
||||
{"type": "sign"},
|
||||
], 0),
|
||||
]
|
||||
existing = {n for (n,) in (await db.execute(select(OrderFormat.name))).all()}
|
||||
for i, (name, blocks, is_default) in enumerate(FORMATS):
|
||||
if name not in existing:
|
||||
db.add(OrderFormat(name=name, order_type=0, sort=i, is_default=is_default,
|
||||
blocks=__import__("json").dumps(blocks, ensure_ascii=False)))
|
||||
await db.commit()
|
||||
print("打印模板就绪")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed())
|
||||
|
||||
Reference in New Issue
Block a user