完善后台分页与令牌刷新
This commit is contained in:
@@ -36,8 +36,14 @@ type ArbitrateRequest struct {
|
||||
Remark string `json:"remark" binding:"required"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
type AuditMeta struct {
|
||||
IP string
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
type PaginatedResult struct {
|
||||
Items []DisputeDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
@@ -48,12 +48,13 @@ func (h *Handler) List(c *gin.Context) {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
items, err := h.service.ListForUser(userID)
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListForUser(userID, page, pageSize)
|
||||
if err != nil {
|
||||
writeDisputeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) Detail(c *gin.Context) {
|
||||
@@ -75,12 +76,13 @@ func (h *Handler) Detail(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) AdminList(c *gin.Context) {
|
||||
items, err := h.service.ListAdmin()
|
||||
page, pageSize := parsePagination(c)
|
||||
result, err := h.service.ListAdmin(page, pageSize)
|
||||
if err != nil {
|
||||
writeDisputeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) AdminArbitrate(c *gin.Context) {
|
||||
@@ -136,6 +138,21 @@ func parseID(c *gin.Context) (uint64, bool) {
|
||||
return id, true
|
||||
}
|
||||
|
||||
func parsePagination(c *gin.Context) (int, int) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
func writeDisputeError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
|
||||
@@ -35,6 +35,7 @@ func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*
|
||||
if order.Status == "completed" || order.Status == "cancelled" || order.Status == "closed" {
|
||||
return ErrInvalidDispute
|
||||
}
|
||||
isCheckoutDispute := order.Status == "pending_checkout_confirm" || order.Status == "pending_checkout_accept"
|
||||
var count int64
|
||||
if err := tx.Model(&model.Dispute{}).
|
||||
Where("order_id = ? AND status IN ?", order.ID, []string{"open", "processing"}).
|
||||
@@ -57,7 +58,7 @@ func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*
|
||||
OrderID: order.ID,
|
||||
InitiatorID: userID,
|
||||
TargetUserID: targetID,
|
||||
Type: req.Type,
|
||||
Type: disputeType(req.Type, isCheckoutDispute),
|
||||
Status: "open",
|
||||
Description: req.Description,
|
||||
EvidenceURLS: evidence,
|
||||
@@ -65,17 +66,42 @@ func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
order.Status = "disputing"
|
||||
if isCheckoutDispute {
|
||||
now := time.Now()
|
||||
order.Status = "checkout_disputing"
|
||||
order.HandoffStatus = "checkout_disputed"
|
||||
order.SettlementStatus = "disputed"
|
||||
updates := map[string]any{
|
||||
"status": "disputed",
|
||||
"updated_at": now,
|
||||
}
|
||||
if userID == order.RenterID {
|
||||
updates["renter_rejected_at"] = now
|
||||
}
|
||||
if err := tx.Model(&model.OrderCheckout{}).
|
||||
Where("order_id = ? AND status IN ?", order.ID, []string{"submitted", "countered"}).
|
||||
Updates(updates).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
order.Status = "disputing"
|
||||
}
|
||||
if err := tx.Save(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
disputeID := row.ID
|
||||
title := "订单进入申诉"
|
||||
content := "对方已发起申诉,请等待客服仲裁或补充沟通记录。"
|
||||
if isCheckoutDispute {
|
||||
title = "订单进入结账争议"
|
||||
content = "对方已发起结账争议,请等待客服仲裁或补充结账证据。"
|
||||
}
|
||||
if err := notification.Append(tx,
|
||||
notification.Entry{
|
||||
UserID: targetID,
|
||||
Type: "dispute",
|
||||
Title: "订单进入申诉",
|
||||
Content: "对方已发起申诉,请等待客服仲裁或补充沟通记录。",
|
||||
Title: title,
|
||||
Content: content,
|
||||
BizType: "dispute",
|
||||
BizID: &disputeID,
|
||||
},
|
||||
@@ -99,16 +125,23 @@ func (r *Repository) Create(userID uint64, orderID uint64, req CreateRequest) (*
|
||||
return r.FindForUser(userID, createdID)
|
||||
}
|
||||
|
||||
func (r *Repository) ListForUser(userID uint64) ([]DisputeDTO, error) {
|
||||
func (r *Repository) ListForUser(userID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
conditions := r.db.Model(&model.Dispute{}).Where("initiator_id = ? OR target_user_id = ?", userID, userID)
|
||||
var total int64
|
||||
if err := conditions.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var rows []disputeRow
|
||||
err := r.baseQuery().
|
||||
Where("d.initiator_id = ? OR d.target_user_id = ?", userID, userID).
|
||||
Order("d.id DESC").
|
||||
Offset(offset).Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toDTOs(rows), nil
|
||||
return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) {
|
||||
@@ -122,13 +155,18 @@ func (r *Repository) FindForUser(userID uint64, id uint64) (*DisputeDTO, error)
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListAdmin() ([]DisputeDTO, error) {
|
||||
func (r *Repository) ListAdmin(page, pageSize int) (*PaginatedResult, error) {
|
||||
var total int64
|
||||
if err := r.db.Model(&model.Dispute{}).Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var rows []disputeRow
|
||||
err := r.baseQuery().Order("d.id DESC").Limit(200).Scan(&rows).Error
|
||||
err := r.baseQuery().Order("d.id DESC").Offset(offset).Limit(pageSize).Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toDTOs(rows), nil
|
||||
return &PaginatedResult{Items: toDTOs(rows), Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) {
|
||||
@@ -177,6 +215,9 @@ func (r *Repository) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest,
|
||||
if req.Result == "order_close" {
|
||||
listing.Status = "offline"
|
||||
account.Status = "offline"
|
||||
} else if req.Result == "mark_abnormal" {
|
||||
listing.Status = "abnormal"
|
||||
account.Status = "abnormal"
|
||||
} else {
|
||||
listing.Status = "published"
|
||||
account.Status = "published"
|
||||
@@ -335,6 +376,8 @@ func buildArbitrationSettlement(order model.RentalOrder, req ArbitrateRequest) (
|
||||
addRenterRefund(order.DepositAmount-deductAmount, "仲裁退回剩余押金给租客")
|
||||
case "order_close":
|
||||
// Only release frozen funds. No available-balance settlement happens in development mode.
|
||||
case "mark_abnormal":
|
||||
// 标记异常只释放冻结账务,后续由客服继续线下复核。
|
||||
default:
|
||||
return settlement, ErrInvalidDispute
|
||||
}
|
||||
@@ -395,11 +438,20 @@ func arbitrateOrderStatus(result string) string {
|
||||
switch result {
|
||||
case "full_refund", "partial_refund", "order_close":
|
||||
return "closed"
|
||||
case "mark_abnormal":
|
||||
return "abnormal"
|
||||
default:
|
||||
return "completed"
|
||||
}
|
||||
}
|
||||
|
||||
func disputeType(input string, isCheckoutDispute bool) string {
|
||||
if isCheckoutDispute {
|
||||
return "checkout_dispute"
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||
raw, err := json.Marshal(detail)
|
||||
if err != nil {
|
||||
|
||||
@@ -28,11 +28,11 @@ func (s *Service) Create(userID uint64, orderID uint64, req CreateRequest) (*Dis
|
||||
return s.repo.Create(userID, orderID, req)
|
||||
}
|
||||
|
||||
func (s *Service) ListForUser(userID uint64) ([]DisputeDTO, error) {
|
||||
func (s *Service) ListForUser(userID uint64, page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListForUser(userID)
|
||||
return s.repo.ListForUser(userID, page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) {
|
||||
@@ -42,11 +42,11 @@ func (s *Service) FindForUser(userID uint64, id uint64) (*DisputeDTO, error) {
|
||||
return s.repo.FindForUser(userID, id)
|
||||
}
|
||||
|
||||
func (s *Service) ListAdmin() ([]DisputeDTO, error) {
|
||||
func (s *Service) ListAdmin(page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListAdmin()
|
||||
return s.repo.ListAdmin(page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) Arbitrate(adminID uint64, id uint64, req ArbitrateRequest, meta AuditMeta) (*DisputeDTO, error) {
|
||||
|
||||
Reference in New Issue
Block a user