$attributes */ public function create(Organisation $organisation, array $attributes): Artist { return DB::transaction(function () use ($organisation, $attributes): Artist { $name = (string) $attributes['name']; $existing = Artist::query() ->where('organisation_id', $organisation->id) ->whereRaw('LOWER(name) = ?', [mb_strtolower($name)]) ->first(); if ($existing !== null) { throw new DuplicateArtistException($existing); } unset($attributes['organisation_id'], $attributes['slug']); $artist = new Artist($attributes); $artist->organisation_id = $organisation->id; $artist->save(); return $artist->refresh(); }); } /** * Master-update. Disallows organisation_id mutation (cross-tenant * move is a separate, audited operation we don't expose here). * Slug is not editable post-create either; rename produces a fresh * slug only on Session 4 admin "force-rename" flow if/when added. * * @param array $attributes */ public function update(Artist $artist, array $attributes): Artist { unset($attributes['organisation_id'], $attributes['slug']); $artist->fill($attributes); $artist->save(); return $artist->refresh(); } /** * Soft delete. Engagements are intentionally untouched per RFC D27 — * historical engagements survive their master being archived; the * frontend renders a "trashed" banner on the engagement detail. */ public function softDelete(Artist $artist): void { $artist->delete(); } public function restore(Artist $artist): Artist { $artist->restore(); return $artist->refresh(); } }