From c711afb5705324059b4ebd216a67a484be928616 Mon Sep 17 00:00:00 2001 From: "Fabian @ Blax Software" Date: Thu, 4 Dec 2025 11:06:09 +0100 Subject: [PATCH] R reserve to claim --- .../create_blax_shop_tables.php.stub | 6 +- src/Console/Commands/ReleaseExpiredStocks.php | 6 +- src/Enums/StockType.php | 4 +- src/Models/Product.php | 27 +- src/Models/ProductStock.php | 42 ++- src/Traits/HasCart.php | 4 +- src/Traits/HasStocks.php | 38 ++- tests/Feature/BookingFeatureTest.php | 8 +- tests/Feature/ProductStockTest.php | 265 ++++++++++++++---- tests/Feature/StockManagementTest.php | 86 +++--- 10 files changed, 360 insertions(+), 126 deletions(-) diff --git a/database/migrations/create_blax_shop_tables.php.stub b/database/migrations/create_blax_shop_tables.php.stub index 3ea6110..7b94837 100644 --- a/database/migrations/create_blax_shop_tables.php.stub +++ b/database/migrations/create_blax_shop_tables.php.stub @@ -150,22 +150,24 @@ return new class extends Migration }); } - // Product stocks table (reservations) + // Product stocks table (claims) if (!Schema::hasTable(config('shop.tables.product_stocks', 'product_stocks'))) { Schema::create(config('shop.tables.product_stocks', 'product_stocks'), function (Blueprint $table) { $table->uuid('id')->primary(); $table->uuid('product_id'); $table->integer('quantity'); - $table->string('type')->default('reservation'); // reservation, adjustment, sale, return + $table->string('type')->default('claimed'); // claimed, adjustment, sale, return $table->string('status')->default('pending'); // pending, completed, cancelled, expired $table->string('reference_type')->nullable(); $table->string('reference_id')->nullable(); + $table->timestamp('claimed_from')->nullable(); $table->timestamp('expires_at')->nullable(); $table->text('note')->nullable(); $table->timestamps(); $table->index(['product_id', 'status']); $table->index(['reference_type', 'reference_id']); + $table->index(['claimed_from', 'expires_at']); $table->foreign('product_id')->references('id')->on(config('shop.tables.products', 'products'))->onDelete('cascade'); }); } diff --git a/src/Console/Commands/ReleaseExpiredStocks.php b/src/Console/Commands/ReleaseExpiredStocks.php index 74edbd6..2e0e5a5 100644 --- a/src/Console/Commands/ReleaseExpiredStocks.php +++ b/src/Console/Commands/ReleaseExpiredStocks.php @@ -9,7 +9,7 @@ class ReleaseExpiredStocks extends Command { protected $signature = 'shop:release-expired-stocks'; - protected $description = 'Release expired stock reservations back to inventory'; + protected $description = 'Release expired stock claims back to inventory'; public function handle(): int { @@ -18,11 +18,11 @@ class ReleaseExpiredStocks extends Command return self::SUCCESS; } - $this->info('Checking for expired stock reservations...'); + $this->info('Checking for expired stock claims...'); $count = ProductStock::releaseExpired(); - $this->info("Released {$count} expired stock reservation(s)."); + $this->info("Released {$count} expired stock claim(s)."); return self::SUCCESS; } diff --git a/src/Enums/StockType.php b/src/Enums/StockType.php index 00d2b8c..ccb208e 100644 --- a/src/Enums/StockType.php +++ b/src/Enums/StockType.php @@ -4,7 +4,7 @@ namespace Blax\Shop\Enums; enum StockType: string { - case RESERVATION = 'reservation'; + case CLAIMED = 'claimed'; case RETURN = 'return'; case INCREASE = 'increase'; case DECREASE = 'decrease'; @@ -12,7 +12,7 @@ enum StockType: string public function label(): string { return match ($this) { - self::RESERVATION => 'Reservation', + self::CLAIMED => 'Claimed', self::RETURN => 'Return', self::INCREASE => 'Increase', self::DECREASE => 'Decrease', diff --git a/src/Models/Product.php b/src/Models/Product.php index 1fc824e..d540870 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -355,26 +355,33 @@ class Product extends Model implements Purchasable, Cartable return true; } - // Get stock reservations that overlap with the requested period - $overlappingReservations = $this->stocks() - ->where('type', StockType::RESERVATION->value) + // Get stock claims that overlap with the requested period + $overlappingClaims = $this->stocks() + ->where('type', StockType::CLAIMED->value) ->where('status', StockStatus::PENDING->value) ->where(function ($query) use ($from, $until) { $query->where(function ($q) use ($from, $until) { - // Reservation starts during the requested period - $q->whereBetween('created_at', [$from, $until]); + // Claim starts during the requested period + $q->whereBetween('claimed_from', [$from, $until]); })->orWhere(function ($q) use ($from, $until) { - // Reservation ends during the requested period + // Claim ends during the requested period $q->whereBetween('expires_at', [$from, $until]); })->orWhere(function ($q) use ($from, $until) { - // Reservation encompasses the entire requested period - $q->where('created_at', '<=', $from) - ->where('expires_at', '>=', $until); + // Claim encompasses the entire requested period + $q->where('claimed_from', '<=', $from) + ->where('expires_at', '>=', $until); + })->orWhere(function ($q) use ($from, $until) { + // Claim without claimed_from (immediately claimed) + $q->whereNull('claimed_from') + ->where(function ($subQ) use ($from, $until) { + $subQ->whereNull('expires_at') + ->orWhere('expires_at', '>=', $from); + }); }); }) ->sum('quantity'); - $availableStock = $this->getAvailableStock() - abs($overlappingReservations); + $availableStock = $this->getAvailableStock() - abs($overlappingClaims); return $availableStock >= $quantity; } diff --git a/src/Models/ProductStock.php b/src/Models/ProductStock.php index eb6e256..25a9e82 100644 --- a/src/Models/ProductStock.php +++ b/src/Models/ProductStock.php @@ -23,6 +23,7 @@ class ProductStock extends Model 'status', 'reference_type', 'reference_id', + 'claimed_from', 'expires_at', 'note', ]; @@ -31,6 +32,7 @@ class ProductStock extends Model 'quantity' => 'integer', 'type' => StockType::class, 'status' => StockStatus::class, + 'claimed_from' => 'datetime', 'expires_at' => 'datetime', ]; @@ -88,14 +90,15 @@ class ProductStock extends Model return $this->expires_at; } - public static function reserve( + public static function claim( Product $product, int $quantity, $reference = null, + ?\DateTimeInterface $from = null, ?\DateTimeInterface $until = null, ?string $note = null ): ?self { - return DB::transaction(function () use ($product, $quantity, $reference, $until, $note) { + return DB::transaction(function () use ($product, $quantity, $reference, $from, $until, $note) { if (!$product->decreaseStock($quantity)) { return null; } @@ -103,10 +106,11 @@ class ProductStock extends Model return self::create([ 'product_id' => $product->id, 'quantity' => $quantity, - 'type' => StockType::RESERVATION, + 'type' => StockType::CLAIMED, 'status' => StockStatus::PENDING, 'reference_type' => $reference ? get_class($reference) : null, 'reference_id' => $reference?->id, + 'claimed_from' => $from, 'expires_at' => $until, 'note' => $note, ]); @@ -187,13 +191,37 @@ class ProductStock extends Model return $query->where('status', StockStatus::COMPLETED->value); } - public static function scopeAvailableReservations($query) + public static function scopeAvailableClaims($query) { - return $query->where('type', StockType::RESERVATION->value)->where('status', StockStatus::PENDING->value); + return $query->where('type', StockType::CLAIMED->value)->where('status', StockStatus::PENDING->value); } - public static function reservations() + public static function claims() { - return self::availableReservations(); + return self::availableClaims(); + } + + public static function scopeAvailableOnDate($query, \DateTimeInterface $date) + { + return $query->where('type', StockType::CLAIMED->value) + ->where('status', StockStatus::PENDING->value) + ->where(function ($q) use ($date) { + $q->where(function ($subQuery) use ($date) { + // Claimed items with claimed_from set + $subQuery->whereNotNull('claimed_from') + ->where('claimed_from', '<=', $date) + ->where(function ($dateQuery) use ($date) { + $dateQuery->whereNull('expires_at') + ->orWhere('expires_at', '>=', $date); + }); + })->orWhere(function ($subQuery) use ($date) { + // Claimed items without claimed_from (always claimed) + $subQuery->whereNull('claimed_from') + ->where(function ($dateQuery) use ($date) { + $dateQuery->whereNull('expires_at') + ->orWhere('expires_at', '>=', $date); + }); + }); + }); } } diff --git a/src/Traits/HasCart.php b/src/Traits/HasCart.php index a89a577..3af72e1 100644 --- a/src/Traits/HasCart.php +++ b/src/Traits/HasCart.php @@ -58,12 +58,12 @@ trait HasCart $product = $product_or_price->purchasable; if ($product instanceof Product) { - $product->reserveStock($quantity); + $product->claimStock($quantity); } } if ($product_or_price instanceof Product) { - $product_or_price->reserveStock($quantity); + $product_or_price->claimStock($quantity); $default_prices = $product_or_price->defaultPrice()->count(); diff --git a/src/Traits/HasStocks.php b/src/Traits/HasStocks.php index d1af5a2..8e83d10 100644 --- a/src/Traits/HasStocks.php +++ b/src/Traits/HasStocks.php @@ -100,9 +100,10 @@ trait HasStocks return true; } - public function reserveStock( + public function claimStock( int $quantity, $reference = null, + ?\DateTimeInterface $from = null, ?\DateTimeInterface $until = null, ?string $note = null ): ?\Blax\Shop\Models\ProductStock { @@ -113,10 +114,11 @@ trait HasStocks $stockModel = config('shop.models.product_stock', 'Blax\Shop\Models\ProductStock'); - return $stockModel::reserve( + return $stockModel::claim( $this, $quantity, $reference, + $from, $until, $note ); @@ -131,7 +133,7 @@ trait HasStocks return max(0, $this->AvailableStocks); } - public function getReservedStock(): int + public function getClaimedStock(): int { return $this->activeStocks()->sum('quantity'); } @@ -180,12 +182,38 @@ trait HasStocks return $this->getAvailableStock() <= $this->low_stock_threshold; } - public function reservations() + public function claims() { $stockModel = config('shop.models.product_stock', 'Blax\Shop\Models\ProductStock'); - return $stockModel::reservations() + return $stockModel::claims() ->willExpire() ->where('product_id', $this->id); } + + public function availableOnDate(\DateTimeInterface $date): int + { + if (!$this->manage_stock) { + return PHP_INT_MAX; + } + + $stockModel = config('shop.models.product_stock', 'Blax\Shop\Models\ProductStock'); + + // Get current available stock (includes all completed stocks minus all currently pending claims) + $currentAvailable = $this->getAvailableStock(); + + // Get all currently pending claimed stocks (not date-filtered) + $allClaimedStocks = $this->stocks() + ->where('type', StockType::CLAIMED->value) + ->where('status', StockStatus::PENDING->value) + ->sum('quantity'); + + // Get stocks claimed on this specific date + $claimedOnDate = $stockModel::availableOnDate($date) + ->where('product_id', $this->id) + ->sum('quantity'); + + // Available on date = current available + all claims - claims active on date + return max(0, $currentAvailable + abs($allClaimedStocks) - abs($claimedOnDate)); + } } diff --git a/tests/Feature/BookingFeatureTest.php b/tests/Feature/BookingFeatureTest.php index ad8d855..9251a30 100644 --- a/tests/Feature/BookingFeatureTest.php +++ b/tests/Feature/BookingFeatureTest.php @@ -124,14 +124,14 @@ class BookingFeatureTest extends TestCase $until ); - // Find the stock reservation - $reservation = $this->bookingProduct->stocks() + // Find the stock claim + $claim = $this->bookingProduct->stocks() ->where('type', 'decrease') ->where('expires_at', $until) ->first(); - $this->assertNotNull($reservation); - $this->assertEquals($until->format('Y-m-d H:i:s'), $reservation->expires_at->format('Y-m-d H:i:s')); + $this->assertNotNull($claim); + $this->assertEquals($until->format('Y-m-d H:i:s'), $claim->expires_at->format('Y-m-d H:i:s')); } /** @test */ diff --git a/tests/Feature/ProductStockTest.php b/tests/Feature/ProductStockTest.php index 2e7cb8e..38b6692 100644 --- a/tests/Feature/ProductStockTest.php +++ b/tests/Feature/ProductStockTest.php @@ -79,120 +79,120 @@ class ProductStockTest extends TestCase } /** @test */ - public function reservation_reduces_available_stock() + public function claim_reduces_available_stock() { $product = Product::factory()->withStocks(100)->create(); - $reservation = $product->reserveStock(25); + $claim = $product->claimStock(25); $this->assertEquals(75, $product->getAvailableStock()); - $this->assertNotNull($reservation); + $this->assertNotNull($claim); } /** @test */ - public function releasing_reservation_increases_available_stock() + public function releasing_claim_increases_available_stock() { $product = Product::factory()->withStocks(100)->create(); - $reservation = $product->reserveStock(25); + $claim = $product->claimStock(25); $this->assertEquals(75, $product->getAvailableStock()); - $reservation->release(); + $claim->release(); $this->assertEquals(100, $product->refresh()->getAvailableStock()); } /** @test */ - public function permanent_reservation_has_no_expiry() + public function permanent_claim_has_no_expiry() { $product = Product::factory()->withStocks(50)->create(); - $reservation = $product->reserveStock(10); + $claim = $product->claimStock(10); - $this->assertNull($reservation->expires_at); - $this->assertTrue($reservation->isPermanent()); + $this->assertNull($claim->expires_at); + $this->assertTrue($claim->isPermanent()); } /** @test */ - public function temporary_reservation_has_expiry() + public function temporary_claim_has_expiry() { $product = Product::factory()->withStocks(50)->create(); - $reservation = $product->reserveStock( + $claim = $product->claimStock( quantity: 10, until: now()->addHours(2) ); - $this->assertNotNull($reservation->expires_at); - $this->assertTrue($reservation->isTemporary()); + $this->assertNotNull($claim->expires_at); + $this->assertTrue($claim->isTemporary()); } /** @test */ - public function reservation_can_have_note() + public function claim_can_have_note() { $product = Product::factory()->withStocks(50)->create(); - $note = 'Reserved for VIP customer'; - $reservation = $product->reserveStock( + $note = 'Claimed for VIP customer'; + $claim = $product->claimStock( quantity: 10, note: $note ); - $this->assertEquals($note, $reservation->note); + $this->assertEquals($note, $claim->note); } /** @test */ - public function cannot_reserve_more_than_available() + public function cannot_claim_more_than_available() { $product = Product::factory()->withStocks(10)->create(); $this->expectException(NotEnoughStockException::class); - $product->reserveStock(15); + $product->claimStock(15); } /** @test */ - public function pending_scope_returns_unreleased_reservations() + public function pending_scope_returns_unreleased_claims() { $product = Product::factory()->withStocks(100)->create(); - $pending = $product->reserveStock(10); - $released = $product->reserveStock(5); + $pending = $product->claimStock(10); + $released = $product->claimStock(5); $released->release(); - $pendingReservations = ProductStock::pending()->get(); + $pendingClaims = ProductStock::pending()->get(); - $this->assertTrue($pendingReservations->contains($pending)); - $this->assertFalse($pendingReservations->contains($released)); + $this->assertTrue($pendingClaims->contains($pending)); + $this->assertFalse($pendingClaims->contains($released)); } /** @test */ - public function released_scope_returns_released_reservations() + public function released_scope_returns_released_claims() { $product = Product::factory()->withStocks(100)->create(); - $pending = $product->reserveStock(10); - $released = $product->reserveStock(5); + $pending = $product->claimStock(10); + $released = $product->claimStock(5); $released->release(); - $releasedReservations = ProductStock::released()->get(); + $releasedClaims = ProductStock::released()->get(); - $this->assertFalse($releasedReservations->contains($pending)); - $this->assertTrue($releasedReservations->contains($released)); + $this->assertFalse($releasedClaims->contains($pending)); + $this->assertTrue($releasedClaims->contains($released)); } /** @test */ - public function expired_reservations_dont_affect_available_stock() + public function expired_claims_dont_affect_available_stock() { $product = Product::factory()->withStocks(100)->create(); - $product->reserveStock( + $product->claimStock( quantity: 20, until: now()->subHour() ); - // Expired reservations should be counted in available stock - $available = $product->reservations()->get(); + // Expired claims should be counted in available stock + $available = $product->claims()->get(); $this->assertEquals(0, $available->count()); } @@ -202,10 +202,10 @@ class ProductStockTest extends TestCase { $product = Product::factory()->withStocks(50)->create(); - $reservation = $product->reserveStock(10); + $claim = $product->claimStock(10); - $this->assertTrue($reservation->release()); - $this->assertFalse($reservation->release()); + $this->assertTrue($claim->release()); + $this->assertFalse($claim->release()); } /** @test */ @@ -242,13 +242,13 @@ class ProductStockTest extends TestCase } /** @test */ - public function reservation_without_stock_management_returns_null() + public function claim_without_stock_management_returns_null() { $product = Product::factory()->create(['manage_stock' => false]); - $reservation = $product->reserveStock(10); + $claim = $product->claimStock(10); - $this->assertNull($reservation); + $this->assertNull($claim); } /** @test */ @@ -263,17 +263,17 @@ class ProductStockTest extends TestCase } /** @test */ - public function reservations_method_filters_active_only() + public function claims_method_filters_active_only() { $product = Product::factory()->withStocks(100)->create(); - $active = $product->reserveStock(10, until: now()->addDay()); - $expired = $product->reserveStock(5, until: now()->subDay()); + $active = $product->claimStock(10, until: now()->addDay()); + $expired = $product->claimStock(5, until: now()->subDay()); - $reservations = $product->reservations()->get(); + $claims = $product->claims()->get(); - $this->assertCount(1, $reservations); - $this->assertEquals($active->id, $reservations->first()->id); + $this->assertCount(1, $claims); + $this->assertEquals($active->id, $claims->first()->id); } /** @test */ @@ -312,4 +312,173 @@ class ProductStockTest extends TestCase $this->assertEquals(25, $product->getAvailableStock()); } + + /** @test */ + public function it_can_claim_stock_with_claimed_from_date() + { + $product = Product::factory()->withStocks(100)->create(); + + $claimedFrom = now()->addDays(5); + $until = now()->addDays(10); + + $claim = $product->claimStock( + quantity: 20, + from: $claimedFrom, + until: $until + ); + + $this->assertNotNull($claim); + $this->assertEquals($claimedFrom->format('Y-m-d H:i:s'), $claim->claimed_from->format('Y-m-d H:i:s')); + $this->assertEquals($until->format('Y-m-d H:i:s'), $claim->expires_at->format('Y-m-d H:i:s')); + } + + /** @test */ + public function it_can_check_available_stock_on_a_date() + { + $product = Product::factory()->withStocks(100)->create(); + + // Claim stock from day 5 to day 10 + $product->claimStock( + quantity: 30, + from: now()->addDays(5), + until: now()->addDays(10) + ); + + // Should have full stock available before claimed_from date + $availableOnDay3 = $product->availableOnDate(now()->addDays(3)); + $this->assertEquals(100, $availableOnDay3); + + // Should have reduced stock during claimed period + $availableOnDay7 = $product->availableOnDate(now()->addDays(7)); + $this->assertEquals(70, $availableOnDay7); + + // Should have full stock available after expires_at date + $availableOnDay12 = $product->availableOnDate(now()->addDays(12)); + $this->assertEquals(100, $availableOnDay12); + } + + /** @test */ + public function it_can_handle_multiple_overlapping_claims_on_date() + { + $product = Product::factory()->withStocks(100)->create(); + + // First claim: days 5-10 + $product->claimStock( + quantity: 20, + from: now()->addDays(5), + until: now()->addDays(10) + ); + + // Second claim: days 8-15 + $product->claimStock( + quantity: 30, + from: now()->addDays(8), + until: now()->addDays(15) + ); + + // Day 6: only first claim is active + $availableOnDay6 = $product->availableOnDate(now()->addDays(6)); + $this->assertEquals(80, $availableOnDay6); + + // Day 9: both claims are active + $availableOnDay9 = $product->availableOnDate(now()->addDays(9)); + $this->assertEquals(50, $availableOnDay9); + + // Day 13: only second claim is active + $availableOnDay13 = $product->availableOnDate(now()->addDays(13)); + $this->assertEquals(70, $availableOnDay13); + + // Day 20: no claims active + $availableOnDay20 = $product->availableOnDate(now()->addDays(20)); + $this->assertEquals(100, $availableOnDay20); + } + + /** @test */ + public function it_handles_claims_without_claimed_from_as_immediately_claimed() + { + $product = Product::factory()->withStocks(100)->create(); + + // Claim without claimed_from (immediately claimed) + $product->claimStock( + quantity: 25, + until: now()->addDays(10) + ); + + // Should be claimed immediately + $availableNow = $product->availableOnDate(now()); + $this->assertEquals(75, $availableNow); + + // Should still be claimed on day 7 + $availableOnDay7 = $product->availableOnDate(now()->addDays(7)); + $this->assertEquals(75, $availableOnDay7); + + // Should be released after expiry + $availableOnDay12 = $product->availableOnDate(now()->addDays(12)); + $this->assertEquals(100, $availableOnDay12); + } + + /** @test */ + public function it_handles_permanent_claims_without_expires_at() + { + $product = Product::factory()->withStocks(100)->create(); + + // Permanent claim from day 5 onwards + $product->claimStock( + quantity: 40, + from: now()->addDays(5) + ); + + // Before claimed_from: full stock available + $availableOnDay3 = $product->availableOnDate(now()->addDays(3)); + $this->assertEquals(100, $availableOnDay3); + + // After claimed_from: reduced stock + $availableOnDay10 = $product->availableOnDate(now()->addDays(10)); + $this->assertEquals(60, $availableOnDay10); + + // Far future: still reduced (permanent claim) + $availableOnDay100 = $product->availableOnDate(now()->addDays(100)); + $this->assertEquals(60, $availableOnDay100); + } + + /** @test */ + public function available_on_date_scope_filters_correctly() + { + $product = Product::factory()->withStocks(100)->create(); + + // Create various claims + $claim1 = $product->claimStock( + quantity: 10, + from: now()->addDays(5), + until: now()->addDays(10) + ); + + $claim2 = $product->claimStock( + quantity: 15, + from: now()->addDays(8), + until: now()->addDays(15) + ); + + $claim3 = $product->claimStock( + quantity: 20, + from: now()->addDays(20), + until: now()->addDays(25) + ); + + // Test scope on day 7 - should only include claim1 + $claimsOnDay7 = \Blax\Shop\Models\ProductStock::availableOnDate(now()->addDays(7)) + ->where('product_id', $product->id) + ->get(); + + $this->assertCount(1, $claimsOnDay7); + $this->assertEquals($claim1->id, $claimsOnDay7->first()->id); + + // Test scope on day 12 - should only include claim2 + $claimsOnDay12 = \Blax\Shop\Models\ProductStock::availableOnDate(now()->addDays(12)) + ->where('product_id', $product->id) + ->get(); + + $this->assertCount(1, $claimsOnDay12); + $this->assertEquals($claim2->id, $claimsOnDay12->first()->id); + } } diff --git a/tests/Feature/StockManagementTest.php b/tests/Feature/StockManagementTest.php index cb41888..a10feec 100644 --- a/tests/Feature/StockManagementTest.php +++ b/tests/Feature/StockManagementTest.php @@ -14,55 +14,55 @@ class StockManagementTest extends TestCase use RefreshDatabase; /** @test */ - public function it_can_reserve_stock_for_a_product() + public function it_can_claim_stock_for_a_product() { $product = Product::factory() ->withStocks(100) ->create(); - $reservation = $product->reserveStock( + $claim = $product->claimStock( quantity: 10, until: now()->addHours(2) ); - $this->assertNotNull($reservation); - $this->assertEquals(10, $reservation->quantity); + $this->assertNotNull($claim); + $this->assertEquals(10, $claim->quantity); $this->assertEquals(90, $product->getAvailableStock()); } /** @test */ - public function it_cannot_reserve_more_stock_than_available() + public function it_cannot_claim_more_stock_than_available() { $product = Product::factory() ->withStocks(5) ->create(); - $reservation = null; + $claim = null; - $this->assertThrows(fn() => $reservation = $product->reserveStock(15), NotEnoughStockException::class); + $this->assertThrows(fn() => $claim = $product->claimStock(15), NotEnoughStockException::class); - $this->assertNull($reservation); + $this->assertNull($claim); $this->assertEquals(5, $product->getAvailableStock()); } /** @test */ - public function it_can_release_reserved_stock() + public function it_can_release_claimed_stock() { $product = Product::factory() ->withStocks(100) ->create(); - $reservation = $product->reserveStock( + $claim = $product->claimStock( quantity: 10, until: now()->addHours(2) ); $this->assertEquals(90, $product->getAvailableStock()); - $reservation->release(); + $claim->release(); $this->assertEquals(100, $product->refresh()->getAvailableStock()); - $this->assertNotNull($reservation->fresh()->released_at); + $this->assertNotNull($claim->fresh()->released_at); } /** @test */ @@ -70,9 +70,9 @@ class StockManagementTest extends TestCase { $product = Product::factory()->withStocks(10)->create(); - $reservation = $product->reserveStock(5); + $claim = $product->claimStock(5); - $pending = ProductStock::pending()->where('id', $reservation->id)->first(); + $pending = ProductStock::pending()->where('id', $claim->id)->first(); $this->assertNotNull($pending); $this->assertNull($pending->released_at); @@ -83,35 +83,35 @@ class StockManagementTest extends TestCase { $product = Product::factory()->withStocks(50)->create(); - $reservation = $product->reserveStock(5); + $claim = $product->claimStock(5); - $reservation->release(); + $claim->release(); - $released = ProductStock::released()->where('id', $reservation->id)->first(); + $released = ProductStock::released()->where('id', $claim->id)->first(); $this->assertNotNull($released); $this->assertNotNull($released->released_at); } /** @test */ - public function it_can_distinguish_temporary_and_permanent_reservations() + public function it_can_distinguish_temporary_and_permanent_claims() { $product = Product::factory()->withStocks(100)->create(); - $permanentReservation = $product->reserveStock( + $permanentClaim = $product->claimStock( quantity: 10 ); - $temporaryReservation = $product->reserveStock( + $temporaryClaim = $product->claimStock( quantity: 5, until: now()->addHours(1) ); - $this->assertTrue($permanentReservation->isPermanent()); - $this->assertFalse($permanentReservation->isTemporary()); + $this->assertTrue($permanentClaim->isPermanent()); + $this->assertFalse($permanentClaim->isTemporary()); - $this->assertTrue($temporaryReservation->isTemporary()); - $this->assertFalse($temporaryReservation->isPermanent()); + $this->assertTrue($temporaryClaim->isTemporary()); + $this->assertFalse($temporaryClaim->isPermanent()); } /** @test */ @@ -119,10 +119,10 @@ class StockManagementTest extends TestCase { $product = Product::factory()->withStocks(20)->create(); - $reservation = $product->reserveStock(5); + $claim = $product->claimStock(5); - $this->assertInstanceOf(Product::class, $reservation->product); - $this->assertEquals($product->id, $reservation->product->id); + $this->assertInstanceOf(Product::class, $claim->product); + $this->assertEquals($product->id, $claim->product->id); } /** @test */ @@ -140,24 +140,24 @@ class StockManagementTest extends TestCase } /** @test */ - public function it_can_get_active_stock_reservations() + public function it_can_get_active_stock_claims() { $product = Product::factory()->withStocks(100)->create(); - $activeReservation = $product->reserveStock( + $activeClaim = $product->claimStock( quantity: 10, until: now()->addHours(2) ); - $expiredReservation = $product->reserveStock( + $expiredClaim = $product->claimStock( quantity: 5, until: now()->subHours(1) ); - $activeReservations = $product->reservations()->get(); + $activeClaims = $product->claims()->get(); - $this->assertCount(1, $activeReservations); - $this->assertEquals($activeReservation->id, $activeReservations->first()->id); + $this->assertCount(1, $activeClaims); + $this->assertEquals($activeClaim->id, $activeClaims->first()->id); } /** @test */ @@ -165,25 +165,25 @@ class StockManagementTest extends TestCase { $product = Product::factory()->withStocks()->create(); - $reservation = $product->reserveStock(5); + $claim = $product->claimStock(5); - $this->assertTrue($reservation->release()); - $this->assertFalse($reservation->release()); + $this->assertTrue($claim->release()); + $this->assertFalse($claim->release()); } /** @test */ - public function it_can_store_reservation_note() + public function it_can_store_claim_note() { $product = Product::factory()->withStocks()->create(); $note = "Customer requested to hold this item for 2 days."; - $reservation = $product->reserveStock( + $claim = $product->claimStock( quantity: 5, note: $note ); - $this->assertEquals($note, $reservation->note); + $this->assertEquals($note, $claim->note); } /** @test */ @@ -191,18 +191,18 @@ class StockManagementTest extends TestCase { $product = Product::factory()->withStocks(100)->create(); - $reservation1 = $product->reserveStock( + $claim1 = $product->claimStock( quantity: 10, until: now()->addHours(2) ); - $reservation2 = $product->reserveStock( + $claim2 = $product->claimStock( quantity: 5, until: now()->addHours(1) ); - $reservation1->refresh(); - $reservation2->refresh(); + $claim1->refresh(); + $claim2->refresh(); $this->assertEquals(85, $product->refresh()->getAvailableStock()); }