diff --git a/Dockerfile b/Dockerfile index b5af105..ac6a314 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,14 @@ -FROM node:18.12.0-alpine3.16 AS web +FROM node:18-alpine AS web WORKDIR /opt/vue-fastapi-admin 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 diff --git a/app/api/v1/app_invoices/app_invoices.py b/app/api/v1/app_invoices/app_invoices.py index e644370..ece7824 100644 --- a/app/api/v1/app_invoices/app_invoices.py +++ b/app/api/v1/app_invoices/app_invoices.py @@ -117,13 +117,23 @@ async def create_with_receipt(payload: AppCreateInvoiceWithReceipt, current_user ) inv = await invoice_controller.create(inv_data) if payload.receipt_urls: - receipts = [] - for url in payload.receipt_urls: - receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=url, note=payload.note)) - detail = await invoice_controller.get_receipt_by_id(receipt.id) - if detail: - receipts.append(detail) - return Success(data={"invoice_id": inv.id, "receipts": receipts}, msg="创建并上传成功") + urls = payload.receipt_urls + main_url = urls[0] if isinstance(urls, list) and urls else None + 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 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: 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) diff --git a/app/api/v1/app_valuations/app_valuations.py b/app/api/v1/app_valuations/app_valuations.py index 342362f..71a9414 100644 --- a/app/api/v1/app_valuations/app_valuations.py +++ b/app/api/v1/app_valuations/app_valuations.py @@ -307,19 +307,13 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate except Exception: pass - # 步骤4:最后更新状态为成功 + # 步骤4:计算完成,保持状态为 pending,等待后台审核 try: - result = await valuation_controller.update_calc( - valuation_id, - ValuationAssessmentUpdate( - status='success' - ) - ) - logger.info("valuation.status_updated valuation_id={} status=success", valuation_id) - except Exception as e: - logger.warning("valuation.failed_to_update_status valuation_id={} err={}", valuation_id, repr(e)) - # 即使状态更新失败,也尝试获取结果用于日志 result = await valuation_controller.get_by_id(valuation_id) + logger.info("valuation.calc_finished valuation_id={} status=pending", valuation_id) + except Exception as e: + logger.warning("valuation.failed_to_fetch_after_calc valuation_id={} err={}", valuation_id, repr(e)) + result = None logger.info("valuation.background_calc_success user_id={} valuation_id={}", user_id, valuation_id) diff --git a/app/api/v1/transactions/transactions.py b/app/api/v1/transactions/transactions.py index 10e84ce..1bd6170 100644 --- a/app/api/v1/transactions/transactions.py +++ b/app/api/v1/transactions/transactions.py @@ -85,6 +85,11 @@ async def send_email(payload: SendEmailRequest = Body(...)): raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析") if payload.file_urls: 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: try: async with httpx.AsyncClient(timeout=10) as client: @@ -122,20 +127,25 @@ async def send_email(payload: SendEmailRequest = Body(...)): else: logger.error("transactions.email_send_fail email={} err={}", payload.email, error) - if status == "OK" and payload.receipt_id: + if payload.receipt_id: try: r = await PaymentReceipt.filter(id=payload.receipt_id).first() if r: - r.extra = (r.extra or {}) | payload.model_dump() - await r.save() try: inv = await r.invoice if inv: - inv.status = "invoiced" + s = str(payload.status or "").lower() + if s in {"invoiced", "success"}: + target = "invoiced" + elif s in {"refunded", "rejected", "pending"}: + target = s + else: + target = "invoiced" + inv.status = target await inv.save() - logger.info("transactions.invoice_mark_invoiced receipt_id={} invoice_id={}", payload.receipt_id, inv.id) + logger.info("transactions.invoice_status_updated receipt_id={} invoice_id={} status={}", payload.receipt_id, inv.id, target) except Exception as e2: - logger.warning("transactions.invoice_mark_invoiced_fail receipt_id={} err={}", payload.receipt_id, str(e2)) + logger.warning("transactions.invoice_status_update_fail receipt_id={} err={}", payload.receipt_id, str(e2)) except Exception as e: logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e)) diff --git a/app/controllers/invoice.py b/app/controllers/invoice.py index f7b1f7b..a525eff 100644 --- a/app/controllers/invoice.py +++ b/app/controllers/invoice.py @@ -271,20 +271,23 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]): items = [] for r in rows: 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({ "id": r.id, "invoice_id": getattr(inv, "id", None), "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 "", "extra": r.extra, - "receipts": [ - { - "id": r.id, - "url": r.url, - "note": r.note, - "verified": r.verified, - } - ], + "receipts": receipts, "phone": inv.phone, "wechat": inv.wechat, "company_name": inv.company_name, @@ -313,20 +316,23 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]): if not r: return None 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 { "id": r.id, "invoice_id": getattr(inv, "id", None), "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 "", "extra": r.extra, - "receipts": [ - { - "id": r.id, - "url": r.url, - "note": r.note, - "verified": r.verified, - } - ], + "receipts": receipts, "phone": inv.phone, "wechat": inv.wechat, "company_name": inv.company_name, diff --git a/app/controllers/valuation.py b/app/controllers/valuation.py index ca99fb1..023b9e3 100644 --- a/app/controllers/valuation.py +++ b/app/controllers/valuation.py @@ -813,7 +813,7 @@ class ValuationController: return None from datetime import datetime - update_data = {"status": "pending", "audited_at": datetime.now(), "updated_at": datetime.now()} + update_data = {"status": "success", "audited_at": datetime.now(), "updated_at": datetime.now()} if admin_notes: update_data["admin_notes"] = admin_notes @@ -852,6 +852,7 @@ class ValuationController: 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() diff --git a/app/schemas/invoice.py b/app/schemas/invoice.py index bc02a28..5102fd6 100644 --- a/app/schemas/invoice.py +++ b/app/schemas/invoice.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Union, Dict, Any from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator @@ -115,7 +115,7 @@ class UpdateType(BaseModel): class PaymentReceiptCreate(BaseModel): url: str = Field(..., min_length=1, max_length=512) note: Optional[str] = Field(None, max_length=256) - extra: Optional[dict] = None + extra: Optional[Union[List[str], Dict[str, Any]]] = None class PaymentReceiptOut(BaseModel): @@ -124,7 +124,7 @@ class PaymentReceiptOut(BaseModel): note: Optional[str] verified: bool created_at: str - extra: Optional[dict] = None + extra: Optional[Union[List[str], Dict[str, Any]]] = None class AppCreateInvoiceWithReceipt(BaseModel): @@ -133,7 +133,7 @@ class AppCreateInvoiceWithReceipt(BaseModel): 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) + receipt_url: Optional[Union[str, List[str]]] = Field(None) receipt_urls: Optional[List[str]] = None note: Optional[str] = Field(None, max_length=256) @@ -145,14 +145,26 @@ class AppCreateInvoiceWithReceipt(BaseModel): @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, list): + cleaned: List[str] = [] + 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): s = v.strip() if s.startswith('`') and s.endswith('`'): s = s[1:-1].strip() + while s.endswith('\\'): + s = s[:-1].strip() return s or None - return v + return None @field_validator('receipt_urls', mode='before') @classmethod @@ -162,10 +174,18 @@ class AppCreateInvoiceWithReceipt(BaseModel): if isinstance(v, str): v = [v] if isinstance(v, list): + seen = set() cleaned = [] for item in v: - if isinstance(item, str) and item.strip(): - cleaned.append(item.strip()) + 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 and s not in seen: + seen.add(s) + cleaned.append(s) return cleaned or None return None diff --git a/app/schemas/transactions.py b/app/schemas/transactions.py index 3111cd5..847a65e 100644 --- a/app/schemas/transactions.py +++ b/app/schemas/transactions.py @@ -8,6 +8,8 @@ class SendEmailRequest(BaseModel): subject: Optional[str] = Field(None, description="邮件主题") body: str = Field(..., description="文案内容") 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): diff --git a/deploy/entrypoint.sh b/deploy/entrypoint.sh index 3aec06e..aa297d4 100644 --- a/deploy/entrypoint.sh +++ b/deploy/entrypoint.sh @@ -1,5 +1,6 @@ #!/bin/sh set -e -nginx +# nginx + python run.py \ No newline at end of file diff --git a/web1/src/views/user-center/components/CorporateTransfer.vue b/web1/src/views/user-center/components/CorporateTransfer.vue index 4e8bdf8..b9eb63c 100644 --- a/web1/src/views/user-center/components/CorporateTransfer.vue +++ b/web1/src/views/user-center/components/CorporateTransfer.vue @@ -185,16 +185,13 @@
- 确认上传 - +
diff --git a/估值字段.txt b/估值字段.txt index 9a077f7..8566973 100644 --- a/估值字段.txt +++ b/估值字段.txt @@ -37,8 +37,8 @@ export DOCKER_DEFAULT_PLATFORM=linux/amd64 -docker build -t zfc931912343/guzhi-fastapi-admin:v2.7 . -docker push zfc931912343/guzhi-fastapi-admin:v2.7 +docker build -t zfc931912343/guzhi-fastapi-admin:v3.1 . +docker push zfc931912343/guzhi-fastapi-admin:v3.1 # 运行容器 @@ -73,6 +73,7 @@ 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 +1 - - 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 \ No newline at end of file + 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 \ No newline at end of file