feat(verzeichnis): LDAP-Abgleich, Zeitplan, CSV-Import und Log (#30)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

This commit was merged in pull request #30.
This commit is contained in:
2026-09-26 00:21:56 +00:00
parent ab5777a57e
commit ae290345b7
57 changed files with 6834 additions and 61 deletions
+148
View File
@@ -0,0 +1,148 @@
import { beforeEach, describe, expect, it } from "vitest";
import { Betriebsmodus, Herkunft } from "@/generated/prisma/enums";
import { abteilungAnlegen } from "@/server/services/abteilungen";
import { ungepflegteAbteilungen } from "@/server/queries/abteilungen";
import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db";
/**
* Abteilungsvorschläge (#016).
*
* Die Liste, aus der ein Administrator mit einem Klick echte `Abteilung`-Einträge
* macht. Jeder Eintrag ist damit ein Versprechen: der Klick muss gelingen.
* Deshalb steht hier nichts, was `abteilungAnlegen` danach abweisen würde — keine
* bereits (auch archiviert) gepflegte Abteilung und kein archivierter Kunde.
*/
async function grundbestand(modus: Betriebsmodus = Betriebsmodus.INTERN) {
const eigene = await testPrisma.kunde.create({
data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
await testPrisma.einstellung.create({ data: { id: "global", modus } });
const benutzer = await testPrisma.benutzer.create({
data: { email: "admin@firma.example", name: "Administrator", rolle: "ADMIN" },
});
return { eigene, admin: { id: benutzer.id, rolle: benutzer.rolle } };
}
async function person(
kundeId: string,
abteilung: string,
weiteres: { aktiv?: boolean; herkunft?: Herkunft } = {},
) {
const nummer = await testPrisma.mitarbeiter.count();
return testPrisma.mitarbeiter.create({
data: {
kundeId,
vorname: "Person",
nachname: `Nummer ${nummer}`,
anzeigename: `Person Nummer ${nummer}`,
abteilung,
...weiteres,
},
});
}
describe.runIf(datenbankVerfuegbar)("Abteilungsvorschläge", () => {
beforeEach(async () => {
await datenbankLeeren();
delete process.env["OIDC_CUSTOMER_GROUP_PREFIX"];
});
it("zeigt nur Namen, zu denen es noch keine Abteilung gibt", async () => {
const { admin, eigene } = await grundbestand();
await abteilungAnlegen({ name: "Buchhaltung", zielOu: "OU=Buchhaltung" }, admin);
await person(eigene.id, "Buchhaltung");
await person(eigene.id, "Vertrieb", { herkunft: Herkunft.ABGLEICH });
await person(eigene.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([
{ name: "Vertrieb", anzahl: 2, kundeId: eigene.id },
]);
});
it("zählt deaktivierte Personen nicht mit", async () => {
// Eine Abteilung, in der nur noch Ausgeschiedene stehen, braucht keine
// Ziel-OU mehr — und ein Vorschlag, den niemand mehr braucht, verdeckt die
// übrigen.
const { admin, eigene } = await grundbestand();
await person(eigene.id, "Altlast", { aktiv: false });
await person(eigene.id, "Vertrieb");
expect((await ungepflegteAbteilungen(admin)).map((eintrag) => eintrag.name)).toEqual(["Vertrieb"]);
});
it("schlägt nicht vor, was es archiviert schon gibt", async () => {
// Der Name ist je Kunde eindeutig: `abteilungAnlegen` würde diesen Klick mit
// „gibt es schon“ abweisen. Ein Knopf, der das tut, ist keiner.
const { admin, eigene } = await grundbestand();
const abteilung = await abteilungAnlegen({ name: "Vertrieb" }, admin);
await testPrisma.abteilung.update({ where: { id: abteilung.id }, data: { archiviert: true } });
await person(eigene.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
await expect(abteilungAnlegen({ name: "Vertrieb" }, admin)).rejects.toThrow(/gibt es schon/);
});
it("übernimmt einen Vorschlag – und zwar nur den Namen", async () => {
const { admin, eigene } = await grundbestand();
await person(eigene.id, "Vertrieb");
const offen = await ungepflegteAbteilungen(admin);
expect(offen).toHaveLength(1);
const abteilung = await abteilungAnlegen({ name: offen[0]!.name, kundeId: offen[0]!.kundeId }, admin);
expect(abteilung).toMatchObject({ name: "Vertrieb", zielOu: null, standardGruppen: [] });
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
});
describe("Mandantengrenze", () => {
async function zweiKunden() {
const { admin, eigene } = await grundbestand(Betriebsmodus.DIENSTLEISTUNG);
const meiner = await testPrisma.kunde.create({ data: { name: "Meiner", kuerzel: "MEIN" } });
const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } });
const techniker = await testPrisma.benutzer.create({
data: { email: "t@firma.example", name: "Techniker", rolle: "TECHNIKER" },
});
await testPrisma.benutzerKunde.create({ data: { benutzerId: techniker.id, kundeId: meiner.id } });
process.env["OIDC_CUSTOMER_GROUP_PREFIX"] = "lz-kunde-";
return { admin, eigene, meiner, fremder, techniker: { id: techniker.id, rolle: techniker.rolle } };
}
it("zeigt nur die Vorschläge der sichtbaren Kunden", async () => {
const { meiner, fremder, techniker } = await zweiKunden();
await person(meiner.id, "Vertrieb");
await person(fremder.id, "Vertrieb");
await person(fremder.id, "Technik");
expect(await ungepflegteAbteilungen(techniker)).toEqual([
{ name: "Vertrieb", anzahl: 1, kundeId: meiner.id },
]);
});
it("hält Kunden auseinander, die denselben Namen benutzen", async () => {
// „Vertrieb“ ist bei einem Kunden gepflegt und beim anderen nicht. Ohne die
// Gruppierung nach Kunde und Name verschwände der offene Fall — genau das
// stand hier, bis der Review von #011 es gefunden hat.
const { admin, meiner, fremder } = await zweiKunden();
await testPrisma.abteilung.create({ data: { kundeId: meiner.id, name: "Vertrieb" } });
await person(meiner.id, "Vertrieb");
await person(fremder.id, "Vertrieb");
expect(await ungepflegteAbteilungen(admin)).toEqual([
{ name: "Vertrieb", anzahl: 1, kundeId: fremder.id },
]);
});
it("schlägt nichts für einen archivierten Kunden vor", async () => {
// `kundeErmitteln` weist einen archivierten Kunden ab; der Klick auf den
// Vorschlag ergäbe nur eine Fehlermeldung.
const { admin, fremder } = await zweiKunden();
await person(fremder.id, "Vertrieb");
await testPrisma.kunde.update({ where: { id: fremder.id }, data: { archiviert: true } });
expect(await ungepflegteAbteilungen(admin)).toEqual([]);
await expect(abteilungAnlegen({ name: "Vertrieb", kundeId: fremder.id }, admin)).rejects.toThrow(
/archiviert/,
);
});
});
});