from __future__ import annotations import uuid from datetime import UTC, date, datetime, timedelta from decimal import Decimal from sqlalchemy import create_engine, func, select from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool import app.models # noqa: F401 - 注册完整 metadata from app.api.deps import CurrentUserContext from app.db.base_class import Base from app.models.budget import BudgetAllocation from app.models.employee import Employee from app.models.expense_case import BusinessEvent from app.models.financial_connector import ( FinancialConnectorConfig, FinancialConnectorEvent, PaymentReconciliationCase, ) from app.models.financial_record import ExpenseClaim from app.models.organization import OrganizationUnit from app.models.savings import SavingsEvidenceLink, SavingsRealization from app.schemas.commercial import ( CommercialCostEventCreate, CommercialPlanCreate, CommercialPricingScenarioWrite, CommercialSubscriptionCreate, ) from app.schemas.financial_connector import FinancialEventEnvelope from app.schemas.reimbursement import ExpenseClaimItemCreate from app.schemas.savings import SavingsRealizationActionCreate from app.services.commercial_admin import CommercialAdminService from app.services.commercial_analytics import CommercialAnalyticsService from app.services.commercial_metering import CommercialMeteringService from app.services.commercial_pricing import CommercialPricingService from app.services.expense_claims import ExpenseClaimService from app.services.financial_connector_auth import ( FinancialConnectorSecretResolver, sign_financial_event, ) from app.services.financial_connector_ingestion import FinancialConnectorIngestionService from app.services.savings_discovery import SavingsDiscoveryService from app.services.savings_realization import SavingsRealizationService def test_expense_application_to_refund_and_value_evidence_chain() -> None: """正式服务贯穿申请、审批、外部财务事实和商业价值口径。""" engine = create_engine( "sqlite+pysqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) try: with factory() as db: _run_chain(db) finally: Base.metadata.drop_all(engine) engine.dispose() def _run_chain(db: Session) -> None: now = datetime.now(UTC) users, application = _seed_application_context(db, now=now) claims = ExpenseClaimService(db) submitted_application = claims.submit_claim( application.id, users["employee"], correlation_id="value-chain-application-submit", ) assert submitted_application is not None assert (submitted_application.status, submitted_application.approval_stage) == ( "submitted", "直属领导审批", ) approved_application = claims.approve_claim( application.id, users["manager"], opinion="差旅必要且预算充足,同意生成报销草稿。", request_id="value-chain-application-approve", expected_status="submitted", expected_approval_stage="直属领导审批", ) assert approved_application is not None assert (approved_application.status, approved_application.approval_stage) == ( "approved", "关联单据状态", ) reimbursement = _generated_reimbursement(db, approved_application) reimbursement = claims.create_claim_item( claim_id=reimbursement.id, payload=ExpenseClaimItemCreate( item_date=date.today(), item_type="hotel", item_reason="上海客户现场住宿", item_location="上海", item_amount=Decimal("66.00"), invoice_id="invoice-value-chain-001", ), current_user=users["employee"], ) assert reimbursement is not None reimbursable_item = next( item for item in reimbursement.items if item.invoice_id == "invoice-value-chain-001" ) opportunity = SavingsDiscoveryService(db).discover_standard_adjustments( claim=reimbursement, items_by_id={reimbursable_item.id: reimbursable_item}, adjustment_flags=[ { "item_id": reimbursable_item.id, "message": "已发布住宿政策将原始 100 元锁定为可报 66 元。", "original_amount": "100.00", "reimbursable_amount": "66.00", "employee_absorbed_amount": "34.00", "policy_rule_version": "value-chain-hotel-v1", "policy_rule_version_source": "published", "policy_grade": "P6", "policy_matched_city": "上海", "calculation_fingerprint": "sha256:" + "a" * 64, } ], current_user=users["employee"], request_id="value-chain-saving-discovery", )[0] db.commit() submitted = claims.submit_claim( reimbursement.id, users["employee"], correlation_id="value-chain-reimbursement-submit", ) assert submitted is not None and submitted.approval_stage == "直属领导审批" manager_approved = claims.approve_claim( reimbursement.id, users["manager"], opinion="业务真实,同意进入财务审核。", request_id="value-chain-manager-approve", expected_status="submitted", expected_approval_stage="直属领导审批", ) assert manager_approved is not None and manager_approved.approval_stage == "财务审批" finance_approved = claims.approve_claim( reimbursement.id, users["finance"], opinion="票据、政策调整和预算均已复核。", request_id="value-chain-finance-approve", expected_status="submitted", expected_approval_stage="财务审批", ) assert finance_approved is not None assert (finance_approved.status, finance_approved.approval_stage) == ( "pending_payment", "待付款", ) _seed_commercial_account(db, now=now) _seed_connector(db) db.commit() timestamp = 1_800_000_000 before_payment_events = _event_count(db, "payment_completed") mismatch = _financial_event( reimbursement, event_id="value-chain-settlement-mismatch", extra={"amount": "999.00"}, ) mismatch_result = _ingest(db, mismatch, timestamp) db.commit() db.refresh(reimbursement) assert mismatch_result.error_code == "amount_mismatch" assert reimbursement.status == "pending_payment" assert _event_count(db, "payment_completed") == before_payment_events assert ( db.scalar( select(func.count(SavingsRealization.id)).where( SavingsRealization.opportunity_id == opportunity.id ) ) == 0 ) settlement = _financial_event(reimbursement, event_id="value-chain-settlement-001") settled = _ingest(db, settlement, timestamp + 1) db.commit() replay = _ingest(db, settlement, timestamp + 1) db.commit() db.refresh(reimbursement) db.refresh(application) assert settled.processing_status == "processed" and replay.replayed is True assert settled.verification_level == "production_verified" assert settled.evidence_classification == "external_cash" assert reimbursement.status == "paid" assert application.approval_stage == "申请归档" assert _event_count(db, "payment_completed") == before_payment_events + 1 realization = db.scalar( select(SavingsRealization).where( SavingsRealization.opportunity_id == opportunity.id, SavingsRealization.realization_type == "actual", ) ) assert realization is not None and realization.actual_net == Decimal("34.0000") confirmation = SavingsRealizationActionCreate( action="confirm", request_id="value-chain-saving-confirm", expected_version=1, comment="独立财务已复核生产银行回执、政策基线和归因键。", ) confirmed = SavingsRealizationService(db).execute_action( realization.id, confirmation, users["confirmer"], ) confirmed_replay = SavingsRealizationService(db).execute_action( realization.id, confirmation, users["confirmer"], ) assert confirmed.response.replayed is False assert confirmed_replay.response.replayed is True evidence = db.scalar( select(SavingsEvidenceLink).where( SavingsEvidenceLink.realization_id == realization.id, SavingsEvidenceLink.resource_type == "financial_connector_event", ) ) assert evidence is not None and evidence.verification_status == "verified" erp = _financial_event( reimbursement, event_id="value-chain-erp-001", event_type="erp_posted", extra={ "origin_external_event_id": settlement.external_event_id, "erp_document_number": "ERP-VALUE-CHAIN-000001", "accounting_period": "2026-07", }, ) erp_result = _ingest(db, erp, timestamp + 2) db.commit() case = db.get(PaymentReconciliationCase, settled.reconciliation_case_id) assert erp_result.processing_status == "processed" assert case is not None and case.erp_status == "posted" assert case.erp_document_tail == "N-000001" window = { "start": now - timedelta(days=2), "end": now + timedelta(days=2), "as_of": now + timedelta(days=1), } before_refund = CommercialAnalyticsService(db).build("default", **window) assert _money(before_refund.verified_cash_savings) == {"CNY": Decimal("34.0000")} pricing = CommercialPricingService(db).build( "default", CommercialPricingScenarioWrite( **window, target_contribution_margin_rate=Decimal("0.50"), max_verified_savings_share=Decimal("0.50"), ), ) assert pricing.recommended_model == "hybrid" assert pricing.scenarios[0].minimum_sustainable_charge == Decimal("10.0000") assert pricing.scenarios[0].maximum_value_aligned_charge == Decimal("17.0000") refund = _financial_event( reimbursement, event_id="value-chain-refund-001", event_type="payment_refunded", extra={"origin_external_event_id": settlement.external_event_id}, ) refunded = _ingest(db, refund, timestamp + 3) db.commit() refund_replay = _ingest(db, refund, timestamp + 3) db.commit() db.refresh(reimbursement) assert refunded.processing_status == "processed" and refund_replay.replayed is True assert reimbursement.status == "pending_payment" realizations = list( db.scalars( select(SavingsRealization).where(SavingsRealization.opportunity_id == opportunity.id) ).all() ) assert len(realizations) == 2 assert sum((row.reporting_amount for row in realizations), Decimal("0")) == Decimal("0") duplicate_reversal = _financial_event( reimbursement, event_id="value-chain-reversal-after-refund", event_type="payment_reversed", extra={"origin_external_event_id": settlement.external_event_id}, ) duplicate_result = _ingest(db, duplicate_reversal, timestamp + 4) db.commit() assert duplicate_result.processing_status == "exception" assert duplicate_result.error_code == "claim_not_paid" assert ( db.scalar( select(func.count(SavingsRealization.id)).where( SavingsRealization.opportunity_id == opportunity.id ) ) == 2 ) after_refund = CommercialAnalyticsService(db).build("default", **window) assert _money(after_refund.verified_cash_savings) == {"CNY": Decimal("0.0000")} pricing_after_refund = CommercialPricingService(db).build( "default", CommercialPricingScenarioWrite( **window, target_contribution_margin_rate=Decimal("0.50"), max_verified_savings_share=Decimal("0.50"), ), ) assert pricing_after_refund.recommended_model == "optimize_unit_economics" assert pricing_after_refund.scenarios[0].maximum_value_aligned_charge == Decimal("0.0000") assert ( CommercialAnalyticsService(db) .build( "tenant-other", **window, ) .verified_cash_savings.values == [] ) assert ( db.scalar( select(func.count(FinancialConnectorEvent.id)).where( FinancialConnectorEvent.external_event_id == refund.external_event_id ) ) == 1 ) def _seed_application_context( db: Session, *, now: datetime, ) -> tuple[dict[str, CurrentUserContext], ExpenseClaim]: department = OrganizationUnit( unit_code="VALUE-CHAIN-DEPT", name="价值链试点部", unit_type="department", ) manager = Employee( employee_no="VALUE-MANAGER", name="价值链经理", email="value-manager@example.com", organization_unit=department, ) employee = Employee( employee_no="VALUE-EMPLOYEE", name="价值链员工", email="value-employee@example.com", organization_unit=department, manager=manager, ) db.add_all([department, manager, employee]) db.flush() db.add( BudgetAllocation( budget_no="BUD-VALUE-CHAIN-2026Q3", fiscal_year=2026, period_type="quarter", period_key="2026Q3", department_id=department.id, department_name=department.name, subject_code="travel", subject_name="差旅", original_amount=Decimal("50000.00"), adjusted_amount=Decimal("0.00"), status="active", warning_threshold=Decimal("80.00"), control_action="block", ) ) application = ExpenseClaim( id=str(uuid.uuid4()), claim_no="APP-VALUE-CHAIN-20260716", employee_id=employee.id, employee_name=employee.name, department_id=department.id, department_name=department.name, expense_type="travel_application", reason="上海客户现场差旅", location="上海", amount=Decimal("66.00"), currency="CNY", invoice_count=0, occurred_at=now, status="draft", approval_stage="待提交", risk_flags_json=[], ) db.add(application) db.commit() return ( { "employee": _user( employee.email, name=employee.name, employee_id=employee.id, ), "manager": _user( manager.email, name=manager.name, employee_id=manager.id, roles=["manager"], ), "finance": _user("value-finance@example.com", roles=["finance"]), "confirmer": _user("value-confirmer@example.com", roles=["finance"]), }, application, ) def _generated_reimbursement( db: Session, application: ExpenseClaim, ) -> ExpenseClaim: draft_id = next( str(flag.get("generated_draft_claim_id")) for flag in application.risk_flags_json if isinstance(flag, dict) and flag.get("generated_draft_claim_id") ) draft = db.get(ExpenseClaim, draft_id) assert draft is not None and draft.status == "draft" return draft def _seed_commercial_account(db: Session, *, now: datetime) -> None: admin = CommercialAdminService(db) plan = admin.create_plan( "default", CommercialPlanCreate( plan_code="value-pilot", name="价值证明试点", pricing_model="hybrid", billing_interval="monthly", currency="CNY", base_fee=Decimal("8.00"), included_seats=10, effective_from=now - timedelta(days=30), ), actor_id="platform-admin", ) admin.activate_plan("default", plan.id, expected_version=plan.version) subscription = admin.create_subscription( "default", CommercialSubscriptionCreate( subscription_key="default-value-pilot-2026", plan_id=plan.id, starts_at=now - timedelta(days=10), current_period_start=now - timedelta(days=1), current_period_end=now + timedelta(days=29), seats=5, ), actor_id="platform-admin", ) CommercialMeteringService(db).record_cost( "default", CommercialCostEventCreate( subscription_id=subscription.id, cost_category="connector", quantity=Decimal("1"), unit="workflow", unit_cost=Decimal("5.00"), original_currency="CNY", reporting_currency="CNY", fx_rate=Decimal("1"), allocation_key="value-chain-workflow", occurred_at=now, source_system="cost-ledger", idempotency_key="value-chain-cost-001", ), ) def _seed_connector(db: Session) -> None: db.add( FinancialConnectorConfig( id=str(uuid.uuid4()), tenant_id="default", provider="value-bank", environment="production", key_version="v1", secret_ref="connector/value-chain", allowed_event_types_json=[ "payment_settled", "payment_failed", "erp_posted", "erp_posting_failed", "payment_refunded", "payment_reversed", ], clock_skew_seconds=300, status="active", created_by="platform-admin", ) ) def _financial_event( claim: ExpenseClaim, *, event_id: str, event_type: str = "payment_settled", extra: dict[str, str] | None = None, ) -> FinancialEventEnvelope: return FinancialEventEnvelope( tenant_id="default", external_event_id=event_id, event_type=event_type, occurred_at=datetime.now(UTC), correlation_id=f"corr-{event_id}", payload={ "claim_id": claim.id, "claim_reference": claim.claim_no, "amount": str(claim.amount), "currency": claim.currency, "external_payment_reference": f"PAY-{claim.claim_no}", **dict(extra or {}), }, ) def _ingest( db: Session, envelope: FinancialEventEnvelope, timestamp: int, ): secret = "value-chain-server-secret" signature = sign_financial_event( envelope, timestamp=timestamp, secret=secret, tenant_id="default", provider="value-bank", key_version="v1", ) return FinancialConnectorIngestionService( db, secrets=FinancialConnectorSecretResolver({"connector/value-chain": secret}), now_epoch=timestamp, ).ingest( envelope, tenant_header="default", provider_header="value-bank", key_version_header="v1", timestamp_header=str(timestamp), signature_header=signature, ) def _event_count(db: Session, event_type: str) -> int: return int( db.scalar( select(func.count(BusinessEvent.id)).where(BusinessEvent.event_type == event_type) ) or 0 ) def _money(metric) -> dict[str, Decimal]: return {item.currency: item.amount for item in metric.values} def _user( username: str, *, name: str | None = None, employee_id: str = "", roles: list[str] | None = None, ) -> CurrentUserContext: return CurrentUserContext( username=username, name=name or username, employee_id=employee_id, role_codes=list(roles or []), is_admin=False, tenant_id="default", )