2026-07-16 18:51:58 +08:00

171 lines
5.2 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.

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