ArtistEngagementObserver: - creating: auto-fills organisation_id from parent Artist (RFC v0.2 D10 denormalisation), asserts artist.organisation_id == event.organisation_id; cross-tenant linkage throws CrossTenantEngagementException (extends DomainException, included in this commit). - saving: no-op marker reserved for Session 2 state-machine validation. - deleted: cascades soft-delete to Performance children, hard-deletes AdvanceSection children. AdvanceSubmission rows are immutable per RFC §5.4 and remain attached. PerformanceObserver: - saving: increments version by 1 on UPDATE only (D14 optimistic lock). MoveTimetablePerformanceRequest in Session 2 uses this for concurrent- edit detection. Both observers registered in AppServiceProvider::boot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30 lines
970 B
PHP
30 lines
970 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Exceptions\Artist;
|
|
|
|
use App\Models\ArtistEngagement;
|
|
use DomainException;
|
|
|
|
/**
|
|
* Raised when an ArtistEngagement is being created with an artist and
|
|
* event that belong to different organisations. The engagement's
|
|
* `organisation_id` is denormalised from the artist (RFC v0.2 D10);
|
|
* the event must match. Cross-tenant linkage is a hard error — fail
|
|
* loud rather than silently denormalise the wrong tenant.
|
|
*/
|
|
final class CrossTenantEngagementException extends DomainException
|
|
{
|
|
public static function forEngagement(ArtistEngagement $engagement): self
|
|
{
|
|
return new self(sprintf(
|
|
'ArtistEngagement cross-tenant: artist=%s (org=%s) vs event=%s (org=%s).',
|
|
$engagement->artist_id ?? 'null',
|
|
$engagement->artist?->organisation_id ?? 'null',
|
|
$engagement->event_id ?? 'null',
|
|
$engagement->event?->organisation_id ?? 'null',
|
|
));
|
|
}
|
|
}
|