From 8158716e239718f6bfac240713da61314d7f99b5 Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Sat, 20 Jun 2026 22:04:31 +0800 Subject: [PATCH] =?UTF-8?q?test(server):=20=E9=80=82=E9=85=8D=20A/R/D=20?= =?UTF-8?q?=E7=B4=A7=E5=87=91=E5=8D=95=E5=8F=B7=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - approval_routing/service/user_agent 测试中报销单查询统一兼容 RE- 旧格式与 R+8 新格式,申请单单号断言改为短格式 - generate_claim_no 用例重命名为短前缀校验,正则改为 R[A-HJ-NP-Z2-9]{8} - 同步更新差旅/交通/通信等财务规则表 --- .../rules/finance-rules/交通工具等级标准.xlsx | Bin 6071 -> 6072 bytes .../rules/finance-rules/交通费用预估表.xlsx | Bin 7196 -> 7197 bytes .../finance-rules/公司通信费报销规则.xlsx | Bin 5933 -> 5933 bytes server/rules/finance-rules/出差补助标准.xlsx | Bin 5930 -> 5931 bytes .../rules/finance-rules/地区淡旺季映射表.xlsx | Bin 11426 -> 11427 bytes .../rules/finance-rules/差旅职级映射表.xlsx | Bin 5782 -> 5783 bytes .../test_expense_claim_approval_routing.py | 13 ++++- server/tests/test_expense_claim_service.py | 53 ++++++++++-------- server/tests/test_user_agent_service.py | 26 ++++++--- 9 files changed, 59 insertions(+), 33 deletions(-) diff --git a/server/rules/finance-rules/交通工具等级标准.xlsx b/server/rules/finance-rules/交通工具等级标准.xlsx index 2b094adf28985260d3baea395c27416d8fcc19ac..c18fb3e60ceb2b2c73eaad8f4c9272d4a469f548 100644 GIT binary patch delta 533 zcmdn4zeArVz?+#xgn@y9gF!ZLBF{li*}N+;ixxd8dp+@*W__IlYv45Q4q*=uAEv@? znf%omw=@j1zQ4~reXa4qw&Xu{bvfExdynW{D1KqXx=wQ)+x0Dhi{iU&H&15}{Qktp z$9<9P;au@K4?jwlJ>adIA?r9XRn#IWnOAy7OJ{t~qZM0rX#7pjNNaRm&?d80M$y06 zVzbY|qxI6yQWV=G^h4_Jf6t6RcV|Z+U%r38{^w7gciqf%CtWzT=!iqx?gh)c|0HIJ z{LxtT_|1E!n^!FyKOMXrHD}WGPw(nJE0jzW4V@I~IWhNBP~6=UuWx@p`E~Vwoszk0 zw|`|lHrYY@$LIWC+{k{v5;HSqjww{kfASuoHdi##N`w~h8n+qylXAcsNakX3{r3-85kNEjfE%s3Cm1Q5a$8u T$`hAil$hKtuEv%k3Q`6DZxO<$ delta 541 zcmdm?zg?dvz?+#xgn@y9gCWXvBF{n2DAOx36S+fvzM6PVvwlmf+@f{UedjnhB?)yz zPpOFB^31t2aCg0b^fo>g^S+P2e{5W9qQ&u4+0I?wVA8=YQaR5j1tgyBb8%!)DRt2f zwiC+Z`ED2G`Pf)a!J~8%d&{KHQaY2aOj7YVq@&|j7MQhF50 z`eE&jn6!G)fc3{Xo6a#_e=}=O*zC9O8n>$IzwWsnd9r+uSozW&>+h*w@BcUZLRId! zXY$8ZFUWmxegD?^$X>q^Gc#t6vH%+c!yI)624P@;ROIMy{>P}r#uR0`S&Ji|6-3YG zHDU(Rlg|r02XS`@wlada_QH=rTon-?Ft4YW(`~uC+j;els#KNWqO{U}#`85}xcYEW?LpRKBsS`Gf!V@e6TI~k6m4kHrL)GdKZdc z*s!kCT*r2OOW>mTZrjb%83eyS@$qqABzrhle9pst@J0PD~ZGNJ{3Fp3%}7 z-}7k2mK_>@(=*Z;Y?V>;FSe-P>~rvFz4Wsb#r6pOko(^=?7q zljmJGGu=rSPAxj((6)QQ^6o#086tl)Ry}_6p6TXQ3&&3fFGtOpbp6x2y3YzF6GcNO zg?diR{S*{;_r&Yl-%oyB{a>eK?%M5NS&vP2(Ejl`{}(rMh+K)888b&&VDo!M3pOU% zyv>>%*Em5mqmU6Zke+NR`W(cS6KiG!aTkj}25~zie8Ajmk~W<1@SH3yWhM{eflXv% zV3?!Mz#t3^Wf*8+ygRu-N(ro{hh>iOB2ET|8g~W;377&P-OwmEd83p%$gW#bG7;Yd z85k;Z^ovU>b5e`-DspoIycwB9m|^jGC1$((>#hSpePT=u4BW`(Flvc1FvJ(7<`nDe zfk;%1ll`P+7=IQOVOkN?a33l4-Hm4S8V8Xc0jPCr}$seTE*iK4<>;V86 C3(|=I delta 577 zcmbPhF~@=@z?+#xgn@y9gCWXvBF{n2DAOx36S+fvzM6PVvwlmf+@f{UedjnhB?)yz zPpOFB^31t2aCg0b^fo>g^S+P2e{5W9qQ&u4+0I?wVA8=YQaR5j1tgyBb8%!)DRt2f zwiC+Z`ED2G`Pf)a!J~8%d&{KHQaY2aOj7YVq@&|j7MQhF50 z`eE&jn6!G)fc3{Xo6a#_e=}=O*zC9O8n>$IzwWsnd9r+uSozW&>+h*w@BcUZLRId! zXY$8ZFUWmxegD?^$X>q^Gc#t6vH%+c!yI)624P@;ROIMy{>P}r#uR0`S&QQuCx~Ve zGGYeOlg&h*gShfy&5R)K67k0%Zl{C~n0sB)1`&*tWu(mHL4k^HKF|spfWsdP8P6mb=cLoLtm;xZ(&?w6>*@{DI@>wY!kcGFUWEj6r{vjn}4brvU{dLy? zpeivY1_o|qU5uKd3=Hu_sX4{^dLS~un~_O`85Y}<{iS6Xg(l}n%YlsS0dl5KUMZ~! hcI@mnrxs~O28Qd*kidYM+Q3*d`J=QN+X+dK2mn!4#%KTl diff --git a/server/rules/finance-rules/公司通信费报销规则.xlsx b/server/rules/finance-rules/公司通信费报销规则.xlsx index 3e0d9e55abbaedadb975172ac8fd4d41838aa06e..f7aeef77625016539fa4ccd2501563a54607b78b 100644 GIT binary patch delta 408 zcmZ3hw^olQz?+#xgn@y9gFzv0BF{k%g}f^<^*N6|PP{5#@0)$tfT!)h*Gb!nlY0*M z%oyL%(gI_mvmccFC`y-OPBHGdY_v2=%&Px@k) z{eFFC;+R})CIuQwb6U4mi}yVGD9QIgbl#Q51pE>mtooef!rtPlwnB^oAs`%UK~2f~$@nK5&e1=tuEHh*F?Wn)sv+pNj)oCQP+ z@ftA$>B(*a&p}*$!DdDfcZ<+t5O=1q519L0#0DOUleI<7K}u3Y?LpL1Q5O*PTGSFm VsfmH*;>D~%yg6cSAnJ*jCjevAse=Fj delta 408 zcmZ3hw^olQz?+#xgn@y9gMle`BF{k%rrawr&!3jdPrNE$f6(u+0Z&_e>Z`q_x19I` zr=M2yQ&2SIJfd5)M>p!STW8?z?aOte*_)(~|M>ssW?0r2q1_JW7k(DG5fsC_E_1QV ze!spmaZD~YlP()dcb4&dk1d#WJl&$adIA?r9XRn#IWnOAy7OJ{t~qZM0rX#7pjNNaRm&?d80M$y06 zVzbY|qxI6yQWV=G^h4_Jf6t6RcV|Z+U%r38{^w7gciqf%CtWzT=!iqx?gh)c|0HIJ z{LxtT_|1E!n^!FyKOMXrHD}WGPw(nJE0jzW4V@I~IWhNBP~6=UuWx@p`E~Vwoszk0 zw|`|lHrYY@$LIWC+{k{v5;HSqjr?;pL5kHx(29ds1q$`tDd=Ba8 zxRnKFZPhsJ{wA$4tU)%aWXggQ+&k(wwp?6hFa0b)%)Lj)~Ds&S<$e4`SIzM zKhNxNxultryLf7Vpz6;Xe&Vg4FPMHTUNm6+G0vuQjMv}H+7mYW?YqXUs`{^c zu1B6M-y>GObjSL8>eu`K&Aw2T`|X+hvDFK5A6(zRbv|-{T#1<(Ge=o~ZSzM)b2g?Z z)6H5OZ&*OI1g{Y@ke=)#@EpW76|80iaW@G)263kg`+&JmMQk|X;W=4L)Jz`4gBZ!c zFh`w%K^PdyFwnqwXL7x$5?IZWH=EY&U}0d`Bf!8Q0aE~^8yaON9}ras+4WLX#)g}h zfuSNtzqq6_C$(6wA~z?%n~_O`85W;cVz#@#?m7U}C&t9Uz>RDUqy1z@F)5Hav0^fe s8k4KU1&u9)5C)FKT`1J{|+UD`GIznB`^HIPLN00adIA?r9XRn#IWnOAy7OJ{t~qZM0rX#7pjNNaRm&?d80M$y06 zVzbY|qxI6yQWV=G^h4_Jf6t6RcV|Z+U%r38{^w7gciqf%CtWzT=!iqx?gh)c|0HIJ z{LxtT_|1E!n^!FyKOMXrHD}WGPw(nJE0jzW4V@I~IWhNBP~6=UuWx@p`E~Vwoszk0 zw|`|lHrYY@$LIWC+{k{v5;HSqjZ*Ig9ygS3$@HZYHn)DFy5W~O-l*Hktq$TO06Eo@Yqe!SoY~qkR_j$67~+dkbBgu#KxBY7Ba;X-ERL?kEKATl8_CGP tV9mt9zz=jM3^Xv#nEX*&4rGd;4#<2x9Ze7?63AhmT&APO=C1`(3jh}DypjL_ delta 577 zcmZ1+xhRq+z?+#xgn@y9gCWXvBF{n2DAOx36S+fvzM6PVvwlmf+@f{UedjnhB?)yz zPpOFB^31t2aCg0b^fo>g^S+P2e{5W9qQ&u4+0I?wVA8=YQaR5j1tgyBb8%!)DRt2f zwiC+Z`ED2G`Pf)a!J~8%d&{KHQaY2aOj7YVq@&|j7MQhF50 z`eE&jn6!G)fc3{Xo6a#_e=}=O*zC9O8n>$IzwWsnd9r+uSozW&>+h*w@BcUZLRId! zXY$8ZFUWmxegD?^$X>q^Gc#t6vH%+c!yI)624P@;ROIMy{>P}r#uR0`S&JiD3PiUn z88HLt$-7mbgSbo7S{OlGEse(@F2AM^m>a8Qg9ygSi?qx@YHn%CFy5K`T}uhXk=B-B zlI56e#UaHP;LXS+!VHUn$%)!Bj8&8Cfbu|PbF^jZ*Qqiv#22OJ6zl7ONL1xlVwNRn zo{eN=V6bLlVBiP38U`8|r>lYeP@Z3uos^%StyhtogRa^5_2XS@fjWKz4UmHA1kw$R kvKnB`@kNu(breBPiPDi_V$z%}uPHjYS4V)&PYa|R0Ko0Y8~^|S diff --git a/server/rules/finance-rules/差旅职级映射表.xlsx b/server/rules/finance-rules/差旅职级映射表.xlsx index ec76db76a69ab4475e1a9726c07f7de5515b5a77..210a41a922a0f7acad210349113d7a9e10ffcdbf 100644 GIT binary patch delta 535 zcmbQHJ6)G2z?+#xgn@y9gF!ZLBF{li*}N+;ixxd8dp+@*W__IlYv45Q4q*=uAEv@? znf%omw=@j1zQ4~reXa4qw&Xu{bvfExdynW{D1KqXx=wQ)+x0Dhi{iU&H&15}{Qktp z$9<9P;au@K4?jwlJ>adIA?r9XRn#IWnOAy7OJ{t~qZM0rX#7pjNNaRm&?d80M$y06 zVzbY|qxI6yQWV=G^h4_Jf6t6RcV|Z+U%r38{^w7gciqf%CtWzT=!iqx?gh)c|0HIJ z{LxtT_|1E!n^!FyKOMXrHD}WGPw(nJE0jzW4V@I~IWhNBP~6=UuWx@p`E~Vwoszk0 zw|`|lHrYY@$LIWC+{k{v5;HSqjCs$s75fgSfK<8W}-cS)s=uE|ahim>VEsg9ygSQ$@@`YR-ztFy5W~N<<07;S-f% zl$)$AsxA)-?kDaMTAa)b42`@D3_t`24U9#T3q@r>8v216mQ7wGDh1-45S6j-7XUd> zzdXMvJ1IXuTdyKFC%~JLNrV{|msetpUq9Zp7HHUSMg|5cxSg^S+P2e{5W9qQ&u4+0I?wVA8=YQaR5j1tgyBb8%!)DRt2f zwiC+Z`ED2G`Pf)a!J~8%d&{KHQaY2aOj7YVq@&|j7MQhF50 z`eE&jn6!G)fc3{Xo6a#_e=}=O*zC9O8n>$IzwWsnd9r+uSozW&>+h*w@BcUZLRId! zXY$8ZFUWmxegD?^$X>q^Gc#t6vH%+c!yI)624P@;ROIMy{>P}r#uR0`S&Jix1w_~J z7%>Cs$(#6}gSfK=8W}-cIibfOF0-%?m>Vc!g9ygS(?rZbYR-wsFy5K`T0{xN;TM%* zl%1?2s%{Gk?kDaMTAa)b42`@D3_t`24UC06V0RRkROX}>>s93D1b8zti7> Session: return session_factory() +def reimbursement_claim_query(db: Session): + return db.query(ExpenseClaim).filter( + or_( + ExpenseClaim.claim_no.like("RE-%"), + ExpenseClaim.claim_no.like("R________"), + ) + ) + + def _seed_budget_monitor_role(db: Session) -> Role: role = Role(role_code="budget_monitor", name="预算管理员") db.add(role) @@ -149,7 +158,7 @@ def test_low_risk_application_skips_budget_manager_and_generates_draft() -> None assert approved is not None assert approved.status == "approved" assert approved.approval_stage == APPLICATION_LINK_STATUS_STAGE - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 1 + assert reimbursement_claim_query(db).count() == 1 assert any( isinstance(flag, dict) and flag.get("source") == "approval_routing" diff --git a/server/tests/test_expense_claim_service.py b/server/tests/test_expense_claim_service.py index 89af7cf..d0712ba 100644 --- a/server/tests/test_expense_claim_service.py +++ b/server/tests/test_expense_claim_service.py @@ -6,7 +6,7 @@ from datetime import UTC, date, datetime, timedelta from decimal import Decimal import pytest -from sqlalchemy import create_engine +from sqlalchemy import create_engine, or_ from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool @@ -18,8 +18,8 @@ from app.models.employee import Employee from app.models.financial_record import ExpenseClaim, ExpenseClaimItem from app.models.organization import OrganizationUnit from app.models.role import Role -from app.schemas.ontology import OntologyParseRequest from app.schemas.ocr import OcrRecognizeBatchRead, OcrRecognizeDocumentRead +from app.schemas.ontology import OntologyParseRequest from app.schemas.reimbursement import ( ExpenseClaimItemCreate, ExpenseClaimItemUpdate, @@ -28,19 +28,19 @@ from app.schemas.reimbursement import ( ) from app.services.agent_conversations import AgentConversationService from app.services.budget import BudgetService -from app.services.expense_claim_budget_flow import ExpenseClaimBudgetFlowMixin from app.services.expense_claim_attachment_storage import ExpenseClaimAttachmentStorage -from app.services.expense_claims import ExpenseClaimService +from app.services.expense_claim_budget_flow import ExpenseClaimBudgetFlowMixin from app.services.expense_claim_workflow_constants import ( - APPROVAL_DONE_STAGE, APPLICATION_ARCHIVE_STAGE, APPLICATION_LINK_STATUS_STAGE, + APPROVAL_DONE_STAGE, BUDGET_MANAGER_APPROVAL_STAGE, DIRECT_MANAGER_APPROVAL_STAGE, FINANCE_APPROVAL_STAGE, ) -from app.services.ontology import SemanticOntologyService +from app.services.expense_claims import ExpenseClaimService from app.services.ocr import OcrService +from app.services.ontology import SemanticOntologyService from app.services.receipt_folder import ReceiptFolderService @@ -121,6 +121,15 @@ def build_session() -> Session: return session_factory() +def reimbursement_claim_query(db: Session): + return db.query(ExpenseClaim).filter( + or_( + ExpenseClaim.claim_no.like("RE-%"), + ExpenseClaim.claim_no.like("R________"), + ) + ) + + def test_append_budget_flags_replaces_duplicate_budget_warning() -> None: base_warning = { "source": "budget_control", @@ -1770,7 +1779,7 @@ def test_upsert_draft_from_ontology_updates_returned_claim_and_preserves_return_ assert manual_returns == [return_flag] -def test_generate_claim_no_uses_re_prefix_timestamp_and_random_suffix() -> None: +def test_generate_claim_no_uses_short_reimbursement_prefix_and_random_suffix() -> None: with build_session() as db: db.add_all( [ @@ -1813,7 +1822,7 @@ def test_generate_claim_no_uses_re_prefix_timestamp_and_random_suffix() -> None: service = ExpenseClaimService(db) assert re.fullmatch( - r"RE-\d{14}-[A-HJ-NP-Z2-9]{8}", + r"R[A-HJ-NP-Z2-9]{8}", service._generate_claim_no(datetime(2026, 5, 14, tzinfo=UTC)), ) @@ -1831,7 +1840,7 @@ def test_upsert_draft_from_ontology_retries_claim_no_conflict() -> None: db.flush() db.add( ExpenseClaim( - claim_no="RE-20260525101010-ABCDEFGH", + claim_no="RABCDEFGH", employee_name="历史单据", department_name="财务部", project_code=None, @@ -1856,9 +1865,7 @@ def test_upsert_draft_from_ontology_retries_claim_no_conflict() -> None: ) ) service = ExpenseClaimService(db) - generated_claim_nos = iter( - ["RE-20260525101010-ABCDEFGH", "RE-20260525101010-HGFEDCBA"] - ) + generated_claim_nos = iter(["RABCDEFGH", "RHGFEDCBA"]) service._generate_claim_no = lambda occurred_at: next(generated_claim_nos) result = service.upsert_draft_from_ontology( @@ -1874,8 +1881,8 @@ def test_upsert_draft_from_ontology_retries_claim_no_conflict() -> None: created_claim = db.get(ExpenseClaim, result["claim_id"]) assert created_claim is not None - assert created_claim.claim_no == "RE-20260525101010-HGFEDCBA" - assert result["claim_no"] == "RE-20260525101010-HGFEDCBA" + assert created_claim.claim_no == "RHGFEDCBA" + assert result["claim_no"] == "RHGFEDCBA" def test_create_claim_item_adds_blank_draft_row_without_forcing_attachment() -> None: @@ -5695,7 +5702,7 @@ def test_direct_manager_can_route_application_claim_to_budget_approval_then_budg assert leader_approved is not None assert leader_approved.status == "submitted" assert leader_approved.approval_stage == "预算管理者审批" - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 0 + assert reimbursement_claim_query(db).count() == 0 assert any( isinstance(flag, dict) and flag.get("source") == "manual_approval" @@ -5727,7 +5734,7 @@ def test_direct_manager_can_route_application_claim_to_budget_approval_then_budg ) ) assert all(claim.claim_no != "APP-20260525-APPROVE" for claim in archived_claims) - generated_draft = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).one() + generated_draft = reimbursement_claim_query(db).one() assert generated_draft.status == "draft" assert generated_draft.approval_stage == "待提交" assert generated_draft.expense_type == "travel" @@ -5949,7 +5956,7 @@ def test_direct_manager_cannot_route_application_to_missing_budget_approver() -> db.refresh(claim) assert claim.status == "submitted" assert claim.approval_stage == DIRECT_MANAGER_APPROVAL_STAGE - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 0 + assert reimbursement_claim_query(db).count() == 0 def test_direct_manager_p8_executive_completes_application_without_duplicate_budget_approval() -> None: @@ -6023,7 +6030,7 @@ def test_direct_manager_p8_executive_completes_application_without_duplicate_bud assert approved is not None assert approved.status == "approved" assert approved.approval_stage == APPLICATION_LINK_STATUS_STAGE - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 1 + assert reimbursement_claim_query(db).count() == 1 assert not any( isinstance(flag, dict) and flag.get("next_approval_stage") == BUDGET_MANAGER_APPROVAL_STAGE @@ -6111,7 +6118,7 @@ def test_direct_manager_budget_monitor_completes_application_claim_without_dupli assert approved is not None assert approved.status == "approved" assert approved.approval_stage == "关联单据状态" - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 1 + assert reimbursement_claim_query(db).count() == 1 assert not any( isinstance(flag, dict) and flag.get("next_approval_stage") == "预算管理者审批" @@ -6130,7 +6137,7 @@ def test_direct_manager_budget_monitor_completes_application_claim_without_dupli and flag.get("budget_approval_merged_reason") == "direct_manager_is_department_budget_approver" for flag in approved.risk_flags_json ) - generated_draft = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).one() + generated_draft = reimbursement_claim_query(db).one() assert generated_draft.status == "draft" assert generated_draft.expense_type == "travel" reviewer_claims = ExpenseClaimService(db).list_claims(manager_user) @@ -6333,10 +6340,10 @@ def test_application_approval_transfers_budget_reservation_to_reimbursement_draf assert leader_approved.approval_stage == "预算管理者审批" assert reservation.source_type == "application" assert reservation.source_id == claim.id - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 0 + assert reimbursement_claim_query(db).count() == 0 approved = service.approve_claim(claim.id, budget_user, opinion="预算通过") - generated_draft = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).one() + generated_draft = reimbursement_claim_query(db).one() db.refresh(reservation) assert approved is not None @@ -6428,7 +6435,7 @@ def test_direct_manager_approval_defaults_blank_opinion_to_agree() -> None: and flag.get("next_approval_stage") == "预算管理者审批" for flag in approved.risk_flags_json ) - assert db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("RE-%")).count() == 0 + assert reimbursement_claim_query(db).count() == 0 def test_budget_analysis_uses_current_application_reservation_without_double_counting() -> None: diff --git a/server/tests/test_user_agent_service.py b/server/tests/test_user_agent_service.py index 650fc68..e0573c7 100644 --- a/server/tests/test_user_agent_service.py +++ b/server/tests/test_user_agent_service.py @@ -4,14 +4,14 @@ import re from datetime import UTC, datetime, timedelta from decimal import Decimal -from sqlalchemy import create_engine +from sqlalchemy import create_engine, or_ from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool +from app.core.agent_enums import AgentAssetType from app.db.base import Base from app.models.employee import Employee from app.models.financial_record import ExpenseClaim -from app.core.agent_enums import AgentAssetType from app.schemas.ontology import OntologyParseRequest from app.schemas.user_agent import UserAgentCitation, UserAgentRequest, UserAgentReviewRiskBrief from app.services.agent_assets import AgentAssetService @@ -30,6 +30,16 @@ def build_session_factory() -> sessionmaker[Session]: return sessionmaker(bind=engine, autoflush=False, autocommit=False) +def application_claim_query(db: Session): + return db.query(ExpenseClaim).filter( + or_( + ExpenseClaim.claim_no.like("AP-%"), + ExpenseClaim.claim_no.like("APP-%"), + ExpenseClaim.claim_no.like("A________"), + ) + ) + + def build_application_user_agent_response( db: Session, message: str, @@ -629,9 +639,9 @@ def test_user_agent_application_submit_enters_leader_review() -> None: assert "系统已推送给 陈硕 审核,当前节点:陈硕审核中" in response.answer assert "下方是简要单据信息" in response.answer assert "申请信息:" not in response.answer - assert re.search(r"AP-\d{14}-[A-HJ-NP-Z2-9]{8}", response.answer) + assert re.search(r"A[A-HJ-NP-Z2-9]{8}", response.answer) assert response.suggested_actions == [] - claim = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("AP-%")).one() + claim = application_claim_query(db).one() assert claim.status == "submitted" assert claim.approval_stage == "直属领导审批" assert claim.expense_type == "travel_application" @@ -675,7 +685,7 @@ def test_user_agent_application_submit_blocks_duplicate_business_time() -> None: context_overrides={"manager_name": "陈硕"}, history=history, ) - first_claim = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("AP-%")).one() + first_claim = application_claim_query(db).one() second_response = build_application_user_agent_response( db, @@ -684,7 +694,7 @@ def test_user_agent_application_submit_blocks_duplicate_business_time() -> None: history=history, ) - claims = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("AP-%")).all() + claims = application_claim_query(db).all() assert len(claims) == 1 assert "申请单据已生成" in first_response.answer assert "已存在申请单" in second_response.answer @@ -745,7 +755,7 @@ def test_user_agent_application_submit_blocks_overlapping_travel_dates() -> None }, ) - claims = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("AP-%")).all() + claims = application_claim_query(db).all() assert len(claims) == 1 assert "已存在申请单" in response.answer assert "系统没有重复创建" in response.answer @@ -841,7 +851,7 @@ def test_user_agent_application_edit_resubmits_returned_application_claim() -> N ) db.refresh(claim) - claims = db.query(ExpenseClaim).filter(ExpenseClaim.claim_no.like("AP-%")).all() + claims = application_claim_query(db).all() assert len(claims) == 1 assert "申请单据已修改并重新提交" in response.answer assert response.draft_payload is not None