feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis (#43)
This commit was merged in pull request #43.
This commit is contained in:
@@ -408,6 +408,36 @@ describe.runIf(datenbankVerfuegbar && ldapDa)("Abgleich gegen das Verzeichnis",
|
||||
expect(await testPrisma.mitarbeiter.count()).toBe(FIXTURE_PERSONEN + 1);
|
||||
});
|
||||
|
||||
it("loescht eine von Hand gesetzte Vorgesetzten-Zuordnung nicht", async () => {
|
||||
// `kberger` traegt im Verzeichnis keinen `manager` — mhuber und jweiss tun
|
||||
// es. Wer die Zuordnung fuer kberger von Hand setzt, pflegt etwas, das das
|
||||
// Verzeichnis nicht fuehrt; also hat es sie auch nicht zu loeschen (#020).
|
||||
const { admin } = await grundbestand();
|
||||
const quelle = await quelleFuerContainer(admin);
|
||||
await abgleichAusfuehren(quelle.id, admin);
|
||||
|
||||
const ohneChef = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { sam: "kberger" } });
|
||||
const vonHand = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { sam: "jweiss" } });
|
||||
expect(ohneChef.vorgesetzterExterneId).toBeNull();
|
||||
expect(ohneChef.vorgesetzterId).toBeNull();
|
||||
await testPrisma.mitarbeiter.update({
|
||||
where: { id: ohneChef.id },
|
||||
data: { vorgesetzterId: vonHand.id },
|
||||
});
|
||||
|
||||
const zweiter = await abgleichAusfuehren(quelle.id, admin);
|
||||
expect(zweiter.status).toBe("ERFOLG");
|
||||
|
||||
const nachher = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { id: ohneChef.id } });
|
||||
expect(nachher.vorgesetzterId).toBe(vonHand.id);
|
||||
|
||||
// Gegenstueck: wo das Verzeichnis einen `manager` fuehrt, bestimmt es — und
|
||||
// zwar in **beiden** Feldern, dem rohen Wert und der Zuordnung.
|
||||
const mitChef = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { sam: "mhuber" } });
|
||||
expect(mitChef.vorgesetzterId).toBe(ohneChef.id);
|
||||
expect(mitChef.vorgesetzterExterneId).toBe(ohneChef.externeId);
|
||||
});
|
||||
|
||||
it("setzt ein per userAccountControl deaktiviertes Konto auf aktiv = false", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
const quelle = await quelleFuerContainer(admin);
|
||||
|
||||
@@ -31,6 +31,7 @@ describe.runIf(datenbankVerfuegbar)("Handgeschriebene Datenbankobjekte", () => {
|
||||
"Lauf_abteilung_gleicher_kunde",
|
||||
"Lauf_geraet_gleicher_kunde",
|
||||
"Lauf_mitarbeiter_gleicher_kunde",
|
||||
"Mitarbeiter_vorgesetzter_gleicher_kunde",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -83,6 +84,7 @@ describe.runIf(datenbankVerfuegbar)("Handgeschriebene Datenbankobjekte", () => {
|
||||
"Lauf_archiviert_mit_zeitpunkt",
|
||||
"Lauf_begonnen_mit_zeitpunkt",
|
||||
"Lauf_mitarbeiter_eindeutig",
|
||||
"Mitarbeiter_vorgesetzter_nicht_selbst",
|
||||
"Schrittergebnis_begruendung_noetig",
|
||||
"TagCodeVergabe_form",
|
||||
"Verzeichnisquelle_bindpasswort_verschluesselt",
|
||||
|
||||
@@ -333,6 +333,21 @@ describe.runIf(datenbankVerfuegbar)("Datenmodell: Regeln in der Datenbank", () =
|
||||
).rejects.toThrow(/Vorlage_seedSchluessel_einmalig/);
|
||||
});
|
||||
|
||||
it("laesst niemanden sein eigener Vorgesetzter sein", async () => {
|
||||
// Die kuerzeste Schleife, und die einzige, die sich ohne Rekursion
|
||||
// abfangen laesst. Laengere haelt der Dienst auf (#020).
|
||||
const kunde = await kundeAnlegen("VORG");
|
||||
const person = await testPrisma.mitarbeiter.create({
|
||||
data: { kundeId: kunde.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" },
|
||||
});
|
||||
await expect(
|
||||
testPrisma.mitarbeiter.update({
|
||||
where: { id: person.id },
|
||||
data: { vorgesetzterId: person.id },
|
||||
}),
|
||||
).rejects.toThrow(/Mitarbeiter_vorgesetzter_nicht_selbst/);
|
||||
});
|
||||
|
||||
it("laesst beliebig viele Vorlagen ohne Seed-Schluessel zu", async () => {
|
||||
// Der Index ist partiell, sonst waere die zweite eigene Vorlage eine
|
||||
// Dublette von null (#021).
|
||||
|
||||
@@ -6,12 +6,15 @@ import {
|
||||
mitarbeiterAktivSetzen,
|
||||
mitarbeiterAnlegen,
|
||||
mitarbeiterBearbeiten,
|
||||
vorgesetztenSetzen,
|
||||
} from "@/server/services/mitarbeiter";
|
||||
import {
|
||||
abteilungsNamen,
|
||||
geraeteDesMitarbeiters,
|
||||
mitarbeiterHolen,
|
||||
mitarbeiterListe,
|
||||
moeglicheVorgesetzte,
|
||||
vorgesetzterVon,
|
||||
} from "@/server/queries/mitarbeiter";
|
||||
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
|
||||
|
||||
@@ -40,6 +43,118 @@ describe.runIf(datenbankVerfuegbar)("Mitarbeiter", () => {
|
||||
await datenbankLeeren();
|
||||
});
|
||||
|
||||
describe("Vorgesetzter", () => {
|
||||
it("laesst sich fuer eine von Hand angelegte Person setzen", async () => {
|
||||
// Der Fall, der vorher gar nicht ging: ohne Verzeichnisquelle gab es kein
|
||||
// Feld fuer die Zuordnung (#020).
|
||||
const { admin } = await grundbestand();
|
||||
const maria = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
|
||||
const petra = await mitarbeiterAnlegen({ vorname: "Petra", nachname: "Klein" }, admin);
|
||||
expect(maria.herkunft).toBe(Herkunft.MANUELL);
|
||||
|
||||
await vorgesetztenSetzen(maria.id, petra.id, admin);
|
||||
expect(await vorgesetzterVon(maria.id, admin)).toEqual({
|
||||
id: petra.id,
|
||||
anzeigename: "Petra Klein",
|
||||
});
|
||||
});
|
||||
|
||||
it("entfernt die Zuordnung bei leerer Angabe", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
const maria = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
|
||||
const petra = await mitarbeiterAnlegen({ vorname: "Petra", nachname: "Klein" }, admin);
|
||||
await vorgesetztenSetzen(maria.id, petra.id, admin);
|
||||
|
||||
await vorgesetztenSetzen(maria.id, "", admin);
|
||||
expect(await vorgesetzterVon(maria.id, admin)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("laesst niemanden sein eigener Vorgesetzter sein", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
const maria = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
|
||||
await expect(vorgesetztenSetzen(maria.id, maria.id, admin)).rejects.toThrow(/eigener Vorgesetzter/);
|
||||
});
|
||||
|
||||
it("weist eine Schleife ueber zwei Personen ab", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
const a = await mitarbeiterAnlegen({ vorname: "A", nachname: "Eins" }, admin);
|
||||
const b = await mitarbeiterAnlegen({ vorname: "B", nachname: "Zwei" }, admin);
|
||||
await vorgesetztenSetzen(b.id, a.id, admin);
|
||||
|
||||
await expect(vorgesetztenSetzen(a.id, b.id, admin)).rejects.toThrow(/Schleife/);
|
||||
});
|
||||
|
||||
it("weist eine Schleife ueber drei Personen ab", async () => {
|
||||
// Die Datenbank faengt nur die kuerzeste ab; diese braucht den Gang nach
|
||||
// oben im Dienst.
|
||||
const { admin } = await grundbestand();
|
||||
const a = await mitarbeiterAnlegen({ vorname: "A", nachname: "Eins" }, admin);
|
||||
const b = await mitarbeiterAnlegen({ vorname: "B", nachname: "Zwei" }, admin);
|
||||
const c = await mitarbeiterAnlegen({ vorname: "C", nachname: "Drei" }, admin);
|
||||
await vorgesetztenSetzen(b.id, a.id, admin);
|
||||
await vorgesetztenSetzen(c.id, b.id, admin);
|
||||
|
||||
await expect(vorgesetztenSetzen(a.id, c.id, admin)).rejects.toThrow(/Schleife/);
|
||||
});
|
||||
|
||||
it("gilt nur innerhalb desselben Kunden", async () => {
|
||||
const { eigene, admin } = await grundbestand();
|
||||
const fremd = await testPrisma.kunde.create({ data: { name: "Fremd GmbH", kuerzel: "FREMD" } });
|
||||
const hier = await testPrisma.mitarbeiter.create({
|
||||
data: { kundeId: eigene.id, vorname: "Maria", nachname: "Huber", anzeigename: "Maria Huber" },
|
||||
});
|
||||
const dort = await testPrisma.mitarbeiter.create({
|
||||
data: { kundeId: fremd.id, vorname: "Petra", nachname: "Klein", anzeigename: "Petra Klein" },
|
||||
});
|
||||
|
||||
await expect(vorgesetztenSetzen(hier.id, dort.id, admin)).rejects.toThrow(/selben Kunden/);
|
||||
// Und auch an der Service-Schicht vorbei nicht: der zusammengesetzte
|
||||
// Fremdschluessel haelt dieselbe Grenze.
|
||||
await expect(
|
||||
testPrisma.mitarbeiter.update({
|
||||
where: { id: hier.id },
|
||||
data: { vorgesetzterId: dort.id },
|
||||
}),
|
||||
).rejects.toThrow(/Mitarbeiter_vorgesetzter_gleicher_kunde/);
|
||||
});
|
||||
|
||||
it("bietet zur Auswahl nur aktive Kollegen desselben Kunden, ohne die Person selbst", async () => {
|
||||
const { eigene, admin } = await grundbestand();
|
||||
const fremd = await testPrisma.kunde.create({ data: { name: "Fremd GmbH", kuerzel: "FREMD" } });
|
||||
const maria = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
|
||||
const petra = await mitarbeiterAnlegen({ vorname: "Petra", nachname: "Klein" }, admin);
|
||||
const weg = await mitarbeiterAnlegen({ vorname: "Alt", nachname: "Kollege" }, admin);
|
||||
await mitarbeiterAktivSetzen(weg.id, false, admin);
|
||||
await testPrisma.mitarbeiter.create({
|
||||
data: { kundeId: fremd.id, vorname: "Fremd", nachname: "Person", anzeigename: "Fremd Person" },
|
||||
});
|
||||
expect(eigene.id).toBe(maria.kundeId);
|
||||
|
||||
const auswahl = await moeglicheVorgesetzte(maria.id, admin);
|
||||
expect(auswahl.map((eintrag) => eintrag.id)).toEqual([petra.id]);
|
||||
});
|
||||
|
||||
it("schreibt die Aenderung ins Protokoll, mit Namen", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
const maria = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
|
||||
const petra = await mitarbeiterAnlegen({ vorname: "Petra", nachname: "Klein" }, admin);
|
||||
|
||||
await vorgesetztenSetzen(maria.id, petra.id, admin);
|
||||
await vorgesetztenSetzen(maria.id, null, admin);
|
||||
|
||||
const eintraege = await testPrisma.protokoll.findMany({
|
||||
where: { entitaet: "Mitarbeiter", entitaetId: maria.id },
|
||||
orderBy: { erstelltAm: "asc" },
|
||||
select: { aktion: true },
|
||||
});
|
||||
// Das Anlegen protokolliert selbst - hier zaehlt, was danach kam.
|
||||
expect(eintraege.map((e) => e.aktion).slice(1)).toEqual([
|
||||
"Vorgesetzter auf Petra Klein gesetzt",
|
||||
"Vorgesetzter entfernt",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Anlegen", () => {
|
||||
it("bildet den Anzeigenamen aus Vor- und Nachname, wenn keiner angegeben ist", async () => {
|
||||
const { admin } = await grundbestand();
|
||||
|
||||
@@ -70,7 +70,10 @@ async function aufbau(modus: Betriebsmodus = Betriebsmodus.INTERN) {
|
||||
abteilung: "Buchhaltung",
|
||||
titel: "Sachbearbeiterin",
|
||||
dn: DN,
|
||||
// Beides, so wie der Abgleich es schreibt: der rohe Wert des
|
||||
// Verzeichnisses und die gepflegte Zuordnung, aus der gelesen wird (#020).
|
||||
vorgesetzterExterneId: "guid-chefin",
|
||||
vorgesetzterId: chefin.id,
|
||||
herkunft: Herkunft.ABGLEICH,
|
||||
},
|
||||
});
|
||||
@@ -170,6 +173,52 @@ describe.runIf(datenbankVerfuegbar)("Werte eines Laufs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("löst den Vorgesetzten auch für eine von Hand angelegte Person auf", async () => {
|
||||
// Der Fall aus #020: ohne Verzeichnisquelle gab es vorher kein Feld fuer die
|
||||
// Zuordnung, und {{vorgesetzter_sam}} blieb im Onboarding-Befehl leer -
|
||||
// genau dort, wo er gebraucht wird.
|
||||
const { eigene, versionId } = await aufbau();
|
||||
const chefin = await testPrisma.mitarbeiter.create({
|
||||
data: {
|
||||
kundeId: eigene.id,
|
||||
vorname: "Petra",
|
||||
nachname: "Klein",
|
||||
anzeigename: "Petra Klein",
|
||||
sam: "pklein",
|
||||
herkunft: Herkunft.MANUELL,
|
||||
},
|
||||
});
|
||||
const vonHand = await testPrisma.mitarbeiter.create({
|
||||
data: {
|
||||
kundeId: eigene.id,
|
||||
vorname: "Neu",
|
||||
nachname: "Kollege",
|
||||
anzeigename: "Neu Kollege",
|
||||
sam: "nkollege",
|
||||
herkunft: Herkunft.MANUELL,
|
||||
vorgesetzterId: chefin.id,
|
||||
},
|
||||
});
|
||||
// Keine Quelle, keine externe Kennung - der alte Leseweg haette hier
|
||||
// ausgestiegen.
|
||||
expect(vonHand.quelleId).toBeNull();
|
||||
expect(vonHand.vorgesetzterExterneId).toBeNull();
|
||||
|
||||
const laufId = await laufAnlegen({
|
||||
vorlagenVersionId: versionId,
|
||||
titel: "Onboarding Neu Kollege",
|
||||
kundeId: eigene.id,
|
||||
mitarbeiterId: vonHand.id,
|
||||
});
|
||||
|
||||
const werte = await platzhalterWerteDesLaufs({
|
||||
laufId,
|
||||
technikerName: "Ali Demir",
|
||||
jetzt: new Date("2026-10-04T10:00:00Z"),
|
||||
});
|
||||
expect(werte).toMatchObject({ vorgesetzter: "Petra Klein", vorgesetzter_sam: "pklein" });
|
||||
});
|
||||
|
||||
it("nimmt für die OU den Eltern-Container des dn, nicht die Ziel-OU", async () => {
|
||||
// Der dn sagt, wo das Konto **wirklich** liegt; die Ziel-OU der Abteilung
|
||||
// ist die Absicht fuer ein Konto, das es noch nicht gibt.
|
||||
|
||||
@@ -450,6 +450,35 @@ describe.runIf(datenbankVerfuegbar)("Seed gegen die Datenbank", () => {
|
||||
expect(await testPrisma.tagCodeVergabe.count()).toBe(OFFENE_TAGS.length + 2);
|
||||
});
|
||||
|
||||
it("macht Petra Klein zur Vorgesetzten von Maria Huber", async () => {
|
||||
// Steht so im Auftrag. Ging vor #020 nicht: eine von Hand angelegte Person
|
||||
// konnte keinen Vorgesetzten haben.
|
||||
const eigene = await eigeneOrganisation();
|
||||
await stammdatenSaeen(testPrisma, eigene.id);
|
||||
|
||||
const maria = await testPrisma.mitarbeiter.findFirstOrThrow({
|
||||
where: { sam: "mhuber" },
|
||||
include: { vorgesetzter: { select: { anzeigename: true, sam: true } } },
|
||||
});
|
||||
expect(maria.vorgesetzter).toEqual({ anzeigename: "Klein, Petra", sam: "pklein" });
|
||||
});
|
||||
|
||||
it("gibt eine im Betrieb entfernte Vorgesetzten-Zuordnung nicht zurueck", async () => {
|
||||
// Dieselbe Regel wie fuer alles andere im Seed: er legt an, er korrigiert
|
||||
// nicht.
|
||||
const eigene = await eigeneOrganisation();
|
||||
await stammdatenSaeen(testPrisma, eigene.id);
|
||||
const maria = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { sam: "mhuber" } });
|
||||
await testPrisma.mitarbeiter.update({
|
||||
where: { id: maria.id },
|
||||
data: { vorgesetzterId: null },
|
||||
});
|
||||
|
||||
await stammdatenSaeen(testPrisma, eigene.id);
|
||||
const nachher = await testPrisma.mitarbeiter.findFirstOrThrow({ where: { id: maria.id } });
|
||||
expect(nachher.vorgesetzterId).toBeNull();
|
||||
});
|
||||
|
||||
it("legt den Bestand beim zweiten Lauf nicht noch einmal an", async () => {
|
||||
const eigene = await eigeneOrganisation();
|
||||
await stammdatenSaeen(testPrisma, eigene.id);
|
||||
|
||||
Reference in New Issue
Block a user