feat(交易记录): 新增交易记录管理页面与API接口 feat(上传): 添加统一上传接口支持自动识别文件类型 feat(用户管理): 为用户模型添加备注字段并更新相关接口 feat(邮件): 实现SMTP邮件发送功能并添加测试脚本 feat(短信): 增强短信服务配置灵活性与日志记录 fix(发票): 修复发票列表时间筛选功能 fix(nginx): 调整上传大小限制与超时配置 docs: 添加多个功能模块的说明文档 docs(估值): 补充估值计算流程与API提交数据说明 chore: 更新依赖与Docker镜像版本
131 lines
4.8 KiB
Python
131 lines
4.8 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.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(data: SendEmailRequest, file: Optional[UploadFile] = File(None)):
|
|
if not data.email or "@" not in data.email:
|
|
raise HTTPException(status_code=422, detail="邮箱格式不正确")
|
|
if not data.body:
|
|
raise HTTPException(status_code=422, detail="文案内容不能为空")
|
|
|
|
file_bytes = None
|
|
file_name = None
|
|
if file is not None:
|
|
file_bytes = await file.read()
|
|
file_name = file.filename
|
|
elif data.file_url:
|
|
try:
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
r = await client.get(data.file_url)
|
|
r.raise_for_status()
|
|
file_bytes = r.content
|
|
file_name = data.file_url.split("/")[-1]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"附件下载失败: {e}")
|
|
|
|
logger.info("transactions.email_send_start email={} subject={}", data.email, data.subject or "")
|
|
result = email_client.send(data.email, data.subject, data.body, file_bytes, file_name, getattr(file, "content_type", None))
|
|
|
|
body_summary = data.body[:500]
|
|
status = result.get("status")
|
|
error = result.get("error")
|
|
log = await EmailSendLog.create(
|
|
email=data.email,
|
|
subject=data.subject,
|
|
body_summary=body_summary,
|
|
file_name=file_name,
|
|
file_url=data.file_url,
|
|
status=status,
|
|
error=error,
|
|
)
|
|
if status == "OK":
|
|
logger.info("transactions.email_send_ok email={}", data.email)
|
|
else:
|
|
logger.error("transactions.email_send_fail email={} err={}", data.email, error)
|
|
|
|
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") |