diff --git a/backend/app/api/protocol_sample_payment.py b/backend/app/api/protocol_sample_payment.py index 04bb989f..2641c415 100644 --- a/backend/app/api/protocol_sample_payment.py +++ b/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), diff --git a/backend/app/crud/protocol_sample_payment.py b/backend/app/crud/protocol_sample_payment.py index fbd6d089..478b7dbf 100644 --- a/backend/app/crud/protocol_sample_payment.py +++ b/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]: diff --git a/backend/tests/test_payment_transfer_delete.py b/backend/tests/test_payment_transfer_delete.py new file mode 100644 index 00000000..ad9d15ba --- /dev/null +++ b/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()