Files
Laufzettel/tests/mitarbeiter.test.ts
T
Florian Wilde 02ca74e661
Prüfung / pruefung (pull_request) Canceled after 0s
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (pull_request) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(mitarbeiter): Vorgesetzter auch fuer Personen ohne Verzeichnis
Die Zuordnung lief allein ueber vorgesetzterExterneId, und die ist nur
innerhalb einer Verzeichnisquelle eindeutig: eine von Hand angelegte Person
konnte keinen Vorgesetzten haben, und {{vorgesetzter_sam}} blieb im
Onboarding-Befehl leer -- genau dort, wo er gebraucht wird.

Entschieden fuer eine eigene Selbstbeziehung vorgesetzterId. Die befuerchteten
"zwei Wege zur selben Auskunft" entstehen nicht, weil der Abgleich sie
mitschreibt: vorgesetzterExterneId ist jetzt wie abteilung nur der rohe Wert
des Verzeichnisses, gelesen wird allein vorgesetzterId. Die Alternative haette
manuellen Personen eine erfundene externeId geben muessen, und externeId heisst
"objectGUID aus dem Verzeichnis".

Liefert das Verzeichnis keinen manager, bleibt eine von Hand gesetzte Zuordnung
stehen -- geleert wird nur, was das Verzeichnis selbst gesetzt hatte.

Die Migration fuehrt bestehende Daten nach. Ohne das waere die Auskunft nach
dem Einspielen bei jeder Verzeichnis-Person still leer gewesen, bis der
naechste Abgleich laeuft. An einem nachgebauten Vor-Zustand gefahren.

Drei Grenzen, drei Stellen: der zusammengesetzte Fremdschluessel haelt die
Mandantengrenze, ein CHECK die kuerzeste Schleife, und der Dienst laeuft die
Kette hinauf fuer laengere.

Im gebauten Stand durchgeklickt: speichern, neu laden, Wert steht, entfernen,
390 px ohne horizontales Scrollen. Gegenprobe am Abgleich gefahren.

Closes #31
2026-10-04 17:09:54 +02:00

