diff --git a/.env.example b/.env.example index 1f4f388..defb4fe 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,7 @@ PURCHASE_CHECKOUT_EXPIRATION_MINUTES=30 PURCHASE_QR_EXPIRATION_MINUTES=15 PURCHASE_TELEPAGOS_EXPIRATION_MINUTES=30 PURCHASE_TRANSFER_EXPIRATION_MINUTES=1440 +STOCK_RESERVATION_EXPIRATION_MINUTES=30 FRONTEND_URLS=http://localhost:4200 APP_LOCALE=es diff --git a/app/Domains/Catalog/Services/StockReservationService.php b/app/Domains/Catalog/Services/StockReservationService.php index e6a1053..24a7f51 100644 --- a/app/Domains/Catalog/Services/StockReservationService.php +++ b/app/Domains/Catalog/Services/StockReservationService.php @@ -7,6 +7,7 @@ use App\Domains\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\StockReservation; use App\Domains\Catalog\Models\Variant; use App\Domains\Purchase\Models\Purchase; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; class StockReservationService @@ -71,6 +72,7 @@ class StockReservationService 'cart_item_id' => $cartItem->getKey(), 'quantity' => $quantity, 'status' => StockReservation::STATUS_ACTIVE, + 'expires_at' => $this->expiration(), ]); continue; @@ -82,6 +84,7 @@ class StockReservationService 'status' => StockReservation::STATUS_ACTIVE, 'committed_at' => null, 'released_at' => null, + 'expires_at' => $this->expiration(), ]); } } @@ -111,7 +114,7 @@ class StockReservationService ->where('status', StockReservation::STATUS_ACTIVE) ->update([ 'purchase_id' => null, - 'expires_at' => null, + 'expires_at' => $this->expiration(), ]); } @@ -144,7 +147,7 @@ class StockReservationService 'cart_item_id' => $target->getKey(), 'purchase_id' => null, 'quantity' => $perItemQuantity * $targetItemQuantity, - 'expires_at' => null, + 'expires_at' => $this->expiration(), ]); continue; @@ -153,6 +156,7 @@ class StockReservationService $targetReservation->update([ 'quantity' => $targetReservation->quantity + $sourceReservation->quantity, 'status' => StockReservation::STATUS_ACTIVE, + 'expires_at' => $this->expiration(), ]); $sourceReservation->delete(); } @@ -170,6 +174,7 @@ class StockReservationService 'cart_item_id' => $cartItem->getKey(), 'quantity' => $requiredQuantity, 'status' => StockReservation::STATUS_ACTIVE, + 'expires_at' => $this->expiration(), ]); continue; @@ -180,6 +185,7 @@ class StockReservationService 'status' => StockReservation::STATUS_ACTIVE, 'committed_at' => null, 'released_at' => null, + 'expires_at' => $this->expiration(), ]); } } @@ -214,4 +220,11 @@ class StockReservationService ->lockForUpdate() ->first(); } + + private function expiration(): Carbon + { + return now()->addMinutes( + max(1, (int) config('catalog.stock_reservation_expiration_minutes', 30)), + ); + } } diff --git a/config/catalog.php b/config/catalog.php new file mode 100644 index 0000000..4bab7dc --- /dev/null +++ b/config/catalog.php @@ -0,0 +1,5 @@ + (int) env('STOCK_RESERVATION_EXPIRATION_MINUTES', 30), +]; diff --git a/tests/Feature/Cart/CartControllerTest.php b/tests/Feature/Cart/CartControllerTest.php index c31214a..3438d57 100644 --- a/tests/Feature/Cart/CartControllerTest.php +++ b/tests/Feature/Cart/CartControllerTest.php @@ -80,6 +80,30 @@ class CartControllerTest extends TestCase ]); } + public function test_it_sets_the_configured_expiration_when_creating_a_stock_reservation(): void + { + config()->set('catalog.stock_reservation_expiration_minutes', 45); + $now = now()->startOfSecond(); + $this->travelTo($now); + + $tenant = $this->createTenant('acme'); + $item = $this->createDirectItem($tenant, 10, '49.90'); + + $this->postJson('/api/tenants/acme/cart/items', [ + 'catalog_item_id' => $item->id, + 'cantidad' => 2, + ])->assertOk(); + + $this->assertDatabaseHas('stock_reservations', [ + 'inventory_id' => $item->inventory_id, + 'quantity' => 2, + 'status' => 'active', + 'expires_at' => $now->copy()->addMinutes(45)->toDateTimeString(), + ]); + + $this->travelBack(); + } + public function test_it_filters_item_images_when_the_tenant_disables_them(): void { $tenant = $this->createTenant('acme');