create(); $event = Event::factory()->for($org)->create(); $artist = Artist::factory()->create(['organisation_id' => $org->id]); $eng = ArtistEngagement::create([ 'artist_id' => $artist->id, 'event_id' => $event->id, 'booking_status' => ArtistEngagementStatus::Draft->value, ]); $this->assertSame($org->id, $eng->fresh()->organisation_id); } public function test_cross_tenant_engagement_throws(): void { $orgA = Organisation::factory()->create(); $orgB = Organisation::factory()->create(); $artist = Artist::factory()->create(['organisation_id' => $orgA->id]); $eventInOtherOrg = Event::factory()->for($orgB)->create(); $this->expectException(CrossTenantEngagementException::class); ArtistEngagement::create([ 'artist_id' => $artist->id, 'event_id' => $eventInOtherOrg->id, 'booking_status' => ArtistEngagementStatus::Draft->value, ]); } public function test_soft_delete_cascades_to_performances_and_hard_deletes_advance_sections(): void { $org = Organisation::factory()->create(); $event = Event::factory()->for($org)->create(); $artist = Artist::factory()->create(['organisation_id' => $org->id]); $eng = ArtistEngagement::create([ 'artist_id' => $artist->id, 'event_id' => $event->id, 'booking_status' => ArtistEngagementStatus::Draft->value, ]); $stage = Stage::factory()->for($event)->create(); $start = CarbonImmutable::now(); $perf = Performance::create([ 'engagement_id' => $eng->id, 'event_id' => $event->id, 'stage_id' => $stage->id, 'start_at' => $start, 'end_at' => $start->addHour(), ]); $section = AdvanceSection::create([ 'engagement_id' => $eng->id, 'name' => 'Production', 'type' => 'production', ]); $submission = AdvanceSubmission::create([ 'advance_section_id' => $section->id, 'submitted_by_name' => 'TM', 'submitted_by_email' => 'tm@example.test', 'submitted_at' => now(), 'status' => 'pending', 'data' => [], ]); $submissionCountBefore = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count(); $eng->delete(); // Performance is soft-deleted (trashed, not removed). $this->assertNotNull(Performance::withoutGlobalScope(OrganisationScope::class)->withTrashed()->find($perf->id)); $this->assertSoftDeleted($perf); // AdvanceSection is hard-deleted. $this->assertNull(AdvanceSection::withoutGlobalScope(OrganisationScope::class)->find($section->id)); // AdvanceSubmission survives as audit-orphan per RFC ยง5.4: row // preserved, advance_section_id nulled by FK ON DELETE SET NULL. $this->assertSame( $submissionCountBefore, AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count(), 'advance_submissions must persist for retention compliance' ); $orphan = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->find($submission->id); $this->assertNotNull($orphan); $this->assertNull($orphan->advance_section_id); } }