This commit is contained in:
mats committed 2026-07-25 23:01:25 +02:00
1 parent 4dc1d7bc1c
commit 78a7d35570
2 files changed
+88

No files matched your search

@@ -3,6 +3,7 @@ package inventory
import (
"context"
"fmt"
"strconv"
"time"
"github.com/redis/go-redis/v9"
@@ -151,6 +152,75 @@ func (s *RedisCartReservationService) GetReservationSummary(ctx context.Context,
}, nil
}
// GetReservationDetail returns the full per-cart reservation detail for a
// SKU+location. Unlike GetReservationSummary (which returns only aggregates),
// this method fetches every active (non-expired) row from Redis and returns
// a ReservationDetailResponse containing the adjusted summary + per-cart lines.
// Expired rows are filtered out so the UI only sees live holds. The two Redis
// fetches (HGetAll + ZRangeWithScores) run sequentially — for an infrequent
// admin endpoint the goroutine+channel overhead is not worth the latency win.
func (s *RedisCartReservationService) GetReservationDetail(ctx context.Context, sku SKU, locationID LocationID) (*ReservationDetailResponse, error) {
qtyKey := cartQtyKey(sku, locationID)
expKey := cartExpKey(sku, locationID)
// Fetch all cart→qty pairs.
qtyMap, err := s.client.HGetAll(ctx, qtyKey).Result()
if err != nil {
return nil, err
}
// Fetch all cart→expiry pairs.
expPairs, err := s.client.ZRangeWithScores(ctx, expKey, 0, -1).Result()
if err != nil {
return nil, err
}
// Index expiry by cartId.
expMap := make(map[string]time.Time, len(expPairs))
for _, z := range expPairs {
key, ok := z.Member.(string)
if !ok {
continue
}
expMap[key] = time.Unix(int64(z.Score), 0)
}
// Build the line list, skipping expired entries.
now := time.Now()
var lines []ReservationDetailLine
var earliestRelease time.Time
for cartID, qtyStr := range qtyMap {
expiresAt, hasExp := expMap[cartID]
if !hasExp || expiresAt.Before(now) {
continue
}
qty, _ := strconv.ParseUint(qtyStr, 10, 32)
lines = append(lines, ReservationDetailLine{
CartID: cartID,
Quantity: uint32(qty),
ExpiresAt: expiresAt,
})
if earliestRelease.IsZero() || expiresAt.Before(earliestRelease) {
earliestRelease = expiresAt
}
}
remainingItems, err := s.Available(ctx, sku, locationID)
if err != nil {
return nil, err
}
return &ReservationDetailResponse{
Summary: &ReservationSummary{
ReservationCount: int64(len(lines)),
EarliestRelease: earliestRelease,
RemainingItems: remainingItems,
},
Lines: lines,
}, nil
}
// Available satisfies inventory.ReservationPolicy (platform/inventory).
// Method renamed from GetAvailableInventory as part of cut-30.x: land the
// ReservationPolicy interface seam — see docs/inventory-shape.md.