机器人显示名与站点标题可配;多段联运列表投影;协同群监听对账;工单附件落存;模板与工单后台补强
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,7 @@ class LedgerClient:
|
||||
"""
|
||||
主账客户端。
|
||||
|
||||
base_url:BIZ_API_BASE_URL(新系统多为 http://127.0.0.1:8180/jeecgboot)。
|
||||
base_url:BIZ_API_BASE_URL(新系统为 http://127.0.0.1:8080/jeecgboot)。
|
||||
超时:默认 10s,禁止无限阻塞 Worker 槽。
|
||||
禁止:在本类拼 TMS HMAC 或写工单 SQL。
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
主账 HTTP 账本:建单 / 查价 / 报价 / 六态只打 8180。
|
||||
主账 HTTP 账本:建单 / 查价 / 报价 / 六态只打 8080。
|
||||
|
||||
本文件职责:把 MemoryLedger 同名方法接到 /inquiry/agent/**;查价走真 TMS(主账出站)。
|
||||
禁止:直连 TMS / MySQL;把 Jeecg 包装失败当成有价。
|
||||
@@ -236,13 +236,15 @@ class HttpLedger:
|
||||
quote: Optional[dict[str, Any]] = None,
|
||||
format: str = "xlsx",
|
||||
sales_wecom_id: str = "",
|
||||
persist_attachment: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
打主账按后台报价模板填 Excel。
|
||||
|
||||
失败(无关键词且无默认)原样带回销售话术。Java 不转 PDF。
|
||||
persist_attachment:True 才写主账附件表(确认落库);激活发群默认 False。
|
||||
"""
|
||||
from agent.quote_templates.match import FAIL_MESSAGE
|
||||
from agent.quote_templates.match import FAIL_MESSAGE, normalize_biz, pick_quote_template
|
||||
from agent.schema.quote_adjust import stamp_prices_for_sheet
|
||||
|
||||
# 出单前把当前费用写入线路表、海运分段、空运舱位。否则报价单仍是旧价。
|
||||
@@ -260,8 +262,9 @@ class HttpLedger:
|
||||
"facts": facts,
|
||||
"quote": quote or {},
|
||||
"salesWecomId": sales_wecom_id,
|
||||
"persistAttachment": bool(persist_attachment),
|
||||
},
|
||||
idem=f"quote-render:{work_order_no}:{format}",
|
||||
idem=f"quote-render:{work_order_no}:{format}:{int(bool(persist_attachment))}",
|
||||
)
|
||||
finally:
|
||||
self._client.timeout_seconds = prev_timeout
|
||||
@@ -270,6 +273,7 @@ class HttpLedger:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": out.get("error") or "NO_TEMPLATE",
|
||||
"failStage": out.get("failStage"),
|
||||
"message": out.get("message") or FAIL_MESSAGE,
|
||||
}
|
||||
from agent.quote_templates.filename import filename_from_render
|
||||
@@ -465,12 +469,16 @@ class HttpLedger:
|
||||
flight_no: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""锁舱出站。航班号和航司一并带上,主账缺了这两项不能当锁舱失败。"""
|
||||
from agent.policy.bot_name import strip_bot_mention
|
||||
|
||||
# 群入站常带「@智价通AIQ …」;出站备注只留业务原文,禁止把 @机器人 带给 TMS。
|
||||
clean_instruction = strip_bot_mention(instruction_text or "")
|
||||
body: dict[str, Any] = {
|
||||
"workOrderNo": work_order_no,
|
||||
"optionNo": option_no,
|
||||
"operatorUserId": sender_id,
|
||||
"operatorName": operator_name,
|
||||
"instructionText": instruction_text,
|
||||
"instructionText": clean_instruction,
|
||||
}
|
||||
if (airline or "").strip():
|
||||
body["airline"] = airline.strip().upper()
|
||||
@@ -491,6 +499,9 @@ class HttpLedger:
|
||||
operator_name: str = "",
|
||||
instruction_text: str = "",
|
||||
) -> dict[str, Any]:
|
||||
from agent.policy.bot_name import strip_bot_mention
|
||||
|
||||
clean_instruction = strip_bot_mention(instruction_text or "")
|
||||
return self._post(
|
||||
"/inquiry/agent/tms/releaseCabin",
|
||||
{
|
||||
@@ -498,7 +509,7 @@ class HttpLedger:
|
||||
"lockId": lock_id,
|
||||
"operatorUserId": sender_id,
|
||||
"operatorName": operator_name,
|
||||
"instructionText": instruction_text,
|
||||
"instructionText": clean_instruction,
|
||||
},
|
||||
idem=f"air-release:{work_order_no}:{lock_id}",
|
||||
)
|
||||
@@ -510,6 +521,38 @@ class HttpLedger:
|
||||
return None
|
||||
return out
|
||||
|
||||
def list_open_collab(self) -> list[dict[str, Any]] | None:
|
||||
"""
|
||||
列出仍绑着协同群的工单(含已结束协同)。
|
||||
|
||||
Worker 对账监听用。
|
||||
返回 None = 接口失败/形态不对(调用方禁止据此做孤儿清群);
|
||||
返回 [] = 确实没有绑群工单。
|
||||
"""
|
||||
try:
|
||||
out = self._post("/inquiry/agent/ticket/listOpenCollab", {})
|
||||
except Exception:
|
||||
logger.exception("listOpenCollab 调用失败")
|
||||
return None
|
||||
if not isinstance(out, dict):
|
||||
logger.error("listOpenCollab 返回非对象")
|
||||
return None
|
||||
# 主账失败常见 success=false / code!=200,不能当成「零条活群」。
|
||||
if out.get("success") is False:
|
||||
logger.error("listOpenCollab 业务失败 code=%s", out.get("code") or out.get("status"))
|
||||
return None
|
||||
rows = out.get("tickets")
|
||||
if rows is None and "result" in out:
|
||||
result = out.get("result")
|
||||
if isinstance(result, dict):
|
||||
rows = result.get("tickets")
|
||||
elif isinstance(result, list):
|
||||
rows = result
|
||||
if not isinstance(rows, list):
|
||||
logger.error("listOpenCollab 无 tickets 列表字段 keys=%s", sorted(out.keys())[:20])
|
||||
return None
|
||||
return [r for r in rows if isinstance(r, dict)]
|
||||
|
||||
def end_collab_group(self, *, work_order_no: str) -> dict[str, Any]:
|
||||
"""协同结束,这单不能再拉群。不改六态。"""
|
||||
return self._post(
|
||||
@@ -539,6 +582,34 @@ class HttpLedger:
|
||||
},
|
||||
)
|
||||
|
||||
def attach_file_bytes(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
file_name: str,
|
||||
file_bytes: bytes,
|
||||
content_type: str = "",
|
||||
sender_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
报价附件字节写入主账(上传对象存储 + inq_attachment)。
|
||||
|
||||
用于 PDF 转换完成后落库;Excel 出单由主账 render 内登记。
|
||||
"""
|
||||
import base64
|
||||
|
||||
return self._post(
|
||||
"/inquiry/agent/ticket/attachFileBytes",
|
||||
{
|
||||
"workOrderNo": work_order_no,
|
||||
"fileName": file_name,
|
||||
"fileBase64": base64.b64encode(file_bytes or b"").decode("ascii"),
|
||||
"contentType": content_type or "application/octet-stream",
|
||||
"senderId": sender_id,
|
||||
},
|
||||
idem=f"attach-bytes:{work_order_no}:{file_name}:{len(file_bytes or b'')}",
|
||||
)
|
||||
|
||||
def upsert_product_quote(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
主账内存假账本(单测 / 无 8180 时)。
|
||||
主账内存假账本(单测 / 无 8080 时)。
|
||||
|
||||
本文件职责:实现建单(每次新开)、相似检索、TMS fixture、报价写入、六态流转,语义对齐 Java 主账。
|
||||
禁止:冒充已打真实 TMS;禁止被生产 Worker 当默认 Owner(生产必须 HTTP 主账)。
|
||||
@@ -427,9 +427,11 @@ class MemoryLedger:
|
||||
quote: Optional[dict[str, Any]] = None,
|
||||
format: str = "xlsx",
|
||||
sales_wecom_id: str = "",
|
||||
persist_attachment: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
按询价需求匹配内存模板。不填真实 xlsx,只回假字节供流程/单测。
|
||||
persist_attachment 与主账同语义:True 表示应登记附件(单测可断言 attachmentSaved)。
|
||||
"""
|
||||
from agent.quote_templates.match import FAIL_MESSAGE, pick_quote_template
|
||||
|
||||
@@ -459,6 +461,8 @@ class MemoryLedger:
|
||||
"fileBase64": "UEsDBAoAAAAAA",
|
||||
"needsPdf": False,
|
||||
"facts": dict(facts or {}),
|
||||
"attachmentSaved": bool(persist_attachment),
|
||||
"persistAttachmentRequested": bool(persist_attachment),
|
||||
}
|
||||
with self._lock:
|
||||
ticket = self._tickets.get((work_order_no or "").strip())
|
||||
@@ -745,6 +749,26 @@ class MemoryLedger:
|
||||
ticket.attachments.append(row)
|
||||
return {"ok": True, "file_name": file_name}
|
||||
|
||||
def attach_file_bytes(
|
||||
self,
|
||||
*,
|
||||
work_order_no: str,
|
||||
file_name: str,
|
||||
file_bytes: bytes,
|
||||
content_type: str = "",
|
||||
sender_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""单测:字节附件只记元数据,不写真实对象存储。"""
|
||||
_ = file_bytes
|
||||
key = f"memory/{work_order_no}/{file_name or 'file.bin'}"
|
||||
return self.attach_file(
|
||||
work_order_no=work_order_no,
|
||||
file_name=file_name,
|
||||
object_key=key,
|
||||
content_type=content_type,
|
||||
sender_id=sender_id,
|
||||
)
|
||||
|
||||
def upsert_product_quote(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user