陆运多条线路按段展示 TMS 费用名和时效,选定后再写回该条成本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 18:28:29 +08:00
co-authored by Cursor
parent e769eb9c1d
commit 49faa35f47
23 changed files with 2323 additions and 55 deletions
+126 -5
View File
@@ -2,8 +2,9 @@
陆运文字询价确定性流程。
本文件职责:三项选项各自必填(不联动)→ 按类型补货物 → 无工单号核对卡 →(确定后)建单查 TMS。
查价后:1 条完整线路直接出价格明细;多于 1 条发入口卡,H5 选定后再出该条明细。
查价后半段复用海运拉群/报价单/成交,拉人按线路类别,禁止陆运岗全员。
禁止:字段齐了就建单;直连 TMS;把海运「出卡即查价」改掉。
禁止:字段齐了就建单;直连 TMS;把中转段当成另一条线路;把海运「出卡即查价」改掉。
调用:handler 在 Worker/inbox 线程;禁止回调线程同步跑。
"""
@@ -31,7 +32,7 @@ from agent.schema.tms_land_query import assemble_land_query
logger = logging.getLogger(__name__)
_LAND_FRONT = frozenset({"need_mode", "need_land_options", "clarify", "wait_confirm", "idle", ""})
_LAND_FRONT = frozenset({"need_mode", "need_land_options", "clarify", "wait_confirm", "wait_land_route", "idle", ""})
class LandTextInquiryFlow(SeaTextInquiryFlow):
@@ -365,7 +366,11 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
reply: ReplyFn,
retrying: bool,
) -> str:
"""查价回包落到有价/无价/技术失败。失败时把工单号记进待重查。"""
"""
查价回包落到有价/无价/技术失败。失败时把工单号记进待重查。
有价时按完整线路计条:1 条投影后出明细;多于 1 条进 H5。
"""
_ = retrying
no = (sess.work_order_no or "").strip()
if not tms.get("ok"):
@@ -395,7 +400,17 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
self._save(sess)
return "tms_miss"
quote = tms["quote"]
quote = dict(tms["quote"] or {})
from agent.schema.land_route_options import (
group_land_routes,
needs_route_pick,
project_selected_route,
)
raw_quote = dict(quote)
routes = group_land_routes(raw_quote)
if routes and not needs_route_pick(raw_quote):
quote = project_selected_route(raw_quote, routes[0])
up = self._ledger.upsert_quote(work_order_no=no, quote=quote, to_status="已报价")
if not up.get("ok"):
reply("主账写入报价失败,不能当作已报价。")
@@ -403,8 +418,10 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
self._save(sess)
return "ledger_fail"
sess.quote = quote
copy.remember_tms_quote(sess, quote)
copy.remember_tms_quote(sess, raw_quote)
sess.status = "已报价"
if needs_route_pick(raw_quote):
return self._offer_land_routes(sess=sess, work_order_no=no, quote=raw_quote, reply=reply)
sess.phase = "wait_collab"
sess.allowed = (copy.BTN_PULL_COLLAB, copy.BTN_SKIP_COLLAB)
sess.wait_version += 1
@@ -420,6 +437,110 @@ class LandTextInquiryFlow(SeaTextInquiryFlow):
)
return "wait_collab"
def _offer_land_routes(
self,
*,
sess: FlowSession,
work_order_no: str,
quote: dict[str, Any],
reply: ReplyFn,
) -> str:
"""多条陆运线路:企微只发入口卡,选线在 H5。"""
from agent.channel.h5.land_route_store import build_land_route_url, issue_land_route_token
from agent.schema.land_route_options import group_land_routes
token = issue_land_route_token(
sender_id=sess.sender_id,
work_order_no=work_order_no,
quote=quote,
facts=dict(sess.facts),
)
url = build_land_route_url(token)
count = len(group_land_routes(quote))
sess.phase = "wait_land_route"
sess.allowed = ()
sess.wait_version += 1
self._save(sess)
reply(
copy.air_route_entry_text(work_order_no=work_order_no, count=count),
copy.air_route_entry_card(work_order_no=work_order_no, count=count, url=url),
)
return "wait_land_route"
def select_land_route(
self,
*,
sender_id: str,
index: int,
reply: ReplyFn,
) -> str:
"""
H5 选定一条陆运线路:只留该条各段,出价格明细。
已采用过则作废旧报价单后再出新卡。
"""
sess = self.session_of(sender_id)
if sess is None or not (sess.work_order_no or "").strip():
reply("线路选择已过期,请回企微重新询价。")
return "route_expired"
from agent.schema.land_route_options import group_land_routes, project_selected_route
raw_quote = dict(sess.tms_quote or sess.quote or {})
routes = group_land_routes(raw_quote)
if index < 0 or index >= len(routes):
reply("没有这条线路,请重新打开页面选择。")
return "route_invalid"
route = routes[index]
quote = project_selected_route(raw_quote, route)
prev_phase = (sess.phase or "").strip()
already_adopted = prev_phase in {"wait_deal", "wait_file", "wait_adopt"}
if already_adopted:
clearer = getattr(self._ledger, "void_quote_files", None)
if callable(clearer):
clearer(work_order_no=sess.work_order_no)
from agent.redis_coord.quote_file import clear_quote_file_ready
clear_quote_file_ready(sess.work_order_no)
self._ledger.upsert_quote(work_order_no=sess.work_order_no, quote=quote, to_status="已报价")
sess.quote = quote
sess.status = "已报价"
sess.wait_version += 1
if already_adopted:
sess.phase = "wait_adopt"
sess.allowed = (copy.BTN_ADOPT_EXCEL, copy.BTN_ADOPT_PDF, copy.BTN_REJECT)
self._save(sess)
mode = copy.scheme_transport_mode(sess.business_line)
reply(
copy.scheme_card(
work_order_no=sess.work_order_no,
status="已报价",
facts=dict(sess.facts),
quote=quote,
transport_mode=mode,
),
copy.scheme_wecom_payload(
work_order_no=sess.work_order_no,
facts=dict(sess.facts),
quote=quote,
status="已报价",
transport_mode=mode,
),
)
return "wait_adopt"
sess.phase = "wait_collab"
sess.allowed = (copy.BTN_PULL_COLLAB, copy.BTN_SKIP_COLLAB)
self._save(sess)
self._emit_sea_hit(
reply,
work_order_no=sess.work_order_no,
quote=quote,
first_or_same=sess.first_or_same,
history_work_order_no=sess.history_work_order_no,
status="已报价",
business_line="LAND",
)
return "wait_collab"
def _land_ready(self, facts: dict[str, str]) -> dict[str, Any]:
"""选项+货物是否可出核对卡。"""
opt = validate_land_options(facts)