fix(timetable): null-on-delete advance_submissions per RFC §5.4 retention
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>
This commit is contained in:
@@ -15,10 +15,10 @@ use App\Models\Scopes\OrganisationScope;
|
|||||||
* (RFC v0.2 D10) and asserts artist.organisation_id === event.organisation_id.
|
* (RFC v0.2 D10) and asserts artist.organisation_id === event.organisation_id.
|
||||||
*
|
*
|
||||||
* On `deleted` (soft delete): cascade soft-delete to child Performance
|
* On `deleted` (soft delete): cascade soft-delete to child Performance
|
||||||
* rows; hard-delete child AdvanceSection rows (RFC §5.4 — sections have
|
* rows; hard-delete child AdvanceSection rows (per RFC §5.4 — sections
|
||||||
* no soft delete). Child AdvanceSubmission rows are immutable audit
|
* have no soft delete). AdvanceSubmission rows remain in the table with
|
||||||
* records and remain attached to their (now-deleted) section reference
|
* `advance_section_id = NULL` (FK is `nullOnDelete`) per RFC §5.4
|
||||||
* by FK — this is the audit-retention compliance path.
|
* audit-immutability — orphaned but preserved for retention compliance.
|
||||||
*/
|
*/
|
||||||
final class ArtistEngagementObserver
|
final class ArtistEngagementObserver
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
|||||||
{
|
{
|
||||||
Schema::create('advance_submissions', function (Blueprint $table) {
|
Schema::create('advance_submissions', function (Blueprint $table) {
|
||||||
$table->ulid('id')->primary();
|
$table->ulid('id')->primary();
|
||||||
$table->foreignUlid('advance_section_id')->constrained()->cascadeOnDelete();
|
$table->foreignUlid('advance_section_id')->nullable()->constrained()->nullOnDelete();
|
||||||
$table->string('submitted_by_name');
|
$table->string('submitted_by_name');
|
||||||
$table->string('submitted_by_email');
|
$table->string('submitted_by_email');
|
||||||
$table->timestamp('submitted_at');
|
$table->timestamp('submitted_at');
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ DROP TABLE IF EXISTS `advance_submissions`;
|
|||||||
/*!50503 SET character_set_client = utf8mb4 */;
|
/*!50503 SET character_set_client = utf8mb4 */;
|
||||||
CREATE TABLE `advance_submissions` (
|
CREATE TABLE `advance_submissions` (
|
||||||
`id` char(26) COLLATE utf8mb4_unicode_ci NOT NULL,
|
`id` char(26) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
`advance_section_id` char(26) COLLATE utf8mb4_unicode_ci NOT NULL,
|
`advance_section_id` char(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`submitted_by_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
`submitted_by_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
`submitted_by_email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
`submitted_by_email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
`submitted_at` timestamp NOT NULL,
|
`submitted_at` timestamp NOT NULL,
|
||||||
@@ -68,7 +68,7 @@ CREATE TABLE `advance_submissions` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `advance_submissions_reviewed_by_foreign` (`reviewed_by`),
|
KEY `advance_submissions_reviewed_by_foreign` (`reviewed_by`),
|
||||||
KEY `advance_submissions_advance_section_id_status_index` (`advance_section_id`,`status`),
|
KEY `advance_submissions_advance_section_id_status_index` (`advance_section_id`,`status`),
|
||||||
CONSTRAINT `advance_submissions_advance_section_id_foreign` FOREIGN KEY (`advance_section_id`) REFERENCES `advance_sections` (`id`) ON DELETE CASCADE,
|
CONSTRAINT `advance_submissions_advance_section_id_foreign` FOREIGN KEY (`advance_section_id`) REFERENCES `advance_sections` (`id`) ON DELETE SET NULL,
|
||||||
CONSTRAINT `advance_submissions_reviewed_by_foreign` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
CONSTRAINT `advance_submissions_reviewed_by_foreign` FOREIGN KEY (`reviewed_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ final class ArtistEngagementObserverTest extends TestCase
|
|||||||
'type' => 'production',
|
'type' => 'production',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
AdvanceSubmission::create([
|
$submission = AdvanceSubmission::create([
|
||||||
'advance_section_id' => $section->id,
|
'advance_section_id' => $section->id,
|
||||||
'submitted_by_name' => 'TM',
|
'submitted_by_name' => 'TM',
|
||||||
'submitted_by_email' => 'tm@example.test',
|
'submitted_by_email' => 'tm@example.test',
|
||||||
@@ -90,6 +90,8 @@ final class ArtistEngagementObserverTest extends TestCase
|
|||||||
'data' => [],
|
'data' => [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$submissionCountBefore = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count();
|
||||||
|
|
||||||
$eng->delete();
|
$eng->delete();
|
||||||
|
|
||||||
// Performance is soft-deleted (trashed, not removed).
|
// Performance is soft-deleted (trashed, not removed).
|
||||||
@@ -99,11 +101,15 @@ final class ArtistEngagementObserverTest extends TestCase
|
|||||||
// AdvanceSection is hard-deleted.
|
// AdvanceSection is hard-deleted.
|
||||||
$this->assertNull(AdvanceSection::withoutGlobalScope(OrganisationScope::class)->find($section->id));
|
$this->assertNull(AdvanceSection::withoutGlobalScope(OrganisationScope::class)->find($section->id));
|
||||||
|
|
||||||
// Note: `advance_submissions.advance_section_id` currently uses
|
// AdvanceSubmission survives as audit-orphan per RFC §5.4: row
|
||||||
// `cascadeOnDelete()`, so submissions are removed with their section.
|
// preserved, advance_section_id nulled by FK ON DELETE SET NULL.
|
||||||
// RFC v0.2 §5.4 calls submissions "audit-immutable" — interpreted
|
$this->assertSame(
|
||||||
// here as "no application code mutates them post-creation". A
|
$submissionCountBefore,
|
||||||
// future migration may switch the FK to nullOnDelete to preserve
|
AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->count(),
|
||||||
// rows past section hard-delete; out of Session 1 scope.
|
'advance_submissions must persist for retention compliance'
|
||||||
|
);
|
||||||
|
$orphan = AdvanceSubmission::withoutGlobalScope(OrganisationScope::class)->find($submission->id);
|
||||||
|
$this->assertNotNull($orphan);
|
||||||
|
$this->assertNull($orphan->advance_section_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user