feat: P0-1 价格脚本引擎(安全公式解析+脚本CRUD+试算+报价集成)

This commit is contained in:
HHY
2026-09-25 03:06:32 -07:00
parent 5965d572b5
commit 8c2bb5e12d
12 changed files with 585 additions and 66 deletions
+16 -4
View File
@@ -16,6 +16,8 @@ class QuoteRequest(BaseModel):
order_id: int | None = Field(default=None, description="或对已有订单报价")
prices: dict | None = Field(default=None, description="价格本覆盖 {frame,mullion,sash,glass,hardware}")
use_price_book: bool = Field(default=False, description="从产品管理价格本取价(按材料类别均价)")
use_scripts: bool = Field(default=False, description="启用价格脚本计价(公司启用脚本覆盖默认单价/总价)")
scripts: dict | None = Field(default=None, description="临时脚本 {target: formula}, 优先于脚本库")
async def _resolve_drawing(db, body: QuoteRequest) -> str | dict | None:
@@ -56,15 +58,25 @@ async def _price_book(db, company_id: int) -> dict | None:
@router.post("")
async def offer_price(body: QuoteRequest, user: CurrentUser, db: DbSession):
"""算料报价:型材清单 + 玻璃面积 + 五金 + 总价。"""
"""算料报价:型材清单 + 玻璃面积 + 五金 + 总价(可选价格脚本计价)。"""
drawing = await _resolve_drawing(db, body)
if drawing is None:
return fail("未找到图纸数据")
prices = body.prices
if prices is None and body.use_price_book:
prices = await _price_book(db, user.company_id)
# 脚本解析:显式传入的临时脚本优先,其次取脚本库中启用脚本
scripts = body.scripts
if scripts is None and body.use_scripts:
from sqlalchemy import select as _select
from huamenchuang.api.script import load_script_map
from huamenchuang.models.business import PriceScript
rows = list((await db.execute(
_select(PriceScript).where(PriceScript.company_id == user.company_id))).scalars())
scripts = load_script_map(rows) or None
try:
result = calc_quote(drawing, prices)
except Exception:
return fail("图纸数据解析失败")
result = calc_quote(drawing, prices, scripts)
except Exception as e: # 公式/图纸解析错误 → 用户可读失败
msg = str(e) if "公式" in str(e) or "未知变量" in str(e) else "图纸数据解析失败"
return fail(msg)
return ok(data=result, message="报价成功")