2026-05-06 17:43:47 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-05-13 03:22:52 +00:00
|
|
|
from datetime import date, datetime
|
2026-05-06 17:43:47 +08:00
|
|
|
from decimal import Decimal
|
2026-05-13 03:22:52 +00:00
|
|
|
from typing import Any
|
2026-05-06 17:43:47 +08:00
|
|
|
|
2026-05-13 03:22:52 +00:00
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
2026-05-06 17:43:47 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-13 03:22:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-21 09:28:33 +08:00
|
|
|
is_system_generated: bool = False
|
2026-05-13 03:22:52 +00:00
|
|
|
created_at: datetime
|
|
|
|
|
updated_at: datetime
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 06:45:04 +00:00
|
|
|
class ExpenseClaimAttachmentAnalysisRead(BaseModel):
|
|
|
|
|
severity: str
|
|
|
|
|
label: str
|
|
|
|
|
headline: str
|
|
|
|
|
summary: str
|
|
|
|
|
points: list[str] = Field(default_factory=list)
|
|
|
|
|
suggestion: str = ""
|
|
|
|
|
|
|
|
|
|
|
2026-05-14 09:32:36 +00:00
|
|
|
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 = ""
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 06:45:04 +00:00
|
|
|
class ExpenseClaimAttachmentRead(BaseModel):
|
|
|
|
|
file_name: str
|
|
|
|
|
storage_key: str
|
|
|
|
|
media_type: str
|
|
|
|
|
size_bytes: int
|
|
|
|
|
uploaded_at: datetime | None = None
|
|
|
|
|
previewable: bool = True
|
2026-05-14 15:42:45 +00:00
|
|
|
preview_kind: str = ""
|
|
|
|
|
preview_url: str = ""
|
2026-05-13 06:45:04 +00:00
|
|
|
analysis: ExpenseClaimAttachmentAnalysisRead | None = None
|
2026-05-14 09:32:36 +00:00
|
|
|
document_info: ExpenseClaimAttachmentDocumentInfoRead | None = None
|
|
|
|
|
requirement_check: ExpenseClaimAttachmentRequirementRead | None = None
|
2026-05-13 06:45:04 +00:00
|
|
|
|
|
|
|
|
|
2026-05-13 03:22:52 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 06:45:04 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 03:22:52 +00:00
|
|
|
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
|
2026-05-13 06:54:27 +00:00
|
|
|
employee_position: str | None = None
|
|
|
|
|
employee_grade: str | None = None
|
|
|
|
|
manager_name: str | None = None
|
|
|
|
|
role_labels: list[str] = Field(default_factory=list)
|
2026-05-13 03:22:52 +00:00
|
|
|
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
|
2026-05-13 06:45:04 +00:00
|
|
|
|
|
|
|
|
|
2026-05-20 14:21:56 +08:00
|
|
|
class ExpenseClaimReturnPayload(BaseModel):
|
|
|
|
|
reason: str | None = Field(default=None, max_length=500)
|
2026-05-20 21:00:47 +08:00
|
|
|
reason_codes: list[str] = Field(default_factory=list, max_length=10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExpenseClaimApprovalPayload(BaseModel):
|
|
|
|
|
opinion: str | None = Field(default=None, max_length=500)
|
2026-05-20 14:21:56 +08:00
|
|
|
|
|
|
|
|
|
2026-05-21 09:28:33 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-05-13 06:45:04 +00:00
|
|
|
class ExpenseClaimAttachmentActionResponse(BaseModel):
|
|
|
|
|
message: str
|
|
|
|
|
claim_id: str
|
|
|
|
|
item_id: str
|
|
|
|
|
invoice_id: str | None = None
|
2026-05-21 09:28:33 +08:00
|
|
|
item_amount: Decimal | None = None
|
|
|
|
|
claim_amount: Decimal | None = None
|
2026-05-13 06:45:04 +00:00
|
|
|
attachment: ExpenseClaimAttachmentRead | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExpenseClaimItemActionResponse(BaseModel):
|
|
|
|
|
message: str
|
|
|
|
|
claim_id: str
|
|
|
|
|
item_id: str
|