feat(server): 申请单支持草稿保存并统一删除权限口径
- user_agent_application 新增草稿分支:识别'保存草稿/存草稿/先保存'等意图,复用可编辑记录更新或建草稿,提交前单据重叠仍拦截 - 草稿态返回单号与待提交提示,submit 仅在确认提交分支触发,避免草稿进入审批流 - reimbursements 删除接口文案与判定统一为系统管理员可删、申请人删自有草稿/退回单,申请单判定改用 is_application_claim_no - 更新财务规则表与 reimbursement 端点测试
This commit is contained in:
@@ -20,7 +20,6 @@ from app.models.risk_observation import RiskObservation, RiskObservationFeedback
|
||||
from app.models.role import Role
|
||||
from app.schemas.ocr import OcrRecognizeBatchRead, OcrRecognizeDocumentRead
|
||||
from app.services.expense_claim_attachment_storage import ExpenseClaimAttachmentStorage
|
||||
from app.services.expense_claims import ExpenseClaimService
|
||||
from app.services.ocr import OcrService
|
||||
|
||||
|
||||
@@ -814,6 +813,37 @@ def test_claim_delete_allows_admin_and_cleans_risk_observations(monkeypatch, tmp
|
||||
assert db.get(RiskObservationFeedback, "risk-observation-feedback-delete-1") is None
|
||||
|
||||
|
||||
def test_claim_delete_allows_applicant_to_delete_own_draft(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(ExpenseClaimAttachmentStorage, "root", lambda self: tmp_path)
|
||||
|
||||
client, session_factory = build_client()
|
||||
with session_factory() as db:
|
||||
claim, _ = seed_claim(db)
|
||||
claim.claim_no = "AP-20260620-DRAFT"
|
||||
claim.expense_type = "travel_application"
|
||||
claim_id = claim.id
|
||||
db.commit()
|
||||
|
||||
response = client.delete(
|
||||
f"/api/v1/reimbursements/claims/{claim_id}",
|
||||
headers={
|
||||
"x-auth-username": "zhangsan@example.com",
|
||||
"x-auth-name": "张三",
|
||||
"x-auth-employee-no": "E10001",
|
||||
"x-auth-role-codes": "user",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["claim_id"] == claim_id
|
||||
assert payload["status"] == "deleted"
|
||||
assert "申请单已删除" in payload["message"]
|
||||
|
||||
with session_factory() as db:
|
||||
assert db.get(ExpenseClaim, claim_id) is None
|
||||
|
||||
|
||||
def test_claim_delete_allows_legacy_superadmin_without_is_admin_header(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(ExpenseClaimAttachmentStorage, "root", lambda self: tmp_path)
|
||||
|
||||
@@ -859,7 +889,19 @@ def test_application_preview_action_submits_without_orchestrator_run(monkeypatch
|
||||
"source": "user_message",
|
||||
"user_id": "zhangsan@example.com",
|
||||
"conversation_id": "conversation-fast-submit",
|
||||
"message": "差旅费用申请提交审批\n申请类型:差旅费用申请\n申请时间:2026-07-01 至 2026-07-03\n地点:北京\n事由:项目实施\n天数:3天\n出行方式:火车\n申请金额:1000元\n直接提交",
|
||||
"message": "\n".join(
|
||||
[
|
||||
"差旅费用申请提交审批",
|
||||
"申请类型:差旅费用申请",
|
||||
"申请时间:2026-07-01 至 2026-07-03",
|
||||
"地点:北京",
|
||||
"事由:项目实施",
|
||||
"天数:3天",
|
||||
"出行方式:火车",
|
||||
"申请金额:1000元",
|
||||
"直接提交",
|
||||
]
|
||||
),
|
||||
"context_json": {
|
||||
"session_type": "application",
|
||||
"entry_source": "workbench_ai_inline",
|
||||
@@ -899,3 +941,81 @@ def test_application_preview_action_submits_without_orchestrator_run(monkeypatch
|
||||
assert claim is not None
|
||||
assert claim.status == "submitted"
|
||||
assert claim.employee_name == "张三"
|
||||
|
||||
|
||||
def test_application_preview_action_saves_draft_with_detail_reference(monkeypatch, tmp_path) -> None:
|
||||
monkeypatch.setattr(ExpenseClaimAttachmentStorage, "root", lambda self: tmp_path)
|
||||
|
||||
client, session_factory = build_client()
|
||||
with session_factory() as db:
|
||||
seed_claim(db)
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/reimbursements/application-preview-action",
|
||||
headers={
|
||||
"x-auth-username": "zhangsan@example.com",
|
||||
"x-auth-name": "Zhang San",
|
||||
"x-auth-employee-no": "E10001",
|
||||
"x-auth-role-codes": "user",
|
||||
},
|
||||
json={
|
||||
"source": "user_message",
|
||||
"user_id": "zhangsan@example.com",
|
||||
"conversation_id": "conversation-fast-save",
|
||||
"message": "\n".join(
|
||||
[
|
||||
"费用申请保存草稿",
|
||||
"申请类型:差旅费用申请",
|
||||
"申请时间:2026-07-04 至 2026-07-05",
|
||||
"地点:上海",
|
||||
"事由:项目验收",
|
||||
"天数:2天",
|
||||
"出行方式:火车",
|
||||
"申请金额:800元",
|
||||
"保存草稿",
|
||||
]
|
||||
),
|
||||
"context_json": {
|
||||
"session_type": "application",
|
||||
"entry_source": "workbench_ai_inline",
|
||||
"document_type": "expense_application",
|
||||
"application_stage": "expense_application",
|
||||
"application_action": "save_draft",
|
||||
"application_save_mode": True,
|
||||
"application_preview": {
|
||||
"fields": {
|
||||
"applicationType": "差旅费用申请",
|
||||
"time": "2026-07-04 至 2026-07-05",
|
||||
"location": "上海",
|
||||
"reason": "项目验收",
|
||||
"days": "2天",
|
||||
"transportMode": "火车",
|
||||
"amount": "800元",
|
||||
"applicant": "张三",
|
||||
"department": "市场部",
|
||||
"position": "招商主管",
|
||||
"grade": "P4",
|
||||
"managerName": "李总",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["status"] == "succeeded"
|
||||
draft_payload = payload["result"]["draft_payload"]
|
||||
assert draft_payload["draft_type"] == "expense_application"
|
||||
assert draft_payload["status"] == "draft"
|
||||
assert draft_payload["approval_stage"] == "待提交"
|
||||
assert draft_payload["claim_id"]
|
||||
assert draft_payload["claim_no"].startswith("AP-")
|
||||
|
||||
with session_factory() as db:
|
||||
claim = db.get(ExpenseClaim, draft_payload["claim_id"])
|
||||
assert claim is not None
|
||||
assert claim.status == "draft"
|
||||
assert claim.approval_stage == "待提交"
|
||||
assert claim.submitted_at is None
|
||||
assert claim.employee_name == "张三"
|
||||
|
||||
Reference in New Issue
Block a user