525 lines
22 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { Betriebsmodus, Herkunft } from "@/generated/prisma/enums";
import {
ABGEGLICHENE_FELDER,
MitarbeiterFehler,
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";
/**
* Mitarbeiter (#011).
*
* Zwei Regeln tragen das Ganze: nichts wird geloescht, und was aus dem
* Verzeichnis kommt, wird nicht von Hand ueberschrieben. Alles hier dreht sich
* darum — dazu die Mandantensicht, die im Review von #009 dreimal die Fundstelle
* war.
*/
async function grundbestand() {
const eigene = await testPrisma.kunde.create({
data: { name: "Eigene Organisation", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
await testPrisma.einstellung.create({ data: { id: "global", modus: Betriebsmodus.INTERN } });
const admin = await testPrisma.benutzer.create({
data: { email: "admin@firma.example", name: "Admin", rolle: "ADMIN" },
});
return { eigene, admin: { id: admin.id, rolle: admin.rolle } };
}
describe.runIf(datenbankVerfuegbar)("Mitarbeiter", () => {
beforeEach(async () => {
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();
const person = await mitarbeiterAnlegen({ vorname: " Maria ", nachname: " Huber " }, admin);
expect(person.anzeigename).toBe("Maria Huber");
expect(person.herkunft).toBe(Herkunft.MANUELL);
expect(person.aktiv).toBe(true);
});
it("nimmt einen eigenen Anzeigenamen an", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen(
{ vorname: "Maria", nachname: "Huber", anzeigename: "Huber, Maria" },
admin,
);
expect(person.anzeigename).toBe("Huber, Maria");
});
it("weist einen leeren Namen ab", async () => {
const { admin } = await grundbestand();
await expect(mitarbeiterAnlegen({ vorname: " ", nachname: "Huber" }, admin)).rejects.toThrow(
MitarbeiterFehler,
);
expect(await testPrisma.mitarbeiter.count()).toBe(0);
});
it("weist einen sAMAccountName über 20 Zeichen ab", async () => {
// Das Active Directory kuerzt nicht, es lehnt ab — und zwar erst beim
// New-ADUser vor dem Kunden.
const { admin } = await grundbestand();
await expect(
mitarbeiterAnlegen({ vorname: "Jörg", nachname: "Müller", sam: "j".repeat(21) }, admin),
).rejects.toThrow(/höchstens 20/);
await expect(
mitarbeiterAnlegen({ vorname: "Jörg", nachname: "Müller", sam: "j".repeat(20) }, admin),
).resolves.toBeTruthy();
});
it("schreibt einen Protokolleintrag", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaetId: person.id },
});
expect(eintrag.aktion).toBe("Mitarbeiter angelegt");
expect(eintrag.benutzerId).toBe(admin.id);
});
});
describe("Aus dem Verzeichnis abgeglichene Personen", () => {
async function abgeglichen(kundeId: string) {
return testPrisma.mitarbeiter.create({
data: {
kundeId,
vorname: "Maria",
nachname: "Huber",
anzeigename: "Maria Huber",
mail: "maria.huber@firma.example",
abteilung: "Buchhaltung",
herkunft: Herkunft.ABGLEICH,
},
});
}
it("lässt abgeglichene Felder nicht von Hand ändern", async () => {
// Der naechste Abgleich wuerde die Aenderung ohnehin zurueckdrehen. Ein
// Wert, der zwischen zwei Laeufen hin- und herspringt, ist schlimmer als
// ein falscher, weil niemand ihm mehr traut.
const { admin, eigene } = await grundbestand();
const person = await abgeglichen(eigene.id);
for (const aenderung of [
{ vorname: "Marie" },
{ nachname: "Hubert" },
{ mail: "anders@firma.example" },
{ abteilung: "Vertrieb" },
]) {
await expect(
mitarbeiterBearbeiten(person.id, aenderung, admin),
JSON.stringify(aenderung),
).rejects.toThrow(/Verzeichnis/);
}
});
it("stört sich nicht an unverändert mitgeschickten Feldern", async () => {
// Das Formular schickt alle Felder mit. Wuerde schon das Mitschicken
// scheitern, liefe der Benutzer gegen eine Fehlermeldung, ohne etwas
// geaendert zu haben. Abgewiesen wird nur, was sich wirklich aendern soll.
const { admin, eigene } = await grundbestand();
const person = await abgeglichen(eigene.id);
const vorherProtokoll = await testPrisma.protokoll.count();
const nachher = await mitarbeiterBearbeiten(
person.id,
{
vorname: "Maria",
nachname: "Huber",
anzeigename: "Maria Huber",
mail: "maria.huber@firma.example",
abteilung: "Buchhaltung",
},
admin,
);
expect(nachher.anzeigename).toBe("Maria Huber");
expect(await testPrisma.protokoll.count()).toBe(vorherProtokoll);
});
it("sperrt bei ABGLEICH jedes gepflegte Feld, nicht nur einige", async () => {
// Alle Stammdaten einer abgeglichenen Person kommen aus dem Verzeichnis —
// auch der Titel. Uebrig bleibt genau ein Weg: aktivieren und
// deaktivieren. Das ist Absicht und keine Luecke.
const { admin, eigene } = await grundbestand();
const person = await abgeglichen(eigene.id);
for (const feld of ABGEGLICHENE_FELDER) {
await expect(
mitarbeiterBearbeiten(person.id, { [feld]: `anders-${feld}` }, admin),
feld,
).rejects.toThrow(/Verzeichnis/);
}
});
it("lässt eine manuell angelegte Person vollständig ändern", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
const nachher = await mitarbeiterBearbeiten(
person.id,
{ vorname: "Marie", mail: "marie@firma.example" },
admin,
);
expect(nachher.vorname).toBe("Marie");
expect(nachher.mail).toBe("marie@firma.example");
});
it("lässt sich auch bei Herkunft ABGLEICH deaktivieren", async () => {
// Das Offboarding muss auch dann gehen, wenn das Verzeichnis noch nicht
// nachgezogen hat.
const { admin, eigene } = await grundbestand();
const person = await abgeglichen(eigene.id);
const nachher = await mitarbeiterAktivSetzen(person.id, false, admin);
expect(nachher.aktiv).toBe(false);
});
});
describe("Bearbeiten", () => {
it("schreibt nichts, wenn sich nichts ändert", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
const vorher = await testPrisma.protokoll.count();
await mitarbeiterBearbeiten(person.id, { vorname: "Maria", nachname: "Huber" }, admin);
expect(await testPrisma.protokoll.count()).toBe(vorher);
});
it("nennt im Protokoll, was sich geändert hat", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
await mitarbeiterBearbeiten(person.id, { mail: "m.huber@firma.example" }, admin);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaetId: person.id, aktion: { contains: "geändert" } },
});
expect(eintrag.aktion).toBe("Mail-Adresse geändert");
expect(eintrag.vorher).toEqual({ mail: null });
expect(eintrag.nachher).toEqual({ mail: "m.huber@firma.example" });
});
});
describe("Deaktivieren statt löschen", () => {
it("setzt aktiv auf false und protokolliert es", async () => {
const { admin } = await grundbestand();
const person = await mitarbeiterAnlegen({ vorname: "Maria", nachname: "Huber" }, admin);
await mitarbeiterAktivSetzen(person.id, false, admin);
expect((await testPrisma.mitarbeiter.findUniqueOrThrow({ where: { id: person.id } })).aktiv).toBe(
false,
);
const eintrag = await testPrisma.protokoll.findFirstOrThrow({
where: { entitaetId: person.id, aktion: "Mitarbeiter deaktiviert" },
});
expect(eintrag).toBeTruthy();
});
it("bietet gar keinen Löschweg an", async () => {
// Waere Loeschen manchmal moeglich, muesste jeder Aufrufer die Bedingung
// kennen — und beim Offboarding entscheidet sich niemand bewusst zwischen
// zwei Wegen. Geprueft wird der Dienst selbst: was er nicht anbietet, kann
// auch niemand versehentlich rufen.
const dienst = await import("@/server/services/mitarbeiter");
const verdaechtig = Object.keys(dienst).filter((name) => /loesch|entfern|delete/iu.test(name));
expect(verdaechtig).toEqual([]);
expect(Object.keys(dienst)).toContain("mitarbeiterAktivSetzen");
});
});
describe("Liste und Suche", () => {
async function bestand() {
const { admin, eigene } = await grundbestand();
await mitarbeiterAnlegen(
{
vorname: "Maria",
nachname: "Huber",
mail: "maria.huber@firma.example",
upn: "maria.huber@firma.example",
sam: "mhuber",
abteilung: "Buchhaltung",
},
admin,
);
const jens = await mitarbeiterAnlegen(
{ vorname: "Jens", nachname: "Ex", sam: "jex", abteilung: "Vertrieb" },
admin,
);
await mitarbeiterAktivSetzen(jens.id, false, admin);
return { admin, eigene, jens };
}
it("zeigt standardmäßig nur aktive Personen", async () => {
const { admin } = await bestand();
const namen = (await mitarbeiterListe(admin)).map((z) => z.anzeigename);
expect(namen).toEqual(["Maria Huber"]);
});
it("zeigt mit Umschalter auch deaktivierte", async () => {
const { admin } = await bestand();
const namen = (await mitarbeiterListe(admin, { auchDeaktivierte: true })).map((z) => z.anzeigename);
expect(namen.sort()).toEqual(["Jens Ex", "Maria Huber"]);
});
it("sucht über Anzeigename, Mail, UPN und sam", async () => {
const { admin } = await bestand();
for (const suche of ["huber", "MARIA.HUBER@firma", "mhuber"]) {
const treffer = await mitarbeiterListe(admin, { suche });
expect(
treffer.map((z) => z.anzeigename),
suche,
).toEqual(["Maria Huber"]);
}
});
it("lässt den Aktiv-Filter auch beim Suchen stehen", async () => {
// Genau der Blocker aus #009 in neuem Gewand: der Suchfilter bringt ein
// eigenes `OR` mit. Liegt es nicht in einem `AND`, gewinnt es.
const { admin } = await bestand();
const treffer = await mitarbeiterListe(admin, { suche: "e" });
expect(treffer.map((z) => z.anzeigename)).toEqual(["Maria Huber"]);
});
it("filtert nach Abteilung", async () => {
const { admin } = await bestand();
expect((await mitarbeiterListe(admin, { abteilung: "Buchhaltung" })).length).toBe(1);
expect((await mitarbeiterListe(admin, { abteilung: "Vertrieb" })).length).toBe(0);
});
it("liefert die vorkommenden Abteilungsnamen", async () => {
const { admin } = await bestand();
expect(await abteilungsNamen(admin)).toEqual(["Buchhaltung", "Vertrieb"]);
});
it("zählt Geräte und Läufe", async () => {
const { admin, eigene } = await bestand();
const person = (await mitarbeiterListe(admin))[0]!;
await testPrisma.geraet.create({
data: { kundeId: eigene.id, mitarbeiterId: person.id, tagCode: "LZ-7K3F9Q", hostname: "PC-001" },
});
const frisch = (await mitarbeiterListe(admin))[0]!;
expect(frisch.anzahlGeraete).toBe(1);
expect(frisch.anzahlLaeufe).toBe(0);
});
it("gibt die Geräte einer Person zurück, Hostname vor Bezeichnung", async () => {
const { admin, eigene } = await bestand();
const person = (await mitarbeiterListe(admin))[0]!;
await testPrisma.geraet.create({
data: { kundeId: eigene.id, mitarbeiterId: person.id, tagCode: "LZ-7K3F9Q", hostname: "PC-001" },
});
await testPrisma.geraet.create({
data: {
kundeId: eigene.id,
mitarbeiterId: person.id,
tagCode: "LZ-8M4G2R",
bezeichnung: "Monitor rechts",
},
});
const geraete = await geraeteDesMitarbeiters(person.id, admin);
expect(geraete.map((g) => g.name).sort()).toEqual(["Monitor rechts", "PC-001"]);
});
});
describe("Mandantensicht", () => {
async function zweiKunden() {
const eigene = await testPrisma.kunde.create({
data: { name: "Eigene", kuerzel: "EIGEN", istEigeneOrganisation: true },
});
const meiner = await testPrisma.kunde.create({ data: { name: "Meiner", kuerzel: "MEIN" } });
const fremder = await testPrisma.kunde.create({ data: { name: "Fremder", kuerzel: "FREMD" } });
await testPrisma.einstellung.create({
data: { id: "global", modus: Betriebsmodus.DIENSTLEISTUNG },
});
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-";
for (const [kundeId, name] of [
[meiner.id, "Meine Person"],
[fremder.id, "Fremde Person"],
] as const) {
await testPrisma.mitarbeiter.create({
data: { kundeId, vorname: name, nachname: "X", anzeigename: name, abteilung: "Geheim" },
});
}
return {
eigene,
techniker: { id: techniker.id, rolle: techniker.rolle },
fremdePerson: await testPrisma.mitarbeiter.findFirstOrThrow({ where: { kundeId: fremder.id } }),
};
}
it("zeigt einem gebundenen Techniker keine fremde Person", async () => {
const { techniker } = await zweiKunden();
expect((await mitarbeiterListe(techniker)).map((z) => z.anzeigename)).toEqual(["Meine Person"]);
});
it("hält die Grenze auch beim Suchen", async () => {
const { techniker } = await zweiKunden();
expect((await mitarbeiterListe(techniker, { suche: "Person" })).map((z) => z.anzeigename)).toEqual([
"Meine Person",
]);
});
it("gibt eine fremde Person nicht einzeln heraus", async () => {
const { techniker, fremdePerson } = await zweiKunden();
expect(await mitarbeiterHolen(fremdePerson.id, techniker)).toBeUndefined();
});
it("zeigt die Geräte einer fremden Person nicht", async () => {
const { techniker, fremdePerson } = await zweiKunden();
await testPrisma.geraet.create({
data: { kundeId: fremdePerson.kundeId, mitarbeiterId: fremdePerson.id, tagCode: "LZ-7K3F9Q" },
});
expect(await geraeteDesMitarbeiters(fremdePerson.id, techniker)).toEqual([]);
});
it("blendet fremde Abteilungsnamen aus dem Filter aus", async () => {
const { techniker } = await zweiKunden();
await testPrisma.mitarbeiter.updateMany({
where: { anzeigename: "Fremde Person" },
data: { abteilung: "Nur-fuer-Fremde" },
});
expect(await abteilungsNamen(techniker)).not.toContain("Nur-fuer-Fremde");
});
it("lässt eine fremde Person nicht deaktivieren", async () => {
const { techniker, fremdePerson } = await zweiKunden();
await expect(mitarbeiterAktivSetzen(fremdePerson.id, false, techniker)).rejects.toThrow(/Berechtigung/);
});
it("lässt eine fremde Person nicht bearbeiten", async () => {
const { techniker, fremdePerson } = await zweiKunden();
await expect(mitarbeiterBearbeiten(fremdePerson.id, { titel: "Chef" }, techniker)).rejects.toThrow(
/Berechtigung/,
);
});
});
});