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] = [] # ── 发票抬头 ────────────────────────────────────────────── class InvoiceHeaderCreate(BaseModel): """创建发票抬头请求。""" type: str = Field(..., pattern="^(personal|company)$", description="抬头类型") name: str = Field(..., min_length=1, max_length=128, description="抬头名称") tax_no: str | None = Field(None, max_length=32, description="税号") register_address: str | None = Field(None, max_length=256, description="注册地址") register_phone: str | None = Field(None, max_length=32, description="注册电话") bank_name: str | None = Field(None, max_length=128, description="开户行") bank_account: str | None = Field(None, max_length=64, description="银行账号") email: str | None = Field(None, max_length=128, description="接收邮箱") is_default: bool = Field(False, description="是否设为默认") @model_validator(mode="after") def validate_company_fields(self) -> "InvoiceHeaderCreate": if self.type == "company" and not self.tax_no: raise ValueError("企业抬头必须填写税号") return self class InvoiceHeaderUpdate(BaseModel): """更新发票抬头请求。""" name: str | None = Field(None, min_length=1, max_length=128, description="抬头名称") tax_no: str | None = Field(None, max_length=32, description="税号") register_address: str | None = Field(None, max_length=256, description="注册地址") register_phone: str | None = Field(None, max_length=32, description="注册电话") bank_name: str | None = Field(None, max_length=128, description="开户行") bank_account: str | None = Field(None, max_length=64, description="银行账号") email: str | None = Field(None, max_length=128, description="接收邮箱") is_default: bool | None = Field(None, description="是否设为默认") class InvoiceHeaderOut(BaseModel): """发票抬头响应体。""" model_config = {"from_attributes": True} id: str user_id: str type: str name: str tax_no: str | None = None register_address: str | None = None register_phone: str | None = None bank_name: str | None = None bank_account: str | None = None email: str | None = None is_default: bool = False created_at: NaiveDatetimeOptional = None updated_at: NaiveDatetimeOptional = None