package financewithdrawal import ( "errors" "strconv" "strings" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/integration/activityclient" "hyapp-admin-server/internal/integration/walletclient" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/repository" "hyapp-admin-server/internal/response" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type Handler struct { service *Service audit shared.OperationLogger } func New(store *repository.Store, wallet walletclient.Client, activity activityclient.Client, audit shared.OperationLogger) *Handler { return &Handler{service: NewService(store, wallet, activity), audit: audit} } func (h *Handler) ListApplications(c *gin.Context) { h.listApplications(c, false) } func (h *Handler) ListOperationsApplications(c *gin.Context) { h.listApplications(c, true) } func (h *Handler) listApplications(c *gin.Context, operations bool) { options := shared.ListOptions(c) listOptions := repository.WithdrawalApplicationListOptions{ Page: options.Page, PageSize: options.PageSize, AppCode: firstQuery(c, "app_code", "appCode"), Keyword: options.Keyword, } var ( items []withdrawalApplicationDTO total int64 err error ) actor := shared.ActorFromContext(c) if operations { items, total, err = h.service.ListOperationsApplications(actor, listOptions) } else { items, total, err = h.service.ListApplications(actor, listOptions) } if err != nil { if errors.Is(err, errWithdrawalMoneyScopeForbidden) { response.Forbidden(c, err.Error()) return } response.ServerError(c, "获取用户提现申请列表失败") return } response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: total}) } func (h *Handler) ApproveApplication(c *gin.Context) { h.auditApplication(c, "approved", false) } func (h *Handler) RejectApplication(c *gin.Context) { h.auditApplication(c, "rejected", false) } func (h *Handler) ApproveOperationsApplication(c *gin.Context) { h.auditApplication(c, "approved", true) } func (h *Handler) RejectOperationsApplication(c *gin.Context) { h.auditApplication(c, "rejected", true) } func (h *Handler) auditApplication(c *gin.Context, decision string, operations bool) { id, ok := shared.ParseID(c, "application_id") if !ok { return } if strings.TrimSpace(c.GetHeader(appctx.HeaderAppCode)) == "" { // 财务工作台可跨 App 列表,审核时必须由前端显式选中目标行 App;不能让 appctx 的 lalu 默认值静默决定资金数据域。 response.BadRequest(c, "缺少 "+appctx.HeaderAppCode) return } var req struct { AuditRemark string `json:"auditRemark"` AuditImageURL string `json:"auditImageUrl"` AuditImageURLSnake string `json:"audit_image_url"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "审核参数不正确") return } var ( item *withdrawalApplicationDTO err error ) if operations && decision == "approved" { item, err = h.service.ApproveOperationsApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c)) } else if operations { item, err = h.service.RejectOperationsApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c)) } else if decision == "approved" { item, err = h.service.ApproveApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, firstNonEmpty(req.AuditImageURL, req.AuditImageURLSnake), middleware.CurrentRequestID(c)) } else { item, err = h.service.RejectApplication(c.Request.Context(), shared.ActorFromContext(c), id, req.AuditRemark, middleware.CurrentRequestID(c)) } if err != nil { writeWithdrawalServiceError(c, err) return } action := "audit-user-withdrawal-finance" if operations { action = "audit-user-withdrawal-operations" } shared.OperationLogWithResourceID(c, h.audit, action, "admin_user_withdrawal_applications", strconv.FormatUint(uint64(item.ID), 10), "success", decision) response.OK(c, item) } func writeWithdrawalServiceError(c *gin.Context, err error) { if errors.Is(err, gorm.ErrRecordNotFound) { response.NotFound(c, "用户提现申请不存在") return } if errors.Is(err, repository.ErrWithdrawalApplicationAlreadyAudited) { response.BadRequest(c, "提现申请已审核") return } if errors.Is(err, repository.ErrWithdrawalApplicationStageNotReviewable) { response.BadRequest(c, "提现申请尚未进入当前审核阶段") return } if errors.Is(err, errWithdrawalMoneyScopeForbidden) { response.Forbidden(c, err.Error()) return } switch strings.TrimSpace(err.Error()) { case "没有操作权限": response.Forbidden(c, err.Error()) case "admin finance withdrawal service is not configured": response.ServerError(c, "审核用户提现申请失败") default: response.BadRequest(c, err.Error()) } } func firstQuery(c *gin.Context, keys ...string) string { for _, key := range keys { if value := strings.TrimSpace(c.Query(key)); value != "" { return value } } return "" } func firstNonEmpty(values ...string) string { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { return trimmed } } return "" }