Files
tyapi-server/internal/infrastructure/http/handlers/finance_handler.go
2025-09-01 18:29:59 +08:00

946 lines
30 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//nolint:unused
package handlers
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/application/finance"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
_ "tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/shared/interfaces"
)
// FinanceHandler 财务HTTP处理器
type FinanceHandler struct {
appService finance.FinanceApplicationService
invoiceAppService finance.InvoiceApplicationService
adminInvoiceAppService finance.AdminInvoiceApplicationService
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
}
// NewFinanceHandler 创建财务HTTP处理器
func NewFinanceHandler(
appService finance.FinanceApplicationService,
invoiceAppService finance.InvoiceApplicationService,
adminInvoiceAppService finance.AdminInvoiceApplicationService,
responseBuilder interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
) *FinanceHandler {
return &FinanceHandler{
appService: appService,
invoiceAppService: invoiceAppService,
adminInvoiceAppService: adminInvoiceAppService,
responseBuilder: responseBuilder,
validator: validator,
logger: logger,
}
}
// GetWallet 获取钱包信息
// @Summary 获取钱包信息
// @Description 获取当前用户的钱包详细信息
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} responses.WalletResponse "获取钱包信息成功"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "钱包不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet [get]
func (h *FinanceHandler) GetWallet(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
query := &queries.GetWalletInfoQuery{UserID: userID}
result, err := h.appService.GetWallet(c.Request.Context(), query)
if err != nil {
h.logger.Error("获取钱包信息失败",
zap.String("user_id", userID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "获取钱包信息成功")
}
// GetUserWalletTransactions 获取用户钱包交易记录
// @Summary 获取用户钱包交易记录
// @Description 获取当前用户的钱包交易记录列表,支持分页和筛选
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
// @Param transaction_id query string false "交易ID"
// @Param product_name query string false "产品名称"
// @Param min_amount query string false "最小金额"
// @Param max_amount query string false "最大金额"
// @Success 200 {object} responses.WalletTransactionListResponse "获取成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/transactions [get]
func (h *FinanceHandler) GetUserWalletTransactions(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 交易ID筛选
if transactionId := c.Query("transaction_id"); transactionId != "" {
filters["transaction_id"] = transactionId
}
// 产品名称筛选
if productName := c.Query("product_name"); productName != "" {
filters["product_name"] = productName
}
// 金额范围筛选
if minAmount := c.Query("min_amount"); minAmount != "" {
filters["min_amount"] = minAmount
}
if maxAmount := c.Query("max_amount"); maxAmount != "" {
filters["max_amount"] = maxAmount
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetUserWalletTransactions(c.Request.Context(), userID, filters, options)
if err != nil {
h.logger.Error("获取用户钱包交易记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取钱包交易记录失败")
return
}
h.responseBuilder.Success(c, result, "获取钱包交易记录成功")
}
// getIntQuery 获取整数查询参数
func (h *FinanceHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
if value := c.Query(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
return intValue
}
}
return defaultValue
}
// HandleAlipayCallback 处理支付宝支付回调
// @Summary 支付宝支付回调
// @Description 处理支付宝异步支付通知
// @Tags 支付管理
// @Accept application/x-www-form-urlencoded
// @Produce text/plain
// @Success 200 {string} string "success"
// @Failure 400 {string} string "fail"
// @Router /api/v1/finance/alipay/callback [post]
func (h *FinanceHandler) HandleAlipayCallback(c *gin.Context) {
// 记录回调请求信息
h.logger.Info("收到支付宝回调请求",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("remote_addr", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
)
// 通过应用服务处理支付宝回调
err := h.appService.HandleAlipayCallback(c.Request.Context(), c.Request)
if err != nil {
h.logger.Error("支付宝回调处理失败", zap.Error(err))
c.String(400, "fail")
return
}
// 返回成功响应支付宝要求返回success
c.String(200, "success")
}
// HandleAlipayReturn 处理支付宝同步回调
// @Summary 支付宝同步回调
// @Description 处理支付宝同步支付通知,跳转到前端成功页面
// @Tags 支付管理
// @Accept application/x-www-form-urlencoded
// @Produce text/html
// @Success 200 {string} string "支付成功页面"
// @Failure 400 {string} string "支付失败页面"
// @Router /api/v1/finance/alipay/return [get]
func (h *FinanceHandler) HandleAlipayReturn(c *gin.Context) {
// 记录同步回调请求信息
h.logger.Info("收到支付宝同步回调请求",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("remote_addr", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
)
// 获取查询参数
outTradeNo := c.Query("out_trade_no")
tradeNo := c.Query("trade_no")
totalAmount := c.Query("total_amount")
h.logger.Info("支付宝同步回调参数",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", tradeNo),
zap.String("total_amount", totalAmount),
)
// 验证必要参数
if outTradeNo == "" {
h.logger.Error("支付宝同步回调缺少商户订单号")
h.redirectToFailPage(c, "", "缺少商户订单号")
return
}
// 通过应用服务处理同步回调,查询订单状态
orderStatus, err := h.appService.HandleAlipayReturn(c.Request.Context(), outTradeNo)
if err != nil {
h.logger.Error("支付宝同步回调处理失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err))
h.redirectToFailPage(c, outTradeNo, "订单处理失败")
return
}
// 根据环境确定前端域名
frontendDomain := "https://console.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
// 根据订单状态跳转到相应页面
switch orderStatus {
case "TRADE_SUCCESS":
// 支付成功,跳转到前端成功页面
successURL := fmt.Sprintf("%s/finance/wallet/success?out_trade_no=%s&trade_no=%s&amount=%s",
frontendDomain, outTradeNo, tradeNo, totalAmount)
c.Redirect(http.StatusFound, successURL)
case "WAIT_BUYER_PAY":
// 支付处理中,跳转到处理中页面
h.redirectToProcessingPage(c, outTradeNo, totalAmount)
default:
// 支付失败或取消,跳转到前端失败页面
h.redirectToFailPage(c, outTradeNo, orderStatus)
}
}
// redirectToFailPage 跳转到失败页面
func (h *FinanceHandler) redirectToFailPage(c *gin.Context, outTradeNo, reason string) {
frontendDomain := "https://console.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
failURL := fmt.Sprintf("%s/finance/wallet/fail?out_trade_no=%s&reason=%s",
frontendDomain, outTradeNo, reason)
c.Redirect(http.StatusFound, failURL)
}
// redirectToProcessingPage 跳转到处理中页面
func (h *FinanceHandler) redirectToProcessingPage(c *gin.Context, outTradeNo, amount string) {
frontendDomain := "https://console.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
processingURL := fmt.Sprintf("%s/finance/wallet/processing?out_trade_no=%s&amount=%s",
frontendDomain, outTradeNo, amount)
c.Redirect(http.StatusFound, processingURL)
}
// CreateAlipayRecharge 创建支付宝充值订单
// @Summary 创建支付宝充值订单
// @Description 创建支付宝充值订单并返回支付链接
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body commands.CreateAlipayRechargeCommand true "充值请求"
// @Success 200 {object} responses.AlipayRechargeOrderResponse "创建充值订单成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/alipay-recharge [post]
func (h *FinanceHandler) CreateAlipayRecharge(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
var cmd commands.CreateAlipayRechargeCommand
cmd.UserID = userID
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
// 调用应用服务进行完整的业务流程编排
result, err := h.appService.CreateAlipayRechargeOrder(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("创建支付宝充值订单失败",
zap.String("user_id", userID),
zap.String("amount", cmd.Amount),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, "创建支付宝充值订单失败: "+err.Error())
return
}
h.logger.Info("支付宝充值订单创建成功",
zap.String("user_id", userID),
zap.String("out_trade_no", result.OutTradeNo),
zap.String("amount", cmd.Amount),
zap.String("platform", cmd.Platform),
)
// 返回支付链接和订单信息
h.responseBuilder.Success(c, result, "支付宝充值订单创建成功")
}
// TransferRecharge 管理员对公转账充值
func (h *FinanceHandler) TransferRecharge(c *gin.Context) {
var cmd commands.TransferRechargeCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
if cmd.UserID == "" {
h.responseBuilder.BadRequest(c, "缺少用户ID")
return
}
result, err := h.appService.TransferRecharge(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("对公转账充值失败",
zap.String("user_id", cmd.UserID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "对公转账充值成功")
}
// GiftRecharge 管理员赠送充值
func (h *FinanceHandler) GiftRecharge(c *gin.Context) {
var cmd commands.GiftRechargeCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
if cmd.UserID == "" {
h.responseBuilder.BadRequest(c, "缺少用户ID")
return
}
result, err := h.appService.GiftRecharge(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("赠送充值失败",
zap.String("user_id", cmd.UserID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "赠送充值成功")
}
// GetUserRechargeRecords 用户获取自己充值记录分页
func (h *FinanceHandler) GetUserRechargeRecords(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 充值类型筛选
if rechargeType := c.Query("recharge_type"); rechargeType != "" {
filters["recharge_type"] = rechargeType
}
// 状态筛选
if status := c.Query("status"); status != "" {
filters["status"] = status
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetUserRechargeRecords(c.Request.Context(), userID, filters, options)
if err != nil {
h.logger.Error("获取用户充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值记录失败")
return
}
h.responseBuilder.Success(c, result, "获取充值记录成功")
}
// GetAdminRechargeRecords 管理员获取充值记录分页
func (h *FinanceHandler) GetAdminRechargeRecords(c *gin.Context) {
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 用户ID筛选
if userID := c.Query("user_id"); userID != "" {
filters["user_id"] = userID
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 充值类型筛选
if rechargeType := c.Query("recharge_type"); rechargeType != "" {
filters["recharge_type"] = rechargeType
}
// 状态筛选
if status := c.Query("status"); status != "" {
filters["status"] = status
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetAdminRechargeRecords(c.Request.Context(), filters, options)
if err != nil {
h.logger.Error("获取充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值记录失败")
return
}
h.responseBuilder.Success(c, result, "获取充值记录成功")
}
// GetRechargeConfig 获取充值配置
// @Summary 获取充值配置
// @Description 获取当前环境的充值配置信息(最低充值金额、最高充值金额等)
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Success 200 {object} responses.RechargeConfigResponse "获取充值配置成功"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/recharge-config [get]
func (h *FinanceHandler) GetRechargeConfig(c *gin.Context) {
result, err := h.appService.GetRechargeConfig(c.Request.Context())
if err != nil {
h.logger.Error("获取充值配置失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值配置失败")
return
}
h.responseBuilder.Success(c, result, "获取充值配置成功")
}
// GetAlipayOrderStatus 获取支付宝订单状态
// @Summary 获取支付宝订单状态
// @Description 获取支付宝订单的当前状态,用于轮询查询
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param out_trade_no query string true "商户订单号"
// @Success 200 {object} responses.AlipayOrderStatusResponse "获取订单状态成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "订单不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/alipay-order-status [get]
func (h *FinanceHandler) GetAlipayOrderStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
outTradeNo := c.Query("out_trade_no")
if outTradeNo == "" {
h.responseBuilder.BadRequest(c, "缺少商户订单号")
return
}
result, err := h.appService.GetAlipayOrderStatus(c.Request.Context(), outTradeNo)
if err != nil {
h.logger.Error("获取支付宝订单状态失败",
zap.String("user_id", userID),
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, "获取订单状态失败: "+err.Error())
return
}
h.responseBuilder.Success(c, result, "获取订单状态成功")
}
// ==================== 发票相关Handler方法 ====================
// ApplyInvoice 申请开票
// @Summary 申请开票
// @Description 用户申请开票
// @Tags 发票管理
// @Accept json
// @Produce json
// @Param request body finance.ApplyInvoiceRequest true "申请开票请求"
// @Success 200 {object} interfaces.APIResponse{data=dto.InvoiceApplicationResponse}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/apply [post]
func (h *FinanceHandler) ApplyInvoice(c *gin.Context) {
var req finance.ApplyInvoiceRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "请求参数错误", err)
return
}
userID := c.GetString("user_id") // 从JWT中获取用户ID
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
result, err := h.invoiceAppService.ApplyInvoice(c.Request.Context(), userID, req)
if err != nil {
h.responseBuilder.InternalError(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "申请开票成功")
}
// GetUserInvoiceInfo 获取用户发票信息
// @Summary 获取用户发票信息
// @Description 获取用户的发票信息
// @Tags 发票管理
// @Produce json
// @Success 200 {object} interfaces.APIResponse{data=dto.InvoiceInfoResponse}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/info [get]
func (h *FinanceHandler) GetUserInvoiceInfo(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
result, err := h.invoiceAppService.GetUserInvoiceInfo(c.Request.Context(), userID)
if err != nil {
h.responseBuilder.InternalError(c, "获取发票信息失败")
return
}
h.responseBuilder.Success(c, result, "获取发票信息成功")
}
// UpdateUserInvoiceInfo 更新用户发票信息
// @Summary 更新用户发票信息
// @Description 更新用户的发票信息
// @Tags 发票管理
// @Accept json
// @Produce json
// @Param request body finance.UpdateInvoiceInfoRequest true "更新发票信息请求"
// @Success 200 {object} interfaces.APIResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/info [put]
func (h *FinanceHandler) UpdateUserInvoiceInfo(c *gin.Context) {
var req finance.UpdateInvoiceInfoRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "请求参数错误", err)
return
}
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
err := h.invoiceAppService.UpdateUserInvoiceInfo(c.Request.Context(), userID, req)
if err != nil {
h.responseBuilder.InternalError(c, err.Error())
return
}
h.responseBuilder.Success(c, nil, "更新发票信息成功")
}
// GetUserInvoiceRecords 获取用户开票记录
// @Summary 获取用户开票记录
// @Description 获取用户的开票记录列表
// @Tags 发票管理
// @Produce json
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param status query string false "状态筛选"
// @Success 200 {object} interfaces.APIResponse{data=dto.InvoiceRecordsResponse}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/records [get]
func (h *FinanceHandler) GetUserInvoiceRecords(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
status := c.Query("status")
startTime := c.Query("start_time")
endTime := c.Query("end_time")
req := finance.GetInvoiceRecordsRequest{
Page: page,
PageSize: pageSize,
Status: status,
StartTime: startTime,
EndTime: endTime,
}
result, err := h.invoiceAppService.GetUserInvoiceRecords(c.Request.Context(), userID, req)
if err != nil {
h.responseBuilder.InternalError(c, "获取开票记录失败")
return
}
h.responseBuilder.Success(c, result, "获取开票记录成功")
}
// DownloadInvoiceFile 下载发票文件
// @Summary 下载发票文件
// @Description 下载指定发票的文件
// @Tags 发票管理
// @Produce application/octet-stream
// @Param application_id path string true "申请ID"
// @Success 200 {file} file
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/{application_id}/download [get]
func (h *FinanceHandler) DownloadInvoiceFile(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
applicationID := c.Param("application_id")
if applicationID == "" {
h.responseBuilder.BadRequest(c, "申请ID不能为空")
return
}
result, err := h.invoiceAppService.DownloadInvoiceFile(c.Request.Context(), userID, applicationID)
if err != nil {
h.responseBuilder.InternalError(c, "下载发票文件失败")
return
}
// 设置响应头
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", result.FileName))
c.Header("Content-Length", fmt.Sprintf("%d", len(result.FileContent)))
// 直接返回文件内容
c.Data(http.StatusOK, "application/pdf", result.FileContent)
}
// GetAvailableAmount 获取可开票金额
// @Summary 获取可开票金额
// @Description 获取用户当前可开票的金额
// @Tags 发票管理
// @Produce json
// @Success 200 {object} interfaces.APIResponse{data=dto.AvailableAmountResponse}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/invoices/available-amount [get]
func (h *FinanceHandler) GetAvailableAmount(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
result, err := h.invoiceAppService.GetAvailableAmount(c.Request.Context(), userID)
if err != nil {
h.responseBuilder.InternalError(c, "获取可开票金额失败")
return
}
h.responseBuilder.Success(c, result, "获取可开票金额成功")
}
// ==================== 管理员发票相关Handler方法 ====================
// GetPendingApplications 获取发票申请列表(支持筛选)
// @Summary 获取发票申请列表
// @Description 管理员获取发票申请列表,支持状态和时间范围筛选
// @Tags 管理员-发票管理
// @Produce json
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param status query string false "状态筛选pending/completed/rejected"
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
// @Success 200 {object} interfaces.APIResponse{data=dto.PendingApplicationsResponse}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/admin/invoices/pending [get]
func (h *FinanceHandler) GetPendingApplications(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
status := c.Query("status")
startTime := c.Query("start_time")
endTime := c.Query("end_time")
req := finance.GetPendingApplicationsRequest{
Page: page,
PageSize: pageSize,
Status: status,
StartTime: startTime,
EndTime: endTime,
}
result, err := h.adminInvoiceAppService.GetPendingApplications(c.Request.Context(), req)
if err != nil {
h.responseBuilder.InternalError(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "获取发票申请列表成功")
}
// ApproveInvoiceApplication 通过发票申请(上传发票)
// @Summary 通过发票申请
// @Description 管理员通过发票申请并上传发票文件
// @Tags 管理员-发票管理
// @Accept multipart/form-data
// @Produce json
// @Param application_id path string true "申请ID"
// @Param file formData file true "发票文件"
// @Param admin_notes formData string false "管理员备注"
// @Success 200 {object} interfaces.APIResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/admin/invoices/{application_id}/approve [post]
func (h *FinanceHandler) ApproveInvoiceApplication(c *gin.Context) {
applicationID := c.Param("application_id")
if applicationID == "" {
h.responseBuilder.BadRequest(c, "申请ID不能为空")
return
}
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
h.responseBuilder.BadRequest(c, "请选择要上传的发票文件")
return
}
// 打开文件
fileHandle, err := file.Open()
if err != nil {
h.responseBuilder.InternalError(c, "文件打开失败")
return
}
defer fileHandle.Close()
// 获取管理员备注
adminNotes := c.PostForm("admin_notes")
req := finance.ApproveInvoiceRequest{
AdminNotes: adminNotes,
}
err = h.adminInvoiceAppService.ApproveInvoiceApplication(c.Request.Context(), applicationID, fileHandle, req)
if err != nil {
h.responseBuilder.InternalError(c, err.Error())
return
}
h.responseBuilder.Success(c, nil, "通过发票申请成功")
}
// RejectInvoiceApplication 拒绝发票申请
// @Summary 拒绝发票申请
// @Description 管理员拒绝发票申请
// @Tags 管理员-发票管理
// @Accept json
// @Produce json
// @Param application_id path string true "申请ID"
// @Param request body finance.RejectInvoiceRequest true "拒绝申请请求"
// @Success 200 {object} interfaces.APIResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/admin/invoices/{application_id}/reject [post]
func (h *FinanceHandler) RejectInvoiceApplication(c *gin.Context) {
applicationID := c.Param("application_id")
if applicationID == "" {
h.responseBuilder.BadRequest(c, "申请ID不能为空")
return
}
var req finance.RejectInvoiceRequest
if err := h.validator.BindAndValidate(c, &req); err != nil {
h.responseBuilder.BadRequest(c, "请求参数错误", err)
return
}
err := h.adminInvoiceAppService.RejectInvoiceApplication(c.Request.Context(), applicationID, req)
if err != nil {
h.responseBuilder.InternalError(c, err.Error())
return
}
h.responseBuilder.Success(c, nil, "拒绝发票申请成功")
}
// AdminDownloadInvoiceFile 管理员下载发票文件
// @Summary 管理员下载发票文件
// @Description 管理员下载指定发票的文件
// @Tags 管理员-发票管理
// @Produce application/octet-stream
// @Param application_id path string true "申请ID"
// @Success 200 {file} file
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/admin/invoices/{application_id}/download [get]
func (h *FinanceHandler) AdminDownloadInvoiceFile(c *gin.Context) {
applicationID := c.Param("application_id")
if applicationID == "" {
h.responseBuilder.BadRequest(c, "申请ID不能为空")
return
}
result, err := h.adminInvoiceAppService.DownloadInvoiceFile(c.Request.Context(), applicationID)
if err != nil {
h.responseBuilder.InternalError(c, "下载发票文件失败")
return
}
// 设置响应头
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", result.FileName))
c.Header("Content-Length", fmt.Sprintf("%d", len(result.FileContent)))
// 直接返回文件内容
c.Data(http.StatusOK, "application/pdf", result.FileContent)
}
// DebugEventSystem 调试事件系统
// @Summary 调试事件系统
// @Description 调试事件系统,用于测试事件触发和处理
// @Tags 系统调试
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "调试成功"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/debug/event-system [post]
func (h *FinanceHandler) DebugEventSystem(c *gin.Context) {
h.logger.Info("🔍 请求事件系统调试信息")
// 这里可以添加事件系统的状态信息
// 暂时返回基本信息
debugInfo := map[string]interface{}{
"timestamp": time.Now().Format("2006-01-02 15:04:05"),
"message": "事件系统调试端点已启用",
"handler": "FinanceHandler",
}
h.responseBuilder.Success(c, debugInfo, "事件系统调试信息")
}