落地转人工:三次补问或明确接管后出入口,真正转人工后冻结本票并允许新开询价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -77,6 +77,7 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
if not (sender_id or "").strip():
|
||||
return "reject_no_sender"
|
||||
sess = self.session_for_action(sender_id, inbound_raw, text=text)
|
||||
sess = self._detach_frozen_bookmark(sess, sender_id)
|
||||
btn = self.match_button(text, sess)
|
||||
if btn:
|
||||
return self.on_button(
|
||||
@@ -173,25 +174,26 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
for key in tms_ready.get("missing") or []:
|
||||
if key not in keys:
|
||||
keys.append(key)
|
||||
reply(
|
||||
copy.ask_clarify(
|
||||
new_sess = FlowSession(
|
||||
sender_id=sender_id,
|
||||
thread_id=sender_id,
|
||||
phase="clarify",
|
||||
business_line="SEA",
|
||||
facts=facts,
|
||||
immutable_text=sess.immutable_text if sess and sess.immutable_text else text,
|
||||
wait_version=(sess.wait_version + 1) if sess else 1,
|
||||
)
|
||||
return self._clarify_or_handoff(
|
||||
sess=sess,
|
||||
keys=keys,
|
||||
new_sess=new_sess,
|
||||
reply=reply,
|
||||
clarify_text=copy.ask_clarify(
|
||||
facts=facts,
|
||||
missing_keys=keys,
|
||||
transport_mode="海运",
|
||||
)
|
||||
),
|
||||
)
|
||||
self._save(
|
||||
FlowSession(
|
||||
sender_id=sender_id,
|
||||
thread_id=sender_id,
|
||||
phase="clarify",
|
||||
business_line="SEA",
|
||||
facts=facts,
|
||||
immutable_text=sess.immutable_text if sess and sess.immutable_text else text,
|
||||
wait_version=(sess.wait_version + 1) if sess else 1,
|
||||
)
|
||||
)
|
||||
return "clarify"
|
||||
|
||||
return self._create_and_quote(
|
||||
sender_id=sender_id,
|
||||
@@ -211,6 +213,11 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
"""建海运工单、先出确认卡、再查 TMS。"""
|
||||
import uuid
|
||||
|
||||
from agent.policy.handoff_human import void_virtual_no
|
||||
|
||||
old = self.session_of(sender_id)
|
||||
if old:
|
||||
void_virtual_no(old)
|
||||
self._save(
|
||||
FlowSession(
|
||||
sender_id=sender_id,
|
||||
@@ -423,6 +430,8 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
) -> str:
|
||||
sess = self.session_for_action(sender_id, card_meta)
|
||||
if sess and action == copy.BTN_PULL_COLLAB and action in sess.allowed:
|
||||
if sess.handoff_offer and not sess.handoff_committed:
|
||||
return self._commit_handoff_pull(sess, reply, card_meta=card_meta)
|
||||
return self._pull_collab_group(sess, reply, card_meta=card_meta)
|
||||
if action in {copy.BTN_DEAL, copy.BTN_LOST, copy.BTN_NEGOTIATE}:
|
||||
from agent.policy.sea_group_ops import explain_deal_click
|
||||
@@ -600,6 +609,133 @@ class SeaTextInquiryFlow(AirTextInquiryFlow):
|
||||
)
|
||||
return "collab_group"
|
||||
|
||||
def _match_handoff_staff(self, sess: FlowSession) -> list[dict[str, Any]]:
|
||||
"""
|
||||
转人工拉人:线路/港口已齐走原匹配;未齐则拉该运输方式全部启用产品。
|
||||
"""
|
||||
line = (sess.business_line or "SEA").upper()
|
||||
facts = dict(sess.facts or {})
|
||||
if line == "LAND":
|
||||
if str(facts.get("线路类别") or "").strip():
|
||||
return self._match_sea_staff(sess)
|
||||
lister = getattr(self._ledger, "list_staff_by_role", None)
|
||||
if not callable(lister):
|
||||
return []
|
||||
try:
|
||||
rows = lister(role_code="land") or []
|
||||
except Exception:
|
||||
logger.exception("转人工拉全部陆运产品失败")
|
||||
return []
|
||||
return [dict(x) for x in rows if isinstance(x, dict)]
|
||||
origin = str(facts.get("起运港") or facts.get("起运地") or "").strip()
|
||||
dest = str(facts.get("目的港") or facts.get("目的地") or "").strip()
|
||||
if origin or dest:
|
||||
return self._match_sea_staff(sess)
|
||||
lister = getattr(self._ledger, "list_staff_by_role", None)
|
||||
if not callable(lister):
|
||||
return []
|
||||
try:
|
||||
rows = lister(role_code="sea") or []
|
||||
except Exception:
|
||||
logger.exception("转人工拉全部海运产品失败")
|
||||
return []
|
||||
return [dict(x) for x in rows if isinstance(x, dict)]
|
||||
|
||||
def _commit_handoff_pull(
|
||||
self,
|
||||
sess: FlowSession,
|
||||
reply: ReplyFn,
|
||||
card_meta: Optional[dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""
|
||||
点转人工「拉产品进群」:建群、发转人工摘要、正式单改「转人工」。
|
||||
虚拟单不写主账。不发 TMS、不发模板、不跟成交。
|
||||
"""
|
||||
from agent.policy.handoff_human import (
|
||||
HANDOFF_STATUS,
|
||||
ensure_virtual_no,
|
||||
is_virtual_no,
|
||||
)
|
||||
|
||||
if sess.collab_ended or (sess.collab_chat_id or "").strip():
|
||||
reply(copy.HANDOFF_ALREADY_COLLAB)
|
||||
return "handoff_already"
|
||||
display_no = ensure_virtual_no(sess) if not (sess.work_order_no or "").strip() else sess.work_order_no
|
||||
staff = self._match_handoff_staff(sess)
|
||||
if not staff:
|
||||
reply(self._group_no_staff_copy())
|
||||
return sess.phase or "handoff_offer"
|
||||
userids = [sess.sender_id]
|
||||
product_names: list[str] = []
|
||||
product_ids: list[str] = []
|
||||
for row in staff:
|
||||
uid = str(row.get("wecomId") or row.get("wecom_id") or "").strip()
|
||||
name = str(row.get("name") or "").strip() or uid
|
||||
if not uid:
|
||||
continue
|
||||
if uid not in userids:
|
||||
userids.append(uid)
|
||||
if uid not in product_ids:
|
||||
product_ids.append(uid)
|
||||
product_names.append(name)
|
||||
if not product_ids:
|
||||
reply(self._group_no_staff_copy())
|
||||
return sess.phase or "handoff_offer"
|
||||
for uid in self._archive_seat_userids():
|
||||
if uid not in userids:
|
||||
userids.append(uid)
|
||||
client = self.group_client()
|
||||
created = client.create_group(name=display_no, userids=userids) or {}
|
||||
if not created.get("ok") or not created.get("chat_id"):
|
||||
reply(copy.sea_group_create_fail(reason=str(created.get("error") or "")))
|
||||
return sess.phase or "handoff_offer"
|
||||
chat_id = str(created.get("chat_id") or "").strip()
|
||||
brief = copy.handoff_group_brief(
|
||||
work_order_no=display_no,
|
||||
facts=sess.facts,
|
||||
business_line=sess.business_line,
|
||||
product_names=product_names,
|
||||
)
|
||||
client.send_group(chat_id=chat_id, content=brief)
|
||||
real = (sess.work_order_no or "").strip()
|
||||
if real and not is_virtual_no(real):
|
||||
trans = getattr(self._ledger, "transition", None)
|
||||
if callable(trans):
|
||||
trans(
|
||||
work_order_no=real,
|
||||
to_status=HANDOFF_STATUS,
|
||||
remark=copy.HANDOFF_CLARIFY_REASON
|
||||
if sess.handoff_offer == "clarify"
|
||||
else copy.HANDOFF_INTENT_REASON,
|
||||
)
|
||||
binder = getattr(self._ledger, "bind_collab_group", None)
|
||||
if callable(binder):
|
||||
binder(
|
||||
work_order_no=real,
|
||||
chat_id=chat_id,
|
||||
member_ids=userids,
|
||||
product_names=product_names,
|
||||
product_ids=product_ids,
|
||||
)
|
||||
sess.status = HANDOFF_STATUS
|
||||
sess.collab_chat_id = chat_id
|
||||
sess.handoff_committed = True
|
||||
sess.handoff_offer = ""
|
||||
sess.phase = "handoff_done"
|
||||
sess.allowed = ()
|
||||
sess.invited_product_names = tuple(product_names)
|
||||
self._save(sess)
|
||||
self._grey_clicked_card(reply, card_meta)
|
||||
sales_name = self._sales_display_name(sess.sender_id)
|
||||
reply(
|
||||
copy.sea_group_created(
|
||||
work_order_no=display_no,
|
||||
sales_name=sales_name,
|
||||
product_names=product_names,
|
||||
)
|
||||
)
|
||||
return "handoff_done"
|
||||
|
||||
def _match_sea_staff(self, sess: FlowSession) -> list[dict[str, Any]]:
|
||||
"""主账按起运/目的匹配启用的海运产品岗。"""
|
||||
matcher = getattr(self._ledger, "match_sea_staff", None)
|
||||
|
||||
Reference in New Issue
Block a user