guzhi/app/schemas/invoice.py
邹方成 0021c94024 fix(valuations): 修正估值评估更新方法中的状态设置问题
修复valuation_controller.update方法中状态被硬编码为"pending"的问题,并添加update1方法用于不同的状态更新场景
同时更新dockerignore文件,添加web1/node_modules和migrations目录的忽略
优化发票抬头相关逻辑,包括空字符串处理和发票信息同步
调整发票抬头列表接口,支持分页和用户过滤
2025-11-27 18:01:12 +08:00

165 lines
5.5 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
@field_validator('register_address', 'register_phone', 'bank_name', 'bank_account', mode='before')
@classmethod
def _empty_to_none(cls, v):
if isinstance(v, str) and v.strip() == "":
return None
return v
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: Optional[str] = None
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