seed(AuthorizationSeeder::class); WebsiteType::query()->create(['codigo' => 'onticket', 'nombre' => 'OnTicket']); } public function test_authentication_is_required_to_read_a_sale_detail(): void { $this->getJson('/api/v1/adminapp/tenant/sales/1')->assertUnauthorized(); } public function test_an_adminapp_user_can_read_a_sale_detail_from_its_tenant(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $purchase = Purchase::query()->create([ 'tenant_codigo' => $tenant->codigo, 'status' => Purchase::STATUS_PAID, 'total' => '40000.00', 'nombre_apellido' => 'Ada Lovelace', ]); PurchaseItem::query()->create([ 'compra_id' => $purchase->id, 'source_catalog_item_id' => 10, 'nombre' => 'Comida', 'item_nombre' => 'Comida', 'variant_attributes' => [ ['name' => 'Fecha', 'value' => ['2026-10-12']], ['name' => 'Servicio', 'value' => 'Almuerzo'], ], 'cantidad' => 2, 'precio_unitario' => '10000.00', 'total' => '20000.00', ]); $this->getJson("/api/v1/adminapp/tenant/sales/{$purchase->id}") ->assertOk() ->assertJsonPath('data.id', $purchase->id) ->assertJsonPath('data.items.0.product', 'Comida') ->assertJsonPath('data.items.0.event_dates.0', '2026-10-12') ->assertJsonPath('data.items.0.quantity', 2) ->assertJsonPath('data.items.0.unit_price', '10000.00') ->assertJsonPath('data.items.0.total', '20000.00') ->assertJsonPath('data.total', '40000.00'); } public function test_an_adminapp_user_cannot_read_a_sale_from_another_tenant(): void { $tenant = $this->createTenant('acme'); $otherTenant = $this->createTenant('other'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $foreignPurchase = Purchase::query()->create([ 'tenant_codigo' => $otherTenant->codigo, 'status' => Purchase::STATUS_PAID, 'total' => '10000.00', ]); $this->getJson("/api/v1/adminapp/tenant/sales/{$foreignPurchase->id}") ->assertNotFound(); } public function test_an_adminapp_user_can_read_all_tickets_from_a_sale_in_its_tenant(): void { $tenant = $this->createTenant('acme'); $admin = $this->createAdminAppUser($tenant); Sanctum::actingAs($admin); $purchase = Purchase::query()->create([ 'tenant_codigo' => $tenant->codigo, 'status' => Purchase::STATUS_PAID, 'total' => '20000.00', ]); $firstTicket = Ticket::query()->create([ 'tenant_code' => $tenant->codigo, 'ticket' => '11111111-1111-4111-8111-111111111111', 'name' => 'Abono general', 'description' => 'Acceso general', 'source_purchase_id' => $purchase->id, 'user_id' => $admin->id, ]); $usedTicket = Ticket::query()->create([ 'tenant_code' => $tenant->codigo, 'ticket' => '22222222-2222-4222-8222-222222222222', 'name' => 'Abono general', 'description' => 'Acceso general', 'source_purchase_id' => $purchase->id, 'used_at' => now()->subMinute(), 'user_id' => $admin->id, ]); $this->getJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/tickets") ->assertOk() ->assertExactJson([ 'data' => [ [ 'product' => 'Abono general', 'id' => $firstTicket->id, 'expires_at' => null, 'status' => Ticket::STATUS_ACTIVE, ], [ 'product' => 'Abono general', 'id' => $usedTicket->id, 'expires_at' => null, 'status' => Ticket::STATUS_USED, ], ], ]); } public function test_an_adminapp_user_cannot_read_tickets_from_another_tenant_sale(): void { $tenant = $this->createTenant('acme'); $otherTenant = $this->createTenant('other'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $foreignPurchase = Purchase::query()->create([ 'tenant_codigo' => $otherTenant->codigo, 'status' => Purchase::STATUS_PAID, 'total' => '10000.00', ]); $this->getJson("/api/v1/adminapp/tenant/sales/{$foreignPurchase->id}/tickets") ->assertNotFound(); } public function test_an_adminapp_user_can_confirm_a_pending_sale(): void { Event::fake([PurchasePaid::class]); $tenant = $this->createTenant('acme'); $admin = $this->createAdminAppUser($tenant); Sanctum::actingAs($admin); $purchase = Purchase::query()->create([ 'tenant_codigo' => $tenant->codigo, 'status' => Purchase::STATUS_PENDING_PAYMENT, 'total' => '10000.00', ]); $this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/confirm") ->assertOk() ->assertJsonPath('data.status', Purchase::STATUS_PAID); $this->assertSame(Purchase::STATUS_PAID, $purchase->fresh()->status); $this->assertDatabaseHas('value_changes', [ 'trackable_id' => $purchase->id, 'attribute' => 'status', 'old_value' => Purchase::STATUS_PENDING_PAYMENT, 'new_value' => Purchase::STATUS_PAID, 'user_id' => $admin->id, ]); Event::assertDispatchedTimes(PurchasePaid::class, 1); $this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/confirm") ->assertOk() ->assertJsonPath('data.status', Purchase::STATUS_PAID); Event::assertDispatchedTimes(PurchasePaid::class, 1); } public function test_adminapp_cancellation_does_not_restore_the_source_cart(): void { $tenant = $this->createTenant('acme'); $admin = $this->createAdminAppUser($tenant); Sanctum::actingAs($admin); $cart = Cart::query()->create([ 'tenant_codigo' => $tenant->codigo, 'user_id' => $admin->id, 'status' => 'converted', ]); $cart->delete(); $purchase = Purchase::query()->create([ 'cart_id' => $cart->id, 'tenant_codigo' => $tenant->codigo, 'status' => Purchase::STATUS_PENDING_PAYMENT, 'total' => '10000.00', ]); $this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/cancel") ->assertOk() ->assertJsonPath('data.status', Purchase::STATUS_CANCELLED); $this->assertSame(Purchase::STATUS_CANCELLED, $purchase->fresh()->status); $this->assertSoftDeleted('carritos', ['id' => $cart->id]); $this->assertSame('converted', Cart::withTrashed()->findOrFail($cart->id)->status); } public function test_an_adminapp_user_cannot_change_a_sale_from_another_tenant(): void { $tenant = $this->createTenant('acme'); $otherTenant = $this->createTenant('other'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $foreignPurchase = Purchase::query()->create([ 'tenant_codigo' => $otherTenant->codigo, 'status' => Purchase::STATUS_PENDING_PAYMENT, 'total' => '10000.00', ]); $this->postJson("/api/v1/adminapp/tenant/sales/{$foreignPurchase->id}/confirm") ->assertNotFound(); $this->postJson("/api/v1/adminapp/tenant/sales/{$foreignPurchase->id}/cancel") ->assertNotFound(); } public function test_a_paid_sale_cannot_be_cancelled_from_adminapp(): void { $tenant = $this->createTenant('acme'); Sanctum::actingAs($this->createAdminAppUser($tenant)); $purchase = Purchase::query()->create([ 'tenant_codigo' => $tenant->codigo, 'status' => Purchase::STATUS_PAID, 'total' => '10000.00', ]); $this->postJson("/api/v1/adminapp/tenant/sales/{$purchase->id}/cancel") ->assertUnprocessable() ->assertJsonValidationErrors('purchase'); } private function createTenant(string $code): Tenant { return Tenant::query()->create([ 'codigo' => $code, 'nombre' => ucfirst($code), 'dominio' => "{$code}.test", 'website_type_code' => 'onticket', ]); } private function createAdminAppUser(Tenant $tenant): User { return User::factory()->create([ 'rol_codigo' => RoleCode::AdminApp->value, 'tenant_codigo' => $tenant->codigo, ]); } }