from __future__ import annotations from datetime import date, datetime from decimal import Decimal from typing import Any from pydantic import BaseModel, ConfigDict, Field class ReimbursementCreate(BaseModel): request_no: str employee_id: str title: str category: str amount: Decimal reason: str | None = None class ReimbursementRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: str request_no: str employee_id: str title: str category: str status: str amount: Decimal reason: str | None created_at: datetime updated_at: datetime class ExpenseClaimItemRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: str item_date: date item_type: str item_reason: str item_location: str item_amount: Decimal invoice_id: str | None is_system_generated: bool = False created_at: datetime updated_at: datetime class ExpenseClaimAttachmentAnalysisRead(BaseModel): severity: str label: str headline: str summary: str points: list[str] = Field(default_factory=list) suggestion: str = "" class ExpenseClaimAttachmentDocumentFieldRead(BaseModel): key: str label: str value: str class ExpenseClaimAttachmentDocumentInfoRead(BaseModel): document_type: str = "other" document_type_label: str = "其他单据" scene_code: str = "other" scene_label: str = "其他票据" fields: list[ExpenseClaimAttachmentDocumentFieldRead] = Field(default_factory=list) class ExpenseClaimAttachmentRequirementRead(BaseModel): matches: bool = False current_expense_type: str = "other" current_expense_type_label: str = "其他" allowed_scene_labels: list[str] = Field(default_factory=list) recognized_scene_code: str = "other" recognized_scene_label: str = "其他票据" recognized_document_type: str = "other" recognized_document_type_label: str = "其他单据" message: str = "" class ExpenseClaimAttachmentRead(BaseModel): file_name: str storage_key: str media_type: str size_bytes: int uploaded_at: datetime | None = None previewable: bool = True preview_kind: str = "" preview_url: str = "" analysis: ExpenseClaimAttachmentAnalysisRead | None = None document_info: ExpenseClaimAttachmentDocumentInfoRead | None = None requirement_check: ExpenseClaimAttachmentRequirementRead | None = None class ExpenseClaimItemUpdate(BaseModel): item_date: date | None = None item_type: str | None = None item_reason: str | None = None item_location: str | None = None item_amount: Decimal | None = None invoice_id: str | None = None class ExpenseClaimItemCreate(BaseModel): item_date: date | None = None item_type: str | None = None item_reason: str | None = None item_location: str | None = None item_amount: Decimal | None = None invoice_id: str | None = None class ExpenseClaimRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: str claim_no: str employee_id: str | None employee_name: str department_id: str | None department_name: str employee_position: str | None = None employee_grade: str | None = None manager_name: str | None = None role_labels: list[str] = Field(default_factory=list) project_code: str | None expense_type: str reason: str location: str amount: Decimal currency: str invoice_count: int occurred_at: datetime submitted_at: datetime | None status: str approval_stage: str | None risk_flags_json: list[Any] = Field(default_factory=list) created_at: datetime updated_at: datetime items: list[ExpenseClaimItemRead] = Field(default_factory=list) class ExpenseClaimActionResponse(BaseModel): message: str claim_id: str status: str | None = None class ExpenseClaimReturnPayload(BaseModel): reason: str | None = Field(default=None, max_length=500) reason_codes: list[str] = Field(default_factory=list, max_length=10) class ExpenseClaimApprovalPayload(BaseModel): opinion: str | None = Field(default=None, max_length=500) class TravelReimbursementCalculatorRequest(BaseModel): days: int = Field(ge=1, le=365) location: str = Field(min_length=1, max_length=120) grade: str | None = Field(default=None, max_length=30) class TravelReimbursementCalculatorResponse(BaseModel): days: int location: str matched_city: str city_tier: str grade: str grade_band: str grade_band_label: str hotel_rate: Decimal hotel_amount: Decimal allowance_region: str meal_allowance_rate: Decimal basic_allowance_rate: Decimal total_allowance_rate: Decimal allowance_amount: Decimal total_amount: Decimal rule_name: str rule_version: str formula_text: str summary_text: str class ExpenseClaimAttachmentActionResponse(BaseModel): message: str claim_id: str item_id: str invoice_id: str | None = None item_amount: Decimal | None = None claim_amount: Decimal | None = None attachment: ExpenseClaimAttachmentRead | None = None class ExpenseClaimItemActionResponse(BaseModel): message: str claim_id: str item_id: str