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 } func (s *FulfillmentService) ListOrders(merchantID uint, page, size int, orderStatus, orderSource, keyword string) ([]model.FulfillmentOrder, int64, error) { page, size = normalizePage(page, size) tx := s.db.Model(&model.FulfillmentOrder{}).Where("merchant_id = ?", merchantID) if orderStatus != "" { switch orderStatus { case model.OrderStatusPaid, model.OrderStatusDelivering, model.OrderStatusDelivered, model.OrderStatusShipFailed, model.OrderStatusCancelled: default: return nil, 0, errors.New("无效的订单状态") } tx = tx.Where("order_status = ?", orderStatus) } if orderSource != "" { switch orderSource { case model.OrderSourceAPI, model.OrderSourceManual, model.OrderSourceTest: tx = tx.Where("order_source = ?", orderSource) default: return nil, 0, errors.New("无效的订单来源") } } if keyword = strings.TrimSpace(keyword); keyword != "" { like := "%" + keyword + "%" tx = tx.Where("order_no LIKE ? OR client_order_no LIKE ?", like, like) } var total int64 if err := tx.Count(&total).Error; err != nil { return nil, 0, err } var orders []model.FulfillmentOrder err := tx.Preload("MerchantProduct.Product").Order("id DESC").Offset((page - 1) * size).Limit(size).Find(&orders).Error return orders, total, err } 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 }