from typing import Optional, List from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator class InvoiceHeaderCreate(BaseModel): company_name: str = Field(..., min_length=1, max_length=128) tax_number: str = Field(..., min_length=1, max_length=32) register_address: Optional[str] = Field(None, min_length=1, max_length=256) register_phone: Optional[str] = Field(None, min_length=1, max_length=32) bank_name: Optional[str] = Field(None, min_length=1, max_length=128) bank_account: Optional[str] = Field(None, min_length=1, max_length=64) email: EmailStr is_default: Optional[bool] = False class InvoiceHeaderOut(BaseModel): id: int app_user_id: Optional[int] = None company_name: str tax_number: str register_address: str register_phone: str bank_name: str bank_account: str email: EmailStr class Config: from_attributes = True is_default: Optional[bool] = False class InvoiceHeaderUpdate(BaseModel): company_name: Optional[str] = Field(None, min_length=1, max_length=128) tax_number: Optional[str] = Field(None, min_length=1, max_length=32) register_address: Optional[str] = Field(None, max_length=256) register_phone: Optional[str] = Field(None, max_length=32) bank_name: Optional[str] = Field(None, max_length=128) bank_account: Optional[str] = Field(None, max_length=64) email: Optional[EmailStr] = None is_default: Optional[bool] = None class InvoiceCreate(BaseModel): ticket_type: str = Field(..., pattern=r"^(electronic|paper)$") invoice_type: str = Field(..., pattern=r"^(special|normal)$") phone: str = Field(..., min_length=5, max_length=20) email: EmailStr company_name: str = Field(..., min_length=1, max_length=128) tax_number: str = Field(..., min_length=1, max_length=32) register_address: str = Field(..., max_length=256) register_phone: str = Field(..., max_length=32) bank_name: str = Field(..., max_length=128) bank_account: str = Field(..., max_length=64) app_user_id: Optional[int] = None header_id: Optional[int] = None wechat: Optional[str] = None class InvoiceUpdate(BaseModel): ticket_type: Optional[str] = Field(None, pattern=r"^(electronic|paper)$") invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$") phone: Optional[str] = Field(None, min_length=5, max_length=20) email: Optional[EmailStr] = None company_name: Optional[str] = Field(None, min_length=1, max_length=128) tax_number: Optional[str] = Field(None, min_length=1, max_length=32) register_address: Optional[str] = Field(None, min_length=1, max_length=256) register_phone: Optional[str] = Field(None, min_length=1, max_length=32) bank_name: Optional[str] = Field(None, min_length=1, max_length=128) bank_account: Optional[str] = Field(None, min_length=1, max_length=64) wechat: Optional[str] = None class InvoiceOut(BaseModel): id: int created_at: str ticket_type: str invoice_type: str phone: str email: EmailStr company_name: str tax_number: str register_address: str register_phone: str bank_name: str bank_account: str status: str app_user_id: Optional[int] header_id: Optional[int] wechat: Optional[str] class InvoiceList(BaseModel): items: List[InvoiceOut] total: int page: int page_size: int class UpdateStatus(BaseModel): id: int status: str = Field(..., pattern=r"^(pending|invoiced|rejected|refunded)$") class UpdateType(BaseModel): ticket_type: str = Field(..., pattern=r"^(electronic|paper)$") invoice_type: str = Field(..., pattern=r"^(special|normal)$") class PaymentReceiptCreate(BaseModel): url: str = Field(..., min_length=1, max_length=512) note: Optional[str] = Field(None, max_length=256) extra: Optional[dict] = None class PaymentReceiptOut(BaseModel): id: int url: str note: Optional[str] verified: bool created_at: str extra: Optional[dict] = None class AppCreateInvoiceWithReceipt(BaseModel): header_id: int ticket_type: Optional[str] = Field(None, pattern=r"^(electronic|paper)$") invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$") # 兼容前端索引字段:"0"→normal,"1"→special invoiceTypeIndex: Optional[str] = None receipt_url: Optional[str] = Field(None, max_length=512) note: Optional[str] = Field(None, max_length=256) @field_validator('ticket_type', mode='before') @classmethod def _default_ticket_type(cls, v): return v or 'electronic' @field_validator('receipt_url', mode='before') @classmethod def _clean_receipt_url(cls, v): if isinstance(v, list) and v: v = v[0] if isinstance(v, str): s = v.strip() if s.startswith('`') and s.endswith('`'): s = s[1:-1].strip() return s or None return v @model_validator(mode='after') def _coerce_invoice_type(self): if not self.invoice_type and self.invoiceTypeIndex is not None: mapping = {'0': 'normal', '1': 'special'} self.invoice_type = mapping.get(str(self.invoiceTypeIndex)) # 若仍为空,默认 normal if not self.invoice_type: self.invoice_type = 'normal' return self