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")