Datenmodell Kern: Projekt, PFA, Vertrag, LV, Nachtrag, Dokument

- Mandant, Auftragnehmer, Projekt mit Suchreihenfolge, Abschnitt (PFA), Vertrag,
  Leistungsverzeichnis, Nachtrag (MKA nur als Bezug), Nachtragsposition, Dokument
- Projektmitglieder mit Projektrolle, Freigaben für vertrauliche Dokumente
- Mandant, Land und Projekt werden vom übergeordneten Datensatz übernommen;
  Fehlzuordnungen über Mandanten oder Projekte hinweg werden abgewiesen
- Feste Morph-Map, Factories, Demodaten (Musterbahn, PFA 1–4, N14/N16/N21), Tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Christoph
2026-10-05 08:34:24 +02:00
co-authored by Claude Opus 5.5
parent ca09d23a78
commit 6aac8b0a16
37 changed files with 1855 additions and 2 deletions
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Models\Concerns;
use App\Models\Projekt;
use BackedEnum;
use Illuminate\Database\Eloquent\Model;
use LogicException;
/**
* Übernimmt mandant_id, land und projekt_id beim Speichern vom übergeordneten Datensatz.
*
* Zeigt ein Datensatz auf einen übergeordneten Datensatz eines anderen Mandanten, Landes oder
* Projekts, wird das Speichern abgewiesen. So kann kein Vertrag in Projekt A an einem Abschnitt
* aus Projekt B hängen.
*/
trait ErbtZuordnung
{
/**
* Der übergeordnete Datensatz, von dem die Zuordnung übernommen wird.
*/
abstract protected function zuordnungsEltern(): ?Model;
protected static function bootErbtZuordnung(): void
{
static::saving(function (self $model): void {
$model->uebernimmZuordnung();
});
}
protected function uebernimmZuordnung(): void
{
$eltern = $this->zuordnungsEltern();
if ($eltern === null) {
return;
}
$this->gleicheZuordnungAn($eltern, [
'mandant_id' => $eltern->getAttribute('mandant_id'),
'land' => $eltern->getAttribute('land'),
'projekt_id' => $eltern instanceof Projekt ? $eltern->getKey() : $eltern->getAttribute('projekt_id'),
]);
}
/**
* Setzt fehlende Werte und weist abweichende ab.
*
* @param array<string, mixed> $werte
*/
protected function gleicheZuordnungAn(Model $anderes, array $werte): void
{
foreach ($werte as $spalte => $wert) {
$eigen = $this->getAttribute($spalte);
if ($eigen === null) {
$this->setAttribute($spalte, $wert);
} elseif (self::vergleichswert($eigen) !== self::vergleichswert($wert)) {
throw new LogicException(sprintf(
'%s: %s passt nicht zu %s.',
class_basename($this),
$spalte,
class_basename($anderes),
));
}
}
}
private static function vergleichswert(mixed $wert): ?string
{
if ($wert instanceof BackedEnum) {
return (string) $wert->value;
}
return $wert === null ? null : (string) $wert;
}
}