test_presign_order.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. """预签约订单:意向金状态机与转正约束单测"""
  2. import unittest
  3. from app.services.protocol_transfer_status import can_confirm_receipt, can_submit_receipt
  4. class TestPresignTransferStatus(unittest.TestCase):
  5. def test_presign_transfer_same_status_machine(self):
  6. self.assertTrue(can_submit_receipt("dfj"))
  7. self.assertFalse(can_submit_receipt("jfqrz"))
  8. self.assertFalse(can_submit_receipt("yjf"))
  9. self.assertTrue(can_confirm_receipt("jfqrz"))
  10. self.assertFalse(can_confirm_receipt("dfj"))
  11. def test_can_withdraw_only_jfqrz(self):
  12. from app.services.protocol_transfer_status import can_withdraw_receipt
  13. self.assertTrue(can_withdraw_receipt("jfqrz"))
  14. self.assertFalse(can_withdraw_receipt("dfj"))
  15. self.assertFalse(can_withdraw_receipt("yjf"))
  16. self.assertFalse(can_withdraw_receipt(None))
  17. self.assertFalse(can_withdraw_receipt(""))
  18. class TestFinanceTransferFields(unittest.TestCase):
  19. def test_sample_type_names_helper_exists(self):
  20. from app.crud.presign_order import _sample_type_names
  21. self.assertTrue(callable(_sample_type_names))
  22. class TestConvertRules(unittest.TestCase):
  23. def test_convert_requires_active_status(self):
  24. from app.crud.presign_order import assert_presign_convertible
  25. with self.assertRaises(ValueError):
  26. assert_presign_convertible(order_status="converted")
  27. with self.assertRaises(ValueError):
  28. assert_presign_convertible(order_status="void")
  29. assert_presign_convertible(order_status="active")
  30. assert_presign_convertible(order_status="draft")
  31. class TestConvertStaffMapping(unittest.TestCase):
  32. """转正式:采集专员→采集专员+市场专员;采集经理→地区经理。"""
  33. def test_collector_fills_collector_and_sales(self):
  34. from app.crud.presign_order import resolve_convert_staff_fields
  35. out = resolve_convert_staff_fields(
  36. collector_user_id="c1",
  37. sales_user_id="s1",
  38. region_manager_user_id="m1",
  39. fallback_user_id="u0",
  40. )
  41. self.assertEqual(out["collector_user_id"], "c1")
  42. self.assertEqual(out["sales_user_id"], "c1")
  43. self.assertEqual(out["region_manager_user_id"], "m1")
  44. def test_collector_fallback_to_operator_when_empty(self):
  45. from app.crud.presign_order import resolve_convert_staff_fields
  46. out = resolve_convert_staff_fields(
  47. collector_user_id=None,
  48. sales_user_id="s1",
  49. region_manager_user_id="",
  50. fallback_user_id="u0",
  51. )
  52. self.assertEqual(out["collector_user_id"], "u0")
  53. self.assertEqual(out["sales_user_id"], "u0")
  54. self.assertIsNone(out["region_manager_user_id"])
  55. class TestProtocolDepositAssociation(unittest.TestCase):
  56. """正式协议意向金通过 source_presign_order_id 关联预签,不落快照表。"""
  57. def test_list_deposit_transfers_for_protocol_uses_presign_source(self):
  58. from app.crud.protocol_sample_payment import list_deposit_transfers_for_protocol
  59. # 无来源预签时返回空列表
  60. class _FakeDb:
  61. pass
  62. self.assertEqual(
  63. list_deposit_transfers_for_protocol(
  64. _FakeDb(), source_presign_order_id=None, tenant_no="t1"
  65. ),
  66. [],
  67. )
  68. self.assertEqual(
  69. list_deposit_transfers_for_protocol(
  70. _FakeDb(), source_presign_order_id=" ", tenant_no="t1"
  71. ),
  72. [],
  73. )
  74. class TestPresignWithdrawReceipt(unittest.TestCase):
  75. def test_withdraw_sets_dfj_keeps_finance_remark(self):
  76. from types import SimpleNamespace
  77. from unittest.mock import MagicMock
  78. from app.crud import presign_order as po
  79. t = SimpleNamespace(
  80. id="t1",
  81. pay_status="jfqrz",
  82. finance_remark="keep-me",
  83. finance_confirm_attachments="http://x",
  84. update_by=None,
  85. update_time=None,
  86. )
  87. db = MagicMock()
  88. orig = po._get_transfer_or_raise
  89. orig_update = po._update_transfer_expected_status
  90. po._get_transfer_or_raise = lambda *a, **k: t
  91. def atomic_update(*args, **kwargs):
  92. for column, value in kwargs["values"].items():
  93. setattr(t, column.key, value)
  94. db.commit()
  95. return t
  96. po._update_transfer_expected_status = atomic_update
  97. try:
  98. out = po.withdraw_receipt(
  99. db, transfer_id="t1", tenant_no="zkxw", user_id="u1"
  100. )
  101. self.assertEqual(out.pay_status, "dfj")
  102. self.assertEqual(out.finance_remark, "keep-me")
  103. self.assertEqual(out.finance_confirm_attachments, "http://x")
  104. db.commit.assert_called()
  105. finally:
  106. po._get_transfer_or_raise = orig
  107. po._update_transfer_expected_status = orig_update
  108. def test_withdraw_rejects_non_jfqrz(self):
  109. from types import SimpleNamespace
  110. from unittest.mock import MagicMock
  111. from app.crud import presign_order as po
  112. t = SimpleNamespace(id="t1", pay_status="yjf", finance_remark="x")
  113. db = MagicMock()
  114. orig = po._get_transfer_or_raise
  115. po._get_transfer_or_raise = lambda *a, **k: t
  116. try:
  117. with self.assertRaises(ValueError) as ctx:
  118. po.withdraw_receipt(
  119. db, transfer_id="t1", tenant_no="zkxw", user_id="u1"
  120. )
  121. self.assertIn("撤回", str(ctx.exception))
  122. finally:
  123. po._get_transfer_or_raise = orig
  124. class TestPresignProtocolNoAllocate(unittest.TestCase):
  125. """预签约协议编号:保存发号、忽略客户端改号、空号补发。"""
  126. def test_assert_biz_no_rejects_presign_occupied(self):
  127. from types import SimpleNamespace
  128. from unittest.mock import MagicMock
  129. from app.crud.protocol_donation_convert import assert_biz_no_available
  130. from app.models.models import BizCollectionProtocol, BizPresignOrder
  131. from app.models.research_donation import BizResearchDonation
  132. db = MagicMock()
  133. def query(model):
  134. q = MagicMock()
  135. if model is BizPresignOrder:
  136. q.filter.return_value.all.return_value = [
  137. SimpleNamespace(id="presign-1")
  138. ]
  139. else:
  140. q.filter.return_value.all.return_value = []
  141. return q
  142. db.query.side_effect = query
  143. with self.assertRaises(ValueError) as ctx:
  144. assert_biz_no_available(db, tenant_no="t1", biz_no="XBCC20261012000001")
  145. self.assertIn("协议编号已存在", str(ctx.exception))
  146. # 排除自身预签后可通过
  147. assert_biz_no_available(
  148. db,
  149. tenant_no="t1",
  150. biz_no="XBCC20261012000001",
  151. exclude_presign_order_id="presign-1",
  152. )
  153. # 静默使用导入,避免 lint 误报
  154. _ = (BizCollectionProtocol, BizResearchDonation)
  155. def test_create_allocates_and_ignores_client_protocol_no(self):
  156. from unittest.mock import MagicMock, patch
  157. from app.crud import presign_order as po
  158. db = MagicMock()
  159. with patch.object(
  160. po, "allocate_next_biz_no", return_value="XBCC20261012000009"
  161. ) as alloc, patch.object(po, "replace_sample_types"):
  162. obj = po.create_presign_order(
  163. db,
  164. tenant_no="t1",
  165. user_id="u1",
  166. fields={"protocolNo": "CLIENT-NO", "motherName": "张三"},
  167. commit=False,
  168. )
  169. self.assertEqual(obj.protocol_no, "XBCC20261012000009")
  170. alloc.assert_called_once()
  171. self.assertEqual(alloc.call_args.kwargs.get("kind"), "collection")
  172. def test_update_ignores_client_change_and_backfills_empty(self):
  173. from types import SimpleNamespace
  174. from unittest.mock import MagicMock, patch
  175. from app.crud import presign_order as po
  176. kept = SimpleNamespace(
  177. id="o1",
  178. protocol_no="KEEP-NO",
  179. order_status="active",
  180. pay_mode=None,
  181. contract_amount=None,
  182. sample_collect_time=None,
  183. update_by=None,
  184. update_time=None,
  185. )
  186. empty = SimpleNamespace(
  187. id="o2",
  188. protocol_no=None,
  189. order_status="active",
  190. pay_mode=None,
  191. contract_amount=None,
  192. sample_collect_time=None,
  193. update_by=None,
  194. update_time=None,
  195. )
  196. db = MagicMock()
  197. with patch.object(po, "_get_order_or_raise", return_value=kept):
  198. po.update_presign_order(
  199. db,
  200. order_id="o1",
  201. tenant_no="t1",
  202. user_id="u1",
  203. fields={"protocolNo": "HACK", "motherName": "李四"},
  204. commit=False,
  205. )
  206. self.assertEqual(kept.protocol_no, "KEEP-NO")
  207. self.assertEqual(kept.mother_name, "李四")
  208. with patch.object(po, "_get_order_or_raise", return_value=empty), patch.object(
  209. po, "allocate_next_biz_no", return_value="XBCC20261012000010"
  210. ) as alloc:
  211. po.update_presign_order(
  212. db,
  213. order_id="o2",
  214. tenant_no="t1",
  215. user_id="u1",
  216. fields={"motherName": "王五"},
  217. commit=False,
  218. )
  219. self.assertEqual(empty.protocol_no, "XBCC20261012000010")
  220. alloc.assert_called_once()
  221. if __name__ == "__main__":
  222. unittest.main()