test_production_cascade_complete.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """生产完成任务/工单状态级联。"""
  2. import unittest
  3. from datetime import datetime
  4. from unittest.mock import ANY, MagicMock, patch
  5. from pydantic import ValidationError
  6. from app.crud import production_task as production_task_svc
  7. from app.crud import production_work_order as production_work_order_svc
  8. from app.schemas.production_work_order import ProductionWorkOrderComplete
  9. class TestProductionWorkOrderCompleteSchema(unittest.TestCase):
  10. def test_requires_conclusion(self):
  11. with self.assertRaises(ValidationError):
  12. ProductionWorkOrderComplete()
  13. def test_accepts_cg_and_remark(self):
  14. body = ProductionWorkOrderComplete(conclusion="cg", remark="ok")
  15. self.assertEqual(body.conclusion, "cg")
  16. self.assertEqual(body.remark, "ok")
  17. class TestForceCompleteTaskSteps(unittest.TestCase):
  18. def test_marks_incomplete_steps_ywc(self):
  19. db = MagicMock()
  20. step_dzx = MagicMock(step_status="dzx", completed_by=None, completed_time=None)
  21. step_jxz = MagicMock(step_status="jxz", completed_by=None, completed_time=None)
  22. q = MagicMock()
  23. db.query.return_value = q
  24. q.filter.return_value = q
  25. q.all.return_value = [step_dzx, step_jxz]
  26. now = datetime(2026, 8, 31, 12, 0, 0)
  27. production_task_svc._force_complete_task_steps(
  28. db, "task-1", "t1", "user-1", now
  29. )
  30. self.assertEqual(step_dzx.step_status, "ywc")
  31. self.assertEqual(step_dzx.completed_by, "user-1")
  32. self.assertEqual(step_dzx.completed_time, now)
  33. self.assertEqual(step_jxz.step_status, "ywc")
  34. db.add.assert_any_call(step_dzx)
  35. db.commit.assert_not_called()
  36. class TestUpdateTaskStatusCascadeSteps(unittest.TestCase):
  37. @patch("app.crud.production_task._consume_task_input_cell_products")
  38. @patch("app.crud.production_work_order.sync_production_work_order_status_from_tasks")
  39. @patch("app.crud.production_task._force_complete_task_steps")
  40. def test_ywc_no_longer_blocks_on_incomplete_steps(
  41. self, mock_force, mock_sync, mock_consume
  42. ):
  43. db = MagicMock()
  44. task = MagicMock(
  45. id="task-1",
  46. tenant_no="t1",
  47. del_flag=0,
  48. task_status="jxz",
  49. work_order_id="wo-1",
  50. started_time=None,
  51. completed_time=None,
  52. conclusion=None,
  53. remark=None,
  54. )
  55. q = MagicMock()
  56. db.query.return_value = q
  57. q.filter.return_value = q
  58. q.first.return_value = task
  59. result = production_task_svc.update_task_status(
  60. db, "task-1", "t1", "user-1", "ywc", conclusion="cg"
  61. )
  62. mock_force.assert_called_once()
  63. self.assertEqual(task.task_status, "ywc")
  64. self.assertEqual(task.conclusion, "cg")
  65. mock_consume.assert_called_once()
  66. mock_sync.assert_called_once()
  67. db.commit.assert_called_once()
  68. self.assertIs(result, task)
  69. class TestCompleteProductionWorkOrderCascade(unittest.TestCase):
  70. @patch("app.crud.pilot_outbound_status_sync.sync_schedule_from_production_work_order")
  71. @patch("app.crud.production_task._consume_task_input_cell_products")
  72. @patch("app.crud.production_task._force_complete_task_steps")
  73. @patch("app.crud.production_work_order.get_production_work_order")
  74. def test_cascades_incomplete_tasks_and_steps(
  75. self, mock_get_wo, mock_force, mock_consume, mock_sync_sched
  76. ):
  77. db = MagicMock()
  78. wo = MagicMock(id="wo-1", work_order_status="jxz", remark=None)
  79. mock_get_wo.return_value = wo
  80. task_a = MagicMock(
  81. id="ta",
  82. task_status="dzx",
  83. completed_time=None,
  84. conclusion=None,
  85. remark=None,
  86. )
  87. q = MagicMock()
  88. db.query.return_value = q
  89. q.filter.return_value = q
  90. q.all.return_value = [task_a]
  91. body = ProductionWorkOrderComplete(conclusion="sb", remark="batch fail")
  92. result = production_work_order_svc.complete_production_work_order(
  93. db, "wo-1", "t1", "user-1", body
  94. )
  95. mock_force.assert_called_once_with(db, "ta", "t1", "user-1", ANY)
  96. self.assertEqual(task_a.task_status, "ywc")
  97. self.assertEqual(task_a.conclusion, "sb")
  98. self.assertEqual(task_a.remark, "batch fail")
  99. mock_consume.assert_called_once()
  100. self.assertEqual(wo.work_order_status, "ywc")
  101. self.assertEqual(wo.remark, "batch fail")
  102. mock_sync_sched.assert_called_once()
  103. db.commit.assert_called_once()
  104. self.assertIs(result, wo)
  105. @patch("app.crud.production_work_order.get_production_work_order")
  106. def test_rejects_invalid_conclusion(self, mock_get_wo):
  107. db = MagicMock()
  108. mock_get_wo.return_value = MagicMock(work_order_status="jxz")
  109. bad = MagicMock(conclusion="xx", remark=None)
  110. with self.assertRaises(ValueError):
  111. production_work_order_svc.complete_production_work_order(
  112. db, "wo-1", "t1", "user-1", bad
  113. )
  114. @patch("app.crud.production_work_order.get_production_work_order")
  115. def test_rejects_already_completed(self, mock_get_wo):
  116. db = MagicMock()
  117. mock_get_wo.return_value = MagicMock(work_order_status="ywc")
  118. with self.assertRaises(ValueError) as ctx:
  119. production_work_order_svc.complete_production_work_order(
  120. db,
  121. "wo-1",
  122. "t1",
  123. "user-1",
  124. ProductionWorkOrderComplete(conclusion="cg"),
  125. )
  126. self.assertIn("已完成", str(ctx.exception))
  127. if __name__ == "__main__":
  128. unittest.main()