Squashed commit of the following:
commit1dc4e29c69Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 13:58:03 2026 -0300 refactor(reservations): unify expiration command commit093e894cc3Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 13:48:09 2026 -0300 feat(cart): expire abandoned stock reservations commitfdf0f3328fAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:53:12 2026 -0300 refactor(stock): implement expiration for stock reservations and add configuration commit8d6bcdcc43Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:38:21 2026 -0300 refactor(cart): invalidate payment on actual changes commit3206e293ebAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:24:48 2026 -0300 refactor(cart): own checkout item editing commitaed99bd05eAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:14:57 2026 -0300 refactor(checkout): remove legacy purchase item reservations commitf1649e0e4bAuthor: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:06:29 2026 -0300 refactor(checkout): materialize purchase items on confirmation commite6c4b40a37Author: ncoronel <ncoronel@quo.ar> Date: Wed Aug 19 12:06:19 2026 -0300 feat(inventory): add traceable cart stock reservations
This commit is contained in:
@@ -7,6 +7,8 @@ use App\Domains\Catalog\Models\CatalogItem;
|
||||
use App\Domains\Catalog\Models\Inventory;
|
||||
use App\Domains\Catalog\Models\Variant;
|
||||
use App\Domains\Catalog\Services\CatalogInventoryService;
|
||||
use App\Domains\Catalog\Services\StockReservationService;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Purchase\Services\UserPurchaseLimitService;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
@@ -24,6 +26,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
'user_id',
|
||||
'guest_token',
|
||||
'status',
|
||||
'origin',
|
||||
])]
|
||||
class Cart extends Model
|
||||
{
|
||||
@@ -32,6 +35,10 @@ class Cart extends Model
|
||||
|
||||
protected $table = 'carritos';
|
||||
|
||||
public const ORIGIN_USER = 'user';
|
||||
|
||||
public const ORIGIN_DIRECT_CHECKOUT = 'direct_checkout';
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@@ -63,6 +70,12 @@ class Cart extends Model
|
||||
return $this->hasMany(CartItem::class, 'cart_id');
|
||||
}
|
||||
|
||||
/** @return HasMany<Purchase, $this> */
|
||||
public function purchases(): HasMany
|
||||
{
|
||||
return $this->hasMany(Purchase::class, 'cart_id');
|
||||
}
|
||||
|
||||
public function getTotalAmount(): float
|
||||
{
|
||||
$items = $this->relationLoaded('items')
|
||||
@@ -114,11 +127,12 @@ class Cart extends Model
|
||||
'cantidad' => $quantity,
|
||||
]);
|
||||
} else {
|
||||
app(StockReservationService::class)->ensure($item, $selectedItem);
|
||||
$item->cantidad += $quantity;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
$inventoryService->reserve($selectedItem, $quantity);
|
||||
app(StockReservationService::class)->reserve($item, $selectedItem, $quantity);
|
||||
|
||||
return $item->fresh();
|
||||
});
|
||||
@@ -129,6 +143,7 @@ class Cart extends Model
|
||||
int $quantity,
|
||||
?int $variantId = null,
|
||||
bool $updateVariant = false,
|
||||
?int $excludedPurchaseId = null,
|
||||
): CartItem {
|
||||
if ($quantity <= 0) {
|
||||
throw ValidationException::withMessages([
|
||||
@@ -141,6 +156,7 @@ class Cart extends Model
|
||||
$quantity,
|
||||
$variantId,
|
||||
$updateVariant,
|
||||
$excludedPurchaseId,
|
||||
): CartItem {
|
||||
/** @var CartItem $item */
|
||||
$item = $this->items()
|
||||
@@ -168,9 +184,10 @@ class Cart extends Model
|
||||
$this->assertUserPurchaseLimit(
|
||||
$nextSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
$excludedPurchaseId,
|
||||
);
|
||||
|
||||
$inventoryService->release($currentSelection, $item->cantidad);
|
||||
app(StockReservationService::class)->release($item, $currentSelection, $item->cantidad);
|
||||
$availableQuantity = $inventoryService->availableQuantity($nextSelection);
|
||||
|
||||
if ($availableQuantity !== null && $availableQuantity < $quantity) {
|
||||
@@ -186,11 +203,11 @@ class Cart extends Model
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
$inventoryService->reserve($nextSelection, $quantity);
|
||||
|
||||
if ($targetItem !== null) {
|
||||
app(StockReservationService::class)->ensure($targetItem, $nextSelection);
|
||||
$targetItem->cantidad += $quantity;
|
||||
$targetItem->save();
|
||||
app(StockReservationService::class)->reserve($targetItem, $nextSelection, $quantity);
|
||||
$item->delete();
|
||||
|
||||
return $targetItem->fresh();
|
||||
@@ -199,6 +216,7 @@ class Cart extends Model
|
||||
$item->variant_id = $variantId;
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
app(StockReservationService::class)->reserve($item, $nextSelection, $quantity);
|
||||
|
||||
return $item->fresh();
|
||||
}
|
||||
@@ -213,6 +231,7 @@ class Cart extends Model
|
||||
$this->assertUserPurchaseLimit(
|
||||
$currentSelection,
|
||||
$otherVariantsQuantity + $quantity,
|
||||
$excludedPurchaseId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -225,17 +244,17 @@ class Cart extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
|
||||
if ($delta > 0) {
|
||||
$inventoryService->reserve($currentSelection, $delta);
|
||||
app(StockReservationService::class)->reserve($item, $currentSelection, $delta);
|
||||
}
|
||||
|
||||
if ($delta < 0) {
|
||||
$inventoryService->release($currentSelection, abs($delta));
|
||||
app(StockReservationService::class)->release($item, $currentSelection, abs($delta));
|
||||
}
|
||||
|
||||
$item->cantidad = $quantity;
|
||||
$item->save();
|
||||
|
||||
return $item->fresh();
|
||||
});
|
||||
}
|
||||
@@ -254,7 +273,8 @@ class Cart extends Model
|
||||
$item->variant_id,
|
||||
true,
|
||||
);
|
||||
app(CatalogInventoryService::class)->release(
|
||||
app(StockReservationService::class)->release(
|
||||
$item,
|
||||
$selectedItem,
|
||||
$item->cantidad,
|
||||
);
|
||||
@@ -334,6 +354,7 @@ class Cart extends Model
|
||||
private function assertUserPurchaseLimit(
|
||||
CatalogItem|Variant $selectedItem,
|
||||
int $cartQuantity,
|
||||
?int $excludedPurchaseId = null,
|
||||
): void {
|
||||
if ($this->user_id === null) {
|
||||
return;
|
||||
@@ -347,6 +368,7 @@ class Cart extends Model
|
||||
$catalogItem,
|
||||
$this->user_id,
|
||||
$cartQuantity,
|
||||
$excludedPurchaseId,
|
||||
field: 'cantidad',
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user