package service import ( "errors" "strings" "time" "affiliate_dash/internal/model" "gorm.io/gorm" ) func (s *FulfillmentService) GetOrder(merchantID uint, orderNo string) (*model.FulfillmentOrder, error) { var order model.FulfillmentOrder err := s.db.Preload("MerchantProduct.Product"). Where("merchant_id = ? AND order_no = ?", merchantID, orderNo). First(&order).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("订单不存在") } if err != nil { return nil, err } return &order, nil } // orderFilters 是商户侧与平台侧共用的订单筛选条件。 // 集中在一处校验,避免以后新增筛选项时只改了一边。 type orderFilters struct { // merchantID 为 nil 表示不限商户(仅平台侧使用);指向 0 时仍会按 merchant_id=0 过滤, // 以保持 ListOrders 的原有语义不变。 merchantID *uint orderStatus string orderSource string keyword string } func (f orderFilters) apply(tx *gorm.DB) (*gorm.DB, error) { if f.merchantID != nil { tx = tx.Where("merchant_id = ?", *f.merchantID) } if f.orderStatus != "" { switch f.orderStatus { case model.OrderStatusPaid, model.OrderStatusDelivering, model.OrderStatusDelivered, model.OrderStatusShipFailed, model.OrderStatusCancelled: default: return nil, errors.New("无效的订单状态") } tx = tx.Where("order_status = ?", f.orderStatus) } if f.orderSource != "" { switch f.orderSource { case model.OrderSourceAPI, model.OrderSourceManual, model.OrderSourceTest: tx = tx.Where("order_source = ?", f.orderSource) default: return nil, errors.New("无效的订单来源") } } if keyword := strings.TrimSpace(f.keyword); keyword != "" { like := "%" + keyword + "%" tx = tx.Where("order_no LIKE ? OR client_order_no LIKE ?", like, like) } return tx, nil } func (s *FulfillmentService) listOrders(f orderFilters, page, size int, withMerchant bool) ([]model.FulfillmentOrder, int64, error) { page, size = normalizePage(page, size) tx, err := f.apply(s.db.Model(&model.FulfillmentOrder{})) if err != nil { return nil, 0, err } var total int64 if err := tx.Count(&total).Error; err != nil { return nil, 0, err } query := tx.Preload("MerchantProduct.Product") if withMerchant { // 平台侧跨商户列表需要展示订单属于哪个商户。 query = query.Preload("Merchant") } var orders []model.FulfillmentOrder err = query.Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&orders).Error return orders, total, err } // ListOrders 商户后台订单列表,强制按单个商户隔离。 func (s *FulfillmentService) ListOrders(merchantID uint, page, size int, orderStatus, orderSource, keyword string) ([]model.FulfillmentOrder, int64, error) { return s.listOrders(orderFilters{ merchantID: &merchantID, orderStatus: orderStatus, orderSource: orderSource, keyword: keyword, }, page, size, false) } // ListPlatformOrders 平台侧跨商户订单查询,供超管在管理端查看全部下游订单。 // merchantID 为 nil 表示不限商户。这是与 ListOrders 并行的另一条路径, // 刻意不改动商户侧「必须落到单个商户」的隔离语义。 func (s *FulfillmentService) ListPlatformOrders(merchantID *uint, page, size int, orderStatus, orderSource, keyword string) ([]model.FulfillmentOrder, int64, error) { return s.listOrders(orderFilters{ merchantID: merchantID, orderStatus: orderStatus, orderSource: orderSource, keyword: keyword, }, page, size, true) } type OpenOrderQuery struct { OrderNo string `json:"order_no"` Status string `json:"status"` CanShip bool `json:"can_ship"` CannotShipReason string `json:"cannot_ship_reason,omitempty"` Product *OpenOrderProduct `json:"product,omitempty"` BuyerName string `json:"buyer_name"` Amount int64 `json:"amount"` ProviderOrderNo string `json:"provider_order_no,omitempty"` CreatedAt time.Time `json:"created_at"` ShippedAt *time.Time `json:"shipped_at"` ShipFailReason string `json:"ship_fail_reason,omitempty"` GameChannel string `json:"game_channel,omitempty"` GameUID string `json:"game_uid,omitempty"` RoleName string `json:"role_name,omitempty"` PayScore int `json:"pay_score,omitempty"` } // OpenOrderProduct 开放接口返回的商品快照。 type OpenOrderProduct struct { Name string `json:"name"` SKU string `json:"sku"` Game string `json:"game"` } // GetByOrderNo 按订单号查询(不限定商户,供上游 SourceOpen 使用)。 func (s *FulfillmentService) GetByOrderNo(orderNo string) (*model.FulfillmentOrder, error) { var order model.FulfillmentOrder err := s.db.Preload("MerchantProduct.Product").Where("order_no = ?", orderNo).First(&order).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("订单不存在") } if err != nil { return nil, err } return &order, nil } // QueryOpenOrder 供上游查询:商品信息 + 是否可发货。 func (s *FulfillmentService) QueryOpenOrder(orderNo string) (*OpenOrderQuery, error) { if orderNo == "" { return nil, errors.New("订单号不能为空") } order, err := s.GetByOrderNo(orderNo) if err != nil { return nil, err } canShip, reason := CanFulfill(order) out := &OpenOrderQuery{ OrderNo: order.OrderNo, Status: normalizeOrderStatus(order), CanShip: canShip, CannotShipReason: reason, BuyerName: order.BuyerReference, Amount: order.Amount, ProviderOrderNo: order.ProviderOrderNo, CreatedAt: order.CreatedAt, ShippedAt: order.DeliveredAt, ShipFailReason: order.FailureReason, } if order.MerchantProduct != nil { game := "" if order.MerchantProduct.Product != nil { game = order.MerchantProduct.Product.Category } out.Product = &OpenOrderProduct{ Name: order.ProductName, SKU: order.ProductSKU, Game: game, } } // 新模型无独立的游戏字段列,从 RequestData / ResultData JSON 中还原。 extractGameFields(order.RequestData, out) extractGameFields(order.ResultData, out) return out, nil }