guzhi/app/api/v1/transactions/transactions.py
邹方成 5ca0152c55 feat: 更新邮件客户端和评估状态处理逻辑
修复邮件发送时的收件方地址验证问题,添加域名解析检查
更新评估状态字段值从"approved"为"pending"以保持一致性
修改发票创建接口以支持无凭证上传的情况
添加用户管理接口的时间范围查询功能
更新SMTP和短信服务的默认配置
2025-11-27 15:04:37 +08:00

156 lines
5.6 KiB
Python

from fastapi import APIRouter, Query, UploadFile, File, HTTPException
from typing import Optional
from app.schemas.base import Success, SuccessExtra, PageResponse, BasicResponse
from app.schemas.invoice import PaymentReceiptOut
from app.controllers.invoice import invoice_controller
from app.models.invoice import PaymentReceipt
from fastapi import Body
from app.schemas.transactions import SendEmailRequest, SendEmailResponse
from app.services.email_client import email_client
from app.models.invoice import EmailSendLog
from app.settings.config import settings
from app.log.log import logger
import httpx
transactions_router = APIRouter(tags=["交易管理"])
@transactions_router.get("/receipts", summary="对公转账记录列表", response_model=PageResponse[PaymentReceiptOut])
async def list_receipts(
phone: Optional[str] = Query(None),
wechat: Optional[str] = Query(None),
company_name: Optional[str] = Query(None),
tax_number: Optional[str] = Query(None),
status: Optional[str] = Query(None),
ticket_type: Optional[str] = Query(None),
invoice_type: Optional[str] = Query(None),
created_at: Optional[list[int]] = Query(None),
submitted_start: Optional[str] = Query(None),
submitted_end: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
):
"""
对公转账记录列表(含提交时间、凭证与关联企业信息)
"""
result = await invoice_controller.list_receipts(
page=page,
page_size=page_size,
phone=phone,
wechat=wechat,
company_name=company_name,
tax_number=tax_number,
status=status,
ticket_type=ticket_type,
invoice_type=invoice_type,
created_at=created_at,
submitted_start=submitted_start,
submitted_end=submitted_end,
)
return SuccessExtra(
data=result["items"],
total=result["total"],
page=result["page"],
page_size=result["page_size"],
msg="获取成功",
)
@transactions_router.get("/receipts/{id}", summary="对公转账记录详情", response_model=BasicResponse[PaymentReceiptOut])
async def get_receipt_detail(id: int):
"""
对公转账记录详情
"""
data = await invoice_controller.get_receipt_by_id(id)
return Success(data=data or {}, msg="获取成功" if data else "未找到")
@transactions_router.post("/send-email", summary="发送邮件", response_model=BasicResponse[SendEmailResponse])
async def send_email(payload: SendEmailRequest = Body(...)):
attachments = []
urls = []
try:
domain = payload.email.split("@")[-1]
import dns.resolver
try:
dns.resolver.resolve(domain, "MX")
except Exception:
dns.resolver.resolve(domain, "A")
except Exception:
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 urls:
try:
async with httpx.AsyncClient(timeout=10) as client:
for u in urls:
r = await client.get(u)
r.raise_for_status()
attachments.append((r.content, u.split("/")[-1]))
except Exception as e:
raise HTTPException(status_code=400, detail=f"附件下载失败: {e}")
logger.info("transactions.email_send_start email={} subject={}", payload.email, payload.subject or "")
try:
result = email_client.send_many(payload.email, payload.subject, payload.body, attachments)
except RuntimeError as e:
result = {"status": "FAIL", "error": str(e)}
except Exception as e:
result = {"status": "FAIL", "error": str(e)}
body_summary = payload.body[:500]
status = result.get("status")
error = result.get("error")
first_name = attachments[0][1] if attachments else None
first_url = urls[0] if urls else None
log = await EmailSendLog.create(
email=payload.email,
subject=payload.subject,
body_summary=body_summary,
file_name=first_name,
file_url=first_url,
status=status,
error=error,
)
if status == "OK":
logger.info("transactions.email_send_ok email={}", payload.email)
else:
logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
if status == "OK" and 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()
except Exception as e:
logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e))
return Success(data={"status": status, "log_id": log.id, "error": error}, msg="发送成功" if status == "OK" else "发送失败")
@transactions_router.get("/smtp-config", summary="SMTP配置状态", response_model=BasicResponse[dict])
async def smtp_config_status():
configured = all([
settings.SMTP_HOST,
settings.SMTP_PORT,
settings.SMTP_FROM,
settings.SMTP_USERNAME,
settings.SMTP_PASSWORD,
])
data = {
"host": bool(settings.SMTP_HOST),
"port": bool(settings.SMTP_PORT),
"from": bool(settings.SMTP_FROM),
"username": bool(settings.SMTP_USERNAME),
"password": bool(settings.SMTP_PASSWORD),
"tls": settings.SMTP_TLS,
"configured": configured,
}
return Success(data=data, msg="OK")