feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis (#43)
This commit was merged in pull request #43.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die
|
||||
-- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes
|
||||
-- (Archivsuche), die partiellen eindeutigen Indizes und die Trigger auf
|
||||
-- veroeffentlichten Versionen: es kennt sie nicht, weil sie sich im Schema nicht
|
||||
-- ausdruecken lassen. Diese Loeschbefehle sind bewusst gestrichen.
|
||||
--
|
||||
-- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind –
|
||||
-- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Vorgesetzter auch fuer von Hand angelegte Personen (#020).
|
||||
--
|
||||
-- Bisher lief die Auskunft allein ueber `vorgesetzterExterneId`, und die ist nur
|
||||
-- innerhalb einer Verzeichnisquelle eindeutig: eine Person ohne Quelle konnte
|
||||
-- keinen Vorgesetzten haben, {{vorgesetzter_sam}} blieb dort immer leer.
|
||||
ALTER TABLE "Mitarbeiter"
|
||||
ADD COLUMN "vorgesetzterId" TEXT;
|
||||
|
||||
CREATE INDEX "Mitarbeiter_vorgesetzterId_idx" ON "Mitarbeiter"("vorgesetzterId");
|
||||
|
||||
ALTER TABLE "Mitarbeiter"
|
||||
ADD CONSTRAINT "Mitarbeiter_vorgesetzterId_fkey"
|
||||
FOREIGN KEY ("vorgesetzterId") REFERENCES "Mitarbeiter" ("id")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Die Mandantengrenze, wie bei Lauf und Geraet: der Vorgesetzte gehoert zum
|
||||
-- selben Kunden. MATCH SIMPLE ist richtig — ist die Spalte NULL, greift der
|
||||
-- Fremdschluessel nicht, eine Person ohne Vorgesetzten bleibt also erlaubt.
|
||||
ALTER TABLE "Mitarbeiter"
|
||||
ADD CONSTRAINT "Mitarbeiter_vorgesetzter_gleicher_kunde"
|
||||
FOREIGN KEY ("vorgesetzterId", "kundeId") REFERENCES "Mitarbeiter" ("id", "kundeId")
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Niemand ist sein eigener Vorgesetzter. Das ist die kuerzeste Schleife und die
|
||||
-- einzige, die sich ohne Rekursion abfangen laesst; laengere haelt der Dienst
|
||||
-- auf (siehe mitarbeiter.ts, vorgesetztenSetzen).
|
||||
ALTER TABLE "Mitarbeiter"
|
||||
ADD CONSTRAINT "Mitarbeiter_vorgesetzter_nicht_selbst"
|
||||
CHECK ("vorgesetzterId" IS NULL OR "vorgesetzterId" <> "id");
|
||||
|
||||
-- Nachfuehrung fuer bestehende Installationen.
|
||||
--
|
||||
-- Ohne sie waere `{{vorgesetzter_sam}}` nach dieser Migration bei **jeder**
|
||||
-- Person aus dem Verzeichnis leer, bis der naechste Abgleich laeuft -- und zwar
|
||||
-- still: die Auskunft lief bisher allein ueber `vorgesetzterExterneId`, der neue
|
||||
-- Leseweg nimmt nur `vorgesetzterId`. Dieselbe Falle wie beim Seed-Schluessel
|
||||
-- in #021: ein Umstieg, der die vorhandenen Daten nicht mitnimmt.
|
||||
--
|
||||
-- Die Zuordnung laeuft ueber die `externeId` **derselben** Quelle, genau wie der
|
||||
-- alte Leseweg: die Kennung ist nur dort eindeutig.
|
||||
UPDATE "Mitarbeiter" AS person
|
||||
SET "vorgesetzterId" = chef."id"
|
||||
FROM "Mitarbeiter" AS chef
|
||||
WHERE person."vorgesetzterExterneId" IS NOT NULL
|
||||
AND person."quelleId" IS NOT NULL
|
||||
AND chef."quelleId" = person."quelleId"
|
||||
AND chef."externeId" = person."vorgesetzterExterneId"
|
||||
-- Die Mandantengrenze gilt auch hier; stand je eine Zuordnung ueber Kunden
|
||||
-- hinweg in den Daten, bliebe sie leer statt den Fremdschluessel zu brechen.
|
||||
AND chef."kundeId" = person."kundeId"
|
||||
AND chef."id" <> person."id";
|
||||
+18
-4
@@ -249,17 +249,30 @@ model Mitarbeiter {
|
||||
/// passende Abteilung bleibt trotzdem erhalten.
|
||||
abteilung String?
|
||||
titel String?
|
||||
/// Der rohe `manager`-Wert aus dem Verzeichnis, aufgeloest auf die `externeId`
|
||||
/// des Vorgesetzten — je Quelle eindeutig. Wie `abteilung` ist das der Wert
|
||||
/// des Verzeichnisses, nicht die gepflegte Zuordnung; die steht in
|
||||
/// `vorgesetzterId`. Leer bei einer von Hand angelegten Person.
|
||||
vorgesetzterExterneId String?
|
||||
/// Die **gepflegte** Zuordnung, und der einzige Leseweg (#020). Der Abgleich
|
||||
/// schreibt sie aus `vorgesetzterExterneId` mit, von Hand gesetzt geht sie
|
||||
/// auch ohne Verzeichnis. Darf nicht auf die Person selbst zeigen (CHECK) und
|
||||
/// nicht auf jemanden eines anderen Kunden (zusammengesetzter Fremdschluessel
|
||||
/// `Mitarbeiter_vorgesetzter_gleicher_kunde`). Laengere Schleifen haelt der
|
||||
/// Dienst auf.
|
||||
vorgesetzterId String?
|
||||
aktiv Boolean @default(true)
|
||||
herkunft Herkunft @default(MANUELL)
|
||||
zuletztGesehenAm DateTime?
|
||||
erstelltAm DateTime @default(now())
|
||||
geaendertAm DateTime @default(now()) @updatedAt
|
||||
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
quelle Verzeichnisquelle? @relation(fields: [quelleId], references: [id], onDelete: Restrict)
|
||||
geraete Geraet[]
|
||||
laeufe Lauf[]
|
||||
kunde Kunde @relation(fields: [kundeId], references: [id], onDelete: Restrict)
|
||||
quelle Verzeichnisquelle? @relation(fields: [quelleId], references: [id], onDelete: Restrict)
|
||||
vorgesetzter Mitarbeiter? @relation("Vorgesetzter", fields: [vorgesetzterId], references: [id], onDelete: Restrict)
|
||||
untergebene Mitarbeiter[] @relation("Vorgesetzter")
|
||||
geraete Geraet[]
|
||||
laeufe Lauf[]
|
||||
|
||||
/// Postgres behandelt NULL als verschieden, deshalb stoeren mehrere manuell
|
||||
/// angelegte Mitarbeiter ohne Quelle und externe Id hier nicht.
|
||||
@@ -270,6 +283,7 @@ model Mitarbeiter {
|
||||
@@index([abteilung])
|
||||
@@index([upn])
|
||||
@@index([sam])
|
||||
@@index([vorgesetzterId])
|
||||
}
|
||||
|
||||
/// Vom Admin gepflegt, Vorschlaege entstehen aus den department-Werten des
|
||||
|
||||
@@ -57,6 +57,8 @@ export type PersonenVorgabe = {
|
||||
readonly upn: string;
|
||||
readonly abteilung: string;
|
||||
readonly titel: string;
|
||||
/** UPN des Vorgesetzten – aufgeloest, nachdem alle Personen stehen. */
|
||||
readonly vorgesetzterUpn?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,10 +66,11 @@ export type PersonenVorgabe = {
|
||||
* Haus ohne Verzeichnis entstehen.
|
||||
*
|
||||
* Maria Huber steht so im Auftrag und taucht in den Abnahmekriterien wieder auf.
|
||||
* Ihre Vorgesetzte Petra Klein ist deshalb ebenfalls dabei; **gespeichert wird
|
||||
* die Zuordnung nicht**: `vorgesetzterExterneId` zeigt auf die `externeId`
|
||||
* derselben Verzeichnisquelle, und eine von Hand angelegte Person hat beides
|
||||
* nicht. Im Onboarding-Lauf wird der Vorgesetzte ohnehin beim Start gewaehlt.
|
||||
* Ihre Vorgesetzte Petra Klein ist deshalb ebenfalls dabei — **und die Zuordnung
|
||||
* wird gespeichert**, seit `vorgesetzterId` es erlaubt (#020). Vorher ging das
|
||||
* nicht: die Auskunft lief allein ueber `vorgesetzterExterneId`, und die zeigt
|
||||
* auf die `externeId` derselben Verzeichnisquelle, die eine von Hand angelegte
|
||||
* Person nicht hat.
|
||||
*/
|
||||
export const PERSONEN: readonly PersonenVorgabe[] = [
|
||||
{
|
||||
@@ -77,6 +80,7 @@ export const PERSONEN: readonly PersonenVorgabe[] = [
|
||||
upn: "m.huber@firma.example",
|
||||
abteilung: "Buchhaltung",
|
||||
titel: "Sachbearbeiterin Rechnungsprüfung",
|
||||
vorgesetzterUpn: "p.klein@firma.example",
|
||||
},
|
||||
{
|
||||
vorname: "Petra",
|
||||
@@ -230,6 +234,8 @@ export async function stammdatenSaeen(
|
||||
|
||||
let personen = 0;
|
||||
const nachUpn = new Map<string, string>();
|
||||
/** Nur diese bekommen eine Vorgesetzten-Zuordnung – siehe unten. */
|
||||
const neuAngelegt = new Set<string>();
|
||||
for (const vorgabe of PERSONEN) {
|
||||
const vorhanden = await db.mitarbeiter.findFirst({ where: { kundeId, upn: vorgabe.upn } });
|
||||
if (vorhanden) {
|
||||
@@ -252,9 +258,27 @@ export async function stammdatenSaeen(
|
||||
},
|
||||
});
|
||||
nachUpn.set(vorgabe.upn, angelegt.id);
|
||||
neuAngelegt.add(vorgabe.upn);
|
||||
personen += 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Die Vorgesetzten erst jetzt, wenn alle Personen stehen: eine Zuordnung
|
||||
* zeigt auf eine Zeile, die beim Anlegen der ersten noch nicht da war.
|
||||
*
|
||||
* Nur fuer Personen, die **dieser Lauf** angelegt hat. Eine leere Zuordnung
|
||||
* sagt nicht, ob sie noch nie gesetzt oder im Betrieb entfernt wurde — und ein
|
||||
* zweiter Seed-Lauf darf nichts zurueckdrehen. Dieselbe Regel wie fuer alles
|
||||
* andere hier.
|
||||
*/
|
||||
for (const vorgabe of PERSONEN) {
|
||||
if (!vorgabe.vorgesetzterUpn || !neuAngelegt.has(vorgabe.upn)) continue;
|
||||
const personId = nachUpn.get(vorgabe.upn);
|
||||
const vorgesetzterId = nachUpn.get(vorgabe.vorgesetzterUpn);
|
||||
if (!personId || !vorgesetzterId) continue;
|
||||
await db.mitarbeiter.update({ where: { id: personId }, data: { vorgesetzterId } });
|
||||
}
|
||||
|
||||
// Im Modus DIENSTLEISTUNG traegt der Hostname das Kundenkuerzel – dieselbe
|
||||
// Regel, die auch die Namensvorschlaege benutzen (`{kundenkuerzel}-PC-{nnn}`).
|
||||
const praefix = optionen.hostnamePraefix ? `${optionen.hostnamePraefix}-` : "";
|
||||
|
||||
Reference in New Issue
Block a user