advance_submissions.advance_section_id FK changed from cascadeOnDelete
to nullOnDelete; column made nullable. Aligns implementation with
RFC v0.2 §5.4 audit-immutability ("submissions remain for retention
compliance") — when ArtistEngagementObserver::deleted hard-deletes a
section, its submissions persist as orphans rather than disappearing.
Migration edited in place (branch unpushed, dev-only). Observer
docblock + test assertion updated to match. Removed pre-existing
follow-up comment that documented the deviation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
62 lines
2.0 KiB
PHP
62 lines
2.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Observers;
|
|
|
|
use App\Exceptions\Artist\CrossTenantEngagementException;
|
|
use App\Models\ArtistEngagement;
|
|
use App\Models\Scopes\OrganisationScope;
|
|
|
|
/**
|
|
* Observer for ArtistEngagement.
|
|
*
|
|
* On `creating`: denormalises `organisation_id` from the parent Artist
|
|
* (RFC v0.2 D10) and asserts artist.organisation_id === event.organisation_id.
|
|
*
|
|
* On `deleted` (soft delete): cascade soft-delete to child Performance
|
|
* rows; hard-delete child AdvanceSection rows (per RFC §5.4 — sections
|
|
* have no soft delete). AdvanceSubmission rows remain in the table with
|
|
* `advance_section_id = NULL` (FK is `nullOnDelete`) per RFC §5.4
|
|
* audit-immutability — orphaned but preserved for retention compliance.
|
|
*/
|
|
final class ArtistEngagementObserver
|
|
{
|
|
public function creating(ArtistEngagement $engagement): void
|
|
{
|
|
$artist = $engagement->artist()->withoutGlobalScope(OrganisationScope::class)->first();
|
|
$event = $engagement->event()->withoutGlobalScope(OrganisationScope::class)->first();
|
|
|
|
if ($artist === null || $event === null) {
|
|
return;
|
|
}
|
|
|
|
if ($engagement->organisation_id === null) {
|
|
$engagement->organisation_id = $artist->organisation_id;
|
|
}
|
|
|
|
if ($artist->organisation_id !== $event->organisation_id) {
|
|
$engagement->setRelation('artist', $artist);
|
|
$engagement->setRelation('event', $event);
|
|
throw CrossTenantEngagementException::forEngagement($engagement);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reserved for Session 2 state-machine validation when
|
|
* `booking_status` transitions land. No-op for now.
|
|
*/
|
|
public function saving(ArtistEngagement $engagement): void
|
|
{
|
|
// intentionally empty — see class docblock
|
|
}
|
|
|
|
public function deleted(ArtistEngagement $engagement): void
|
|
{
|
|
if (! $engagement->isForceDeleting() && $engagement->trashed()) {
|
|
$engagement->performances()->delete();
|
|
$engagement->advanceSections()->delete();
|
|
}
|
|
}
|
|
}
|