2026-07-23 13:38:43 +08:00
|
|
|
|
# ============================================================
|
|
|
|
|
|
# 主数据/订单自然语言只读查询(moduleId: domain-master-query, 可重生 ✅)
|
|
|
|
|
|
# 让对话能检索 world 中的销售订单、物料、工序、BOM、工艺路线、柔性资源。
|
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
|
|
from server.engines.queries import find_bom_items, find_routing_steps
|
|
|
|
|
|
|
|
|
|
|
|
World = dict[str, Any]
|
|
|
|
|
|
|
|
|
|
|
|
ENTITIES = ("order", "material", "operation", "bom", "routing", "overview", "flex", "mrp")
|
2026-09-14 15:40:10 +08:00
|
|
|
|
CODE_REQUIRED_ENTITIES = frozenset({"order", "material", "operation", "bom", "routing"})
|
|
|
|
|
|
MRP_ASPECTS = frozenset({"outsource", "purchase", "all"})
|
2026-07-23 13:38:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-09-14 15:40:10 +08:00
|
|
|
|
def _input_error(message: str, *, entity: str | None = None) -> dict[str, Any]:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"ok": False,
|
|
|
|
|
|
"entity": entity,
|
|
|
|
|
|
"error": message,
|
|
|
|
|
|
"count": 0,
|
|
|
|
|
|
"rows": [],
|
|
|
|
|
|
}
|
2026-07-23 13:38:43 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _find_product(world: World, code_or_name: str | None) -> dict | None:
|
|
|
|
|
|
mats = world.get("materials") or []
|
|
|
|
|
|
if not code_or_name:
|
2026-09-14 15:40:10 +08:00
|
|
|
|
return None
|
2026-07-23 13:38:43 +08:00
|
|
|
|
key = code_or_name.strip().lower()
|
|
|
|
|
|
for m in mats:
|
|
|
|
|
|
if str(m.get("code", "")).lower() == key:
|
|
|
|
|
|
return m
|
2026-09-14 15:40:10 +08:00
|
|
|
|
if str(m.get("name", "")).lower() == key:
|
2026-07-23 13:38:43 +08:00
|
|
|
|
return m
|
|
|
|
|
|
# flex 成品
|
|
|
|
|
|
for m in world.get("flexMaterials") or []:
|
|
|
|
|
|
if m.get("type") == "FINISHED_PRODUCT" and (
|
2026-09-14 15:40:10 +08:00
|
|
|
|
str(m.get("code", "")).lower() == key
|
|
|
|
|
|
or str(m.get("name", "")).lower() == key
|
2026-07-23 13:38:43 +08:00
|
|
|
|
):
|
|
|
|
|
|
return {"id": None, "code": m["code"], "name": m["name"], "type": "FINISHED_PRODUCT",
|
|
|
|
|
|
"_flex": True}
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_orders(world: World, code: str | None = None, limit: int = 20) -> dict[str, Any]:
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for so in world.get("salesOrders") or []:
|
|
|
|
|
|
if code and code not in str(so.get("orderNo", "")) and code not in str(so.get("customerName", "")):
|
|
|
|
|
|
continue
|
|
|
|
|
|
item = (so.get("items") or [{}])[0]
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"orderNo": so.get("orderNo"),
|
|
|
|
|
|
"status": so.get("status"),
|
|
|
|
|
|
"customer": so.get("customerName"),
|
|
|
|
|
|
"level": so.get("customerLevel"),
|
|
|
|
|
|
"productCode": item.get("productCode"),
|
|
|
|
|
|
"productName": item.get("productName"),
|
|
|
|
|
|
"quantity": item.get("quantity"),
|
|
|
|
|
|
"deliveryDate": so.get("deliveryDate") or item.get("deliveryDate"),
|
|
|
|
|
|
"wbs": so.get("wbs") or "",
|
|
|
|
|
|
"special": so.get("specialRequirements") or "",
|
|
|
|
|
|
})
|
|
|
|
|
|
# 柔性订单补充(若固定轨无命中)
|
|
|
|
|
|
if not rows or (code and not any(r["orderNo"] == code for r in rows)):
|
|
|
|
|
|
for fo in world.get("flexOrders") or []:
|
|
|
|
|
|
if code and code not in str(fo.get("orderNo", "")):
|
|
|
|
|
|
continue
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"orderNo": fo.get("orderNo"),
|
|
|
|
|
|
"status": fo.get("status"),
|
|
|
|
|
|
"customer": fo.get("project") or "柔性订单",
|
|
|
|
|
|
"level": "",
|
|
|
|
|
|
"productCode": fo.get("productCode"),
|
|
|
|
|
|
"productName": fo.get("productCode"),
|
|
|
|
|
|
"quantity": fo.get("quantity"),
|
|
|
|
|
|
"deliveryDate": fo.get("dueDate"),
|
|
|
|
|
|
"wbs": fo.get("wbs") or "",
|
|
|
|
|
|
"special": "flex",
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "order", "count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_materials(world: World, code: str | None = None, limit: int = 40) -> dict[str, Any]:
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for m in world.get("materials") or []:
|
|
|
|
|
|
if code:
|
|
|
|
|
|
c = code.lower()
|
|
|
|
|
|
if c not in str(m.get("code", "")).lower() and c not in str(m.get("name", "")).lower():
|
|
|
|
|
|
continue
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"code": m.get("code"), "name": m.get("name"), "type": m.get("type"),
|
|
|
|
|
|
"unit": m.get("unit"), "stock": m.get("stock"), "inTransit": m.get("inTransit"),
|
|
|
|
|
|
"spec": m.get("spec") or "",
|
|
|
|
|
|
})
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
for m in world.get("flexMaterials") or []:
|
|
|
|
|
|
if code:
|
|
|
|
|
|
c = code.lower()
|
|
|
|
|
|
if c not in str(m.get("code", "")).lower() and c not in str(m.get("name", "")).lower():
|
|
|
|
|
|
continue
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"code": m.get("code"), "name": m.get("name"), "type": m.get("type"),
|
|
|
|
|
|
"unit": m.get("unit"), "stock": m.get("stock"), "inTransit": m.get("inTransit"),
|
|
|
|
|
|
"spec": "",
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "material", "count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_operations(world: World, code: str | None = None, limit: int = 40) -> dict[str, Any]:
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
ops = world.get("operations") or []
|
|
|
|
|
|
if not ops:
|
|
|
|
|
|
ops = [
|
|
|
|
|
|
{"code": o.get("code"), "name": o.get("name"),
|
|
|
|
|
|
"type": "INTERNAL", "standardTime": o.get("changeoverMin")}
|
|
|
|
|
|
for o in (world.get("flexOperations") or [])
|
|
|
|
|
|
]
|
|
|
|
|
|
for o in ops:
|
|
|
|
|
|
if code:
|
|
|
|
|
|
c = code.lower()
|
|
|
|
|
|
if c not in str(o.get("code", "")).lower() and c not in str(o.get("name", "")).lower():
|
|
|
|
|
|
continue
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"code": o.get("code"), "name": o.get("name"),
|
|
|
|
|
|
"type": o.get("type"), "standardTime": o.get("standardTime"),
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "operation", "count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_bom(world: World, code: str | None = None, limit: int = 50) -> dict[str, Any]:
|
|
|
|
|
|
product = _find_product(world, code)
|
|
|
|
|
|
if not product:
|
|
|
|
|
|
return {"entity": "bom", "count": 0, "rows": [], "hint": "未找到成品,请带料号如 28200003654300"}
|
|
|
|
|
|
if product.get("_flex") or product.get("id") is None:
|
|
|
|
|
|
pc = product["code"]
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
mats = {m["code"]: m for m in (world.get("flexMaterials") or [])}
|
|
|
|
|
|
for b in world.get("flexBom") or []:
|
|
|
|
|
|
if b.get("productCode") != pc:
|
|
|
|
|
|
continue
|
|
|
|
|
|
mat = mats.get(b.get("materialCode") or "", {})
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"materialCode": b.get("materialCode"),
|
|
|
|
|
|
"materialName": mat.get("name") or b.get("materialCode"),
|
|
|
|
|
|
"quantity": b.get("quantity"),
|
|
|
|
|
|
"consumeOp": b.get("consumeOp"),
|
|
|
|
|
|
"isKey": b.get("isKey"),
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "bom", "productCode": pc, "productName": product.get("name"),
|
|
|
|
|
|
"count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
pid = product["id"]
|
|
|
|
|
|
mats = {m["id"]: m for m in world.get("materials") or []}
|
|
|
|
|
|
ops = {o["id"]: o for o in world.get("operations") or []}
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for it in find_bom_items(world, pid):
|
|
|
|
|
|
mat = mats.get(it["materialId"], {})
|
|
|
|
|
|
op = ops.get(it.get("operationId"), {})
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"materialCode": mat.get("code"),
|
|
|
|
|
|
"materialName": mat.get("name"),
|
|
|
|
|
|
"quantity": it.get("quantity"),
|
|
|
|
|
|
"consumeOp": op.get("code") or op.get("name"),
|
|
|
|
|
|
"isKey": it.get("isKeyMaterial"),
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "bom", "productCode": product.get("code"), "productName": product.get("name"),
|
|
|
|
|
|
"count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_routing(world: World, code: str | None = None, limit: int = 40) -> dict[str, Any]:
|
|
|
|
|
|
product = _find_product(world, code)
|
|
|
|
|
|
if not product:
|
|
|
|
|
|
return {"entity": "routing", "count": 0, "rows": [], "hint": "未找到成品"}
|
|
|
|
|
|
if product.get("_flex") or product.get("id") is None:
|
|
|
|
|
|
pc = product["code"]
|
|
|
|
|
|
ops = {o["code"]: o for o in (world.get("flexOperations") or [])}
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for r in sorted((world.get("flexRoutings") or []), key=lambda x: x.get("seq", 0)):
|
|
|
|
|
|
if r.get("productCode") != pc:
|
|
|
|
|
|
continue
|
|
|
|
|
|
op = ops.get(r.get("operationCode") or "", {})
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"seq": r.get("seq"),
|
|
|
|
|
|
"operationCode": r.get("operationCode"),
|
|
|
|
|
|
"operationName": op.get("name") or r.get("operationCode"),
|
|
|
|
|
|
"stdTimePerUnit": r.get("stdTimePerUnit"),
|
|
|
|
|
|
"requireMold": r.get("requireMold"),
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "routing", "productCode": pc, "productName": product.get("name"),
|
|
|
|
|
|
"count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
pid = product["id"]
|
|
|
|
|
|
ops = {o["id"]: o for o in world.get("operations") or []}
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for s in find_routing_steps(world, pid):
|
|
|
|
|
|
op = ops.get(s["operationId"], {})
|
|
|
|
|
|
rows.append({
|
|
|
|
|
|
"seq": s.get("sequenceNo"),
|
|
|
|
|
|
"operationCode": op.get("code"),
|
|
|
|
|
|
"operationName": op.get("name"),
|
|
|
|
|
|
"setupTime": s.get("setupTime"),
|
|
|
|
|
|
"runTimePerUnit": s.get("runTimePerUnit"),
|
|
|
|
|
|
"isExternal": s.get("isExternal"),
|
|
|
|
|
|
})
|
|
|
|
|
|
return {"entity": "routing", "productCode": product.get("code"), "productName": product.get("name"),
|
|
|
|
|
|
"count": len(rows), "rows": rows[:limit]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_overview(world: World) -> dict[str, Any]:
|
|
|
|
|
|
factories = [f.get("name") for f in (world.get("factories") or [])]
|
|
|
|
|
|
return {
|
|
|
|
|
|
"entity": "overview",
|
|
|
|
|
|
"factory": factories[0] if factories else "(无)",
|
|
|
|
|
|
"counts": {
|
|
|
|
|
|
"factories": len(world.get("factories") or []),
|
|
|
|
|
|
"lines": len(world.get("lines") or []),
|
|
|
|
|
|
"workstations": len(world.get("workstations") or []),
|
|
|
|
|
|
"materials": len(world.get("materials") or []),
|
|
|
|
|
|
"operations": len(world.get("operations") or []),
|
|
|
|
|
|
"boms": len(world.get("boms") or []),
|
|
|
|
|
|
"routings": len(world.get("routings") or []),
|
|
|
|
|
|
"salesOrders": len(world.get("salesOrders") or []),
|
|
|
|
|
|
"flexOrders": len(world.get("flexOrders") or []),
|
|
|
|
|
|
"flexEquipment": len(world.get("flexEquipment") or []),
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_flex(world: World, limit: int = 20) -> dict[str, Any]:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"entity": "flex",
|
|
|
|
|
|
"orders": [
|
|
|
|
|
|
{"orderNo": o.get("orderNo"), "productCode": o.get("productCode"),
|
|
|
|
|
|
"qty": o.get("quantity"), "due": o.get("dueDate")}
|
|
|
|
|
|
for o in (world.get("flexOrders") or [])[:limit]
|
|
|
|
|
|
],
|
|
|
|
|
|
"equipment": [
|
|
|
|
|
|
{"code": e.get("code"), "name": e.get("name"), "zone": e.get("zone"),
|
|
|
|
|
|
"caps": len(e.get("capabilities") or [])}
|
|
|
|
|
|
for e in (world.get("flexEquipment") or [])[:limit]
|
|
|
|
|
|
],
|
|
|
|
|
|
"operations": [
|
|
|
|
|
|
{"code": o.get("code"), "name": o.get("name"), "bottleneck": o.get("isBottleneck")}
|
|
|
|
|
|
for o in (world.get("flexOperations") or [])[:limit]
|
|
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-14 15:40:10 +08:00
|
|
|
|
def query_mrp(world: World, order_no: str | None = None, *, aspect: str | None = None,
|
2026-07-23 13:38:43 +08:00
|
|
|
|
text: str = "") -> dict[str, Any]:
|
|
|
|
|
|
"""查某单(或全部)的采购/委外建议,并核对工艺是否有外协步骤。"""
|
2026-09-14 15:40:10 +08:00
|
|
|
|
del text # 兼容旧签名;禁止从原始自然语言推断 aspect。
|
|
|
|
|
|
if aspect not in MRP_ASPECTS:
|
|
|
|
|
|
return _input_error(
|
|
|
|
|
|
"请明确采购/委外查询范围:aspect 必须为 outsource、purchase 或 all。",
|
|
|
|
|
|
entity="mrp",
|
|
|
|
|
|
)
|
2026-07-23 13:38:43 +08:00
|
|
|
|
order_no = (order_no or "").strip() or None
|
|
|
|
|
|
|
|
|
|
|
|
def _match_so(row: dict) -> bool:
|
|
|
|
|
|
if not order_no:
|
|
|
|
|
|
return True
|
|
|
|
|
|
return str(row.get("salesOrderNo") or "") == order_no
|
|
|
|
|
|
|
|
|
|
|
|
purchase = [r for r in (world.get("purchaseOrders") or []) if _match_so(r)]
|
|
|
|
|
|
outsource = [r for r in (world.get("outsourceOrders") or []) if _match_so(r)]
|
|
|
|
|
|
|
|
|
|
|
|
# 工艺外协核对(即使尚未分解也能答「有没有委外」)
|
|
|
|
|
|
external_steps: list[dict] = []
|
|
|
|
|
|
product_code = None
|
|
|
|
|
|
if order_no:
|
|
|
|
|
|
so = next((s for s in (world.get("salesOrders") or [])
|
|
|
|
|
|
if str(s.get("orderNo")) == order_no), None)
|
|
|
|
|
|
fo = next((o for o in (world.get("flexOrders") or [])
|
|
|
|
|
|
if str(o.get("orderNo")) == order_no), None)
|
|
|
|
|
|
product_id = None
|
|
|
|
|
|
if so:
|
|
|
|
|
|
item = (so.get("items") or [{}])[0]
|
|
|
|
|
|
product_code = item.get("productCode")
|
|
|
|
|
|
product_id = item.get("productId")
|
|
|
|
|
|
if product_id is None and product_code:
|
|
|
|
|
|
mat = next((m for m in (world.get("materials") or [])
|
|
|
|
|
|
if m.get("code") == product_code), None)
|
|
|
|
|
|
product_id = mat["id"] if mat else None
|
|
|
|
|
|
elif fo:
|
|
|
|
|
|
product_code = fo.get("productCode")
|
|
|
|
|
|
mat = next((m for m in (world.get("materials") or [])
|
|
|
|
|
|
if m.get("code") == product_code), None)
|
|
|
|
|
|
product_id = mat["id"] if mat else None
|
|
|
|
|
|
for s in (world.get("flexRoutings") or []):
|
|
|
|
|
|
if s.get("productCode") == product_code and s.get("isExternal"):
|
|
|
|
|
|
external_steps.append({
|
|
|
|
|
|
"seq": s.get("seq"), "operationCode": s.get("operationCode"),
|
|
|
|
|
|
"operationName": s.get("operationName") or s.get("operationCode"),
|
|
|
|
|
|
})
|
|
|
|
|
|
if product_id is not None and not external_steps:
|
|
|
|
|
|
for step in find_routing_steps(world, product_id):
|
|
|
|
|
|
if step.get("isExternal"):
|
|
|
|
|
|
op = next((o for o in (world.get("operations") or [])
|
|
|
|
|
|
if o.get("id") == step.get("operationId")), {})
|
|
|
|
|
|
external_steps.append({
|
|
|
|
|
|
"seq": step.get("sequenceNo"),
|
|
|
|
|
|
"operationCode": op.get("code"),
|
|
|
|
|
|
"operationName": op.get("name") or op.get("code"),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"entity": "mrp",
|
|
|
|
|
|
"aspect": aspect,
|
|
|
|
|
|
"orderNo": order_no,
|
|
|
|
|
|
"productCode": product_code,
|
|
|
|
|
|
"purchase": [
|
|
|
|
|
|
{"orderNo": r.get("orderNo"), "materialCode": r.get("materialCode"),
|
|
|
|
|
|
"materialName": r.get("materialName"), "quantity": r.get("quantity"),
|
|
|
|
|
|
"status": r.get("status"), "suggestedOrderDate": r.get("suggestedOrderDate")}
|
|
|
|
|
|
for r in purchase
|
|
|
|
|
|
],
|
|
|
|
|
|
"outsource": [
|
|
|
|
|
|
{"orderNo": r.get("orderNo"), "operationName": r.get("operationName"),
|
|
|
|
|
|
"quantity": r.get("quantity"), "status": r.get("status"),
|
|
|
|
|
|
"requiredDate": r.get("requiredDate")}
|
|
|
|
|
|
for r in outsource
|
|
|
|
|
|
],
|
|
|
|
|
|
"externalSteps": external_steps,
|
|
|
|
|
|
"decomposed": bool(purchase or outsource),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_master_query(world: World, *, entity: str | None = None, code: str | None = None,
|
|
|
|
|
|
text: str = "", limit: int = 40, aspect: str | None = None) -> dict[str, Any]:
|
2026-09-14 15:40:10 +08:00
|
|
|
|
del text # 兼容旧 caller;原始 query 不再参与实体、编码或 MRP 语义推断。
|
|
|
|
|
|
ent = str(entity or "").strip().lower()
|
|
|
|
|
|
if not ent:
|
|
|
|
|
|
return _input_error(
|
|
|
|
|
|
"请明确要查询的主数据类型:order、material、operation、bom、routing、overview、flex 或 mrp。"
|
|
|
|
|
|
)
|
2026-07-23 13:38:43 +08:00
|
|
|
|
if ent not in ENTITIES:
|
2026-09-14 15:40:10 +08:00
|
|
|
|
return _input_error(
|
|
|
|
|
|
"主数据类型不受支持,请从 order、material、operation、bom、routing、overview、flex、mrp 中选择。",
|
|
|
|
|
|
entity=ent,
|
|
|
|
|
|
)
|
|
|
|
|
|
normalized_code = str(code or "").strip()
|
|
|
|
|
|
if ent in CODE_REQUIRED_ENTITIES and not normalized_code:
|
|
|
|
|
|
return _input_error(
|
|
|
|
|
|
f"请提供要查询的 {ent} 编码,不能从原始问句自动猜测。",
|
|
|
|
|
|
entity=ent,
|
|
|
|
|
|
)
|
|
|
|
|
|
code = normalized_code or None
|
2026-07-23 13:38:43 +08:00
|
|
|
|
if ent == "order":
|
|
|
|
|
|
return query_orders(world, code, limit=limit)
|
|
|
|
|
|
if ent == "material":
|
|
|
|
|
|
return query_materials(world, code, limit=limit)
|
|
|
|
|
|
if ent == "operation":
|
|
|
|
|
|
return query_operations(world, code, limit=limit)
|
|
|
|
|
|
if ent == "bom":
|
|
|
|
|
|
return query_bom(world, code, limit=limit)
|
|
|
|
|
|
if ent == "routing":
|
|
|
|
|
|
return query_routing(world, code, limit=limit)
|
|
|
|
|
|
if ent == "flex":
|
|
|
|
|
|
return query_flex(world, limit=limit)
|
|
|
|
|
|
if ent == "mrp":
|
2026-09-14 15:40:10 +08:00
|
|
|
|
if aspect not in MRP_ASPECTS:
|
|
|
|
|
|
return _input_error(
|
|
|
|
|
|
"请明确采购/委外查询范围:aspect 必须为 outsource、purchase 或 all。",
|
|
|
|
|
|
entity="mrp",
|
|
|
|
|
|
)
|
|
|
|
|
|
return query_mrp(world, code, aspect=aspect)
|
2026-07-23 13:38:43 +08:00
|
|
|
|
return query_overview(world)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_master_query(result: dict[str, Any]) -> str:
|
|
|
|
|
|
"""把查询结果格式化为对话可读文本。"""
|
2026-09-14 15:40:10 +08:00
|
|
|
|
if result.get("ok") is False:
|
|
|
|
|
|
return str(result.get("error") or "缺少必要查询参数,请补充后重试。")
|
2026-07-23 13:38:43 +08:00
|
|
|
|
ent = result.get("entity")
|
|
|
|
|
|
if ent == "overview":
|
|
|
|
|
|
c = result.get("counts") or {}
|
|
|
|
|
|
return (
|
|
|
|
|
|
f"主数据概览 · 工厂【{result.get('factory')}】\n"
|
|
|
|
|
|
f"· 产线 {c.get('lines', 0)} / 工位 {c.get('workstations', 0)}\n"
|
|
|
|
|
|
f"· 物料 {c.get('materials', 0)} / 工序 {c.get('operations', 0)} / "
|
|
|
|
|
|
f"BOM {c.get('boms', 0)} / 路线 {c.get('routings', 0)}\n"
|
|
|
|
|
|
f"· 销售订单 {c.get('salesOrders', 0)} / 柔性订单 {c.get('flexOrders', 0)} / "
|
|
|
|
|
|
f"柔性设备 {c.get('flexEquipment', 0)}\n"
|
|
|
|
|
|
"可继续说:「查订单」「查物料」「查 BOM 28200003654300」「查工艺路线」。"
|
|
|
|
|
|
)
|
|
|
|
|
|
if ent == "order":
|
|
|
|
|
|
rows = result.get("rows") or []
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return "未找到匹配订单。可说「有哪些订单」或带订单号如「查订单 102285668」。"
|
|
|
|
|
|
lines = [f"订单共 {result.get('count')} 条:"]
|
|
|
|
|
|
for r in rows:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f"· {r['orderNo']} [{r.get('status')}] {r.get('productName') or r.get('productCode')}"
|
|
|
|
|
|
f" ×{r.get('quantity')} 交期 {r.get('deliveryDate')} "
|
|
|
|
|
|
f"客户 {r.get('customer')}"
|
|
|
|
|
|
+ (f" WBS={r['wbs']}" if r.get("wbs") else "")
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if ent == "material":
|
|
|
|
|
|
rows = result.get("rows") or []
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return "未找到匹配物料。"
|
|
|
|
|
|
lines = [f"物料共 {result.get('count')} 条(展示前 {len(rows)}):"]
|
|
|
|
|
|
for r in rows:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f"· {r['code']} {r.get('name')} [{r.get('type')}] "
|
|
|
|
|
|
f"库存 {r.get('stock')}{r.get('unit') or ''}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if ent == "operation":
|
|
|
|
|
|
rows = result.get("rows") or []
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return "工序库为空。"
|
|
|
|
|
|
lines = [f"工序共 {result.get('count')} 条:"]
|
|
|
|
|
|
for r in rows:
|
|
|
|
|
|
st = r.get("standardTime")
|
|
|
|
|
|
lines.append(f"· {r['code']} {r.get('name')} 标准工时={st}")
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
if ent == "bom":
|
|
|
|
|
|
if result.get("hint") and not result.get("rows"):
|
|
|
|
|
|
return result["hint"]
|
|
|
|
|
|
rows = result.get("rows") or []
|
|
|
|
|
|
head = f"产品 {result.get('productCode')} {result.get('productName') or ''} BOM · {result.get('count')} 项:"
|
|
|
|
|
|
lines = [head]
|
|
|
|
|
|
for r in rows:
|
|
|
|
|
|
key = "★" if r.get("isKey") else " "
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f"·{key} {r.get('materialCode')} {r.get('materialName')} ×{r.get('quantity')}"
|
|
|
|
|
|
+ (f" @{r.get('consumeOp')}" if r.get("consumeOp") else "")
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join(lines) if rows else head + "(空)"
|
|
|
|
|
|
if ent == "routing":
|
|
|
|
|
|
if result.get("hint") and not result.get("rows"):
|
|
|
|
|
|
return result["hint"]
|
|
|
|
|
|
rows = result.get("rows") or []
|
|
|
|
|
|
head = f"产品 {result.get('productCode')} 工艺路线 · {result.get('count')} 步:"
|
|
|
|
|
|
lines = [head]
|
|
|
|
|
|
for r in rows:
|
|
|
|
|
|
t = r.get("runTimePerUnit") or r.get("stdTimePerUnit")
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f"· seq={r.get('seq')} {r.get('operationCode')} {r.get('operationName')} "
|
|
|
|
|
|
f"单件 {t} 分"
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join(lines) if rows else head + "(空)"
|
|
|
|
|
|
if ent == "flex":
|
|
|
|
|
|
lines = ["柔性资源:"]
|
|
|
|
|
|
for o in result.get("orders") or []:
|
|
|
|
|
|
lines.append(f"· 订单 {o['orderNo']} {o['productCode']} ×{o['qty']} 交期 {o['due']}")
|
|
|
|
|
|
for e in result.get("equipment") or []:
|
|
|
|
|
|
lines.append(f"· 设备 {e['code']} {e['name']} 区={e.get('zone')} 能力数={e.get('caps')}")
|
|
|
|
|
|
for o in result.get("operations") or []:
|
|
|
|
|
|
flag = "★瓶颈" if o.get("bottleneck") else ""
|
|
|
|
|
|
lines.append(f"· 工序 {o['code']} {o['name']} {flag}")
|
|
|
|
|
|
return "\n".join(lines) if len(lines) > 1 else "柔性资源为空。"
|
|
|
|
|
|
if ent == "mrp":
|
|
|
|
|
|
order_no = result.get("orderNo") or "全部订单"
|
|
|
|
|
|
aspect = result.get("aspect") or "all"
|
|
|
|
|
|
purchase = result.get("purchase") or []
|
|
|
|
|
|
outsource = result.get("outsource") or []
|
|
|
|
|
|
external = result.get("externalSteps") or []
|
|
|
|
|
|
lines = [f"订单 {order_no} 的采购/委外情况:"]
|
|
|
|
|
|
if aspect in ("outsource", "all"):
|
|
|
|
|
|
if outsource:
|
|
|
|
|
|
lines.append(f"· 委外建议:{len(outsource)} 条")
|
|
|
|
|
|
for r in outsource[:8]:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" - {r.get('orderNo')} {r.get('operationName')} ×{r.get('quantity')} "
|
|
|
|
|
|
f"[{r.get('status')}] 需求日 {r.get('requiredDate')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
elif external:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f"· 委外建议:尚未分解,但工艺有 {len(external)} 道外协步骤"
|
|
|
|
|
|
f"({ '、'.join(str(s.get('operationName') or s.get('operationCode')) for s in external[:5]) })。"
|
|
|
|
|
|
"可说「分解订单」生成委外建议单。"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
lines.append("· 委外建议:无。工艺路线未标记外协步骤,当前没有委外订单。")
|
|
|
|
|
|
if aspect in ("purchase", "all"):
|
|
|
|
|
|
if purchase:
|
|
|
|
|
|
lines.append(f"· 采购建议:{len(purchase)} 条")
|
|
|
|
|
|
for r in purchase[:8]:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" - {r.get('orderNo')} {r.get('materialName') or r.get('materialCode')} "
|
|
|
|
|
|
f"×{r.get('quantity')} [{r.get('status')}] 建议下单 {r.get('suggestedOrderDate')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
tip = "(可先说「分解订单」生成建议)" if not result.get("decomposed") else ""
|
|
|
|
|
|
lines.append(f"· 采购建议:无{tip}")
|
|
|
|
|
|
if aspect == "outsource" and not outsource and not external:
|
|
|
|
|
|
lines.append("结论:这个订单没有委外。")
|
|
|
|
|
|
elif aspect == "outsource" and (outsource or external):
|
|
|
|
|
|
lines.append("结论:这个订单涉及委外。")
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
return str(result)
|