销售侧汇总统一叫费用合计,并认准空运费的金额。

企微跳转改为账号密码登录,报价卡上的费用按原金额改回;航线按单价数量金额报价时不再记成海运费。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-24 11:15:33 +08:00
co-authored by Cursor
parent 84207bb4e5
commit 4dfd756bcf
24 changed files with 778 additions and 150 deletions
+19 -15
View File
@@ -856,7 +856,7 @@ def _total_label(
business_line: str = "",
transport_mode: str = "",
) -> str:
"""空运总销售价;海运/陆运看这版报价是成本还是销售调价。"""
"""销售侧汇总行。企微卡片、文字明细、成交通知都叫费用合计。"""
src = dict(quote or {})
return total_price_label(
business_line or str(src.get("business_line") or ""),
@@ -2282,7 +2282,7 @@ def deal_horizontal_rows(
transport_mode: str = "",
) -> list[dict[str, str]]:
"""
成交跟进卡字段:总销售价、报价有效期;海运/陆运再加时效。
成交跟进卡字段:费用合计、报价有效期;海运/陆运再加时效。
空运业务确认没有时效,这里不写。
不要历史报价 / 工单状态 / 来源。来源只留卡片顶部红字。
@@ -2339,7 +2339,7 @@ def deal_wecom_payload(
成交跟进卡:对齐现网 button_interaction。
顶部来源红字;标题询价工单号;副标题跟进成交;三个按钮。
字段:总销售价、报价有效期;海运/陆运再加时效。空运不写时效。
字段:费用合计、报价有效期;海运/陆运再加时效。空运不写时效。
"""
src = dict(quote or {})
source = str(src.get("source_label") or "TMS标准报价").replace(" ", "")
@@ -2593,6 +2593,19 @@ def deal_notify_card(
return "\n".join(lines)
def site_card_url() -> str:
"""
整张卡片点击后打开的后台地址。
事件异常、系统异常、成交通知都走这里,点开是同一个网站。
不用企业微信授权页,也不要用 work.weixin.qq.com。
"""
from agent.config import get_settings
base = str(getattr(get_settings(), "public_base_url", "") or "").strip().rstrip("/")
return base or "https://ai.ytd-scm.com"
def deal_notify_wecom_payload(
*,
work_order_no: str,
@@ -2615,10 +2628,7 @@ def deal_notify_wecom_payload(
quote=dict(quote or {}),
transport_mode=mode,
)
from agent.config import get_settings
base = str(getattr(get_settings(), "public_base_url", "") or "").strip().rstrip("/")
action_url = base or "https://work.weixin.qq.com"
action_url = site_card_url()
return {
"msgtype": "template_card",
"wecom_app": "notify",
@@ -2991,10 +3001,7 @@ def system_exception_alert_payload(
{"keyname": "影响范围", "value": system_exception_impact(step)},
{"keyname": "发生时间", "value": (occurred_at or "").strip() or "-"},
]
from agent.config import get_settings
base = str(getattr(get_settings(), "public_base_url", "") or "").strip().rstrip("/")
action_url = base or "https://work.weixin.qq.com"
action_url = site_card_url()
return {
"msgtype": "template_card",
"wecom_app": "notify",
@@ -3090,10 +3097,7 @@ def event_exception_alert_payload(
no = (work_order_no or "").strip() or "-"
title = (card_title or "").strip() or "事件响应异常"
hour_text = format_timeout_hours(hours)
from agent.config import get_settings
base = str(getattr(get_settings(), "public_base_url", "") or "").strip().rstrip("/")
action_url = base or "https://work.weixin.qq.com"
action_url = site_card_url()
return {
"msgtype": "template_card",
"wecom_app": "notify",
+12 -4
View File
@@ -80,17 +80,25 @@ def split_segments(text: str) -> SplitOutcome:
"""
按运输方式出现的先后切段。
「段1:海运」和同一句里的「陆运+海运」都认。同一方式出现两次则 same_mode,不建单。
已经标了两段以上时,只按「段N:」切,表里重复的「海运/陆运」不当新段。
「段1:海运」和同一句里的「陆运+海运」都认。
写了「段N:」就只按段标切:一份海运报价单文件名会先标「段1:海运」,
表里再写「运输方式:海运」仍是单段,不能报同一方式两段。
没写段标时,报价单反复出现「海运」也当单段;只有「陆运+海运」才切多段。
真的两段都是海运(段1:海运 + 段2:海运)才 same_mode,不建单。
每段正文只收「字段名:值」,不猜没写标签的港口。
"""
raw = text or ""
explicit = list(_EXPLICIT_SEG.finditer(raw))
hits = explicit if len(explicit) >= 2 else list(_MODE_RE.finditer(raw))
# explicit_only_segment_split:有段标就只认段标。一份附件常被标「段1:海运」,表里还会再写一次海运。
hits = explicit if explicit else list(_MODE_RE.finditer(raw))
if len(hits) < 2:
return SplitOutcome()
modes = [_MODE_BY_WORD[hit.group(2)] for hit in hits]
if len(modes) != len(set(modes)):
unique = set(modes)
if len(modes) != len(unique):
# 没标出两段时,同一种方式写两次仍是单段(海运报价单标题+运输方式)。
if len(explicit) < 2 and len(unique) == 1:
return SplitOutcome()
return SplitOutcome(same_mode=True)
segments: list[Segment] = []
for i, hit in enumerate(hits):
@@ -12,6 +12,7 @@ from typing import Any, Optional
from agent.policy import inquiry_copy as copy
from agent.schema.quote_adjust import (
adjustable_rows,
apply_adjust,
extract_adjust,
private_session_can_adjust,
@@ -151,7 +152,7 @@ def try_private_adjust(
"""
if not session_quoted(sess):
return None
names = [str(r.get("name") or "") for r in quote_fee_rows(getattr(sess, "quote", None))]
names = [str(r.get("name") or "") for r in adjustable_rows(getattr(sess, "quote", None))]
extract = extract_adjust(text, fee_names=names, injected=injected_adjust)
if not extract.is_adjust:
return None
@@ -262,7 +263,7 @@ def apply_group_adjust(
send_group_text(client, room, copy.adjust_only_sales())
return "adjust_not_sales"
current = _current_quote(view, sess)
names = [str(r.get("name") or "") for r in quote_fee_rows(current)]
names = [str(r.get("name") or "") for r in adjustable_rows(current)]
extract = extract_adjust(text, fee_names=names, injected=injected_adjust)
if not extract.is_adjust:
return "not_adjust"