package data import ( "strings" "time" "github.com/GMWalletApp/epusdt/model/dao" "github.com/GMWalletApp/epusdt/model/mdb" "gorm.io/gorm" ) // OrderListFilter bundles every filter supported by the admin orders // list page. Zero values are ignored so callers can pass only what the // user actually filtered on. type OrderListFilter struct { Status int Network string Token string Address string Keyword string // matches trade_id / order_id / block_transaction_id StartAt *time.Time EndAt *time.Time Page int PageSize int // ParentOnly restricts the result to top-level orders only // (parent_trade_id = ''). Sub-orders are excluded from the listing. ParentOnly bool } // ListOrders returns a paginated order slice plus the total count under // the same filter (for the UI pagination bar). func ListOrders(f OrderListFilter) ([]mdb.Orders, int64, error) { tx := buildOrderListQuery(f) var total int64 if err := tx.Count(&total).Error; err != nil { return nil, 0, err } page := f.Page if page < 1 { page = 1 } size := f.PageSize if size < 1 { size = 20 } if size > 200 { size = 200 } var rows []mdb.Orders err := tx.Order("id DESC"). Offset((page - 1) * size).Limit(size). Find(&rows).Error return rows, total, err } func buildOrderListQuery(f OrderListFilter) *gorm.DB { tx := dao.Mdb.Model(&mdb.Orders{}) if f.ParentOnly { tx = tx.Where("parent_trade_id = ?", "") } if f.Status > 0 { tx = tx.Where("status = ?", f.Status) } if f.Network != "" { tx = tx.Where("network = ?", strings.ToLower(f.Network)) } if f.Token != "" { tx = tx.Where("token = ?", strings.ToUpper(f.Token)) } if f.Address != "" { tx = tx.Where("receive_address = ?", f.Address) } if f.Keyword != "" { kw := "%" + strings.TrimSpace(f.Keyword) + "%" tx = tx.Where("trade_id LIKE ? OR order_id LIKE ? OR block_transaction_id LIKE ?", kw, kw, kw) } if f.StartAt != nil { tx = tx.Where("created_at >= ?", *f.StartAt) } if f.EndAt != nil { tx = tx.Where("created_at <= ?", *f.EndAt) } return tx } // CountOrdersByStatus returns how many orders exist in each status. // Used by the dashboard overview card. func CountOrdersByStatus() (map[int]int64, error) { type row struct { Status int Total int64 } var rows []row err := dao.Mdb.Model(&mdb.Orders{}). Select("status, COUNT(*) AS total"). Group("status"). Scan(&rows).Error if err != nil { return nil, err } out := map[int]int64{} for _, r := range rows { out[r.Status] = r.Total } return out, nil } // CloseOrderManually transitions a pending order to expired. It covers both // payable orders and placeholder orders waiting for token/network selection. func CloseOrderManually(tradeID string) (bool, error) { result := dao.Mdb.Model(&mdb.Orders{}). Where("trade_id = ?", tradeID). Where("status IN ?", []int{mdb.StatusWaitPay, mdb.StatusWaitSelect}). Update("status", mdb.StatusExpired) return result.RowsAffected > 0, result.Error } // ReopenOrderCallback flips callback_confirm back to NO so the mq // worker picks it up on the next tick. Used by "resend callback". func ReopenOrderCallback(tradeID string) (bool, error) { result := dao.Mdb.Model(&mdb.Orders{}). Where("trade_id = ?", tradeID). Where("status = ?", mdb.StatusPaySuccess). Updates(map[string]interface{}{ "callback_confirm": mdb.CallBackConfirmNo, "callback_num": 0, }) return result.RowsAffected > 0, result.Error } // CountOrdersByAddress returns order counts grouped by receive_address. // The admin wallet list annotates each wallet row with this number. func CountOrdersByAddress() (map[string]int64, error) { type row struct { Address string `gorm:"column:receive_address"` Total int64 } var rows []row err := dao.Mdb.Model(&mdb.Orders{}). Select("receive_address, COUNT(*) AS total"). Group("receive_address"). Scan(&rows).Error if err != nil { return nil, err } out := map[string]int64{} for _, r := range rows { out[r.Address] = r.Total } return out, nil }