guzhi/app/schemas/invoice.py
邹方成 552c02516a feat(发票): 支持多附件上传和邮件发送功能
refactor(用户管理): 优化用户列表查询和备注字段处理

feat(估值): 评估报告和证书URL改为数组类型并添加下载地址

docs: 添加交易管理与用户备注功能增强实施计划

fix(邮件): 修复邮件发送接口的多附件支持问题

style: 清理注释代码和格式化文件
2025-11-25 20:09:50 +08:00

158 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(..., min_length=1, max_length=256)
register_phone: str = Field(..., min_length=1, max_length=32)
bank_name: str = Field(..., min_length=1, max_length=128)
bank_account: str = Field(..., min_length=1, 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: str = Field(..., min_length=1, 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
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