test_pl_plan_stop_rules.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """运输计划多段:内部客户地磅默认值与数量平衡校验"""
  2. from decimal import Decimal
  3. from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
  4. from app.services.pl_plan_stop_rules import (
  5. normalize_stop_dibang_fields,
  6. resolve_stop_dibang_defaults,
  7. warn_qty_imbalance,
  8. )
  9. def test_internal_shanxia_load_outbound():
  10. name = "安庆市山峡化工有限公司"
  11. assert name in INTERNAL_CUSTOMER_NAMES
  12. assert resolve_stop_dibang_defaults(name, "load") == (True, "outbound")
  13. def test_internal_shanxia_delivery_inbound():
  14. name = "安庆市山峡化工有限公司"
  15. assert resolve_stop_dibang_defaults(name, "delivery") == (True, "inbound")
  16. def test_internal_yundong_load_outbound():
  17. name = "安徽云栋化工有限公司"
  18. assert resolve_stop_dibang_defaults(name, "load") == (True, "outbound")
  19. def test_internal_yundong_delivery_inbound():
  20. name = "安徽云栋化工有限公司"
  21. assert resolve_stop_dibang_defaults(name, "delivery") == (True, "inbound")
  22. def test_external_customer_no_dibang():
  23. assert resolve_stop_dibang_defaults("外部客户有限公司", "load") == (False, "none")
  24. assert resolve_stop_dibang_defaults("外部客户有限公司", "delivery") == (False, "none")
  25. def test_delivery_defaults_use_delivery_customer():
  26. """送货段地磅默认以本段客户是否为内部客户为准。"""
  27. internal = "安庆市山峡化工有限公司"
  28. assert resolve_stop_dibang_defaults(internal, "delivery") == (True, "inbound")
  29. assert resolve_stop_dibang_defaults("外部客户有限公司", "delivery") == (False, "none")
  30. def test_normalize_stop_dibang_fields_repairs_none_direction():
  31. gen, direction = normalize_stop_dibang_fields(
  32. "安庆市山峡化工有限公司",
  33. "delivery",
  34. 1,
  35. "none",
  36. )
  37. assert gen == 1
  38. assert direction == "inbound"
  39. def test_normalize_respects_explicit_uncheck_for_internal_customer():
  40. """内部客户显式传 0(如可选车皖H21522 取消勾选)时须保留,不得用默认值强制回 1。"""
  41. gen, direction = normalize_stop_dibang_fields(
  42. "安庆市山峡化工有限公司",
  43. "load",
  44. 0,
  45. "none",
  46. )
  47. assert gen == 0
  48. assert direction == "none"
  49. def test_normalize_respects_explicit_uncheck_for_internal_delivery():
  50. gen, direction = normalize_stop_dibang_fields(
  51. "安徽云栋化工有限公司",
  52. "delivery",
  53. 0,
  54. "inbound",
  55. )
  56. assert gen == 0
  57. assert direction == "none"
  58. def test_normalize_forces_off_for_external_even_when_checked():
  59. gen, direction = normalize_stop_dibang_fields(
  60. "外部客户有限公司",
  61. "load",
  62. 1,
  63. "outbound",
  64. )
  65. assert gen == 0
  66. assert direction == "none"
  67. def test_normalize_keeps_explicit_check_for_internal():
  68. gen, direction = normalize_stop_dibang_fields(
  69. "安庆市山峡化工有限公司",
  70. "load",
  71. 1,
  72. "outbound",
  73. )
  74. assert gen == 1
  75. assert direction == "outbound"
  76. def test_external_empty_name_no_dibang():
  77. assert resolve_stop_dibang_defaults(None, "load") == (False, "none")
  78. assert resolve_stop_dibang_defaults(" ", "delivery") == (False, "none")
  79. def test_warn_qty_balanced_returns_empty():
  80. plan = [{"quantity": Decimal("30")}]
  81. load = [{"plan_qty": Decimal("30")}]
  82. delivery = [{"plan_qty": Decimal("30")}]
  83. assert warn_qty_imbalance(plan, load, delivery) == []
  84. def test_warn_qty_uses_plan_qty_on_plan_products():
  85. plan = [{"plan_qty": Decimal("28")}]
  86. load = [{"plan_qty": Decimal("28")}]
  87. delivery = [{"plan_qty": Decimal("28")}]
  88. assert warn_qty_imbalance(plan, load, delivery) == []
  89. def test_warn_qty_load_vs_delivery_imbalance():
  90. plan = [{"quantity": Decimal("28")}]
  91. load = [{"plan_qty": Decimal("30")}]
  92. delivery = [{"plan_qty": Decimal("28")}]
  93. warnings = warn_qty_imbalance(plan, load, delivery)
  94. assert warnings == [
  95. "装货合计(30)与送货合计(28)不一致,请核对",
  96. ]
  97. def test_warn_qty_delivery_vs_load_imbalance():
  98. plan = [{"quantity": Decimal("30")}]
  99. load = [{"plan_qty": Decimal("30")}]
  100. delivery = [{"plan_qty": Decimal("25")}]
  101. warnings = warn_qty_imbalance(plan, load, delivery)
  102. assert warnings == [
  103. "装货合计(30)与送货合计(25)不一致,请核对",
  104. ]
  105. def test_warn_qty_load_delivery_decimal_imbalance():
  106. plan = [{"quantity": Decimal("30")}]
  107. load = [{"plan_qty": Decimal("30")}]
  108. delivery = [{"plan_qty": Decimal("30.31")}]
  109. warnings = warn_qty_imbalance(plan, load, delivery)
  110. assert warnings == [
  111. "装货合计(30)与送货合计(30.31)不一致,请核对",
  112. ]
  113. def test_warn_qty_multi_stop_30_equals_10_15_5_balanced():
  114. """验收场景:计划 30 = 送货 10+15+5,装货 30。"""
  115. plan = [{"quantity": Decimal("30")}]
  116. load = [{"plan_qty": Decimal("30")}]
  117. delivery = [
  118. {"plan_qty": Decimal("10")},
  119. {"plan_qty": Decimal("15")},
  120. {"plan_qty": Decimal("5")},
  121. ]
  122. assert warn_qty_imbalance(plan, load, delivery) == []
  123. def test_warn_qty_sums_multiple_rows_balanced_across_stops():
  124. plan = [{"quantity": Decimal("10")}, {"quantity": Decimal("20")}]
  125. load = [{"plan_qty": Decimal("10")}, {"plan_qty": Decimal("15")}]
  126. delivery = [{"plan_qty": Decimal("25")}]
  127. assert warn_qty_imbalance(plan, load, delivery) == []
  128. def test_warn_qty_sums_multiple_rows_imbalanced_across_stops():
  129. plan = [{"quantity": Decimal("10")}, {"quantity": Decimal("20")}]
  130. load = [{"plan_qty": Decimal("10")}, {"plan_qty": Decimal("15")}]
  131. delivery = [{"plan_qty": Decimal("24")}]
  132. warnings = warn_qty_imbalance(plan, load, delivery)
  133. assert warnings == [
  134. "装货合计(25)与送货合计(24)不一致,请核对",
  135. ]