feat(vorlagen): Abschnitte nur fuer bestimmte Abteilungen (#58)
This commit was merged in pull request #58.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Prisma, type Lauf } from "@/generated/prisma/client";
|
||||
import { LaufStatus, SchrittZustand, VariablenTyp } from "@/generated/prisma/enums";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { geltendeAbschnitteErmitteln, nurGeltende } from "@/lib/geltende-abschnitte";
|
||||
import { darf } from "@/server/auth/rollen";
|
||||
import {
|
||||
MandantFehler,
|
||||
@@ -190,6 +191,13 @@ type VersionFuerLauf = {
|
||||
readonly pflicht: boolean;
|
||||
}[];
|
||||
readonly schrittIds: readonly string[];
|
||||
/** Fuer die Auswahl nach Abteilung beim Start (#031). */
|
||||
readonly abschnitte: readonly {
|
||||
readonly id: string;
|
||||
readonly titel: string;
|
||||
readonly nurFuerAbteilungen: readonly string[];
|
||||
readonly schrittIds: readonly string[];
|
||||
}[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -219,7 +227,12 @@ async function versionFuerLaufHolen(versionId: string, kundeId: string): Promise
|
||||
},
|
||||
abschnitte: {
|
||||
orderBy: { position: "asc" },
|
||||
select: { schritte: { orderBy: { position: "asc" }, select: { id: true } } },
|
||||
select: {
|
||||
id: true,
|
||||
titel: true,
|
||||
nurFuerAbteilungen: true,
|
||||
schritte: { orderBy: { position: "asc" }, select: { id: true } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -238,6 +251,12 @@ async function versionFuerLaufHolen(versionId: string, kundeId: string): Promise
|
||||
vorlagenName: version.vorlage.name,
|
||||
variablen: version.variablen,
|
||||
schrittIds: version.abschnitte.flatMap((abschnitt) => abschnitt.schritte.map((s) => s.id)),
|
||||
abschnitte: version.abschnitte.map((abschnitt) => ({
|
||||
id: abschnitt.id,
|
||||
titel: abschnitt.titel,
|
||||
nurFuerAbteilungen: abschnitt.nurFuerAbteilungen,
|
||||
schrittIds: abschnitt.schritte.map((schritt) => schritt.id),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -246,6 +265,8 @@ type Bezuege = {
|
||||
readonly mitarbeiterName: string | null;
|
||||
readonly geraetId: string | null;
|
||||
readonly abteilungId: string | null;
|
||||
/** Name der gewaehlten Abteilung – fuer die Abschnittsauswahl (#031). */
|
||||
readonly abteilungsName: string | null;
|
||||
/** Nur fuer den Titelvorschlag. */
|
||||
readonly geraetName: string | null;
|
||||
readonly personName: string | null;
|
||||
@@ -310,6 +331,7 @@ async function bezuegePruefen(
|
||||
}
|
||||
|
||||
const abteilungId = text(eingabe.abteilungId);
|
||||
let abteilungsName: string | null = null;
|
||||
if (abteilungId) {
|
||||
const abteilung = await prisma.abteilung.findUnique({
|
||||
where: { id: abteilungId },
|
||||
@@ -322,9 +344,18 @@ async function bezuegePruefen(
|
||||
if (abteilung.archiviert) {
|
||||
throw new LaufFehler(`Die Abteilung „${abteilung.name}“ ist archiviert.`);
|
||||
}
|
||||
abteilungsName = abteilung.name;
|
||||
}
|
||||
|
||||
return { mitarbeiterId, mitarbeiterName, geraetId, abteilungId, geraetName, personName };
|
||||
return {
|
||||
mitarbeiterId,
|
||||
mitarbeiterName,
|
||||
geraetId,
|
||||
abteilungId,
|
||||
abteilungsName,
|
||||
geraetName,
|
||||
personName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,6 +472,28 @@ export async function laufAnlegen(eingabe: NeuerLauf, benutzer: HandelnderBenutz
|
||||
person: bezuege.personName,
|
||||
});
|
||||
|
||||
/*
|
||||
* Die Abschnittsauswahl **jetzt** festlegen und am Lauf festhalten (#031).
|
||||
* Danach aendert sie sich nie wieder: ein spaeterer Abteilungswechsel darf
|
||||
* keinen Schritt entfernen, an dem schon eine Notiz haengt, und ein
|
||||
* archivierter Lauf muss genau das zeigen, was damals abgearbeitet wurde.
|
||||
*/
|
||||
const geltende = geltendeAbschnitteErmitteln(version.abschnitte, bezuege.abteilungsName);
|
||||
if (version.abschnitte.length > 0 && geltende.length === 0) {
|
||||
throw new LaufFehler(
|
||||
bezuege.abteilungsName === null
|
||||
? `Von „${version.vorlagenName}“ gilt ohne Abteilung kein Abschnitt — jeder ist auf ` +
|
||||
"bestimmte Abteilungen eingeschränkt. Wähle eine Abteilung."
|
||||
: `Von „${version.vorlagenName}“ gilt für die Abteilung „${bezuege.abteilungsName}“ kein ` +
|
||||
"Abschnitt. Es gäbe nichts abzuarbeiten.",
|
||||
);
|
||||
}
|
||||
const geltendeSchrittIds = new Set(
|
||||
version.abschnitte
|
||||
.filter((abschnitt) => geltende.includes(abschnitt.id))
|
||||
.flatMap((abschnitt) => abschnitt.schrittIds),
|
||||
);
|
||||
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const lauf = await tx.lauf.create({
|
||||
data: {
|
||||
@@ -457,12 +510,17 @@ export async function laufAnlegen(eingabe: NeuerLauf, benutzer: HandelnderBenutz
|
||||
zugewiesenAnId,
|
||||
faelligAm: eingabe.faelligAm ?? null,
|
||||
erstelltVonId: benutzer.id,
|
||||
geltendeAbschnitte: [...geltende],
|
||||
},
|
||||
});
|
||||
|
||||
if (version.schrittIds.length > 0) {
|
||||
// Nur fuer die geltenden Abschnitte. Ein Schrittergebnis zu einem
|
||||
// ausgefilterten Schritt waere eine Zeile, die niemand je sieht — und sie
|
||||
// zaehlte in jeder Statistik mit.
|
||||
const anzulegen = version.schrittIds.filter((id) => geltendeSchrittIds.has(id));
|
||||
if (anzulegen.length > 0) {
|
||||
await tx.schrittergebnis.createMany({
|
||||
data: version.schrittIds.map((vorlagenSchrittId) => ({
|
||||
data: anzulegen.map((vorlagenSchrittId) => ({
|
||||
laufId: lauf.id,
|
||||
vorlagenSchrittId,
|
||||
zustand: SchrittZustand.OFFEN,
|
||||
@@ -605,11 +663,13 @@ export async function offenePflichtschritte(
|
||||
const lauf = await tx.lauf.findUniqueOrThrow({
|
||||
where: { id: laufId },
|
||||
select: {
|
||||
geltendeAbschnitte: true,
|
||||
vorlagenVersion: {
|
||||
select: {
|
||||
abschnitte: {
|
||||
orderBy: { position: "asc" },
|
||||
select: {
|
||||
id: true,
|
||||
titel: true,
|
||||
schritte: {
|
||||
where: { pflicht: true },
|
||||
@@ -626,7 +686,12 @@ export async function offenePflichtschritte(
|
||||
|
||||
const zustaende = new Map(lauf.ergebnisse.map((e) => [e.vorlagenSchrittId, e.zustand]));
|
||||
const offen: OffenerPflichtschritt[] = [];
|
||||
for (const abschnitt of lauf.vorlagenVersion.abschnitte) {
|
||||
/*
|
||||
* Nur die geltenden Abschnitte (#031). Ein ausgefilterter Pflichtschritt darf
|
||||
* den Abschluss nicht blockieren — sonst waere er unerreichbar: abhaken laesst
|
||||
* sich nur, was in der Abarbeitung steht.
|
||||
*/
|
||||
for (const abschnitt of nurGeltende(lauf.vorlagenVersion.abschnitte, lauf.geltendeAbschnitte)) {
|
||||
for (const schritt of abschnitt.schritte) {
|
||||
// Ohne Ergebniszeile gilt OFFEN – ein Schritt ohne Eintrag ist nicht erledigt.
|
||||
const zustand = zustaende.get(schritt.id) ?? SchrittZustand.OFFEN;
|
||||
|
||||
Reference in New Issue
Block a user