修复valuation_controller.update方法中状态被硬编码为"pending"的问题,并添加update1方法用于不同的状态更新场景 同时更新dockerignore文件,添加web1/node_modules和migrations目录的忽略 优化发票抬头相关逻辑,包括空字符串处理和发票信息同步 调整发票抬头列表接口,支持分页和用户过滤
165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
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
|