refactor(stock): make expired reservations terminal

This commit is contained in:
2026-08-25 15:58:41 -03:00
parent 8545fbc645
commit 896fb81bcf
12 changed files with 205 additions and 30 deletions

View File

@@ -86,4 +86,14 @@ class CartController extends Controller
'message' => __('api.cart.item_removed'),
]);
}
public function restart(Request $request, Tenant $tenant): CartResource
{
return CartResource::make(
$this->cartService->restartExpired($tenant, $request),
)->additional([
'code' => 'cart.restarted',
'message' => __('api.cart.restarted'),
]);
}
}

View File

@@ -119,6 +119,7 @@ class Cart extends Model
return DB::transaction(function () use ($catalogItemId, $variantId, $quantity): CartItem {
$this->invalidateCurrentCheckout();
app(StockReservationService::class)->assertCartReservationUsable($this);
$selectedItem = $this->resolveScopedItem($catalogItemId, $variantId, true);
$cartQuantity = (int) $this->items()
->where('catalog_item_id', $catalogItemId)
@@ -183,6 +184,7 @@ class Cart extends Model
$excludedPurchaseId,
): CartItem {
$this->invalidateCurrentCheckout();
app(StockReservationService::class)->assertCartReservationUsable($this);
/** @var CartItem $item */
$item = $this->items()
@@ -284,6 +286,7 @@ class Cart extends Model
{
DB::transaction(function () use ($cartItemId): void {
$this->invalidateCurrentCheckout();
app(StockReservationService::class)->assertCartReservationUsable($this);
/** @var CartItem $item */
$item = $this->items()
@@ -334,9 +337,14 @@ class Cart extends Model
Purchase::STATUS_CREATED,
Purchase::STATUS_PENDING_PAYMENT,
], true)) {
app(StockReservationService::class)->returnToCart($currentPurchase, $cart);
$currentPurchase->update([
'status' => Purchase::STATUS_SUPERSEDED,
]);
$this->current_purchase_id = null;
return;
}
app(StockReservationService::class)->releaseForPurchase(

View File

@@ -4,9 +4,12 @@ namespace App\Domains\Cart\Services;
use App\Domains\Auth\Models\User;
use App\Domains\Cart\Models\Cart;
use App\Domains\Catalog\Models\StockReservation;
use App\Domains\Catalog\Services\ExpireStockReservationsService;
use App\Domains\Tenant\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Cookie;
@@ -94,6 +97,45 @@ class CartService
return $this->loadCart($cart, $tenant);
}
public function restartExpired(Tenant $tenant, Request $request): Cart
{
$identity = $this->requireIdentity($request);
$cart = $this->findCartOrFail($tenant, $identity);
if ($cart->current_stock_reservation_id === null
|| ! app(ExpireStockReservationsService::class)
->expireIfOverdue($cart->current_stock_reservation_id)) {
throw ValidationException::withMessages([
'cart' => __('api.cart.reservation_not_expired'),
]);
}
$newCart = DB::transaction(function () use ($cart, $identity, $tenant): Cart {
/** @var Cart $cart */
$cart = Cart::query()->lockForUpdate()->findOrFail($cart->getKey());
/** @var StockReservation|null $reservation */
$reservation = $cart->current_stock_reservation_id === null
? null
: StockReservation::query()
->lockForUpdate()
->find($cart->current_stock_reservation_id);
if ($reservation?->status !== StockReservation::STATUS_EXPIRED) {
throw ValidationException::withMessages([
'cart' => __('api.cart.reservation_not_expired'),
]);
}
$cart->update([
'status' => 'abandoned',
'current_purchase_id' => null,
]);
return $this->findOrCreateCart($tenant, $identity);
});
return $this->loadCart($newCart, $tenant);
}
public function makeGuestTokenCookie(string $guestToken): Cookie
{
$secure = (bool) config('session.secure');

View File

@@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Route;
Route::prefix('tenants/{tenant:codigo}')
->group(function (): void {
Route::get('cart', [CartController::class, 'show']);
Route::post('cart/restart', [CartController::class, 'restart']);
Route::post('cart/items', [CartController::class, 'addItem']);
Route::patch('cart/items/{cartItem}', [CartController::class, 'updateItemQuantity']);
Route::delete('cart/items/{cartItem}', [CartController::class, 'removeItem']);