134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
"""顺丰月结抬头与结算规则。不访问数据库。"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
|
|
from typing import Any, Optional
|
|
|
|
SF_TRANSPORT_MODE = "sfkd"
|
|
SETTLE_UNSETTLED = "wjs"
|
|
SETTLE_SETTLED = "yjs"
|
|
_AMOUNT_QUANT = Decimal("0.01")
|
|
_MAX_AMOUNT = Decimal("9999999999999999.99")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FreightHeaderPlan:
|
|
transport_mode: str
|
|
waybill_no: Optional[str]
|
|
freight_amount: Optional[Decimal]
|
|
settle_status: Optional[str]
|
|
clear_settle_time: bool
|
|
|
|
|
|
def normalize_waybill(value: Any) -> str:
|
|
return str(value or "").strip()
|
|
|
|
|
|
def _is_blank(value: Any) -> bool:
|
|
return value is None or (isinstance(value, str) and value.strip() == "")
|
|
|
|
|
|
def parse_optional_amount(value: Any) -> Optional[Decimal]:
|
|
if _is_blank(value):
|
|
return None
|
|
try:
|
|
amount = Decimal(str(value))
|
|
except (InvalidOperation, ValueError):
|
|
raise ValueError("运费金额格式不正确")
|
|
if not amount.is_finite():
|
|
raise ValueError("运费金额格式不正确")
|
|
if amount < 0:
|
|
raise ValueError("运费不能为负数")
|
|
if amount > _MAX_AMOUNT:
|
|
raise ValueError("运费金额超出范围")
|
|
return amount.quantize(_AMOUNT_QUANT, rounding=ROUND_HALF_UP)
|
|
|
|
|
|
def freight_amount_label(transport_mode: Optional[str], amount: Optional[Decimal]) -> str:
|
|
if (transport_mode or "") == SF_TRANSPORT_MODE and amount is None:
|
|
return "月结"
|
|
if amount is None:
|
|
return ""
|
|
return format(Decimal(amount).quantize(_AMOUNT_QUANT, rounding=ROUND_HALF_UP), "f")
|
|
|
|
|
|
def plan_freight_header(
|
|
*,
|
|
transport_mode: str,
|
|
waybill_no: Any = None,
|
|
freight_amount: Any = None,
|
|
) -> FreightHeaderPlan:
|
|
mode = str(transport_mode or "").strip()
|
|
if not mode:
|
|
raise ValueError("请选择运输方式")
|
|
if mode == SF_TRANSPORT_MODE:
|
|
waybill = normalize_waybill(waybill_no)
|
|
if not waybill:
|
|
raise ValueError("请填写顺丰单号")
|
|
if len(waybill) > 64:
|
|
raise ValueError("顺丰单号不能超过 64 个字符")
|
|
return FreightHeaderPlan(mode, waybill, None, SETTLE_UNSETTLED, True)
|
|
if normalize_waybill(waybill_no):
|
|
raise ValueError("该运输方式不能填写顺丰单号")
|
|
amount = parse_optional_amount(freight_amount)
|
|
if amount is None:
|
|
raise ValueError("请填写运费")
|
|
return FreightHeaderPlan(mode, None, amount, None, True)
|
|
|
|
|
|
def resolve_header_for_save(
|
|
*,
|
|
transport_mode: str,
|
|
existing_waybill: Any,
|
|
existing_amount: Any,
|
|
existing_settle_status: Any,
|
|
waybill_supplied: bool,
|
|
waybill_no: Any,
|
|
amount_supplied: bool,
|
|
freight_amount: Any,
|
|
) -> FreightHeaderPlan:
|
|
if (existing_settle_status or "") == SETTLE_SETTLED:
|
|
raise ValueError("已结算的运费不能修改运输方式或顺丰单号")
|
|
if transport_mode == SF_TRANSPORT_MODE:
|
|
waybill = waybill_no if waybill_supplied else existing_waybill
|
|
return plan_freight_header(
|
|
transport_mode=transport_mode, waybill_no=waybill, freight_amount=None
|
|
)
|
|
amount = freight_amount if amount_supplied else existing_amount
|
|
return plan_freight_header(
|
|
transport_mode=transport_mode, waybill_no="", freight_amount=amount
|
|
)
|
|
|
|
|
|
def plan_settle(*, current_amount: Optional[Decimal], submitted_amount: Any, now: datetime):
|
|
amount = current_amount if _is_blank(submitted_amount) else parse_optional_amount(submitted_amount)
|
|
return amount, SETTLE_SETTLED, now
|
|
|
|
|
|
def assert_settle_request(ids: list, submitted_amount: Any) -> None:
|
|
if not ids:
|
|
raise ValueError("请选择运费批次")
|
|
if len(ids) != 1 and not _is_blank(submitted_amount):
|
|
raise ValueError("批量结算不能填写运费")
|
|
|
|
|
|
def plan_fill_amount(*, current_amount: Optional[Decimal], settle_status: Optional[str], submitted_amount: Any) -> Decimal:
|
|
if settle_status != SETTLE_SETTLED or current_amount is not None:
|
|
raise ValueError("只有已结算且仍为月结的运费可以补填金额")
|
|
amount = parse_optional_amount(submitted_amount)
|
|
if amount is None:
|
|
raise ValueError("请填写运费")
|
|
return amount
|
|
|
|
|
|
def assert_freight_mutable(settle_status: Optional[str], action: str) -> None:
|
|
if (settle_status or "") != SETTLE_SETTLED:
|
|
return
|
|
if action == "delete":
|
|
raise ValueError("已结算的运费不能删除")
|
|
if action == "items":
|
|
raise ValueError("已结算的运费不能调整样本")
|
|
raise ValueError("已结算的运费不能修改运输方式或顺丰单号")
|