feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis (#43)
This commit was merged in pull request #43.
This commit is contained in:
@@ -3,11 +3,17 @@ import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, Laptop } from "lucide-react";
|
||||
import { MitarbeiterAktiv } from "@/components/mitarbeiter-aktiv";
|
||||
import { MitarbeiterStammdaten } from "@/components/mitarbeiter-stammdaten";
|
||||
import { MitarbeiterVorgesetzter } from "@/components/mitarbeiter-vorgesetzter";
|
||||
import { Herkunft } from "@/generated/prisma/enums";
|
||||
import { darf } from "@/server/auth/rollen";
|
||||
import { sitzungErforderlich } from "@/server/auth/sitzungspflicht";
|
||||
import { kundenEbeneSichtbar } from "@/server/mandant";
|
||||
import { geraeteDesMitarbeiters, mitarbeiterHolen, vorgesetzterVon } from "@/server/queries/mitarbeiter";
|
||||
import {
|
||||
geraeteDesMitarbeiters,
|
||||
mitarbeiterHolen,
|
||||
moeglicheVorgesetzte,
|
||||
vorgesetzterVon,
|
||||
} from "@/server/queries/mitarbeiter";
|
||||
|
||||
/**
|
||||
* Die Seite einer Person (#011).
|
||||
@@ -26,10 +32,13 @@ export default async function MitarbeiterSeite({ params }: { params: Promise<{ i
|
||||
if (!person) notFound();
|
||||
|
||||
const darfBearbeiten = darf(benutzer.rolle, "mitarbeiterBearbeiten");
|
||||
const [geraete, kundenEbene, vorgesetzter] = await Promise.all([
|
||||
const [geraete, kundenEbene, vorgesetzter, auswahl] = await Promise.all([
|
||||
geraeteDesMitarbeiters(person.id, benutzer),
|
||||
kundenEbeneSichtbar(),
|
||||
vorgesetzterVon(person.id, benutzer),
|
||||
// Nur holen, wer sie auch braucht: fuer einen Leser ist die Liste aller
|
||||
// Kolleginnen und Kollegen eine Abfrage ohne Zweck.
|
||||
darfBearbeiten ? moeglicheVorgesetzte(person.id, benutzer) : [],
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -60,8 +69,14 @@ export default async function MitarbeiterSeite({ params }: { params: Promise<{ i
|
||||
) : null}
|
||||
|
||||
{darfBearbeiten ? (
|
||||
<div className="mt-6">
|
||||
<div className="mt-6 flex flex-col gap-6">
|
||||
<MitarbeiterAktiv mitarbeiterId={person.id} aktiv={person.aktiv} />
|
||||
<MitarbeiterVorgesetzter
|
||||
mitarbeiterId={person.id}
|
||||
vorgesetzterId={vorgesetzter?.id ?? null}
|
||||
auswahl={auswahl}
|
||||
ausDemVerzeichnis={person.herkunft === Herkunft.ABGLEICH}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { vorgesetztenSetzenAktion, type AktionsErgebnis } from "@/server/actions/mitarbeiter";
|
||||
|
||||
/**
|
||||
* Vorgesetzten setzen oder entfernen (#020).
|
||||
*
|
||||
* Ein Auswahlfeld und ein Knopf, kein eigener Bearbeiten-Modus: die Zuordnung
|
||||
* ist eine einzelne Angabe, und sie zu aendern soll nicht erst ein Formular
|
||||
* aufklappen.
|
||||
*
|
||||
* Die leere Option heisst ausdruecklich „niemand“ und nicht „—“: sie ist eine
|
||||
* Angabe, nicht das Fehlen einer. Wer sie waehlt und speichert, entfernt die
|
||||
* Zuordnung.
|
||||
*/
|
||||
export function MitarbeiterVorgesetzter({
|
||||
mitarbeiterId,
|
||||
vorgesetzterId,
|
||||
auswahl,
|
||||
ausDemVerzeichnis,
|
||||
}: {
|
||||
mitarbeiterId: string;
|
||||
vorgesetzterId: string | null;
|
||||
auswahl: readonly { readonly id: string; readonly anzeigename: string }[];
|
||||
ausDemVerzeichnis: boolean;
|
||||
}) {
|
||||
const [ergebnis, aktion, laeuft] = useActionState<AktionsErgebnis, FormData>(vorgesetztenSetzenAktion, {});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form action={aktion} className="flex flex-wrap items-end gap-2">
|
||||
<input type="hidden" name="mitarbeiterId" value={mitarbeiterId} />
|
||||
<label className="flex min-w-0 flex-1 flex-col gap-1 text-sm">
|
||||
<span className="text-nebentext">Vorgesetzter</span>
|
||||
<select
|
||||
name="vorgesetzterId"
|
||||
defaultValue={vorgesetzterId ?? ""}
|
||||
className="min-h-11 w-full rounded-pille border border-linie bg-flaeche-hoch px-4 text-sm md:min-h-10"
|
||||
>
|
||||
<option value="">niemand</option>
|
||||
{auswahl.map((person) => (
|
||||
<option key={person.id} value={person.id}>
|
||||
{person.anzeigename}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={laeuft}
|
||||
className="min-h-11 rounded-pille bg-akzent px-4 text-sm font-semibold text-grund md:min-h-10"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{ausDemVerzeichnis ? (
|
||||
<p className="mt-2 max-w-prose text-sm text-nebentext">
|
||||
Diese Person kommt aus dem Verzeichnis. Liefert es einen Vorgesetzten, setzt der nächste Abgleich
|
||||
die Zuordnung dort wieder nach seinem Stand; liefert es keinen, bleibt die hier gesetzte Zuordnung
|
||||
stehen.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{ergebnis.fehler ? (
|
||||
<p role="alert" className="mt-2 max-w-prose text-sm text-fehler">
|
||||
{ergebnis.fehler}
|
||||
</p>
|
||||
) : null}
|
||||
{ergebnis.erfolg ? (
|
||||
<p role="status" className="mt-2 text-sm text-erledigt">
|
||||
{ergebnis.erfolg}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
mitarbeiterAnlegen,
|
||||
mitarbeiterBearbeiten,
|
||||
mitarbeiterImportieren,
|
||||
vorgesetztenSetzen,
|
||||
type MitarbeiterImportAbweisung,
|
||||
} from "@/server/services/mitarbeiter";
|
||||
import type { HandelnderBenutzer } from "@/server/mandant";
|
||||
@@ -161,6 +162,40 @@ export async function mitarbeiterAktivSetzenAktion(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vorgesetzten setzen oder entfernen (#020).
|
||||
*
|
||||
* Ein leeres Feld entfernt die Zuordnung — deshalb `optional` und nicht
|
||||
* `min(1)`: „niemand“ ist hier eine Angabe und kein Versehen.
|
||||
*/
|
||||
export async function vorgesetztenSetzenAktion(
|
||||
_vorher: AktionsErgebnis,
|
||||
formular: FormData,
|
||||
): Promise<AktionsErgebnis> {
|
||||
const zugang = await angemeldetOderMeldung();
|
||||
if ("fehler" in zugang) return zugang;
|
||||
|
||||
const eingabe = z
|
||||
.object({ mitarbeiterId: z.string().min(1), vorgesetzterId: z.string().optional() })
|
||||
.safeParse({
|
||||
mitarbeiterId: formular.get("mitarbeiterId"),
|
||||
vorgesetzterId: formular.get("vorgesetzterId") ?? undefined,
|
||||
});
|
||||
if (!eingabe.success) return { fehler: "Die Eingabe ist unvollständig." };
|
||||
|
||||
try {
|
||||
await vorgesetztenSetzen(
|
||||
eingabe.data.mitarbeiterId,
|
||||
eingabe.data.vorgesetzterId ?? null,
|
||||
zugang.benutzer,
|
||||
);
|
||||
revalidatePath(`/mitarbeiter/${eingabe.data.mitarbeiterId}`);
|
||||
return { erfolg: eingabe.data.vorgesetzterId ? "Gespeichert." : "Vorgesetzter entfernt." };
|
||||
} catch (fehler) {
|
||||
return alsErgebnis(fehler, "Die Änderung konnte nicht gespeichert werden.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Personen aus einer CSV-Datei einspielen (#016).
|
||||
*
|
||||
|
||||
@@ -174,11 +174,15 @@ export async function geraeteDesMitarbeiters(
|
||||
/**
|
||||
* Der Vorgesetzte einer Person – oder `undefined`.
|
||||
*
|
||||
* Die Verknuepfung laeuft ueber `vorgesetzterExterneId` und die `externeId`
|
||||
* **derselben Verzeichnisquelle**: die Kennung ist nur innerhalb einer Quelle
|
||||
* eindeutig, und ohne diese Einschraenkung zoege ein zweites Verzeichnis
|
||||
* fremde Treffer herbei. Gefuellt wird das Feld vom Abgleich (#016); bei einer
|
||||
* von Hand angelegten Person steht hier nichts, und das ist richtig so.
|
||||
* Gelesen wird **allein** `vorgesetzterId`: die gepflegte Zuordnung, die der
|
||||
* Abgleich aus dem Verzeichnis mitschreibt und die sich auch von Hand setzen
|
||||
* laesst (#020). Vorher lief die Auskunft ueber `vorgesetzterExterneId` und die
|
||||
* `externeId` derselben Quelle — richtig fuer Verzeichnis-Personen, aber eine
|
||||
* von Hand angelegte Person konnte so keinen Vorgesetzten haben.
|
||||
*
|
||||
* Die Mandantensicht wird trotzdem noch angewandt, obwohl ein zusammengesetzter
|
||||
* Fremdschluessel dieselbe Grenze haelt: die Rollensicht des Benutzers ist eine
|
||||
* andere Frage als die Struktur der Daten.
|
||||
*/
|
||||
export async function vorgesetzterVon(
|
||||
mitarbeiterId: string,
|
||||
@@ -186,22 +190,45 @@ export async function vorgesetzterVon(
|
||||
): Promise<{ readonly id: string; readonly anzeigename: string } | undefined> {
|
||||
const person = await prisma.mitarbeiter.findFirst({
|
||||
where: { AND: [{ id: mitarbeiterId }, ...(await kundenBedingung(benutzer))] },
|
||||
select: { quelleId: true, vorgesetzterExterneId: true },
|
||||
select: { vorgesetzterId: true },
|
||||
});
|
||||
if (!person?.vorgesetzterExterneId || !person.quelleId) return undefined;
|
||||
if (!person?.vorgesetzterId) return undefined;
|
||||
|
||||
const vorgesetzter = await prisma.mitarbeiter.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{ quelleId: person.quelleId, externeId: person.vorgesetzterExterneId },
|
||||
...(await kundenBedingung(benutzer)),
|
||||
],
|
||||
},
|
||||
where: { AND: [{ id: person.vorgesetzterId }, ...(await kundenBedingung(benutzer))] },
|
||||
select: { id: true, anzeigename: true },
|
||||
});
|
||||
return vorgesetzter ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wen man als Vorgesetzten waehlen kann (#020).
|
||||
*
|
||||
* Nur **aktive** Personen desselben Kunden, und die Person selbst nicht. Die
|
||||
* eigene Zeile und ein fremder Kunde fielen sonst erst im Dienst auf; eine
|
||||
* Auswahlliste, die unmoegliche Eintraege anbietet, ist eine Falle.
|
||||
*
|
||||
* Laengere Schleifen werden hier **nicht** herausgefiltert: dafuer muesste die
|
||||
* Liste je Eintrag die Kette hinauflaufen. Der Dienst weist sie ab, mit einem
|
||||
* Satz, der sagt warum.
|
||||
*/
|
||||
export async function moeglicheVorgesetzte(
|
||||
mitarbeiterId: string,
|
||||
benutzer: HandelnderBenutzer,
|
||||
): Promise<readonly { readonly id: string; readonly anzeigename: string }[]> {
|
||||
const person = await prisma.mitarbeiter.findFirst({
|
||||
where: { AND: [{ id: mitarbeiterId }, ...(await kundenBedingung(benutzer))] },
|
||||
select: { kundeId: true },
|
||||
});
|
||||
if (!person) return [];
|
||||
|
||||
return prisma.mitarbeiter.findMany({
|
||||
where: { AND: [{ kundeId: person.kundeId, aktiv: true, id: { not: mitarbeiterId } }] },
|
||||
select: { id: true, anzeigename: true },
|
||||
orderBy: { anzeigename: "asc" },
|
||||
});
|
||||
}
|
||||
|
||||
/** Die vorkommenden Abteilungsnamen – fuer den Filter. */
|
||||
export async function abteilungsNamen(benutzer: HandelnderBenutzer): Promise<readonly string[]> {
|
||||
const zeilen = await prisma.mitarbeiter.findMany({
|
||||
|
||||
@@ -317,6 +317,7 @@ async function lauf(quelle: Verzeichnisquelle, benutzer: HandelnderBenutzer): Pr
|
||||
// Der Vorgesetzte kommt im zweiten Durchgang: sein DN laesst sich
|
||||
// erst aufloesen, wenn alle Kennungen bekannt sind.
|
||||
vorgesetzterExterneId: null,
|
||||
vorgesetzterId: null,
|
||||
aktiv: true,
|
||||
herkunft: Herkunft.ABGLEICH,
|
||||
zuletztGesehenAm: beginn,
|
||||
@@ -365,7 +366,19 @@ async function lauf(quelle: Verzeichnisquelle, benutzer: HandelnderBenutzer): Pr
|
||||
// Verzeichnis diese Person, und der naechste Lauf findet sie ueber die
|
||||
// Kennung.
|
||||
...(uebernommenJetzt ? { quelleId, externeId: eintrag.externeId } : {}),
|
||||
...(vorgesetztenLeeren ? { vorgesetzterExterneId: null } : {}),
|
||||
/*
|
||||
* Steht im Verzeichnis kein Vorgesetzter, wird der **rohe** Wert
|
||||
* geleert. Die gepflegte Zuordnung nur dann, wenn sie vom
|
||||
* Verzeichnis kam — erkennbar daran, dass dort eine Kennung stand.
|
||||
* Eine von Hand gesetzte Zuordnung bleibt stehen: das Verzeichnis
|
||||
* fuehrt sie nicht, also hat es sie auch nicht zu loeschen (#020).
|
||||
*/
|
||||
...(vorgesetztenLeeren
|
||||
? {
|
||||
vorgesetzterExterneId: null,
|
||||
...(vorhanden.vorgesetzterExterneId !== null ? { vorgesetzterId: null } : {}),
|
||||
}
|
||||
: {}),
|
||||
// Wer wieder im Ergebnis steht, ist wieder aktiv — eine
|
||||
// wiedereingestellte Person behaelt so ihre Laeufe.
|
||||
aktiv: true,
|
||||
@@ -386,12 +399,24 @@ async function lauf(quelle: Verzeichnisquelle, benutzer: HandelnderBenutzer): Pr
|
||||
gelesen = bilanz.gesamtGelesen;
|
||||
|
||||
// --- Zweiter Durchgang: Vorgesetzte --------------------------------------
|
||||
//
|
||||
// Geschrieben werden beide Felder: `vorgesetzterExterneId` als roher Wert des
|
||||
// Verzeichnisses, `vorgesetzterId` als die gepflegte Zuordnung, aus der
|
||||
// jeder Leseweg schoepft (#020). Vorher gab es nur das erste, und eine von
|
||||
// Hand angelegte Person konnte keinen Vorgesetzten haben.
|
||||
for (const offen of offene) {
|
||||
const kennung = kennungNachDn.get(dnSchluessel(offen.vorgesetzterDn)) ?? null;
|
||||
if (kennung === offen.vorherigeKennung) continue;
|
||||
const vorgesetzter =
|
||||
kennung === null
|
||||
? null
|
||||
: await prisma.mitarbeiter.findUnique({
|
||||
where: { quelleId_externeId: { quelleId, externeId: kennung } },
|
||||
select: { id: true },
|
||||
});
|
||||
await prisma.mitarbeiter.updateMany({
|
||||
where: { quelleId, externeId: offen.externeId },
|
||||
data: { vorgesetzterExterneId: kennung },
|
||||
data: { vorgesetzterExterneId: kennung, vorgesetzterId: vorgesetzter?.id ?? null },
|
||||
});
|
||||
// Eine gerade angelegte Person ist angelegt, nicht zusaetzlich
|
||||
// aktualisiert.
|
||||
|
||||
@@ -212,6 +212,94 @@ export async function mitarbeiterBearbeiten(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt oder entfernt den Vorgesetzten.
|
||||
*
|
||||
* Eigener Weg und nicht Teil von `mitarbeiterBearbeiten`, weil es kein
|
||||
* abgeglichenes Feld im Sinn der Sperre ist: eine Person aus dem Verzeichnis
|
||||
* darf hier geaendert werden. Der naechste Abgleich setzt die Zuordnung
|
||||
* allerdings zurueck, wenn das Verzeichnis einen `manager` liefert — dort ist
|
||||
* sie dann gepflegt. Liefert es keinen, bleibt die Zuordnung von Hand stehen
|
||||
* (#020).
|
||||
*
|
||||
* `vorgesetzterExterneId` bleibt unangetastet: das ist der rohe Wert des
|
||||
* Verzeichnisses, nicht die Zuordnung.
|
||||
*/
|
||||
export async function vorgesetztenSetzen(
|
||||
mitarbeiterId: string,
|
||||
vorgesetzterId: string | null,
|
||||
benutzer: HandelnderBenutzer,
|
||||
): Promise<Mitarbeiter> {
|
||||
const vorher = await prisma.mitarbeiter.findUnique({ where: { id: mitarbeiterId } });
|
||||
if (!vorher) throw new MitarbeiterFehler("Diese Person gibt es nicht (mehr).");
|
||||
await kundeZugriffPruefen(vorher.kundeId, benutzer);
|
||||
|
||||
const gewuenscht = vorgesetzterId === null || vorgesetzterId === "" ? null : vorgesetzterId;
|
||||
if (gewuenscht === vorher.vorgesetzterId) return vorher;
|
||||
|
||||
let name = "niemand";
|
||||
if (gewuenscht !== null) {
|
||||
if (gewuenscht === mitarbeiterId) {
|
||||
throw new MitarbeiterFehler("Niemand ist sein eigener Vorgesetzter.");
|
||||
}
|
||||
const kuenftig = await prisma.mitarbeiter.findUnique({ where: { id: gewuenscht } });
|
||||
if (!kuenftig) throw new MitarbeiterFehler("Diese Person gibt es nicht (mehr).");
|
||||
// Dieselbe Pruefung haelt der zusammengesetzte Fremdschluessel fest; hier
|
||||
// steht sie fuer die Meldung.
|
||||
if (kuenftig.kundeId !== vorher.kundeId) {
|
||||
throw new MitarbeiterFehler("Der Vorgesetzte muss zum selben Kunden gehören.");
|
||||
}
|
||||
await schleifePruefen(mitarbeiterId, gewuenscht);
|
||||
name = kuenftig.anzeigename;
|
||||
}
|
||||
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const nachher = await tx.mitarbeiter.update({
|
||||
where: { id: mitarbeiterId },
|
||||
data: { vorgesetzterId: gewuenscht },
|
||||
});
|
||||
await tx.protokoll.create({
|
||||
data: {
|
||||
entitaet: "Mitarbeiter",
|
||||
entitaetId: mitarbeiterId,
|
||||
aktion: gewuenscht === null ? "Vorgesetzter entfernt" : `Vorgesetzter auf ${name} gesetzt`,
|
||||
benutzerId: benutzer.id,
|
||||
vorher: auszug(vorher, ["vorgesetzterId"]),
|
||||
nachher: auszug(nachher, ["vorgesetzterId"]),
|
||||
},
|
||||
});
|
||||
return nachher;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Weist eine Schleife ab: A ist Vorgesetzter von B ist Vorgesetzter von A.
|
||||
*
|
||||
* Die Datenbank faengt nur die kuerzeste ab (`vorgesetzterId <> id`); laengere
|
||||
* brauchen einen Gang nach oben. Die Obergrenze ist kein Schoenheitsfehler,
|
||||
* sondern die Notbremse: waere die Kette schon beschaedigt, liefe die Suche
|
||||
* sonst ewig.
|
||||
*/
|
||||
async function schleifePruefen(mitarbeiterId: string, kuenftigerVorgesetzter: string): Promise<void> {
|
||||
const gesehen = new Set<string>([mitarbeiterId]);
|
||||
let aktuell: string | null = kuenftigerVorgesetzter;
|
||||
|
||||
for (let schritt = 0; schritt < 100 && aktuell !== null; schritt += 1) {
|
||||
if (gesehen.has(aktuell)) {
|
||||
throw new MitarbeiterFehler(
|
||||
"Das ergäbe eine Schleife: diese Person ist dem künftigen Vorgesetzten " +
|
||||
"bereits selbst vorgesetzt, direkt oder über weitere Personen.",
|
||||
);
|
||||
}
|
||||
gesehen.add(aktuell);
|
||||
const naechster: { readonly vorgesetzterId: string | null } | null = await prisma.mitarbeiter.findUnique({
|
||||
where: { id: aktuell },
|
||||
select: { vorgesetzterId: true },
|
||||
});
|
||||
aktuell = naechster?.vorgesetzterId ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deaktivieren und wieder aktivieren.
|
||||
*
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function platzhalterWerteDesLaufs(kontext: LaufKontext): Promise<Pl
|
||||
titel: true,
|
||||
dn: true,
|
||||
quelleId: true,
|
||||
vorgesetzterExterneId: true,
|
||||
vorgesetzterId: true,
|
||||
},
|
||||
},
|
||||
abteilung: { select: { name: true, zielOu: true, standardGruppen: true } },
|
||||
@@ -207,18 +207,18 @@ export async function platzhalterWerteDesLaufs(kontext: LaufKontext): Promise<Pl
|
||||
}
|
||||
|
||||
/**
|
||||
* Der Vorgesetzte, ueber `vorgesetzterExterneId` **derselben** Verzeichnisquelle.
|
||||
* Der Vorgesetzte, ueber die gepflegte Zuordnung `vorgesetzterId`.
|
||||
*
|
||||
* Die Kennung ist nur innerhalb einer Quelle eindeutig; ohne diese
|
||||
* Einschraenkung zoege ein zweites Verzeichnis fremde Treffer herbei.
|
||||
* Vorher lief das ueber `vorgesetzterExterneId` und die `externeId` derselben
|
||||
* Quelle. Dann blieb `{{vorgesetzter_sam}}` bei jeder von Hand angelegten Person
|
||||
* leer — genau im Onboarding, wo der Befehl ihn braucht (#020).
|
||||
*/
|
||||
async function vorgesetztenLesen(person: {
|
||||
readonly quelleId: string | null;
|
||||
readonly vorgesetzterExterneId: string | null;
|
||||
readonly vorgesetzterId: string | null;
|
||||
}): Promise<{ anzeigename: string; sam: string | null } | null> {
|
||||
if (!person.vorgesetzterExterneId || !person.quelleId) return null;
|
||||
return prisma.mitarbeiter.findFirst({
|
||||
where: { quelleId: person.quelleId, externeId: person.vorgesetzterExterneId },
|
||||
if (!person.vorgesetzterId) return null;
|
||||
return prisma.mitarbeiter.findUnique({
|
||||
where: { id: person.vorgesetzterId },
|
||||
select: { anzeigename: true, sam: true },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user