落地海运私聊文字询价全流程,海运多项费用改走文本明细,避免企微卡片引用条截断。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-15 14:26:09 +08:00
co-authored by Cursor
parent 58b3c2f3e6
commit 0a3c74e3a1
41 changed files with 3702 additions and 48 deletions
+67 -4
View File
@@ -45,11 +45,16 @@ class FlowSession:
status: str = ""
allowed: tuple[str, ...] = ()
immutable_text: str = ""
collab_chat_id: str = ""
collab_facts: dict[str, str] = field(default_factory=dict)
invited_userids: tuple[str, ...] = ()
invited_sales_name: str = ""
invited_product_names: tuple[str, ...] = ()
class AirTextInquiryFlow:
"""
空运文字询价 Owner(本轮产品范围);海运/陆运先补问运输方式或陆运二级。
空运文字询价 Owner。海运走 SeaTextInquiryFlow,不要再往本文件堆海运。陆运仍补问二级。
同一 sender 新开询价会覆盖「当前书签」(旧单留在主账)。
价格卡按工单号另存一份当时会话:点哪张卡就生成哪张卡当时的报价单。
@@ -69,6 +74,25 @@ class AirTextInquiryFlow:
with self._lock:
return self._by_sender.get(sender_id)
def session_by_ticket(self, sender_id: str, work_order_no: str) -> Optional[FlowSession]:
"""
只查进程内该书签,不读主账。
续办选空运/海运流程用:海运已跳过协同时必须找回 wait_adopt,
不能因为主账把已报价海运一律收成 wait_collab 就套空运协同卡。
"""
no = (work_order_no or "").strip().upper()
if not (sender_id or "").strip() or not no:
return None
with self._lock:
hit = self._by_ticket.get(no)
if hit and hit.sender_id == sender_id:
return hit
for key, sess in self._by_ticket.items():
if (key or "").upper() == no and sess.sender_id == sender_id:
return sess
return None
def session_for_action(
self,
sender_id: str,
@@ -152,6 +176,7 @@ class AirTextInquiryFlow:
else:
keep = None
if keep:
self._align_resume_phase(keep)
return self._replay_wait(keep, reply, source="session")
getter = getattr(self._ledger, "get_for_agent", None)
if not callable(getter):
@@ -165,6 +190,7 @@ class AirTextInquiryFlow:
reply("这张工单不属于当前账号,不能继续。")
return "ticket_forbidden"
sess = self._session_from_ledger(sender_id=sender_id, view=view)
self._align_resume_phase(sess)
self._save(sess)
return self._replay_wait(sess, reply, source="ledger")
@@ -204,6 +230,20 @@ class AirTextInquiryFlow:
return "wait_adopt"
return "tms_miss"
def _align_resume_phase(self, sess: FlowSession) -> None:
"""
报价文件已经发给销售,续办必须回到成交跟进。
主账六态仍是「已报价」,不能据此重放到协同卡或生成报价单。
Redis 就绪键由出文件时写入;主账 wait_phase=wait_deal 也认。
"""
if sess.phase not in {"wait_collab", "wait_adopt", "wait_file"}:
return
issued = bool((sess.quote or {}).get("quoteFileIssued"))
if issued or self._quote_file_already_out(sess.work_order_no):
logger.info("resume 报价文件已出,回到成交跟进 wo=%s was=%s", sess.work_order_no, sess.phase)
self._promote_wait_deal(sess)
def _replay_wait(self, sess: FlowSession, reply: ReplyFn, *, source: str) -> str:
"""按书签重出当时节点,不改六态。"""
logger.info(
@@ -213,18 +253,21 @@ class AirTextInquiryFlow:
source,
)
if sess.phase == "wait_adopt":
mode = copy.scheme_transport_mode(sess.business_line)
reply(
copy.scheme_card(
work_order_no=sess.work_order_no,
status=sess.status or "已报价",
facts=sess.facts,
quote=sess.quote,
transport_mode=mode,
),
copy.scheme_wecom_payload(
work_order_no=sess.work_order_no,
facts=sess.facts,
quote=sess.quote,
status=sess.status or "已报价",
transport_mode=mode,
),
)
return "wait_adopt"
@@ -349,9 +392,23 @@ class AirTextInquiryFlow:
if mode == "LAND":
reply(copy.ASK_LAND_SUBTYPE)
return "need_land_subtype"
# 海运本轮只提示先走空运闭环(不半做海运查价)
reply("本轮先开通空运文字询价。请按空运描述需求,或补充为「空运」。")
return "sea_deferred"
if mode == "SEA":
# 海运交给海运 Owner,空运不再回「本轮先开通空运」
from agent.policy.sea_text_flow import get_sea_text_flow
return get_sea_text_flow().on_text(
sender_id=sender_id,
text=text,
reply=reply,
injected_facts=facts,
injected_mode=mode,
b_result=b_result,
allow_b=allow_b,
chat_fn=chat_fn,
inbound_raw=inbound_raw,
)
reply("请补充运输方式:空运、海运或陆运。")
return "need_mode"
check = validate_required_fields(facts=facts, business_line="AIR")
facts = dict(check["facts"])
@@ -640,18 +697,21 @@ class AirTextInquiryFlow:
sess.allowed = (copy.BTN_ADOPT_EXCEL, copy.BTN_ADOPT_PDF, copy.BTN_REJECT)
sess.wait_version += 1
self._save(sess)
mode = copy.scheme_transport_mode(sess.business_line)
reply(
copy.scheme_card(
work_order_no=sess.work_order_no,
status=sess.status or "已报价",
facts=sess.facts,
quote=sess.quote,
transport_mode=mode,
),
copy.scheme_wecom_payload(
work_order_no=sess.work_order_no,
facts=sess.facts,
quote=sess.quote,
status=sess.status or "已报价",
transport_mode=mode,
),
)
return "wait_adopt"
@@ -763,6 +823,9 @@ class AirTextInquiryFlow:
file_extra,
)
self._wait_outbound_sent(str(out_id or ""), work_order_no=sess.work_order_no, what="报价文件")
from agent.redis_coord.quote_file import mark_quote_file_ready
mark_quote_file_ready(sess.work_order_no)
sess.phase = "wait_deal"
sess.allowed = (copy.BTN_DEAL, copy.BTN_LOST, copy.BTN_NEGOTIATE)
sess.wait_version += 1