261 lines
7.6 KiB
Go
261 lines
7.6 KiB
Go
package adminfinance
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/auditlog"
|
|
"hfb_sys/backend/internal/middleware"
|
|
"hfb_sys/backend/internal/timeutil"
|
|
"hfb_sys/backend/pkg/response"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
}
|
|
|
|
func NewHandler(service *Service) *Handler {
|
|
return &Handler{service: service}
|
|
}
|
|
|
|
func (h *Handler) Dashboard(c *gin.Context) {
|
|
query, ok := parseDashboardQuery(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
item, err := h.service.Dashboard(c.Request.Context(), query)
|
|
if err != nil {
|
|
writeFinanceError(c, err)
|
|
return
|
|
}
|
|
response.OK(c, item)
|
|
}
|
|
|
|
func (h *Handler) Details(c *gin.Context) {
|
|
query, ok := parseDetailQuery(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
result, err := h.service.Details(c.Request.Context(), query)
|
|
if err != nil {
|
|
writeFinanceError(c, err)
|
|
return
|
|
}
|
|
response.OK(c, result)
|
|
}
|
|
|
|
func (h *Handler) Disbursements(c *gin.Context) {
|
|
query, ok := parseDisbursementQuery(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
result, err := h.service.Disbursements(c.Request.Context(), query)
|
|
if err != nil {
|
|
writeFinanceError(c, err)
|
|
return
|
|
}
|
|
response.OK(c, result)
|
|
}
|
|
|
|
func (h *Handler) CreateManualDisbursement(c *gin.Context) {
|
|
adminID, ok := financeAdminID(c)
|
|
if !ok {
|
|
response.Unauthorized(c, "缺少管理员上下文")
|
|
return
|
|
}
|
|
var req CreateManualDisbursementRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "线下出款信息不正确")
|
|
return
|
|
}
|
|
item, err := h.service.CreateManualDisbursement(c.Request.Context(), req, adminID, financeAuditMeta(c))
|
|
if err != nil {
|
|
writeFinanceError(c, err)
|
|
return
|
|
}
|
|
response.Created(c, item)
|
|
}
|
|
|
|
func (h *Handler) VoidManualDisbursement(c *gin.Context) {
|
|
adminID, ok := financeAdminID(c)
|
|
if !ok {
|
|
response.Unauthorized(c, "缺少管理员上下文")
|
|
return
|
|
}
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil || id == 0 {
|
|
response.BadRequest(c, "线下出款记录 ID 不正确")
|
|
return
|
|
}
|
|
var req VoidManualDisbursementRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "请填写作废原因")
|
|
return
|
|
}
|
|
item, err := h.service.VoidManualDisbursement(c.Request.Context(), id, req.Reason, adminID, financeAuditMeta(c))
|
|
if err != nil {
|
|
writeFinanceError(c, err)
|
|
return
|
|
}
|
|
response.OK(c, item)
|
|
}
|
|
|
|
func parseDashboardQuery(c *gin.Context) (DashboardQuery, bool) {
|
|
start, end, ok := parseDateRange(c, 6)
|
|
if !ok {
|
|
return DashboardQuery{}, false
|
|
}
|
|
return DashboardQuery{StartDate: start, EndDate: end}, true
|
|
}
|
|
|
|
func parseDetailQuery(c *gin.Context) (DetailQuery, bool) {
|
|
start, end, ok := parseDateRange(c, 29)
|
|
if !ok {
|
|
return DetailQuery{}, false
|
|
}
|
|
query := DetailQuery{
|
|
OrderNo: c.Query("order_no"),
|
|
OrderStatus: c.Query("order_status"),
|
|
SettlementStatus: c.Query("settlement_status"),
|
|
SettlementMode: c.Query("settlement_mode"),
|
|
OfflineSettlementStatus: c.Query("offline_settlement_status"),
|
|
DateType: c.DefaultQuery("date_type", "settled"),
|
|
StartDate: start,
|
|
EndDate: end,
|
|
}
|
|
if query.DateType != "created" && query.DateType != "settled" && query.DateType != "offline_settled" {
|
|
response.BadRequest(c, "日期类型不正确")
|
|
return query, false
|
|
}
|
|
if query.SettlementMode != "" && query.SettlementMode != "owner_wallet" && query.SettlementMode != "platform_managed" {
|
|
response.BadRequest(c, "结算模式不正确")
|
|
return query, false
|
|
}
|
|
if query.OfflineSettlementStatus != "" && query.OfflineSettlementStatus != "none" && query.OfflineSettlementStatus != "pending" && query.OfflineSettlementStatus != "settled" {
|
|
response.BadRequest(c, "线下结算状态不正确")
|
|
return query, false
|
|
}
|
|
if raw := c.Query("user_id"); raw != "" {
|
|
value, err := strconv.ParseUint(raw, 10, 64)
|
|
if err != nil || value == 0 {
|
|
response.BadRequest(c, "用户 ID 不正确")
|
|
return query, false
|
|
}
|
|
query.UserID = value
|
|
}
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
query.Page = page
|
|
query.PageSize = pageSize
|
|
return query, true
|
|
}
|
|
|
|
func parseDisbursementQuery(c *gin.Context) (DisbursementQuery, bool) {
|
|
start, end, ok := parseDateRange(c, 29)
|
|
if !ok {
|
|
return DisbursementQuery{}, false
|
|
}
|
|
query := DisbursementQuery{
|
|
SourceType: strings.TrimSpace(c.Query("source_type")),
|
|
Status: strings.TrimSpace(c.Query("status")),
|
|
DateType: c.DefaultQuery("date_type", "created"),
|
|
BusinessNo: strings.TrimSpace(c.Query("business_no")),
|
|
Keyword: strings.TrimSpace(c.Query("keyword")),
|
|
StartDate: start,
|
|
EndDate: end,
|
|
}
|
|
if query.SourceType != "" && query.SourceType != "platform_managed" && query.SourceType != "withdrawal" && query.SourceType != "manual_offline" {
|
|
response.BadRequest(c, "出款类型不正确")
|
|
return query, false
|
|
}
|
|
if query.Status != "" && query.Status != "review_pending" && query.Status != "payment_pending" && query.Status != "paid" && query.Status != "rejected" && query.Status != "cancelled" && query.Status != "voided" {
|
|
response.BadRequest(c, "出款状态不正确")
|
|
return query, false
|
|
}
|
|
if query.DateType != "created" && query.DateType != "paid" {
|
|
response.BadRequest(c, "日期类型不正确")
|
|
return query, false
|
|
}
|
|
if raw := c.Query("user_id"); raw != "" {
|
|
value, err := strconv.ParseUint(raw, 10, 64)
|
|
if err != nil || value == 0 {
|
|
response.BadRequest(c, "用户 ID 不正确")
|
|
return query, false
|
|
}
|
|
query.UserID = value
|
|
}
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
query.Page = page
|
|
query.PageSize = pageSize
|
|
return query, true
|
|
}
|
|
|
|
func parseDateRange(c *gin.Context, defaultLookbackDays int) (time.Time, time.Time, bool) {
|
|
loc := timeutil.ShanghaiLocation()
|
|
today := timeutil.ShanghaiNow()
|
|
defaultStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, loc).AddDate(0, 0, -defaultLookbackDays)
|
|
defaultEnd := time.Date(today.Year(), today.Month(), today.Day(), 23, 59, 59, int(time.Second-time.Nanosecond), loc)
|
|
|
|
start := defaultStart
|
|
end := defaultEnd
|
|
if raw := c.Query("start_date"); raw != "" {
|
|
parsed, err := time.ParseInLocation("2006-01-02", raw, loc)
|
|
if err != nil {
|
|
response.BadRequest(c, "开始日期不正确")
|
|
return start, end, false
|
|
}
|
|
start = parsed
|
|
}
|
|
if raw := c.Query("end_date"); raw != "" {
|
|
parsed, err := time.ParseInLocation("2006-01-02", raw, loc)
|
|
if err != nil {
|
|
response.BadRequest(c, "结束日期不正确")
|
|
return start, end, false
|
|
}
|
|
end = parsed.AddDate(0, 0, 1).Add(-time.Nanosecond)
|
|
}
|
|
if start.After(end) {
|
|
response.BadRequest(c, "开始日期不能晚于结束日期")
|
|
return start, end, false
|
|
}
|
|
return start, end, true
|
|
}
|
|
|
|
func writeFinanceError(c *gin.Context, err error) {
|
|
switch {
|
|
case errors.Is(err, ErrDependencyUnavailable):
|
|
response.ServiceUnavailable(c, "数据库未连接")
|
|
case errors.Is(err, ErrInvalidManualDisbursement):
|
|
response.BadRequest(c, "线下出款信息不正确")
|
|
case errors.Is(err, ErrManualDisbursementNotFound):
|
|
response.NotFound(c, "线下出款记录不存在")
|
|
case errors.Is(err, ErrManualDisbursementNotPaid):
|
|
response.BadRequest(c, "该线下出款记录已作废")
|
|
default:
|
|
response.Error(c, http.StatusInternalServerError, "finance_error", "财务数据暂时不可用")
|
|
}
|
|
}
|
|
|
|
func financeAdminID(c *gin.Context) (uint64, bool) {
|
|
value, ok := c.Get(middleware.ContextAdminID)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
adminID, ok := value.(uint64)
|
|
return adminID, ok
|
|
}
|
|
|
|
func financeAuditMeta(c *gin.Context) auditlog.Meta {
|
|
return auditlog.Meta{
|
|
IP: c.ClientIP(),
|
|
UserAgent: c.GetHeader("User-Agent"),
|
|
RequestID: middleware.GetRequestID(c),
|
|
}
|
|
}
|