diff --git a/app/Domains/Cart/Services/InvalidateEventDateCartsService.php b/app/Domains/Cart/Services/InvalidateEventDateCartsService.php index 5480113..0d92748 100644 --- a/app/Domains/Cart/Services/InvalidateEventDateCartsService.php +++ b/app/Domains/Cart/Services/InvalidateEventDateCartsService.php @@ -15,9 +15,12 @@ class InvalidateEventDateCartsService public function __construct(private readonly StockReservationService $reservations) {} /** @param Collection $eventDateIds */ - public function invalidate(Tenant $tenant, Collection $eventDateIds): void - { - DB::transaction(function () use ($tenant, $eventDateIds): void { + public function invalidate( + Tenant $tenant, + Collection $eventDateIds, + string $reason = StockReservationService::REASON_EVENT_DATE_RESCHEDULED, + ): void { + DB::transaction(function () use ($tenant, $eventDateIds, $reason): void { $carts = Cart::query() ->where('tenant_codigo', $tenant->codigo) ->where('status', Cart::STATUS_ACTIVE) @@ -44,7 +47,7 @@ class InvalidateEventDateCartsService $this->reservations->releaseCurrentCartReservation( $cart, - StockReservationService::REASON_EVENT_DATE_RESCHEDULED, + $reason, ); // Reuse the existing expired-cart flow: stale mutations receive the diff --git a/app/Domains/Catalog/Services/StockReservationService.php b/app/Domains/Catalog/Services/StockReservationService.php index bd6e0b5..87228a5 100644 --- a/app/Domains/Catalog/Services/StockReservationService.php +++ b/app/Domains/Catalog/Services/StockReservationService.php @@ -21,6 +21,8 @@ class StockReservationService public const REASON_EVENT_DATE_RESCHEDULED = 'event_date_rescheduled'; + public const REASON_EVENT_DATE_SUSPENDED = 'event_date_suspended'; + public const REASON_PURCHASE_SUPERSEDED = 'purchase_superseded'; public const REASON_PURCHASE_CANCELLED = 'purchase_cancelled'; diff --git a/app/Domains/Event/Services/EventService.php b/app/Domains/Event/Services/EventService.php index 0662d09..7cecbd1 100644 --- a/app/Domains/Event/Services/EventService.php +++ b/app/Domains/Event/Services/EventService.php @@ -5,6 +5,7 @@ namespace App\Domains\Event\Services; use App\Domains\Auth\Models\User; use App\Domains\Cart\Services\InvalidateEventDateCartsService; use App\Domains\Catalog\Models\Variant; +use App\Domains\Catalog\Services\StockReservationService; use App\Domains\Catalog\Services\VariantReplacementService; use App\Domains\Event\Enums\EventDateChangeType; use App\Domains\Event\Events\EventDateRescheduled; @@ -177,10 +178,13 @@ class EventService return $date->load('validityTime'); } - $purchaseTickets = $this->affectedPurchaseResolver->resolve( + $affectedDateIds = $this->affectedDateIds($tenant, $date); + $this->invalidateEventDateCarts->invalidate( $tenant, - $this->affectedDateIds($tenant, $date), + $affectedDateIds, + StockReservationService::REASON_EVENT_DATE_SUSPENDED, ); + $purchaseTickets = $this->affectedPurchaseResolver->resolve($tenant, $affectedDateIds); $date->update(['suspended_at' => now()]); $this->variantReplacementService->disableForSuspension($date); $this->disableTicketsWithoutUsableDates($tenant, $date); diff --git a/tests/Feature/Event/AdminAppEventControllerTest.php b/tests/Feature/Event/AdminAppEventControllerTest.php index e07ffe2..5c4f79a 100644 --- a/tests/Feature/Event/AdminAppEventControllerTest.php +++ b/tests/Feature/Event/AdminAppEventControllerTest.php @@ -26,6 +26,7 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Laravel\Sanctum\Sanctum; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\TestCase; class AdminAppEventControllerTest extends TestCase @@ -422,9 +423,18 @@ class AdminAppEventControllerTest extends TestCase ); } - public function test_rescheduling_invalidates_reserved_carts_before_replacing_variants(): void + public static function cartInvalidatingDateChanges(): array { - Event::fake([EventDateRescheduled::class]); + return [ + 'reschedule' => ['reschedule', ['date' => '2027-10-20'], 'event_date_rescheduled'], + 'suspend' => ['suspend', [], 'event_date_suspended'], + ]; + } + + #[DataProvider('cartInvalidatingDateChanges')] + public function test_date_changes_invalidate_reserved_carts(string $action, array $payload, string $reason): void + { + Event::fake([EventDateRescheduled::class, EventDateSuspended::class]); $tenant = $this->createActiveEvent($this->createTenant('acme'), 'Festival Acme'); $source = $tenant->eventDates()->create([ 'date' => '2027-10-09', 'time_start' => '09:00', 'time_end' => '18:30', @@ -449,13 +459,15 @@ class AdminAppEventControllerTest extends TestCase ]); Sanctum::actingAs($admin); - $this->postJson("/api/v1/adminapp/tenant/event-dates/{$source->id}/reschedule", [ - 'date' => '2027-10-20', - ])->assertOk(); + $this->postJson("/api/v1/adminapp/tenant/event-dates/{$source->id}/{$action}", $payload)->assertOk(); $this->assertSame(Cart::STATUS_EXPIRED, $cart->fresh()->status); $this->assertSame(StockReservation::STATUS_RELEASED, $reservation->fresh()->status); - $this->assertSame(0, $variant->fresh()->replacement->inventory->reserved_stock); + $this->assertSame($reason, $reservation->fresh()->release_reason); + $this->assertSame(0, $variant->fresh()->inventory->reserved_stock); + if ($action === 'reschedule') { + $this->assertSame(0, $variant->fresh()->replacement->inventory->reserved_stock); + } $this->getJson('/api/tenants/acme/cart')->assertOk()->assertJsonCount(0, 'data.items'); } diff --git a/tests/Unit/Cart/InvalidateEventDateCartsServiceTest.php b/tests/Unit/Cart/InvalidateEventDateCartsServiceTest.php index a551c4e..3d59a4d 100644 --- a/tests/Unit/Cart/InvalidateEventDateCartsServiceTest.php +++ b/tests/Unit/Cart/InvalidateEventDateCartsServiceTest.php @@ -6,10 +6,12 @@ use App\Domains\Cart\Models\Cart; use App\Domains\Cart\Services\InvalidateEventDateCartsService; use App\Domains\Catalog\Models\Inventory; use App\Domains\Catalog\Models\StockReservation; +use App\Domains\Catalog\Services\StockReservationService; use App\Domains\Tenant\Models\Tenant; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\TestCase; class InvalidateEventDateCartsServiceTest extends TestCase @@ -82,7 +84,16 @@ class InvalidateEventDateCartsServiceTest extends TestCase DB::table('variant_event_dates')->insert(['variant_id' => 3, 'event_date_id' => 1]); } - public function test_invalidates_whole_cart_and_releases_all_its_stock_only_once(): void + public static function releaseReasons(): array + { + return [ + [StockReservationService::REASON_EVENT_DATE_RESCHEDULED], + [StockReservationService::REASON_EVENT_DATE_SUSPENDED], + ]; + } + + #[DataProvider('releaseReasons')] + public function test_invalidates_whole_cart_and_releases_all_its_stock_only_once(string $reason): void { $cart = $this->cart(1); $otherInventory = DB::table('inventories')->insertGetId([]); @@ -93,8 +104,8 @@ class InvalidateEventDateCartsServiceTest extends TestCase 'quantity' => 2, ]); $reservationId = $cart->current_stock_reservation_id; - $this->invalidate(); - $this->invalidate(); + $this->invalidate($reason); + $this->invalidate($reason); $this->assertSame(Cart::STATUS_EXPIRED, $cart->fresh()->status); $this->assertNull($cart->fresh()->current_stock_reservation_id); @@ -102,7 +113,7 @@ class InvalidateEventDateCartsServiceTest extends TestCase $this->assertSame(20, (int) Inventory::query()->sum('real_stock')); $this->assertDatabaseHas('stock_reservations', [ 'id' => $reservationId, 'status' => StockReservation::STATUS_RELEASED, - 'release_reason' => 'event_date_rescheduled', + 'release_reason' => $reason, ]); } @@ -138,9 +149,9 @@ class InvalidateEventDateCartsServiceTest extends TestCase $this->assertSame(2, (int) Inventory::query()->sum('reserved_stock')); } - private function invalidate(): void + private function invalidate(string $reason = StockReservationService::REASON_EVENT_DATE_RESCHEDULED): void { - app(InvalidateEventDateCartsService::class)->invalidate(new Tenant(['codigo' => 'acme']), collect([1])); + app(InvalidateEventDateCartsService::class)->invalidate(new Tenant(['codigo' => 'acme']), collect([1]), $reason); } private function cart(int $variantId, string $tenantCode = 'acme'): Cart