feat: 部位级材料选择(对应部位使用对应材料: 估算器overrides+series-materials聚合端点+QuoteDialog部位选材面板实时重算)
This commit is contained in:
@@ -57,6 +57,45 @@ async def list_series(user: CurrentUser, db: DbSession):
|
||||
return ok(data=[_brief(t) for t in rows])
|
||||
|
||||
|
||||
@router.get("/costbook/series-materials")
|
||||
async def series_materials(user: CurrentUser, db: DbSession):
|
||||
"""聚合全部系列模板的用料清单,按部位分组(frame/mullion/sash/pressline/glass/hardware)。
|
||||
|
||||
供商家「按部位选材」下拉:跨系列混搭(如外框用 85 系列的 33框、扇用 75 的内开扇)。
|
||||
"""
|
||||
import json as _json
|
||||
|
||||
from huamenchuang.services.costbook_service import _profile_role
|
||||
rows = list((await db.execute(select(CostSeriesTemplate).where(
|
||||
CostSeriesTemplate.company_id == user.company_id, CostSeriesTemplate.enabled == 1)
|
||||
.order_by(CostSeriesTemplate.series))).scalars())
|
||||
groups: dict[str, list] = {"frame": [], "mullion": [], "sash": [], "pressline": [], "glass": [], "hardware": []}
|
||||
seen: set = set()
|
||||
|
||||
def push(role: str, item: dict):
|
||||
key = (role, item.get("name"), item.get("unit_price"))
|
||||
if key in seen:
|
||||
return
|
||||
seen.add(key)
|
||||
groups[role].append(item)
|
||||
|
||||
for t in rows:
|
||||
try:
|
||||
data = _json.loads(t.data)
|
||||
except Exception:
|
||||
continue
|
||||
for p in data.get("profile", []):
|
||||
role = _profile_role(p.get("name", ""))
|
||||
if role:
|
||||
push(role, {"name": p.get("name"), "unit_price": p.get("unit_price"),
|
||||
"weight_per_m": p.get("weight_per_m"), "series": t.name})
|
||||
for g in data.get("glass", []):
|
||||
push("glass", {"name": g.get("config"), "unit_price": g.get("unit_price"), "series": t.name})
|
||||
for hw in data.get("hardware", []):
|
||||
push("hardware", {"name": hw.get("name"), "unit_price": hw.get("unit_price"), "series": t.name})
|
||||
return ok(data=groups)
|
||||
|
||||
|
||||
class EstimateBody(BaseModel):
|
||||
series_id: int
|
||||
width: float = Field(gt=0, description="窗宽 mm")
|
||||
|
||||
@@ -15,6 +15,7 @@ class QuoteRequest(BaseModel):
|
||||
template_id: int | None = Field(default=None, description="或直接指定样板")
|
||||
order_id: int | None = Field(default=None, description="或对已有订单报价")
|
||||
series_id: int | None = Field(default=None, description="材料系列(成本系列模板): 附加该系列的成本估算供商家选材")
|
||||
materials: dict | None = Field(default=None, description="部位级材料覆盖 {frame,mullion,sash,glass,hardware: {name,unit_price,weight_per_m}}——对应部位使用对应材料(可跨系列)")
|
||||
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="启用价格脚本计价(公司启用脚本覆盖默认单价/总价)")
|
||||
@@ -88,12 +89,13 @@ async def offer_price(body: QuoteRequest, user: CurrentUser, db: DbSession):
|
||||
if tpl is not None and tpl.company_id == user.company_id:
|
||||
w = result.get("width_mm") or 1600
|
||||
h = result.get("height_mm") or 1600
|
||||
est = _svc.estimate(tpl, w, h, 1)
|
||||
est = _svc.estimate(tpl, w, h, 1, material_overrides=body.materials)
|
||||
result["series"] = {"id": tpl.id, "name": tpl.name, "series": tpl.series, "wall_thick": tpl.wall_thick}
|
||||
result["series_estimate"] = {
|
||||
"width": w, "height": h, "total": est["total"], "per_sqm": est["per_sqm"],
|
||||
"profile_cost": est["profile_cost"], "glass_cost": est["glass_cost"],
|
||||
"hardware_cost": est["hardware_cost"], "labor_cost": est["labor_cost"],
|
||||
"margin_ladder": est["margin_ladder"],
|
||||
"overrides": est.get("overrides"),
|
||||
}
|
||||
return ok(data=result, message="报价成功")
|
||||
|
||||
Reference in New Issue
Block a user