hyapp-server/server/admin/internal/repository/withdrawal_application_repository.go
2026-07-02 10:53:44 +08:00

106 lines
3.8 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 repository
import (
"errors"
"strings"
"hyapp-admin-server/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
var ErrWithdrawalApplicationAlreadyAudited = errors.New("withdrawal application already audited")
type WithdrawalApplicationListOptions struct {
Page int
PageSize int
AppCode string
Keyword string
}
type WithdrawalApplicationAuditInput struct {
Decision string
ApproverUserID uint
ApproverName string
AuditRemark string
AuditImageURL string
AuditCommandID string
AuditTransactionID string
ApprovedAtMS int64
}
func (s *Store) GetWithdrawalApplication(id uint) (*model.UserWithdrawalApplication, error) {
var application model.UserWithdrawalApplication
if err := s.db.First(&application, id).Error; err != nil {
return nil, err
}
return &application, nil
}
func (s *Store) ListWithdrawalApplications(options WithdrawalApplicationListOptions) ([]model.UserWithdrawalApplication, int64, error) {
page, pageSize := normalizePage(options.Page, options.PageSize)
query := s.db.Model(&model.UserWithdrawalApplication{})
query = applyWithdrawalApplicationFilters(query, options)
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var applications []model.UserWithdrawalApplication
// 提现申请是财务审核场景默认按最新申请优先展示id 作为同毫秒写入时的稳定次序,避免分页翻页时记录抖动。
err := query.
Order("created_at_ms DESC, id DESC").
Limit(pageSize).
Offset((page - 1) * pageSize).
Find(&applications).Error
return applications, total, err
}
func (s *Store) AuditWithdrawalApplication(id uint, input WithdrawalApplicationAuditInput) (*model.UserWithdrawalApplication, error) {
if id == 0 {
return nil, errors.New("withdrawal application id is required")
}
if input.Decision != model.WithdrawalApplicationStatusApproved && input.Decision != model.WithdrawalApplicationStatusRejected {
return nil, errors.New("withdrawal application decision is invalid")
}
var application model.UserWithdrawalApplication
err := s.db.Transaction(func(tx *gorm.DB) error {
// 审核只能从 pending 进入终态;行锁阻止两个财务同时覆盖同一张提现单的执行结果。
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&application, id).Error; err != nil {
return err
}
if application.Status != model.WithdrawalApplicationStatusPending {
return ErrWithdrawalApplicationAlreadyAudited
}
return tx.Model(&application).Updates(map[string]any{
"status": input.Decision,
"approver_user_id": input.ApproverUserID,
"approver_name": strings.TrimSpace(input.ApproverName),
"audit_remark": strings.TrimSpace(input.AuditRemark),
"audit_image_url": strings.TrimSpace(input.AuditImageURL),
"audit_command_id": strings.TrimSpace(input.AuditCommandID),
"audit_transaction_id": strings.TrimSpace(input.AuditTransactionID),
"approved_at_ms": input.ApprovedAtMS,
"updated_at_ms": input.ApprovedAtMS,
}).Error
})
if err != nil {
return nil, err
}
return s.GetWithdrawalApplication(id)
}
func applyWithdrawalApplicationFilters(query *gorm.DB, options WithdrawalApplicationListOptions) *gorm.DB {
if appCode := strings.TrimSpace(options.AppCode); appCode != "" {
query = query.Where("app_code = ?", appCode)
}
if keyword := strings.TrimSpace(options.Keyword); keyword != "" {
like := "%" + keyword + "%"
// 关键词面向财务排查,覆盖用户、收款方式、收款地址和审批人;不做金额模糊,避免 decimal 隐式转换拖慢常规查询。
query = query.Where("user_id LIKE ? OR withdraw_method LIKE ? OR withdraw_address LIKE ? OR approver_name LIKE ? OR CAST(id AS CHAR) LIKE ?", like, like, like, like, like)
}
return query
}