import re from typing import Any from pydantic import BaseModel, Field, model_validator from app.schemas.common import NaiveDatetimeOptional _EMAIL_REGEX = re.compile(r"^[\w.\-]+@[\w.\-]+\.\w+$") class InvoiceCreateRequest(BaseModel): """创建发票请求。""" header_type: str = Field(..., pattern="^(personal|company)$", description="抬头类型") header_name: str = Field(..., min_length=1, max_length=128, description="抬头名称") header_tax_no: str | None = Field(None, max_length=32, description="税号") header_register_address: str | None = Field(None, max_length=256, description="注册地址") header_register_phone: str | None = Field(None, max_length=32, description="注册电话") header_bank_name: str | None = Field(None, max_length=128, description="开户行") header_bank_account: str | None = Field(None, max_length=64, description="银行账号") email: str = Field(..., max_length=128, description="电子邮箱(必填)") order_ids: list[str] = Field(..., min_length=1, description="订单ID列表") @model_validator(mode="after") def validate_email(self) -> "InvoiceCreateRequest": if not _EMAIL_REGEX.match(self.email): raise ValueError("邮箱格式不正确") return self @model_validator(mode="after") def validate_company_fields(self) -> "InvoiceCreateRequest": if self.header_type == "company" and not self.header_tax_no: raise ValueError("企业抬头必须填写税号") return self class InvoiceStatusUpdateRequest(BaseModel): """更新发票状态请求。""" status: str = Field(..., pattern="^(success|failed)$", description="目标状态") failure_reason: str | None = Field(None, max_length=500, description="失败原因") @model_validator(mode="after") def validate_failure_reason(self) -> "InvoiceStatusUpdateRequest": if self.status == "failed" and not self.failure_reason: raise ValueError("开具失败时必须填写失败原因") return self class InvoiceOrderOut(BaseModel): """发票关联订单响应。""" model_config = {"from_attributes": True} id: str invoice_id: str order_id: str order_no: str amount: float credits: float class InvoiceOut(BaseModel): """发票响应体。""" model_config = {"from_attributes": True} id: str user_id: str invoice_no: str header_type: str header_name: str header_tax_no: str | None = None header_register_address: str | None = None header_register_phone: str | None = None header_bank_name: str | None = None header_bank_account: str | None = None email: str total_amount: float total_credits: float status: str failure_reason: str | None = None issued_at: NaiveDatetimeOptional = None created_at: NaiveDatetimeOptional = None updated_at: NaiveDatetimeOptional = None orders: list[InvoiceOrderOut] = []