Browse Source

feat(backend): allow soft-delete of dfj payment transfers

Supports 缴费明细 list-mode row delete for draft transfers.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 weeks ago
parent
commit
628a20dbbd

+ 24 - 0
backend/app/api/protocol_sample_payment.py

@@ -467,6 +467,30 @@ async def withdraw_payment_transfer(
         return error_response(400, str(e))
 
 
+@router.delete("/payment-transfers/{transfer_id}")
+async def delete_payment_transfer(
+    transfer_id: str,
+    user: dict = Depends(require_admin_or_super_admin),
+    db: Session = Depends(get_db),
+):
+    try:
+        tenant_no = _get_tenant_no(db, user)
+        user_id = _get_current_user_id(user)
+        obj = psp_svc._get_transfer_or_raise(
+            db, transfer_id=transfer_id, tenant_no=tenant_no
+        )
+        if not _protocol_accessible(db, obj.collection_protocol_id, tenant_no, user):
+            return error_response(403, "无权操作该协议")
+        psp_svc.soft_delete_payment_transfer(
+            db, transfer_id=transfer_id, tenant_no=tenant_no, user_id=user_id
+        )
+        return success_response(None, message="删除成功")
+    except PermissionError as e:
+        return error_response(403, str(e))
+    except ValueError as e:
+        return error_response(400, str(e))
+
+
 @router.get("/payment-transfers")
 async def list_payment_transfers(
     page: int = Query(1, ge=1),

+ 17 - 0
backend/app/crud/protocol_sample_payment.py

@@ -901,6 +901,23 @@ def withdraw_receipt(
     return t
 
 
+def soft_delete_payment_transfer(
+    db: Session,
+    *,
+    transfer_id: str,
+    tenant_no: str,
+    user_id: Optional[str],
+) -> None:
+    t = _get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
+    if t.pay_status != "dfj":
+        raise ValueError("仅待缴费(已缴费草稿)转账笔可删除")
+    t.del_flag = 1
+    t.update_by = user_id
+    t.update_time = _now()
+    db.add(t)
+    db.commit()
+
+
 def _transfer_to_dict(
     db: Session, t: BizProtocolPaymentTransfer, tenant_no: str
 ) -> Dict[str, Any]:

+ 55 - 0
backend/tests/test_payment_transfer_delete.py

@@ -0,0 +1,55 @@
+"""软删除待缴费(dfj)转账笔"""
+
+import unittest
+from types import SimpleNamespace
+from unittest.mock import MagicMock, patch
+
+from app.crud import protocol_sample_payment as psp
+
+
+class TestSoftDeletePaymentTransfer(unittest.TestCase):
+    def test_function_exists(self):
+        self.assertTrue(callable(getattr(psp, "soft_delete_payment_transfer", None)))
+
+    def test_soft_delete_dfj_sets_del_flag_and_commits(self):
+        t = SimpleNamespace(
+            id="t1",
+            pay_status="dfj",
+            del_flag=0,
+            update_by=None,
+            update_time=None,
+        )
+        db = MagicMock()
+        orig = psp._get_transfer_or_raise
+        psp._get_transfer_or_raise = lambda *a, **k: t
+        try:
+            with patch.object(psp, "_now", return_value="2026-10-10T12:00:00"):
+                psp.soft_delete_payment_transfer(
+                    db, transfer_id="t1", tenant_no="zkxw", user_id="u1"
+                )
+            self.assertEqual(t.del_flag, 1)
+            self.assertEqual(t.update_by, "u1")
+            self.assertEqual(t.update_time, "2026-10-10T12:00:00")
+            db.add.assert_called_once_with(t)
+            db.commit.assert_called_once()
+        finally:
+            psp._get_transfer_or_raise = orig
+
+    def test_soft_delete_rejects_non_dfj(self):
+        t = SimpleNamespace(id="t1", pay_status="jfqrz", del_flag=0)
+        db = MagicMock()
+        orig = psp._get_transfer_or_raise
+        psp._get_transfer_or_raise = lambda *a, **k: t
+        try:
+            with self.assertRaises(ValueError) as ctx:
+                psp.soft_delete_payment_transfer(
+                    db, transfer_id="t1", tenant_no="zkxw", user_id="u1"
+                )
+            self.assertIn("删除", str(ctx.exception))
+            db.commit.assert_not_called()
+        finally:
+            psp._get_transfer_or_raise = orig
+
+
+if __name__ == "__main__":
+    unittest.main()