空运成交后可在本群继续锁舱;调价同步舱位费用桶;出单按空运段骨架落费并保留陆运车型尺寸;导出文件名乱码修复。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -461,6 +461,11 @@ def effective_quote(tms: dict[str, Any] | None, overlay: dict[str, Any] | None)
|
||||
_apply_amount(old, str(item.get("amount") or ""))
|
||||
if item.get("currency"):
|
||||
old["currency"] = item.get("currency")
|
||||
# 与 merge_quote_fees 一致:盖价保留新行的单位/数量/分段,禁止旧默认「票」残留。
|
||||
for key in ("unit", "unit_price", "unitPrice", "quantity", "project", "segment"):
|
||||
val = item.get(key)
|
||||
if val not in (None, ""):
|
||||
old[key] = val
|
||||
rows[idx] = old
|
||||
out = dict(base)
|
||||
out["fee_rows"] = rows
|
||||
@@ -763,6 +768,52 @@ def _match_fee_index(name: str, fees: list[dict[str, Any]], used: set[int]) -> i
|
||||
)
|
||||
|
||||
|
||||
def _fee_air_project(fee: dict[str, Any]) -> str:
|
||||
"""
|
||||
空运费用落到哪一段:有 project 跟 project;否则空运费进空运段,其余进起运港。
|
||||
|
||||
与主账 mergeAirBucketRowsWithFlatManualFees 兜底一致,避免增值费进不了起运港。
|
||||
"""
|
||||
project = str(fee.get("project") or fee.get("segment") or "").strip()
|
||||
if project in {"空运段", "空运"}:
|
||||
return "空运段"
|
||||
if project in {"起运港", "起运地"}:
|
||||
return "起运港"
|
||||
if project in {"目的地", "目的港"}:
|
||||
return "目的地"
|
||||
if project in {"其它", "其他"}:
|
||||
return "其它"
|
||||
name = _fee_title(fee)
|
||||
if "空运" in name and "增值" not in name and "增点" not in name:
|
||||
return "空运段"
|
||||
return "起运港"
|
||||
|
||||
|
||||
def _fee_as_air_charge(fee: dict[str, Any], *, quote: dict[str, Any]) -> dict[str, Any]:
|
||||
"""扁平 fee_lines 转舱位 departureCharges / feeItems 会读的结构。"""
|
||||
title = _fee_title(fee)
|
||||
plain = _plain_amount(str(fee.get("amount") or ""))
|
||||
unit_price = _plain_amount(
|
||||
str(fee.get("unit_price") or fee.get("unitPrice") or fee.get("amount") or "")
|
||||
)
|
||||
out: dict[str, Any] = {
|
||||
"chargeName": title,
|
||||
"feeName": title,
|
||||
"name": title,
|
||||
"amount": plain or unit_price,
|
||||
"unitPrice": unit_price or plain,
|
||||
"currency": fee.get("currency") or quote.get("currency") or "",
|
||||
"project": _fee_air_project(fee),
|
||||
}
|
||||
unit = str(fee.get("unit") or fee.get("unitName") or "").strip()
|
||||
if unit:
|
||||
out["unit"] = unit
|
||||
qty = str(fee.get("quantity") or fee.get("chargeQuantity") or "").strip()
|
||||
if qty:
|
||||
out["quantity"] = qty
|
||||
return out
|
||||
|
||||
|
||||
def _stamp_fee_items(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None:
|
||||
raw, was_str = _json_struct(quote.get("feeItems"))
|
||||
items = [dict(x) for x in raw if isinstance(x, dict)] if isinstance(raw, list) else []
|
||||
@@ -775,8 +826,20 @@ def _stamp_fee_items(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None:
|
||||
"currency": fee.get("currency") or quote.get("currency") or "",
|
||||
}
|
||||
for fee in fees
|
||||
if _fee_title(fee)
|
||||
if _fee_title(fee) and _fee_air_project(fee) == "空运段"
|
||||
]
|
||||
if not items:
|
||||
# 无空运段费目时仍按原逻辑铺全量,避免只有起运附加费时整表空掉
|
||||
items = [
|
||||
{
|
||||
"name": _fee_title(fee),
|
||||
"feeName": _fee_title(fee),
|
||||
"amount": _plain_amount(str(fee.get("amount") or "")),
|
||||
"currency": fee.get("currency") or quote.get("currency") or "",
|
||||
}
|
||||
for fee in fees
|
||||
if _fee_title(fee)
|
||||
]
|
||||
else:
|
||||
used: set[int] = set()
|
||||
for item in items:
|
||||
@@ -789,6 +852,22 @@ def _stamp_fee_items(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None:
|
||||
item["name"] = _fee_title(fees[idx])
|
||||
if not item.get("feeName"):
|
||||
item["feeName"] = _fee_title(fees[idx])
|
||||
# 调价新增的空运段费目也要进 feeItems,否则模板只读舱位桶时看不到
|
||||
for i, fee in enumerate(fees):
|
||||
if i in used or _fee_air_project(fee) != "空运段":
|
||||
continue
|
||||
title = _fee_title(fee)
|
||||
if not title:
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"name": title,
|
||||
"feeName": title,
|
||||
"amount": _plain_amount(str(fee.get("amount") or "")),
|
||||
"currency": fee.get("currency") or quote.get("currency") or "",
|
||||
}
|
||||
)
|
||||
used.add(i)
|
||||
if items:
|
||||
_put_struct(quote, "feeItems", items, was_str)
|
||||
|
||||
@@ -870,6 +949,67 @@ def _stamp_buckets(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None:
|
||||
_store_bucket(quote, key, items)
|
||||
|
||||
|
||||
def _stamp_air_bucket_list(
|
||||
opt: dict[str, Any],
|
||||
key_primary: str,
|
||||
key_alt: str,
|
||||
fees: list[dict[str, Any]],
|
||||
used: set[int],
|
||||
*,
|
||||
projects: set[str],
|
||||
quote: dict[str, Any],
|
||||
) -> None:
|
||||
"""
|
||||
更新舱位某一费用桶,并把 fee_lines 里多出来的同段费用追加进去。
|
||||
|
||||
为何:调价/产品报价常只改扁平明细;模板读 departureCharges,不追加就会缺「增值费」行。
|
||||
"""
|
||||
charge_key = key_primary if key_primary in opt or key_alt not in opt else key_alt
|
||||
if charge_key not in opt and key_primary not in opt and key_alt not in opt:
|
||||
# 舱位从未带过该桶:只要有待追加费用就新建,否则不动
|
||||
if not any(i not in used and _fee_air_project(fees[i]) in projects for i in range(len(fees))):
|
||||
return
|
||||
charge_key = key_primary
|
||||
charges, charges_str = [], False
|
||||
else:
|
||||
charges, charges_str = _json_struct(opt.get(charge_key))
|
||||
if not isinstance(charges, list):
|
||||
charges = []
|
||||
copied = [dict(x) if isinstance(x, dict) else x for x in charges]
|
||||
for charge in copied:
|
||||
if not isinstance(charge, dict):
|
||||
continue
|
||||
idx = _match_fee_index(_fee_title(charge), fees, used)
|
||||
if idx < 0:
|
||||
continue
|
||||
used.add(idx)
|
||||
plain = _plain_amount(str(fees[idx].get("amount") or ""))
|
||||
_write_price(charge, plain)
|
||||
# 单价/数量若调价行有值也盖上,模板按单价×数量重算时才对
|
||||
fee = fees[idx]
|
||||
up = _plain_amount(str(fee.get("unit_price") or fee.get("unitPrice") or ""))
|
||||
if up:
|
||||
charge["unitPrice"] = up
|
||||
charge["unit_price"] = up
|
||||
qty = str(fee.get("quantity") or "").strip()
|
||||
if qty:
|
||||
charge["quantity"] = qty
|
||||
unit = str(fee.get("unit") or "").strip()
|
||||
if unit:
|
||||
charge["unit"] = unit
|
||||
for i, fee in enumerate(fees):
|
||||
if i in used:
|
||||
continue
|
||||
if _fee_air_project(fee) not in projects:
|
||||
continue
|
||||
title = _fee_title(fee)
|
||||
if not title:
|
||||
continue
|
||||
copied.append(_fee_as_air_charge(fee, quote=quote))
|
||||
used.add(i)
|
||||
opt[charge_key] = json.dumps(copied, ensure_ascii=False) if charges_str else copied
|
||||
|
||||
|
||||
def _stamp_air_copies(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None:
|
||||
"""空运报价单读舱位 departureCharges 和 salePrice,不读改过的 fee_lines。"""
|
||||
total_plain = _plain_amount(str(quote.get("total") or ""))
|
||||
@@ -882,23 +1022,25 @@ def _stamp_air_copies(quote: dict[str, Any], fees: list[dict[str, Any]]) -> None
|
||||
for opt in options:
|
||||
if not isinstance(opt, dict):
|
||||
continue
|
||||
charge_key = "departureCharges" if "departureCharges" in opt or "departure_charges" not in opt else "departure_charges"
|
||||
if charge_key not in opt and "departureCharges" not in opt and "departure_charges" not in opt:
|
||||
charge_key = ""
|
||||
if charge_key:
|
||||
charges, charges_str = _json_struct(opt.get(charge_key))
|
||||
if isinstance(charges, list) and charges:
|
||||
used: set[int] = set()
|
||||
copied = [dict(x) if isinstance(x, dict) else x for x in charges]
|
||||
for charge in copied:
|
||||
if not isinstance(charge, dict):
|
||||
continue
|
||||
idx = _match_fee_index(_fee_title(charge), fees, used)
|
||||
if idx < 0:
|
||||
continue
|
||||
used.add(idx)
|
||||
_write_price(charge, _plain_amount(str(fees[idx].get("amount") or "")))
|
||||
opt[charge_key] = json.dumps(copied, ensure_ascii=False) if charges_str else copied
|
||||
used: set[int] = set()
|
||||
_stamp_air_bucket_list(
|
||||
opt,
|
||||
"departureCharges",
|
||||
"departure_charges",
|
||||
fees,
|
||||
used,
|
||||
projects={"起运港", "其它"},
|
||||
quote=quote,
|
||||
)
|
||||
_stamp_air_bucket_list(
|
||||
opt,
|
||||
"destinationCharges",
|
||||
"destination_charges",
|
||||
fees,
|
||||
used,
|
||||
projects={"目的地"},
|
||||
quote=quote,
|
||||
)
|
||||
if total_plain:
|
||||
if opt.get("salePrice") not in (None, ""):
|
||||
opt["salePrice"] = total_plain
|
||||
|
||||
Reference in New Issue
Block a user