完善空运锁舱与海运报价单单选回填,并增强模板预览与协同字段盖写。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-02 22:57:04 +08:00
co-authored by Cursor
parent 5402f94d30
commit d989154485
51 changed files with 4748 additions and 188 deletions
+91 -7
View File
@@ -401,17 +401,21 @@ def hydrate_ticket(flow: Any, ticket: object) -> object:
)
)
quote = dict(view.get("quote") or {})
has_quote = bool(quote.get("total") or _quote_fee_rows(quote))
# total="-" 在 Python 里也是 truthy,不能当成已有总价;只有附加费明细也不算完整。
has_sale_total = _quote_has_usable_sale_total(quote)
has_fee_rows = bool(_quote_fee_rows(quote))
has_quote = has_sale_total or has_fee_rows
has_status = bool(ticket_status(ticket))
has_tms = bool(dict(view.get("tms_quote") or view.get("tmsQuote") or {}))
# 调价后 latest quote 仍有价,但不能因此跳过补 TMS 原价。
if has_facts and has_quote and has_status and has_tms:
return ticket
# 仅有 fee_rows、没有可用总价时仍要往下补全,否则空运出单预估费用会落成明细 SUM。
if has_facts and has_sale_total and has_status and has_tms:
return _soft_fill_quote_sale_fields(ticket)
no = ticket_no(ticket)
sales = str(view.get("sales_wecom_id") or view.get("salesWecomId") or "").strip()
book = getattr(flow, "ledger", None)
if book is None or not no:
return ticket
return _soft_fill_quote_sale_fields(ticket)
full: Any = None
getter = getattr(book, "get_for_agent", None)
if callable(getter) and sales:
@@ -423,13 +427,33 @@ def hydrate_ticket(flow: Any, ticket: object) -> object:
if callable(getter):
full = getter(work_order_no=no)
if full is None:
return ticket
return _soft_fill_quote_sale_fields(ticket)
merged = dict(view)
full_view = ticket_view(full)
if (not has_facts) and full_view.get("facts"):
merged["facts"] = dict(full_view.get("facts") or {})
if (not has_quote) and full_view.get("quote"):
merged["quote"] = dict(full_view.get("quote") or {})
if (not has_sale_total) and full_view.get("quote"):
# 薄报价保留已有明细,补上完整快照里的总价/线路。
rich = dict(full_view.get("quote") or {})
cur = dict(quote)
for key in (
"total",
"salePrice",
"totalSalePrice",
"airOptions",
"air_options",
"selected_air_option",
"selectedAirOption",
"chargeableWeightKg",
"chargeable_weight_kg",
"currency",
"valid_until",
):
if cur.get(key) in (None, "", "-", [], {}) and rich.get(key) not in (None, "", "-", [], {}):
cur[key] = rich[key]
if not has_fee_rows and (rich.get("fee_lines") or rich.get("fee_rows")):
cur["fee_lines"] = rich.get("fee_lines") or rich.get("fee_rows")
merged["quote"] = cur
if full_view.get("tms_quote") or full_view.get("tmsQuote"):
merged["tms_quote"] = dict(full_view.get("tms_quote") or full_view.get("tmsQuote") or {})
if (not has_status) and full_view.get("status"):
@@ -449,6 +473,66 @@ def hydrate_ticket(flow: Any, ticket: object) -> object:
):
if full_view.get(key) and not merged.get(key):
merged[key] = full_view.get(key)
return _soft_fill_quote_sale_fields(merged)
def _quote_has_usable_sale_total(quote: dict[str, Any] | None) -> bool:
"""
是否已有可用于出单预估费用的总价。
total="-" 不算;仅有 fee_rows 也不算。
"""
src = dict(quote or {})
total = str(src.get("total") or src.get("total_amount") or src.get("salePrice") or "").strip()
if total and total != "-":
return True
selected = src.get("selected_air_option") or src.get("selectedAirOption")
if isinstance(selected, dict):
sale = str(selected.get("salePrice") or selected.get("sale_price") or "").strip()
if sale and sale != "-":
return True
opts = src.get("airOptions") or src.get("air_options") or []
if isinstance(opts, list) and opts and isinstance(opts[0], dict):
sale = str(opts[0].get("salePrice") or opts[0].get("sale_price") or "").strip()
if sale and sale != "-":
return True
return False
def _soft_fill_quote_sale_fields(ticket: object) -> object:
"""
从 tms_quote 补齐当前 quote 缺失的总销售价 / 线路对象。
群 findByCollabChat 常只有薄 latest quote,出单前必须能落到 salePrice。
"""
view = ticket_view(ticket)
cur = dict(view.get("quote") or {})
rich = dict(view.get("tms_quote") or view.get("tmsQuote") or {})
if not rich:
return ticket if isinstance(ticket, dict) else view
changed = False
for key in (
"total",
"salePrice",
"totalSalePrice",
"airOptions",
"air_options",
"selected_air_option",
"selectedAirOption",
"chargeableWeightKg",
"chargeable_weight_kg",
"currency",
"valid_until",
):
if cur.get(key) in (None, "", "-", [], {}) and rich.get(key) not in (None, "", "-", [], {}):
cur[key] = rich[key]
changed = True
if not changed:
return ticket if isinstance(ticket, dict) else view
if isinstance(ticket, dict):
out = dict(ticket)
out["quote"] = cur
return out
merged = dict(view)
merged["quote"] = cur
return merged