sf_express_settlement.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. """顺丰月结抬头与结算规则。不访问数据库。"""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. from datetime import datetime
  5. from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
  6. from typing import Any, Optional
  7. SF_TRANSPORT_MODE = "sfkd"
  8. SETTLE_UNSETTLED = "wjs"
  9. SETTLE_SETTLED = "yjs"
  10. _AMOUNT_QUANT = Decimal("0.01")
  11. _MAX_AMOUNT = Decimal("9999999999999999.99")
  12. @dataclass(frozen=True)
  13. class FreightHeaderPlan:
  14. transport_mode: str
  15. waybill_no: Optional[str]
  16. freight_amount: Optional[Decimal]
  17. settle_status: Optional[str]
  18. clear_settle_time: bool
  19. def normalize_waybill(value: Any) -> str:
  20. return str(value or "").strip()
  21. def _is_blank(value: Any) -> bool:
  22. return value is None or (isinstance(value, str) and value.strip() == "")
  23. def parse_optional_amount(value: Any) -> Optional[Decimal]:
  24. if _is_blank(value):
  25. return None
  26. try:
  27. amount = Decimal(str(value))
  28. except (InvalidOperation, ValueError):
  29. raise ValueError("运费金额格式不正确")
  30. if not amount.is_finite():
  31. raise ValueError("运费金额格式不正确")
  32. if amount < 0:
  33. raise ValueError("运费不能为负数")
  34. if amount > _MAX_AMOUNT:
  35. raise ValueError("运费金额超出范围")
  36. return amount.quantize(_AMOUNT_QUANT, rounding=ROUND_HALF_UP)
  37. def freight_amount_label(transport_mode: Optional[str], amount: Optional[Decimal]) -> str:
  38. if (transport_mode or "") == SF_TRANSPORT_MODE and amount is None:
  39. return "月结"
  40. if amount is None:
  41. return ""
  42. return format(Decimal(amount).quantize(_AMOUNT_QUANT, rounding=ROUND_HALF_UP), "f")
  43. def plan_freight_header(
  44. *,
  45. transport_mode: str,
  46. waybill_no: Any = None,
  47. freight_amount: Any = None,
  48. ) -> FreightHeaderPlan:
  49. mode = str(transport_mode or "").strip()
  50. if not mode:
  51. raise ValueError("请选择运输方式")
  52. if mode == SF_TRANSPORT_MODE:
  53. waybill = normalize_waybill(waybill_no)
  54. if not waybill:
  55. raise ValueError("请填写顺丰单号")
  56. if len(waybill) > 64:
  57. raise ValueError("顺丰单号不能超过 64 个字符")
  58. return FreightHeaderPlan(mode, waybill, None, SETTLE_UNSETTLED, True)
  59. if normalize_waybill(waybill_no):
  60. raise ValueError("该运输方式不能填写顺丰单号")
  61. amount = parse_optional_amount(freight_amount)
  62. if amount is None:
  63. raise ValueError("请填写运费")
  64. return FreightHeaderPlan(mode, None, amount, None, True)
  65. def resolve_header_for_save(
  66. *,
  67. transport_mode: str,
  68. existing_waybill: Any,
  69. existing_amount: Any,
  70. existing_settle_status: Any,
  71. waybill_supplied: bool,
  72. waybill_no: Any,
  73. amount_supplied: bool,
  74. freight_amount: Any,
  75. ) -> FreightHeaderPlan:
  76. if (existing_settle_status or "") == SETTLE_SETTLED:
  77. raise ValueError("已结算的运费不能修改运输方式或顺丰单号")
  78. if transport_mode == SF_TRANSPORT_MODE:
  79. waybill = waybill_no if waybill_supplied else existing_waybill
  80. return plan_freight_header(
  81. transport_mode=transport_mode, waybill_no=waybill, freight_amount=None
  82. )
  83. amount = freight_amount if amount_supplied else existing_amount
  84. return plan_freight_header(
  85. transport_mode=transport_mode, waybill_no="", freight_amount=amount
  86. )
  87. def plan_settle(*, current_amount: Optional[Decimal], submitted_amount: Any, now: datetime):
  88. amount = current_amount if _is_blank(submitted_amount) else parse_optional_amount(submitted_amount)
  89. return amount, SETTLE_SETTLED, now
  90. def assert_settle_request(ids: list, submitted_amount: Any) -> None:
  91. if not ids:
  92. raise ValueError("请选择运费批次")
  93. if len(ids) != 1 and not _is_blank(submitted_amount):
  94. raise ValueError("批量结算不能填写运费")
  95. def plan_fill_amount(*, current_amount: Optional[Decimal], settle_status: Optional[str], submitted_amount: Any) -> Decimal:
  96. if settle_status != SETTLE_SETTLED or current_amount is not None:
  97. raise ValueError("只有已结算且仍为月结的运费可以补填金额")
  98. amount = parse_optional_amount(submitted_amount)
  99. if amount is None:
  100. raise ValueError("请填写运费")
  101. return amount
  102. def assert_freight_mutable(settle_status: Optional[str], action: str) -> None:
  103. if (settle_status or "") != SETTLE_SETTLED:
  104. return
  105. if action == "delete":
  106. raise ValueError("已结算的运费不能删除")
  107. if action == "items":
  108. raise ValueError("已结算的运费不能调整样本")
  109. raise ValueError("已结算的运费不能修改运输方式或顺丰单号")