Compare commits
No commits in common. "f1c1db580c5f8828a5092952a4b9a1b15c9c8246" and "cd8170ac0297217d96fbb9aaaacf97a2fc02899a" have entirely different histories.
f1c1db580c
...
cd8170ac02
10
Dockerfile
10
Dockerfile
@ -1,14 +1,8 @@
|
|||||||
FROM node:18-alpine AS web
|
FROM node:18.12.0-alpine3.16 AS web
|
||||||
|
|
||||||
WORKDIR /opt/vue-fastapi-admin
|
WORKDIR /opt/vue-fastapi-admin
|
||||||
COPY /web ./web
|
COPY /web ./web
|
||||||
|
RUN npm install -g pnpm && cd /opt/vue-fastapi-admin/web && pnpm install --registry=https://registry.npmmirror.com && pnpm run build
|
||||||
# 安装pnpm并设置配置
|
|
||||||
RUN npm install -g pnpm && \
|
|
||||||
cd /opt/vue-fastapi-admin/web && \
|
|
||||||
pnpm config set registry https://registry.npmmirror.com && \
|
|
||||||
pnpm install && \
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye
|
FROM python:3.11-slim-bullseye
|
||||||
|
|||||||
@ -117,23 +117,13 @@ async def create_with_receipt(payload: AppCreateInvoiceWithReceipt, current_user
|
|||||||
)
|
)
|
||||||
inv = await invoice_controller.create(inv_data)
|
inv = await invoice_controller.create(inv_data)
|
||||||
if payload.receipt_urls:
|
if payload.receipt_urls:
|
||||||
urls = payload.receipt_urls
|
receipts = []
|
||||||
main_url = urls[0] if isinstance(urls, list) and urls else None
|
for url in payload.receipt_urls:
|
||||||
receipt = await invoice_controller.create_receipt(
|
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=url, note=payload.note))
|
||||||
inv.id,
|
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
||||||
PaymentReceiptCreate(url=main_url, note=payload.note, extra=urls)
|
if detail:
|
||||||
)
|
receipts.append(detail)
|
||||||
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
return Success(data={"invoice_id": inv.id, "receipts": receipts}, msg="创建并上传成功")
|
||||||
return Success(data={"invoice_id": inv.id, "receipts": [detail] if detail else []}, msg="创建并上传成功")
|
|
||||||
if isinstance(payload.receipt_url, list) and payload.receipt_url:
|
|
||||||
urls = payload.receipt_url
|
|
||||||
main_url = urls[0]
|
|
||||||
receipt = await invoice_controller.create_receipt(
|
|
||||||
inv.id,
|
|
||||||
PaymentReceiptCreate(url=main_url, note=payload.note, extra=urls)
|
|
||||||
)
|
|
||||||
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
|
||||||
return Success(data={"invoice_id": inv.id, "receipts": [detail] if detail else []}, msg="创建并上传成功")
|
|
||||||
if payload.receipt_url:
|
if payload.receipt_url:
|
||||||
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
|
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
|
||||||
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
||||||
|
|||||||
@ -166,7 +166,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
|
|
||||||
# 步骤1:立即更新计算输入参数(不管后续是否成功)
|
# 步骤1:立即更新计算输入参数(不管后续是否成功)
|
||||||
try:
|
try:
|
||||||
await valuation_controller.update_calc(
|
await valuation_controller.update(
|
||||||
valuation_id,
|
valuation_id,
|
||||||
ValuationAssessmentUpdate(
|
ValuationAssessmentUpdate(
|
||||||
calculation_input=input_data,
|
calculation_input=input_data,
|
||||||
@ -212,7 +212,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
# api_calc_fields["flow_correction"] = None
|
# api_calc_fields["flow_correction"] = None
|
||||||
|
|
||||||
if api_calc_fields:
|
if api_calc_fields:
|
||||||
await valuation_controller.update_calc(
|
await valuation_controller.update(
|
||||||
valuation_id,
|
valuation_id,
|
||||||
ValuationAssessmentUpdate(**api_calc_fields)
|
ValuationAssessmentUpdate(**api_calc_fields)
|
||||||
)
|
)
|
||||||
@ -225,7 +225,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
|
|
||||||
# 步骤2:更新计算结果字段(模型估值B、市场估值C、最终估值AB、完整计算结果)
|
# 步骤2:更新计算结果字段(模型估值B、市场估值C、最终估值AB、完整计算结果)
|
||||||
try:
|
try:
|
||||||
await valuation_controller.update_calc(
|
await valuation_controller.update(
|
||||||
valuation_id,
|
valuation_id,
|
||||||
ValuationAssessmentUpdate(
|
ValuationAssessmentUpdate(
|
||||||
model_value_b=calculation_result.get('model_value_b'),
|
model_value_b=calculation_result.get('model_value_b'),
|
||||||
@ -280,7 +280,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
base_pledge_rate_value = "0.5" # 固定值:基础质押率 = 0.5
|
base_pledge_rate_value = "0.5" # 固定值:基础质押率 = 0.5
|
||||||
flow_correction_value = "0.3" # 固定值:流量修正系数 = 0.3
|
flow_correction_value = "0.3" # 固定值:流量修正系数 = 0.3
|
||||||
|
|
||||||
await valuation_controller.update_calc(
|
await valuation_controller.update(
|
||||||
valuation_id,
|
valuation_id,
|
||||||
ValuationAssessmentUpdate(
|
ValuationAssessmentUpdate(
|
||||||
dynamic_pledge_rate=drp_result,
|
dynamic_pledge_rate=drp_result,
|
||||||
@ -307,13 +307,19 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 步骤4:计算完成,保持状态为 pending,等待后台审核
|
# 步骤4:最后更新状态为成功
|
||||||
try:
|
try:
|
||||||
result = await valuation_controller.get_by_id(valuation_id)
|
result = await valuation_controller.update(
|
||||||
logger.info("valuation.calc_finished valuation_id={} status=pending", valuation_id)
|
valuation_id,
|
||||||
|
ValuationAssessmentUpdate(
|
||||||
|
status='success'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logger.info("valuation.status_updated valuation_id={} status=success", valuation_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("valuation.failed_to_fetch_after_calc valuation_id={} err={}", valuation_id, repr(e))
|
logger.warning("valuation.failed_to_update_status valuation_id={} err={}", valuation_id, repr(e))
|
||||||
result = None
|
# 即使状态更新失败,也尝试获取结果用于日志
|
||||||
|
result = await valuation_controller.get_by_id(valuation_id)
|
||||||
|
|
||||||
logger.info("valuation.background_calc_success user_id={} valuation_id={}", user_id, valuation_id)
|
logger.info("valuation.background_calc_success user_id={} valuation_id={}", user_id, valuation_id)
|
||||||
|
|
||||||
@ -356,7 +362,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
fail_update_fields.update(api_calc_fields)
|
fail_update_fields.update(api_calc_fields)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await valuation_controller.update_calc(
|
await valuation_controller.update(
|
||||||
valuation_id,
|
valuation_id,
|
||||||
ValuationAssessmentUpdate(**fail_update_fields)
|
ValuationAssessmentUpdate(**fail_update_fields)
|
||||||
)
|
)
|
||||||
@ -366,7 +372,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
# 如果保存失败,至少更新状态
|
# 如果保存失败,至少更新状态
|
||||||
try:
|
try:
|
||||||
fail_update = ValuationAssessmentUpdate(status='rejected')
|
fail_update = ValuationAssessmentUpdate(status='rejected')
|
||||||
await valuation_controller.update_calc(valuation_id, fail_update)
|
await valuation_controller.update(valuation_id, fail_update)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -85,11 +85,6 @@ async def send_email(payload: SendEmailRequest = Body(...)):
|
|||||||
raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析")
|
raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析")
|
||||||
if payload.file_urls:
|
if payload.file_urls:
|
||||||
urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)])
|
urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)])
|
||||||
if payload.file_url:
|
|
||||||
if isinstance(payload.file_url, str):
|
|
||||||
urls.append(payload.file_url.strip().strip('`'))
|
|
||||||
elif isinstance(payload.file_url, list):
|
|
||||||
urls.extend([u.strip().strip('`') for u in payload.file_url if isinstance(u, str)])
|
|
||||||
if urls:
|
if urls:
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=10) as client:
|
async with httpx.AsyncClient(timeout=10) as client:
|
||||||
@ -127,25 +122,20 @@ async def send_email(payload: SendEmailRequest = Body(...)):
|
|||||||
else:
|
else:
|
||||||
logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
|
logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
|
||||||
|
|
||||||
if payload.receipt_id:
|
if status == "OK" and payload.receipt_id:
|
||||||
try:
|
try:
|
||||||
r = await PaymentReceipt.filter(id=payload.receipt_id).first()
|
r = await PaymentReceipt.filter(id=payload.receipt_id).first()
|
||||||
if r:
|
if r:
|
||||||
|
r.extra = (r.extra or {}) | payload.model_dump()
|
||||||
|
await r.save()
|
||||||
try:
|
try:
|
||||||
inv = await r.invoice
|
inv = await r.invoice
|
||||||
if inv:
|
if inv:
|
||||||
s = str(payload.status or "").lower()
|
inv.status = "invoiced"
|
||||||
if s in {"invoiced", "success"}:
|
|
||||||
target = "invoiced"
|
|
||||||
elif s in {"refunded", "rejected", "pending"}:
|
|
||||||
target = s
|
|
||||||
else:
|
|
||||||
target = "invoiced"
|
|
||||||
inv.status = target
|
|
||||||
await inv.save()
|
await inv.save()
|
||||||
logger.info("transactions.invoice_status_updated receipt_id={} invoice_id={} status={}", payload.receipt_id, inv.id, target)
|
logger.info("transactions.invoice_mark_invoiced receipt_id={} invoice_id={}", payload.receipt_id, inv.id)
|
||||||
except Exception as e2:
|
except Exception as e2:
|
||||||
logger.warning("transactions.invoice_status_update_fail receipt_id={} err={}", payload.receipt_id, str(e2))
|
logger.warning("transactions.invoice_mark_invoiced_fail receipt_id={} err={}", payload.receipt_id, str(e2))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e))
|
logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e))
|
||||||
|
|
||||||
|
|||||||
@ -271,23 +271,20 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]):
|
|||||||
items = []
|
items = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
inv = await r.invoice
|
inv = await r.invoice
|
||||||
urls = []
|
|
||||||
if isinstance(r.extra, list):
|
|
||||||
urls = [str(u) for u in r.extra if u]
|
|
||||||
elif isinstance(r.extra, dict):
|
|
||||||
v = r.extra.get("urls")
|
|
||||||
if isinstance(v, list):
|
|
||||||
urls = [str(u) for u in v if u]
|
|
||||||
if not urls:
|
|
||||||
urls = [r.url] if r.url else []
|
|
||||||
receipts = [{"id": r.id, "url": u, "note": r.note, "verified": r.verified} for u in urls]
|
|
||||||
items.append({
|
items.append({
|
||||||
"id": r.id,
|
"id": r.id,
|
||||||
"invoice_id": getattr(inv, "id", None),
|
"invoice_id": getattr(inv, "id", None),
|
||||||
"submitted_at": r.created_at.isoformat() if r.created_at else "",
|
"submitted_at": r.created_at.isoformat() if r.created_at else "",
|
||||||
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
|
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
|
||||||
"extra": r.extra,
|
"extra": r.extra,
|
||||||
"receipts": receipts,
|
"receipts": [
|
||||||
|
{
|
||||||
|
"id": r.id,
|
||||||
|
"url": r.url,
|
||||||
|
"note": r.note,
|
||||||
|
"verified": r.verified,
|
||||||
|
}
|
||||||
|
],
|
||||||
"phone": inv.phone,
|
"phone": inv.phone,
|
||||||
"wechat": inv.wechat,
|
"wechat": inv.wechat,
|
||||||
"company_name": inv.company_name,
|
"company_name": inv.company_name,
|
||||||
@ -316,23 +313,20 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]):
|
|||||||
if not r:
|
if not r:
|
||||||
return None
|
return None
|
||||||
inv = await r.invoice
|
inv = await r.invoice
|
||||||
urls = []
|
|
||||||
if isinstance(r.extra, list):
|
|
||||||
urls = [str(u) for u in r.extra if u]
|
|
||||||
elif isinstance(r.extra, dict):
|
|
||||||
v = r.extra.get("urls")
|
|
||||||
if isinstance(v, list):
|
|
||||||
urls = [str(u) for u in v if u]
|
|
||||||
if not urls:
|
|
||||||
urls = [r.url] if r.url else []
|
|
||||||
receipts = [{"id": r.id, "url": u, "note": r.note, "verified": r.verified} for u in urls]
|
|
||||||
return {
|
return {
|
||||||
"id": r.id,
|
"id": r.id,
|
||||||
"invoice_id": getattr(inv, "id", None),
|
"invoice_id": getattr(inv, "id", None),
|
||||||
"submitted_at": r.created_at.isoformat() if r.created_at else "",
|
"submitted_at": r.created_at.isoformat() if r.created_at else "",
|
||||||
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
|
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
|
||||||
"extra": r.extra,
|
"extra": r.extra,
|
||||||
"receipts": receipts,
|
"receipts": [
|
||||||
|
{
|
||||||
|
"id": r.id,
|
||||||
|
"url": r.url,
|
||||||
|
"note": r.note,
|
||||||
|
"verified": r.verified,
|
||||||
|
}
|
||||||
|
],
|
||||||
"phone": inv.phone,
|
"phone": inv.phone,
|
||||||
"wechat": inv.wechat,
|
"wechat": inv.wechat,
|
||||||
"company_name": inv.company_name,
|
"company_name": inv.company_name,
|
||||||
|
|||||||
@ -127,7 +127,7 @@ class UserValuationController:
|
|||||||
inheritor_ages=valuation.inheritor_ages,
|
inheritor_ages=valuation.inheritor_ages,
|
||||||
inheritor_age_count=valuation.inheritor_age_count,
|
inheritor_age_count=valuation.inheritor_age_count,
|
||||||
inheritor_certificates=valuation.inheritor_certificates,
|
inheritor_certificates=valuation.inheritor_certificates,
|
||||||
heritage_level=getattr(valuation, "heritage_level", None),
|
heritage_level=valuation.heritage_level,
|
||||||
heritage_asset_level=valuation.heritage_asset_level,
|
heritage_asset_level=valuation.heritage_asset_level,
|
||||||
patent_application_no=valuation.patent_application_no,
|
patent_application_no=valuation.patent_application_no,
|
||||||
patent_remaining_years=valuation.patent_remaining_years,
|
patent_remaining_years=valuation.patent_remaining_years,
|
||||||
@ -197,7 +197,7 @@ class UserValuationController:
|
|||||||
inheritor_ages=valuation.inheritor_ages,
|
inheritor_ages=valuation.inheritor_ages,
|
||||||
inheritor_age_count=valuation.inheritor_age_count,
|
inheritor_age_count=valuation.inheritor_age_count,
|
||||||
inheritor_certificates=valuation.inheritor_certificates,
|
inheritor_certificates=valuation.inheritor_certificates,
|
||||||
heritage_level=getattr(valuation, "heritage_level", None),
|
heritage_level=valuation.heritage_level,
|
||||||
heritage_asset_level=valuation.heritage_asset_level,
|
heritage_asset_level=valuation.heritage_asset_level,
|
||||||
patent_application_no=valuation.patent_application_no,
|
patent_application_no=valuation.patent_application_no,
|
||||||
patent_remaining_years=valuation.patent_remaining_years,
|
patent_remaining_years=valuation.patent_remaining_years,
|
||||||
|
|||||||
@ -813,7 +813,7 @@ class ValuationController:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
update_data = {"status": "success", "audited_at": datetime.now(), "updated_at": datetime.now()}
|
update_data = {"status": "pending", "audited_at": datetime.now(), "updated_at": datetime.now()}
|
||||||
if admin_notes:
|
if admin_notes:
|
||||||
update_data["admin_notes"] = admin_notes
|
update_data["admin_notes"] = admin_notes
|
||||||
|
|
||||||
@ -847,18 +847,6 @@ class ValuationController:
|
|||||||
out = ValuationAssessmentOut.model_validate(valuation)
|
out = ValuationAssessmentOut.model_validate(valuation)
|
||||||
return await self._attach_user_phone(out)
|
return await self._attach_user_phone(out)
|
||||||
|
|
||||||
async def update_calc(self, valuation_id: int, data: ValuationAssessmentUpdate) -> Optional[ValuationAssessmentOut]:
|
|
||||||
valuation = await self.model.filter(id=valuation_id, is_active=True).first()
|
|
||||||
if not valuation:
|
|
||||||
return None
|
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
|
||||||
valuation.status ="pending"
|
|
||||||
if update_data:
|
|
||||||
await valuation.update_from_dict(update_data)
|
|
||||||
await valuation.save()
|
|
||||||
out = ValuationAssessmentOut.model_validate(valuation)
|
|
||||||
return await self._attach_user_phone(out)
|
|
||||||
|
|
||||||
async def _attach_user_phone(self, out: ValuationAssessmentOut) -> ValuationAssessmentOut:
|
async def _attach_user_phone(self, out: ValuationAssessmentOut) -> ValuationAssessmentOut:
|
||||||
user = await AppUser.filter(id=out.user_id).first()
|
user = await AppUser.filter(id=out.user_id).first()
|
||||||
out.user_phone = getattr(user, "phone", None) if user else None
|
out.user_phone = getattr(user, "phone", None) if user else None
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional, List, Union, Dict, Any
|
from typing import Optional, List
|
||||||
from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator
|
from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator
|
||||||
|
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ class UpdateType(BaseModel):
|
|||||||
class PaymentReceiptCreate(BaseModel):
|
class PaymentReceiptCreate(BaseModel):
|
||||||
url: str = Field(..., min_length=1, max_length=512)
|
url: str = Field(..., min_length=1, max_length=512)
|
||||||
note: Optional[str] = Field(None, max_length=256)
|
note: Optional[str] = Field(None, max_length=256)
|
||||||
extra: Optional[Union[List[str], Dict[str, Any]]] = None
|
extra: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
class PaymentReceiptOut(BaseModel):
|
class PaymentReceiptOut(BaseModel):
|
||||||
@ -124,7 +124,7 @@ class PaymentReceiptOut(BaseModel):
|
|||||||
note: Optional[str]
|
note: Optional[str]
|
||||||
verified: bool
|
verified: bool
|
||||||
created_at: str
|
created_at: str
|
||||||
extra: Optional[Union[List[str], Dict[str, Any]]] = None
|
extra: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
class AppCreateInvoiceWithReceipt(BaseModel):
|
class AppCreateInvoiceWithReceipt(BaseModel):
|
||||||
@ -133,7 +133,7 @@ class AppCreateInvoiceWithReceipt(BaseModel):
|
|||||||
invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$")
|
invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$")
|
||||||
# 兼容前端索引字段:"0"→normal,"1"→special
|
# 兼容前端索引字段:"0"→normal,"1"→special
|
||||||
invoiceTypeIndex: Optional[str] = None
|
invoiceTypeIndex: Optional[str] = None
|
||||||
receipt_url: Optional[Union[str, List[str]]] = Field(None)
|
receipt_url: Optional[str] = Field(None, max_length=512)
|
||||||
receipt_urls: Optional[List[str]] = None
|
receipt_urls: Optional[List[str]] = None
|
||||||
note: Optional[str] = Field(None, max_length=256)
|
note: Optional[str] = Field(None, max_length=256)
|
||||||
|
|
||||||
@ -145,26 +145,14 @@ class AppCreateInvoiceWithReceipt(BaseModel):
|
|||||||
@field_validator('receipt_url', mode='before')
|
@field_validator('receipt_url', mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
def _clean_receipt_url(cls, v):
|
def _clean_receipt_url(cls, v):
|
||||||
if isinstance(v, list):
|
if isinstance(v, list) and v:
|
||||||
cleaned: List[str] = []
|
v = v[0]
|
||||||
for item in v:
|
|
||||||
if isinstance(item, str):
|
|
||||||
s = item.strip()
|
|
||||||
if s.startswith('`') and s.endswith('`'):
|
|
||||||
s = s[1:-1].strip()
|
|
||||||
while s.endswith('\\'):
|
|
||||||
s = s[:-1].strip()
|
|
||||||
if s:
|
|
||||||
cleaned.append(s)
|
|
||||||
return cleaned or None
|
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
s = v.strip()
|
s = v.strip()
|
||||||
if s.startswith('`') and s.endswith('`'):
|
if s.startswith('`') and s.endswith('`'):
|
||||||
s = s[1:-1].strip()
|
s = s[1:-1].strip()
|
||||||
while s.endswith('\\'):
|
|
||||||
s = s[:-1].strip()
|
|
||||||
return s or None
|
return s or None
|
||||||
return None
|
return v
|
||||||
|
|
||||||
@field_validator('receipt_urls', mode='before')
|
@field_validator('receipt_urls', mode='before')
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -174,18 +162,10 @@ class AppCreateInvoiceWithReceipt(BaseModel):
|
|||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
v = [v]
|
v = [v]
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
seen = set()
|
|
||||||
cleaned = []
|
cleaned = []
|
||||||
for item in v:
|
for item in v:
|
||||||
if isinstance(item, str):
|
if isinstance(item, str) and item.strip():
|
||||||
s = item.strip()
|
cleaned.append(item.strip())
|
||||||
if s.startswith('`') and s.endswith('`'):
|
|
||||||
s = s[1:-1].strip()
|
|
||||||
while s.endswith('\\'):
|
|
||||||
s = s[:-1].strip()
|
|
||||||
if s and s not in seen:
|
|
||||||
seen.add(s)
|
|
||||||
cleaned.append(s)
|
|
||||||
return cleaned or None
|
return cleaned or None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,6 @@ class SendEmailRequest(BaseModel):
|
|||||||
subject: Optional[str] = Field(None, description="邮件主题")
|
subject: Optional[str] = Field(None, description="邮件主题")
|
||||||
body: str = Field(..., description="文案内容")
|
body: str = Field(..., description="文案内容")
|
||||||
file_urls: Optional[List[str]] = Field(None, description="附件URL列表")
|
file_urls: Optional[List[str]] = Field(None, description="附件URL列表")
|
||||||
file_url: Optional[Union[str, List[str]]] = Field(None, description="附件URL或列表(兼容前端传参)")
|
|
||||||
status: Optional[str] = Field(None, description="开票状态标记: success|invoiced|rejected|refunded")
|
|
||||||
|
|
||||||
|
|
||||||
class SendEmailBody(BaseModel):
|
class SendEmailBody(BaseModel):
|
||||||
|
|||||||
@ -193,7 +193,7 @@ class ValuationAssessmentOut(ValuationAssessmentBase):
|
|||||||
status: str = Field(..., description="评估状态")
|
status: str = Field(..., description="评估状态")
|
||||||
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
||||||
created_at: datetime = Field(..., description="创建时间")
|
created_at: datetime = Field(..., description="创建时间")
|
||||||
updated_at: Optional[datetime] = Field(None, description="更新时间")
|
updated_at: datetime = Field(..., description="更新时间")
|
||||||
audited_at: Optional[datetime] = Field(None, description="审核时间")
|
audited_at: Optional[datetime] = Field(None, description="审核时间")
|
||||||
is_active: bool = Field(..., description="是否激活")
|
is_active: bool = Field(..., description="是否激活")
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ class UserValuationOut(ValuationAssessmentBase):
|
|||||||
status: str = Field(..., description="评估状态")
|
status: str = Field(..., description="评估状态")
|
||||||
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
||||||
created_at: datetime = Field(..., description="创建时间")
|
created_at: datetime = Field(..., description="创建时间")
|
||||||
updated_at: Optional[datetime] = Field(None, description="更新时间")
|
updated_at: datetime = Field(..., description="更新时间")
|
||||||
is_active: Optional[bool] = Field(None, description="是否激活")
|
is_active: Optional[bool] = Field(None, description="是否激活")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@ -290,7 +290,7 @@ class UserValuationDetail(ValuationAssessmentBase):
|
|||||||
status: str = Field(..., description="评估状态")
|
status: str = Field(..., description="评估状态")
|
||||||
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
admin_notes: Optional[str] = Field(None, description="管理员备注")
|
||||||
created_at: datetime = Field(..., description="创建时间")
|
created_at: datetime = Field(..., description="创建时间")
|
||||||
updated_at: Optional[datetime] = Field(None, description="更新时间")
|
updated_at: datetime = Field(..., description="更新时间")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@ -422,7 +422,7 @@ class ValuationCalculationStepOut(ValuationCalculationStepBase):
|
|||||||
id: int = Field(..., description="主键ID")
|
id: int = Field(..., description="主键ID")
|
||||||
valuation_id: int = Field(..., description="关联的估值评估ID")
|
valuation_id: int = Field(..., description="关联的估值评估ID")
|
||||||
created_at: datetime = Field(..., description="创建时间")
|
created_at: datetime = Field(..., description="创建时间")
|
||||||
updated_at: Optional[datetime] = Field(None, description="更新时间")
|
updated_at: datetime = Field(..., description="更新时间")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# nginx
|
nginx
|
||||||
|
|
||||||
python run.py
|
python run.py
|
||||||
BIN
web1/dist.zip
BIN
web1/dist.zip
Binary file not shown.
9
估值字段.txt
9
估值字段.txt
@ -37,8 +37,8 @@
|
|||||||
|
|
||||||
|
|
||||||
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||||
docker build -t zfc931912343/guzhi-fastapi-admin:v3.1 .
|
docker build -t zfc931912343/guzhi-fastapi-admin:v2.6 .
|
||||||
docker push zfc931912343/guzhi-fastapi-admin:v3.1
|
docker push zfc931912343/guzhi-fastapi-admin:v2.6
|
||||||
|
|
||||||
|
|
||||||
# 运行容器
|
# 运行容器
|
||||||
@ -73,7 +73,6 @@ docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.4 &&
|
|||||||
|
|
||||||
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:80 -v ~/guzhi-data-dev/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7
|
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:80 -v ~/guzhi-data-dev/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7
|
||||||
|
|
||||||
1
|
|
||||||
|
|
||||||
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.1 && docker rm -f guzhi_pro && docker run -itd --name=guzhi_pro -p 8080:80 -v ~/guzhi-data/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.1
|
|
||||||
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.2 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:9999 -v ~/guzhi-data/static:/opt/vue-fastapi-admin/app/static --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.2
|
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5 && docker rm -f guzhi_pro && docker run -itd --name=guzhi_pro -p 8080:80 -v ~/guzhi-data/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5
|
||||||
Loading…
x
Reference in New Issue
Block a user