feat(catalog): add max_units_per_user field to catalog items and implement purchase limit validation

This commit is contained in:
2026-08-06 16:37:48 -03:00
parent 20dd246cd8
commit 5ad61eb217
13 changed files with 378 additions and 7 deletions

View File

@@ -195,6 +195,19 @@ class CheckoutService
try {
if ($difference > 0) {
$otherItemQuantity = (int) $purchase->items()
->where('source_catalog_item_id', $purchaseItem->source_catalog_item_id)
->whereKeyNot($purchaseItem->getKey())
->sum('cantidad');
$catalogItem = $selection instanceof Variant
? $selection->catalogItem
: $selection;
$this->assertUserPurchaseLimit(
$catalogItem,
(int) $purchase->user_id,
$otherItemQuantity + $quantity,
$purchase->getKey(),
);
$this->catalogInventoryService->reserve($selection, $difference);
} else {
$this->catalogInventoryService->release($selection, abs($difference));
@@ -410,6 +423,15 @@ class CheckoutService
$variantId = isset($directItem['variant_id']) ? (int) $directItem['variant_id'] : null;
$quantity = (int) $directItem['cantidad'];
$selection = $this->resolveSelection($tenant, $catalogItemId, $variantId);
$catalogItem = $selection instanceof Variant
? $selection->catalogItem
: $selection;
$this->assertUserPurchaseLimit(
$catalogItem,
$userId,
$quantity,
field: 'direct_item.cantidad',
);
$availableQuantity = $this->catalogInventoryService->availableQuantity($selection);
if ($availableQuantity !== null && $availableQuantity < $quantity) {
@@ -461,6 +483,7 @@ class CheckoutService
$this->loadCartItems($cartItems);
$this->verifyTenantItems($tenant, $cartItems);
$this->assertCartUserPurchaseLimits($tenant, $userId, $cartItems);
$cart->setRelation('items', $cartItems);
$purchase = $this->createPurchase(
@@ -648,6 +671,74 @@ class CheckoutService
}
}
/** @param Collection<int, CartItem> $cartItems */
private function assertCartUserPurchaseLimits(
Tenant $tenant,
int $userId,
Collection $cartItems,
): void {
$quantities = $cartItems
->groupBy('catalog_item_id')
->map(fn (Collection $items): int => (int) $items->sum('cantidad'))
->sortKeys();
$catalogItems = CatalogItem::query()
->where('tenant_code', $tenant->codigo)
->whereKey($quantities->keys())
->orderBy('id')
->lockForUpdate()
->get()
->keyBy('id');
foreach ($quantities as $catalogItemId => $quantity) {
/** @var CatalogItem $catalogItem */
$catalogItem = $catalogItems->get($catalogItemId);
$this->assertUserPurchaseLimit(
$catalogItem,
$userId,
$quantity,
field: 'cart_id',
);
}
}
private function assertUserPurchaseLimit(
CatalogItem $catalogItem,
int $userId,
int $requestedQuantity,
?int $excludedPurchaseId = null,
string $field = 'quantity',
): void {
$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');
if ($purchasedQuantity + $requestedQuantity > $limit) {
throw ValidationException::withMessages([
$field => __('api.purchase.max_units_per_user', ['max' => $limit]),
]);
}
}
protected function resolveCheckoutCart(Tenant $tenant, int $userId, int $cartId): Cart
{
/** @var Cart|null $cart */