171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
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 ""
|
||
}
|