test_pl_plan_contract_gap_export.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. """合同完善导出:按段次×产品展开,缺口类型跟本行段。"""
  2. from datetime import datetime
  3. from app.api.pl.plan import (
  4. CONTRACT_GAP_EXPORT_COLUMNS,
  5. build_contract_gap_export_rows,
  6. )
  7. from app.schemas.pl.plan import (
  8. PlanContractGapRead,
  9. PlanContractLink,
  10. PlanDeliveryRead,
  11. PlanLoadRead,
  12. PlanStopProductRead,
  13. )
  14. from app.services.pl_internal_customer import INTERNAL_CUSTOMER_NAMES
  15. NOW = datetime(2026, 10, 11, 0, 0, 0)
  16. INTERNAL = next(iter(INTERNAL_CUSTOMER_NAMES))
  17. def _stop_product(name: str, *, product_id: str) -> PlanStopProductRead:
  18. return PlanStopProductRead(
  19. id=f"sp-{product_id}",
  20. planId="p1",
  21. tenantNo="t1",
  22. productId=product_id,
  23. productNo=f"NO-{product_id}",
  24. productName=name,
  25. createTime=NOW,
  26. updateTime=NOW,
  27. )
  28. def _load(*, customer_name: str, contracts: list, products: list) -> PlanLoadRead:
  29. return PlanLoadRead(
  30. id="load1",
  31. planId="p1",
  32. tenantNo="t1",
  33. customerName=customer_name,
  34. products=products,
  35. contracts=contracts,
  36. createTime=NOW,
  37. updateTime=NOW,
  38. )
  39. def _delivery(
  40. *,
  41. stop_id: str,
  42. seq: int,
  43. customer_name: str,
  44. delivery_time: str,
  45. contracts: list,
  46. products: list,
  47. ) -> PlanDeliveryRead:
  48. return PlanDeliveryRead(
  49. id=stop_id,
  50. planId="p1",
  51. tenantNo="t1",
  52. seq=seq,
  53. customerName=customer_name,
  54. deliveryTime=delivery_time,
  55. products=products,
  56. contracts=contracts,
  57. createTime=NOW,
  58. updateTime=NOW,
  59. )
  60. def _gap_item() -> PlanContractGapRead:
  61. return PlanContractGapRead(
  62. id="p1",
  63. planNo="PL001",
  64. planDate="2026-10-11",
  65. planType="direct",
  66. followStatus="arranged",
  67. supplierName="供方甲",
  68. customerName="需方乙",
  69. supplierContractExempt=False,
  70. demanderContractExempt=False,
  71. )
  72. def test_contract_gap_export_columns_match_segment_rows():
  73. assert [column.field for column in CONTRACT_GAP_EXPORT_COLUMNS] == [
  74. "planNo",
  75. "stopSegment",
  76. "planDate",
  77. "deliveryTimes",
  78. "planType",
  79. "followStatus",
  80. "loadCustomer",
  81. "deliveryCustomer",
  82. "productName",
  83. "supplierName",
  84. "customerName",
  85. "gapType",
  86. ]
  87. assert [column.title for column in CONTRACT_GAP_EXPORT_COLUMNS] == [
  88. "计划编号",
  89. "段次",
  90. "计划日期",
  91. "送货时间",
  92. "单据类型",
  93. "跟进状态",
  94. "装货客户",
  95. "送货客户",
  96. "产品名称",
  97. "供方",
  98. "需方",
  99. "缺口类型",
  100. ]
  101. def test_build_contract_gap_export_rows_expands_multi_segment_products():
  102. """装货缺合同 + 两送货段(一段有合同、一段两产品缺合同)展开为 4 行。"""
  103. load = _load(
  104. customer_name="外部装货客户",
  105. contracts=[],
  106. products=[_stop_product("甲醇", product_id="prod-load")],
  107. )
  108. delivery_ok = _delivery(
  109. stop_id="d1",
  110. seq=1,
  111. customer_name="外部送货客户A",
  112. delivery_time="2026-10-12",
  113. contracts=[PlanContractLink(contractId="c1", contractNo="HT001")],
  114. products=[_stop_product("甲醇", product_id="prod-d1")],
  115. )
  116. delivery_gap = _delivery(
  117. stop_id="d2",
  118. seq=2,
  119. customer_name="外部送货客户B",
  120. delivery_time="2026-10-13",
  121. contracts=[],
  122. products=[
  123. _stop_product("乙醇", product_id="prod-d2a"),
  124. _stop_product("丙酮", product_id="prod-d2b"),
  125. ],
  126. )
  127. rows = build_contract_gap_export_rows(
  128. [_gap_item()],
  129. {"p1": (load, [delivery_ok, delivery_gap])},
  130. )
  131. assert len(rows) == 4
  132. assert [row["stopSegment"] for row in rows] == ["装货", "送货 #1", "送货 #2", "送货 #2"]
  133. assert [row["productName"] for row in rows] == ["甲醇", "甲醇", "乙醇", "丙酮"]
  134. assert [row["gapType"] for row in rows] == ["缺供方合同", "", "缺需方合同", "缺需方合同"]
  135. load_row = rows[0]
  136. assert load_row["loadCustomer"] == "外部装货客户"
  137. assert load_row["deliveryCustomer"] == ""
  138. assert load_row["deliveryTimes"] == ""
  139. ok_row = rows[1]
  140. assert ok_row["loadCustomer"] == ""
  141. assert ok_row["deliveryCustomer"] == "外部送货客户A"
  142. assert ok_row["deliveryTimes"] == "2026-10-12"
  143. gap_row = rows[3]
  144. assert gap_row["deliveryCustomer"] == "外部送货客户B"
  145. assert gap_row["deliveryTimes"] == "2026-10-13"
  146. assert gap_row["loadCustomer"] == ""
  147. for row in rows:
  148. assert row["planNo"] == "PL001"
  149. assert row["planDate"] == "2026-10-11"
  150. assert row["planType"] == "直销单"
  151. assert row["followStatus"] == "已安排"
  152. assert row["supplierName"] == "供方甲"
  153. assert row["customerName"] == "需方乙"
  154. def test_build_contract_gap_export_rows_internal_side_has_empty_gap_type():
  155. """内置客户免合同:装货行缺口为空,外送段仍标缺需方合同。"""
  156. load = _load(
  157. customer_name=INTERNAL,
  158. contracts=[],
  159. products=[_stop_product("甲醇", product_id="prod-load")],
  160. )
  161. delivery = _delivery(
  162. stop_id="d1",
  163. seq=1,
  164. customer_name="外部送货客户",
  165. delivery_time="2026-10-12T08:30:00",
  166. contracts=[],
  167. products=[_stop_product("甲醇", product_id="prod-d1")],
  168. )
  169. rows = build_contract_gap_export_rows(
  170. [_gap_item()],
  171. {"p1": (load, [delivery])},
  172. )
  173. assert [row["stopSegment"] for row in rows] == ["装货", "送货 #1"]
  174. assert [row["gapType"] for row in rows] == ["", "缺需方合同"]
  175. assert rows[1]["deliveryTimes"] == "2026-10-12"