whereKey($catalogItem->getKey()) ->lockForUpdate() ->firstOrFail(); $limit = $catalogItem->max_units_per_user; if ($limit === null) { return; } $purchasedQuantity = (int) PurchaseItem::query() ->where('source_catalog_item_id', $catalogItem->getKey()) ->whereHas('purchase', function ($query) use ($userId, $excludedPurchaseId): void { $query ->where('user_id', $userId) ->whereIn('status', [ Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT, Purchase::STATUS_PAID, ]) ->when( $excludedPurchaseId !== null, fn ($query) => $query->whereKeyNot($excludedPurchaseId), ); }) ->sum('cantidad'); $checkoutQuantity = (int) CartItem::query() ->where('catalog_item_id', $catalogItem->getKey()) ->whereHas('cart.purchases', function ($query) use ($userId, $excludedPurchaseId): void { $query ->where('user_id', $userId) ->whereIn('status', [ Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT, ]) ->whereDoesntHave('items') ->when( $excludedPurchaseId !== null, fn ($query) => $query->whereKeyNot($excludedPurchaseId), ); }) ->sum('cantidad'); $reservedCartQuantity = (int) CartItem::query() ->where('catalog_item_id', $catalogItem->getKey()) ->whereHas('cart', fn ($query) => $query ->where('user_id', $userId) ->where('status', 'active') ->when( $excludedCartId !== null, fn ($query) => $query->whereKeyNot($excludedCartId), )) ->whereHas('stockReservations', fn ($query) => $query->where('status', 'active')) ->sum('cantidad'); if ($purchasedQuantity + $checkoutQuantity + $reservedCartQuantity + $requestedQuantity > $limit) { $remainingQuota = max( 0, $limit - $purchasedQuantity - $checkoutQuantity - $reservedCartQuantity, ); $maximumAddableQuantity = max(0, $remainingQuota - $heldQuantity); throw new PurchaseLimitExceededException( $catalogItem, $maximumAddableCeiling === null ? $maximumAddableQuantity : min($maximumAddableQuantity, $maximumAddableCeiling), $field, ); } }); } /** * @param Collection $catalogItems * @return Collection */ public function remainingByCatalogItem(Collection $catalogItems, ?int $userId): Collection { $limits = $catalogItems ->unique('id') ->mapWithKeys(fn (CatalogItem $item): array => [$item->getKey() => $item->max_units_per_user]); if ($userId === null || $limits->filter(fn ($limit) => $limit !== null)->isEmpty()) { return $limits->map(fn (): ?int => null); } $ids = $limits->keys(); $purchased = PurchaseItem::query() ->selectRaw('source_catalog_item_id, SUM(cantidad) AS quantity') ->whereIn('source_catalog_item_id', $ids) ->whereHas('purchase', fn ($query) => $query ->where('user_id', $userId) ->whereIn('status', [ Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT, Purchase::STATUS_PAID, ])) ->groupBy('source_catalog_item_id') ->pluck('quantity', 'source_catalog_item_id'); $checkout = CartItem::query() ->selectRaw('catalog_item_id, SUM(cantidad) AS quantity') ->whereIn('catalog_item_id', $ids) ->whereHas('cart.purchases', fn ($query) => $query ->where('user_id', $userId) ->whereIn('status', [Purchase::STATUS_CREATED, Purchase::STATUS_PENDING_PAYMENT]) ->whereDoesntHave('items')) ->groupBy('catalog_item_id') ->pluck('quantity', 'catalog_item_id'); $reserved = CartItem::query() ->selectRaw('catalog_item_id, SUM(cantidad) AS quantity') ->whereIn('catalog_item_id', $ids) ->whereHas('cart', fn ($query) => $query ->where('user_id', $userId) ->where('status', 'active')) ->whereHas('stockReservations', fn ($query) => $query->where('status', 'active')) ->groupBy('catalog_item_id') ->pluck('quantity', 'catalog_item_id'); return $limits->map(function (?int $limit, int $catalogItemId) use ($purchased, $checkout, $reserved): ?int { if ($limit === null) { return null; } $used = (int) ($purchased[$catalogItemId] ?? 0) + (int) ($checkout[$catalogItemId] ?? 0) + (int) ($reserved[$catalogItemId] ?? 0); return max(0, $limit - $used); }); } }