|
|
@@ -0,0 +1,127 @@
|
|
|
+from datetime import datetime
|
|
|
+from decimal import Decimal
|
|
|
+from types import SimpleNamespace
|
|
|
+from unittest.mock import MagicMock
|
|
|
+
|
|
|
+import pytest
|
|
|
+
|
|
|
+from app.services.pl_io_order_service import apply_site_feedback
|
|
|
+
|
|
|
+
|
|
|
+def _header(**kwargs):
|
|
|
+ defaults = dict(
|
|
|
+ status="in_progress",
|
|
|
+ site_feedback_remark=None,
|
|
|
+ site_feedback_images=None,
|
|
|
+ site_feedback_time=None,
|
|
|
+ site_first_submitted_at=None,
|
|
|
+ update_by=None,
|
|
|
+ )
|
|
|
+ defaults.update(kwargs)
|
|
|
+ return SimpleNamespace(**defaults)
|
|
|
+
|
|
|
+
|
|
|
+def _item(item_id="item-1"):
|
|
|
+ return SimpleNamespace(
|
|
|
+ id=item_id,
|
|
|
+ actual_qty=None,
|
|
|
+ qty_source=None,
|
|
|
+ update_by=None,
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_sets_status_completed():
|
|
|
+ header = _header()
|
|
|
+ item = _item()
|
|
|
+ db = MagicMock()
|
|
|
+ apply_site_feedback(
|
|
|
+ db,
|
|
|
+ header=header,
|
|
|
+ items=[item],
|
|
|
+ item_qty={"item-1": Decimal("12.5")},
|
|
|
+ remark="ok",
|
|
|
+ images=["a.jpg"],
|
|
|
+ status="completed",
|
|
|
+ )
|
|
|
+ assert header.status == "completed"
|
|
|
+ assert item.actual_qty == Decimal("12.5")
|
|
|
+ assert header.site_first_submitted_at is not None
|
|
|
+ assert header.site_feedback_remark == "ok"
|
|
|
+ db.flush.assert_called_once()
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_sets_status_voided():
|
|
|
+ header = _header()
|
|
|
+ apply_site_feedback(
|
|
|
+ MagicMock(),
|
|
|
+ header=header,
|
|
|
+ items=[_item()],
|
|
|
+ item_qty={"item-1": Decimal("1")},
|
|
|
+ remark=None,
|
|
|
+ images=[],
|
|
|
+ status="voided",
|
|
|
+ )
|
|
|
+ assert header.status == "voided"
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_keeps_in_progress_when_requested():
|
|
|
+ """首次提交选进行中时,不得隐式改成已完成。"""
|
|
|
+ header = _header(status="in_progress")
|
|
|
+ apply_site_feedback(
|
|
|
+ MagicMock(),
|
|
|
+ header=header,
|
|
|
+ items=[_item()],
|
|
|
+ item_qty={"item-1": Decimal("1")},
|
|
|
+ remark=None,
|
|
|
+ images=[],
|
|
|
+ status="in_progress",
|
|
|
+ )
|
|
|
+ assert header.status == "in_progress"
|
|
|
+ assert header.site_first_submitted_at is not None
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_rejects_invalid_status():
|
|
|
+ header = _header()
|
|
|
+ with pytest.raises(ValueError, match="无效状态"):
|
|
|
+ apply_site_feedback(
|
|
|
+ MagicMock(),
|
|
|
+ header=header,
|
|
|
+ items=[_item()],
|
|
|
+ item_qty={},
|
|
|
+ remark=None,
|
|
|
+ images=[],
|
|
|
+ status="done",
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_rejects_already_voided():
|
|
|
+ header = _header(status="voided")
|
|
|
+ with pytest.raises(ValueError, match="单据已作废"):
|
|
|
+ apply_site_feedback(
|
|
|
+ MagicMock(),
|
|
|
+ header=header,
|
|
|
+ items=[_item()],
|
|
|
+ item_qty={},
|
|
|
+ remark=None,
|
|
|
+ images=[],
|
|
|
+ status="completed",
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_site_feedback_does_not_reset_first_submitted_at():
|
|
|
+ first = datetime(2026, 1, 1, 12, 0, 0)
|
|
|
+ header = _header(
|
|
|
+ status="completed",
|
|
|
+ site_first_submitted_at=first,
|
|
|
+ )
|
|
|
+ apply_site_feedback(
|
|
|
+ MagicMock(),
|
|
|
+ header=header,
|
|
|
+ items=[_item()],
|
|
|
+ item_qty={"item-1": Decimal("2")},
|
|
|
+ remark="二改",
|
|
|
+ images=[],
|
|
|
+ status="in_progress",
|
|
|
+ )
|
|
|
+ assert header.status == "in_progress"
|
|
|
+ assert header.site_first_submitted_at == first
|