修复邮件发送时的收件方地址验证问题,添加域名解析检查 更新评估状态字段值从"approved"为"pending"以保持一致性 修改发票创建接口以支持无凭证上传的情况 添加用户管理接口的时间范围查询功能 更新SMTP和短信服务的默认配置
156 lines
5.6 KiB
Python
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")
|