diff --git a/prisma/migrations/20250726100050_add_status_tracking/migration.sql b/prisma/migrations/20250726100050_add_status_tracking/migration.sql
new file mode 100644
index 0000000..ade0aec
--- /dev/null
+++ b/prisma/migrations/20250726100050_add_status_tracking/migration.sql
@@ -0,0 +1,62 @@
+/*
+ Warnings:
+
+ - You are about to alter the column `noteDeutsch` on the `anmeldungen` table. The data in that column could be lost. The data in that column will be cast from `String` to `Int`.
+ - You are about to alter the column `noteMathe` on the `anmeldungen` table. The data in that column could be lost. The data in that column will be cast from `String` to `Int`.
+ - You are about to alter the column `timestamp` on the `anmeldungen` table. The data in that column could be lost. The data in that column will be cast from `BigInt` to `DateTime`.
+ - Added the required column `geburtsdatum` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `hausnummer` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `ort` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `plz` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `schulart` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `strasse` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `telefon` to the `anmeldungen` table without a default value. This is not possible if the table is not empty.
+ - Made the column `noteDeutsch` on table `anmeldungen` required. This step will fail if there are existing NULL values in that column.
+ - Made the column `noteMathe` on table `anmeldungen` required. This step will fail if there are existing NULL values in that column.
+
+*/
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_anmeldungen" (
+ "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ "anrede" TEXT NOT NULL,
+ "vorname" TEXT NOT NULL,
+ "nachname" TEXT NOT NULL,
+ "geburtsdatum" TEXT NOT NULL,
+ "strasse" TEXT NOT NULL,
+ "hausnummer" TEXT NOT NULL,
+ "ort" TEXT NOT NULL,
+ "plz" TEXT NOT NULL,
+ "telefon" TEXT NOT NULL,
+ "email" TEXT NOT NULL,
+ "schulart" TEXT NOT NULL,
+ "schulklasse" TEXT,
+ "noteDeutsch" INTEGER NOT NULL,
+ "noteMathe" INTEGER NOT NULL,
+ "sozialverhalten" TEXT,
+ "motivation" TEXT,
+ "alter" INTEGER,
+ "status" TEXT NOT NULL DEFAULT 'OFFEN',
+ "processedBy" TEXT,
+ "processedAt" DATETIME,
+ "praktikumId" INTEGER,
+ "zugewiesenId" INTEGER,
+ "wunsch1Id" INTEGER,
+ "wunsch2Id" INTEGER,
+ "wunsch3Id" INTEGER,
+ "timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT "anmeldungen_praktikumId_fkey" FOREIGN KEY ("praktikumId") REFERENCES "Praktikumszeitraum" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+ CONSTRAINT "anmeldungen_zugewiesenId_fkey" FOREIGN KEY ("zugewiesenId") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+ CONSTRAINT "anmeldungen_wunsch1Id_fkey" FOREIGN KEY ("wunsch1Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+ CONSTRAINT "anmeldungen_wunsch2Id_fkey" FOREIGN KEY ("wunsch2Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
+ CONSTRAINT "anmeldungen_wunsch3Id_fkey" FOREIGN KEY ("wunsch3Id") REFERENCES "Dienststelle" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+INSERT INTO "new_anmeldungen" ("anrede", "email", "id", "nachname", "noteDeutsch", "noteMathe", "sozialverhalten", "status", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zugewiesenId") SELECT "anrede", "email", "id", "nachname", "noteDeutsch", "noteMathe", "sozialverhalten", "status", "timestamp", "vorname", "wunsch1Id", "wunsch2Id", "wunsch3Id", "zugewiesenId" FROM "anmeldungen";
+DROP TABLE "anmeldungen";
+ALTER TABLE "new_anmeldungen" RENAME TO "anmeldungen";
+CREATE INDEX "anmeldungen_status_idx" ON "anmeldungen"("status");
+CREATE INDEX "anmeldungen_processedAt_idx" ON "anmeldungen"("processedAt");
+CREATE INDEX "anmeldungen_zugewiesenId_idx" ON "anmeldungen"("zugewiesenId");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 963d672..b4ed039 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -40,10 +40,12 @@ model Praktikumszeitraum {
anmeldungen Anmeldung[] @relation("PraktikumszeitraumAnmeldungen")
}
+// Erweiterte Status-Enum für bessere Nachverfolgung
enum Status {
- OFFEN
- ANGENOMMEN
- ABGELEHNT
+ OFFEN // pending - neu eingegangen
+ BEARBEITUNG // processing - wird gerade bearbeitet
+ ANGENOMMEN // accepted - wurde angenommen
+ ABGELEHNT // rejected - wurde abgelehnt
}
model Anmeldung {
@@ -67,6 +69,10 @@ model Anmeldung {
alter Int? // Neu hinzugefügt für Altersvalidierung
status Status @default(OFFEN)
+ // Neue Felder für Status-Tracking
+ processedBy String? // Wer bearbeitet die Anmeldung
+ processedAt DateTime? // Wann wurde sie bearbeitet
+
// Praktikumszeitraum Relation
praktikumId Int?
praktikum Praktikumszeitraum? @relation("PraktikumszeitraumAnmeldungen", fields: [praktikumId], references: [id])
@@ -84,6 +90,10 @@ model Anmeldung {
timestamp DateTime @default(now())
pdfs PdfDatei[]
+ // Indizes für bessere Performance
+ @@index([status])
+ @@index([processedAt])
+ @@index([zugewiesenId])
@@map("anmeldungen")
}
diff --git a/src/lib/components/AnmeldungenTable.svelte b/src/lib/components/AnmeldungenTable.svelte
index 2eb6412..5e3a462 100644
--- a/src/lib/components/AnmeldungenTable.svelte
+++ b/src/lib/components/AnmeldungenTable.svelte
@@ -2,30 +2,16 @@
@@ -54,7 +47,10 @@
- Bewerber
+ Status
+
+
+ Bewerber/in
Noten
@@ -63,129 +59,174 @@
Wünsche
- Dokumente
+ Zugewiesen
- Anmeldung
+ Eingegangen
-
+
+ Bearbeitet
+
+
Aktionen
{#each anmeldungen as anmeldung (anmeldung.id)}
-
-
+
+
-
-
- {anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname}
-
-
- {anmeldung.email}
+
+ {getStatusText(anmeldung.status || 'pending')}
+
+ {#if anmeldung.status === 'processing' && anmeldung.processedBy}
+
+ von {anmeldung.processedBy}
+ {/if}
+
+
+
+
+
+ {anmeldung.anrede} {anmeldung.vorname} {anmeldung.nachname}
+
+ {anmeldung.email}
+
+ {#if anmeldung.pdfs && anmeldung.pdfs.length > 0}
+
+ {/if}
-
-
-
Deutsch: {anmeldung.noteDeutsch || '—'}
-
Mathe: {anmeldung.noteMathe || '—'}
- {#if anmeldung.sozialverhalten}
-
- Sozialverhalten:
- {anmeldung.sozialverhalten}
-
- {/if}
-
+
+ {#if anmeldung.noteDeutsch || anmeldung.noteMathe}
+
+ {#if anmeldung.noteDeutsch}
+
D: {anmeldung.noteDeutsch}
+ {/if}
+ {#if anmeldung.noteMathe}
+
M: {anmeldung.noteMathe}
+ {/if}
+
+ {:else}
+ -
+ {/if}
+ {#if anmeldung.sozialverhalten}
+
+ SV: {anmeldung.sozialverhalten}
+
+ {/if}
-
-
+
+
{#if anmeldung.wunsch1}
- 1
- {anmeldung.wunsch1.name}
+ 1
+ {anmeldung.wunsch1.name}
{/if}
-
{#if anmeldung.wunsch2}
- 2
- {anmeldung.wunsch2.name}
+ 2
+ {anmeldung.wunsch2.name}
{/if}
-
{#if anmeldung.wunsch3}
- 3
- {anmeldung.wunsch3.name}
+ 3
+ {anmeldung.wunsch3.name}
{/if}
-
-
-
- {#each anmeldung.pdfs as pdf, index}
-
- {/each}
-
+
+
+ {#if anmeldung.assignedDienststelle}
+
+
+
+
+
{anmeldung.assignedDienststelle.name}
+
+ {:else}
+ -
+ {/if}
-
+
{formatDate(anmeldung.timestamp)}
+
+
+ {formatProcessedDate(anmeldung.processedAt)}
+ {#if anmeldung.processedBy && anmeldung.processedAt}
+
+ von {anmeldung.processedBy}
+
+ {/if}
+
+
-
-
-
handleAccept(anmeldung.id)}
- class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
- >
-
-
-
- Annehmen
-
+
+
+ {#if canBeAccepted(anmeldung.status || 'pending')}
+
dispatch('accept', { id: anmeldung.id })}
+ class="text-green-600 hover:text-green-900 px-2 py-1 rounded text-xs font-medium border border-green-200 hover:bg-green-50"
+ class:opacity-50={anmeldung.status === 'processing'}
+ class:cursor-not-allowed={anmeldung.status === 'processing'}
+ >
+ {anmeldung.status === 'processing' ? 'Wird bearbeitet' : 'Annehmen'}
+
+ {/if}
+
+ {#if canBeRejected(anmeldung.status || 'pending')}
+
dispatch('reject', { id: anmeldung.id })}
+ class="text-red-600 hover:text-red-900 px-2 py-1 rounded text-xs font-medium border border-red-200 hover:bg-red-50"
+ >
+ Ablehnen
+
+ {/if}
handleReject(anmeldung.id)}
- class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500"
+ on:click={() => dispatch('delete', { id: anmeldung.id })}
+ class="text-gray-600 hover:text-gray-900 px-2 py-1 rounded text-xs font-medium border border-gray-200 hover:bg-gray-50"
>
-
-
-
- Ablehnen
-
-
-
handleDelete(anmeldung.id)}
- class="inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
- >
-
-
-
Löschen
+
+
+ {#if anmeldung.status === 'processing'}
+
+
+
+
+ Wird bereits bearbeitet
+
+ {/if}
{/each}
diff --git a/src/routes/admin/anmeldungen/+page.svelte b/src/routes/admin/anmeldungen/+page.svelte
index 20c7035..2999187 100644
--- a/src/routes/admin/anmeldungen/+page.svelte
+++ b/src/routes/admin/anmeldungen/+page.svelte
@@ -20,6 +20,10 @@
wunsch3?: { id: number; name: string };
timestamp: number;
id: number;
+ status?: 'pending' | 'accepted' | 'rejected' | 'processing'; // Neuer Status
+ assignedDienststelle?: { id: number; name: string }; // Zugewiesene Dienststelle
+ processedBy?: string; // Wer die Anmeldung bearbeitet
+ processedAt?: number; // Wann bearbeitet
}
interface EmailConfig {
@@ -31,6 +35,10 @@
let isLoading = true;
let error = '';
+ // Filter für Status
+ let statusFilter: 'all' | 'pending' | 'accepted' | 'rejected' | 'processing' = 'all';
+ let filteredAnmeldungen: Anmeldung[] = [];
+
// Dialog state
let showDialog = false;
let selectedAnmeldungId: number | null = null;
@@ -55,6 +63,40 @@ Ihr Praktikumsteam`;
let isLoadingEmailConfig = false;
let isSavingEmailConfig = false;
+ // Status-Badge Funktionen
+ function getStatusColor(status: string): string {
+ switch (status) {
+ case 'pending': return 'bg-yellow-100 text-yellow-800';
+ case 'processing': return 'bg-blue-100 text-blue-800';
+ case 'accepted': return 'bg-green-100 text-green-800';
+ case 'rejected': return 'bg-red-100 text-red-800';
+ default: return 'bg-gray-100 text-gray-800';
+ }
+ }
+
+ function getStatusText(status: string): string {
+ switch (status) {
+ case 'pending': return 'Offen';
+ case 'processing': return 'In Bearbeitung';
+ case 'accepted': return 'Angenommen';
+ case 'rejected': return 'Abgelehnt';
+ default: return 'Unbekannt';
+ }
+ }
+
+ // Filter-Funktionen
+ function filterAnmeldungen() {
+ if (statusFilter === 'all') {
+ filteredAnmeldungen = anmeldungen;
+ } else {
+ filteredAnmeldungen = anmeldungen.filter(a => (a.status || 'pending') === statusFilter);
+ }
+ }
+
+ $: {
+ filterAnmeldungen();
+ }
+
async function loadAnmeldungen() {
try {
isLoading = true;
@@ -67,6 +109,8 @@ Ihr Praktikumsteam`;
}
anmeldungen = await res.json();
+ // Standardstatus setzen falls nicht vorhanden
+ anmeldungen = anmeldungen.map(a => ({ ...a, status: a.status || 'pending' }));
} catch (err) {
error = err instanceof Error ? err.message : 'Unbekannter Fehler';
console.error('Fehler beim Laden der Anmeldungen:', err);
@@ -75,6 +119,28 @@ Ihr Praktikumsteam`;
}
}
+ async function setProcessingStatus(anmeldungId: number) {
+ try {
+ const res = await fetch(`/api/admin/anmeldungen?id=${anmeldungId}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ action: 'set_processing',
+ processedBy: 'current_user' // Hier sollte der aktuelle Benutzer stehen
+ })
+ });
+
+ if (!res.ok) {
+ throw new Error(`Fehler beim Setzen des Status: ${res.status}`);
+ }
+
+ await loadAnmeldungen();
+ } catch (err) {
+ error = err instanceof Error ? err.message : 'Fehler beim Setzen des Status';
+ console.error(err);
+ }
+ }
+
async function loadEmailConfig() {
try {
isLoadingEmailConfig = true;
@@ -127,6 +193,16 @@ Ihr Praktikumsteam`;
const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
if (!anmeldung) return;
+ // Prüfen ob bereits bearbeitet wird
+ if (anmeldung.status === 'processing') {
+ if (!confirm('Diese Anmeldung wird bereits bearbeitet. Trotzdem fortfahren?')) {
+ return;
+ }
+ }
+
+ // Status auf "in Bearbeitung" setzen
+ setProcessingStatus(event.detail.id);
+
availableWishes = [
anmeldung.wunsch1 && { id: anmeldung.wunsch1.id, name: `1. Wunsch: ${anmeldung.wunsch1.name}` },
anmeldung.wunsch2 && { id: anmeldung.wunsch2.id, name: `2. Wunsch: ${anmeldung.wunsch2.name}` },
@@ -189,6 +265,15 @@ Ihr Praktikumsteam`;
}
async function handleReject(event: CustomEvent<{id: number}>) {
+ const anmeldung = anmeldungen.find(a => a.id === event.detail.id);
+
+ // Prüfen ob bereits bearbeitet wird
+ if (anmeldung?.status === 'processing') {
+ if (!confirm('Diese Anmeldung wird bereits bearbeitet. Trotzdem ablehnen?')) {
+ return;
+ }
+ }
+
if (!confirm('Diese Anmeldung wirklich ablehnen?')) return;
try {
@@ -231,6 +316,10 @@ Ihr Praktikumsteam`;
function closeDialog() {
showDialog = false;
selectedAnmeldungId = null;
+ // Status zurücksetzen falls Dialog abgebrochen wird
+ if (selectedAnmeldungId) {
+ // Hier könnten Sie den Status zurück auf "pending" setzen
+ }
}
onMount(() => {
@@ -250,8 +339,25 @@ Ihr Praktikumsteam`;
/>
-
-
+
+
+
+
+ Filter:
+
+ Alle ({anmeldungen.length})
+ Offen ({anmeldungen.filter(a => (a.status || 'pending') === 'pending').length})
+ In Bearbeitung ({anmeldungen.filter(a => a.status === 'processing').length})
+ Angenommen ({anmeldungen.filter(a => a.status === 'accepted').length})
+ Abgelehnt ({anmeldungen.filter(a => a.status === 'rejected').length})
+
+
+
+
showEmailConfig = !showEmailConfig}
class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-md text-sm font-medium inline-flex items-center"
@@ -268,6 +374,81 @@ Ihr Praktikumsteam`;
+
+
+
+
+
+
+
Offen
+
+ {anmeldungen.filter(a => (a.status || 'pending') === 'pending').length}
+
+
+
+
+
+
+
+
+
+
In Bearbeitung
+
+ {anmeldungen.filter(a => a.status === 'processing').length}
+
+
+
+
+
+
+
+
+
+
Angenommen
+
+ {anmeldungen.filter(a => a.status === 'accepted').length}
+
+
+
+
+
+
+
+
+
+
Abgelehnt
+
+ {anmeldungen.filter(a => a.status === 'rejected').length}
+
+
+
+
+
+
{#if showEmailConfig}
@@ -349,18 +530,26 @@ Ihr Praktikumsteam`;
Lade Anmeldungen...
- {:else if anmeldungen.length === 0}
+ {:else if filteredAnmeldungen.length === 0}
-
Keine Anmeldungen
-
Es sind noch keine Praktikumsanmeldungen eingegangen.
+
+ {statusFilter === 'all' ? 'Keine Anmeldungen' : `Keine ${getStatusText(statusFilter).toLowerCase()}en Anmeldungen`}
+
+
+ {statusFilter === 'all'
+ ? 'Es sind noch keine Praktikumsanmeldungen eingegangen.'
+ : `Es gibt keine Anmeldungen mit dem Status "${getStatusText(statusFilter).toLowerCase()}".`}
+
{:else}
{
- if (!checkAuth(cookies)) {
- return new Response(
- JSON.stringify({ error: 'Nicht autorisiert' }),
- {
- status: 401,
- headers: { 'Content-Type': 'application/json' }
- }
- );
- }
-
+export async function GET() {
try {
const anmeldungen = await prisma.anmeldung.findMany({
include: {
wunsch1: true,
wunsch2: true,
wunsch3: true,
+ zugewiesen: true,
+ praktikum: true,
pdfs: true
},
- orderBy: { timestamp: 'desc' }
+ orderBy: [
+ {
+ status: 'asc' // BEARBEITUNG zuerst, dann OFFEN, etc.
+ },
+ {
+ timestamp: 'desc'
+ }
+ ]
});
- return new Response(JSON.stringify(anmeldungen), {
- headers: { 'Content-Type': 'application/json' }
- });
+ // Daten für Frontend formatieren
+ const formattedAnmeldungen = anmeldungen.map(anmeldung => ({
+ id: anmeldung.id,
+ anrede: anmeldung.anrede,
+ vorname: anmeldung.vorname,
+ nachname: anmeldung.nachname,
+ email: anmeldung.email,
+ noteDeutsch: anmeldung.noteDeutsch?.toString(),
+ noteMathe: anmeldung.noteMathe?.toString(),
+ sozialverhalten: anmeldung.sozialverhalten,
+
+ // Status-Mapping für Frontend
+ status: mapPrismaStatusToFrontend(anmeldung.status),
+ processedBy: anmeldung.processedBy,
+ processedAt: anmeldung.processedAt?.getTime(),
+
+ // Wünsche
+ wunsch1: anmeldung.wunsch1 ? {
+ id: anmeldung.wunsch1.id,
+ name: anmeldung.wunsch1.name
+ } : null,
+ wunsch2: anmeldung.wunsch2 ? {
+ id: anmeldung.wunsch2.id,
+ name: anmeldung.wunsch2.name
+ } : null,
+ wunsch3: anmeldung.wunsch3 ? {
+ id: anmeldung.wunsch3.id,
+ name: anmeldung.wunsch3.name
+ } : null,
+
+ // Zugewiesene Dienststelle
+ assignedDienststelle: anmeldung.zugewiesen ? {
+ id: anmeldung.zugewiesen.id,
+ name: anmeldung.zugewiesen.name
+ } : null,
+
+ timestamp: anmeldung.timestamp.getTime(),
+ pdfs: anmeldung.pdfs
+ }));
+
+ return json(formattedAnmeldungen);
} catch (error) {
console.error('Fehler beim Laden der Anmeldungen:', error);
return json({ error: 'Fehler beim Laden der Anmeldungen' }, { status: 500 });
}
-};
-
-export const POST: RequestHandler = async ({ url, cookies, request }) => {
- if (!checkAuth(cookies)) {
- return json({ error: 'Nicht autorisiert' }, { status: 401 });
- }
-
- const id = Number(url.searchParams.get('id'));
- if (!id) return json({ error: 'Ungültige ID' }, { status: 400 });
+}
+export async function POST({ request, url }) {
try {
- // Prüfe ob eine spezifische Dienststelle zugewiesen werden soll
- const body = await request.json().catch(() => ({}));
- const dienststelleId = body.dienststelleId;
+ const id = parseInt(url.searchParams.get('id') || '0');
+ const { dienststelleId } = await request.json();
- const anmeldung = await prisma.anmeldung.findUnique({
- where: { id },
- include: {
- wunsch1: true,
- wunsch2: true,
- wunsch3: true
- }
+ if (!id || !dienststelleId) {
+ return json({ error: 'ID und Dienststelle erforderlich' }, { status: 400 });
+ }
+
+ // Prüfen ob Anmeldung existiert und bearbeitet werden kann
+ const existingAnmeldung = await prisma.anmeldung.findUnique({
+ where: { id }
});
- if (!anmeldung) {
+ if (!existingAnmeldung) {
return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
}
- // Falls spezifische Dienststelle gewählt wurde
- if (dienststelleId) {
- const dienststelle = await prisma.dienststelle.findUnique({
- where: { id: dienststelleId }
- });
-
- if (!dienststelle) {
- return json({ error: 'Dienststelle nicht gefunden' }, { status: 404 });
- }
-
- if (dienststelle.plaetze <= 0) {
- return json({ error: 'Keine verfügbaren Plätze bei dieser Dienststelle' }, { status: 409 });
- }
-
- await prisma.$transaction([
- prisma.anmeldung.update({
- where: { id },
- data: {
- status: Status.ANGENOMMEN,
- zugewiesenId: dienststelleId
- }
- }),
- prisma.dienststelle.update({
- where: { id: dienststelleId },
- data: {
- plaetze: { decrement: 1 }
- }
- })
- ]);
-
- return json({ success: true, message: `Zugewiesen an: ${dienststelle.name}` });
+ if (existingAnmeldung.status === 'ANGENOMMEN') {
+ return json({ error: 'Anmeldung bereits angenommen' }, { status: 409 });
}
- // Fallback: Automatische Zuweisung nach Wunschreihenfolge
- const wuensche = [anmeldung.wunsch1, anmeldung.wunsch2, anmeldung.wunsch3];
-
- for (const wunsch of wuensche) {
- if (wunsch && wunsch.plaetze > 0) {
- await prisma.$transaction([
- prisma.anmeldung.update({
- where: { id },
- data: {
- status: Status.ANGENOMMEN,
- zugewiesenId: wunsch.id
- }
- }),
- prisma.dienststelle.update({
- where: { id: wunsch.id },
- data: {
- plaetze: { decrement: 1 }
- }
- })
- ]);
-
- return json({ success: true, message: `Zugewiesen an: ${wunsch.name}` });
+ // Anmeldung als angenommen markieren
+ await prisma.anmeldung.update({
+ where: { id },
+ data: {
+ status: 'ANGENOMMEN',
+ zugewiesenId: dienststelleId,
+ processedBy: 'current_user', // TODO: Echten Benutzer verwenden
+ processedAt: new Date()
}
- }
+ });
- return json({ error: 'Keine verfügbaren Plätze bei Wunsch-Dienststellen' }, { status: 409 });
- } catch (err) {
- console.error('Fehler beim Annehmen der Anmeldung:', err);
- return json({ error: 'Interner Serverfehler' }, { status: 500 });
+ return json({ success: true });
+ } catch (error) {
+ console.error('Fehler beim Annehmen der Anmeldung:', error);
+ return json({ error: 'Fehler beim Annehmen der Anmeldung' }, { status: 500 });
}
-};
-
-// Neue PATCH-Route für Ablehnung
-export const PATCH: RequestHandler = async ({ url, cookies, request }) => {
- if (!checkAuth(cookies)) {
- return json({ error: 'Nicht autorisiert' }, { status: 401 });
- }
-
- const id = Number(url.searchParams.get('id'));
- if (!id) return json({ error: 'Ungültige ID' }, { status: 400 });
+}
+export async function PATCH({ request, url }) {
try {
- const body = await request.json().catch(() => ({}));
-
- if (body.action === 'reject') {
- await prisma.anmeldung.update({
- where: { id },
- data: {
- status: Status.ABGELEHNT
+ const id = parseInt(url.searchParams.get('id') || '0');
+ const { action, processedBy } = await request.json();
+
+ if (!id) {
+ return json({ error: 'ID erforderlich' }, { status: 400 });
+ }
+
+ let updateData = {};
+
+ switch (action) {
+ case 'reject':
+ updateData = {
+ status: 'ABGELEHNT',
+ processedBy: 'current_user', // TODO: Echten Benutzer verwenden
+ processedAt: new Date()
+ };
+ break;
+
+ case 'set_processing':
+ // Nur setzen wenn noch OFFEN
+ const anmeldung = await prisma.anmeldung.findUnique({
+ where: { id }
+ });
+
+ if (!anmeldung) {
+ return json({ error: 'Anmeldung nicht gefunden' }, { status: 404 });
+ }
+
+ if (anmeldung.status !== 'OFFEN') {
+ return json({ error: 'Anmeldung kann nicht mehr bearbeitet werden' }, { status: 409 });
}
- });
- return json({ success: true, message: 'Anmeldung abgelehnt' });
+ updateData = {
+ status: 'BEARBEITUNG',
+ processedBy: processedBy || 'current_user',
+ processedAt: new Date()
+ };
+ break;
+
+ case 'reset_processing':
+ updateData = {
+ status: 'OFFEN',
+ processedBy: null,
+ processedAt: null
+ };
+ break;
+
+ default:
+ return json({ error: 'Unbekannte Aktion' }, { status: 400 });
}
- return json({ error: 'Unbekannte Aktion' }, { status: 400 });
- } catch (err) {
- console.error('Fehler beim Ablehnen der Anmeldung:', err);
- return json({ error: 'Interner Serverfehler' }, { status: 500 });
- }
-};
+ const result = await prisma.anmeldung.update({
+ where: { id },
+ data: updateData
+ });
-export const DELETE: RequestHandler = async ({ cookies, url }) => {
- if (!checkAuth(cookies)) {
- return json({ error: 'Nicht autorisiert' }, { status: 401 });
- }
-
- const id = Number(url.searchParams.get('id'));
- if (isNaN(id)) {
- return json({ error: 'Ungültige ID' }, { status: 400 });
+ return json({ success: true });
+ } catch (error) {
+ console.error('Fehler beim Aktualisieren der Anmeldung:', error);
+ return json({ error: 'Fehler beim Aktualisieren der Anmeldung' }, { status: 500 });
}
+}
+export async function DELETE({ url }) {
try {
- // 1. Alle PDF-Einträge zur Anmeldung laden
- const pdfs = await prisma.pdfDatei.findMany({
- where: { anmeldungId: id }
- });
+ const id = parseInt(url.searchParams.get('id') || '0');
- // 2. Dateien vom Dateisystem löschen
- for (const pdf of pdfs) {
- const filePath = path.resolve('static', pdf.pfad.replace(/^\/+/, ''));
- try {
- await fs.unlink(filePath);
- } catch (err) {
- console.warn(
- `Datei konnte nicht gelöscht werden: ${filePath}`,
- err instanceof Error ? err.message : String(err)
- );
- // Fehler ignorieren, Datei evtl. manuell entfernt
- }
+ if (!id) {
+ return json({ error: 'ID erforderlich' }, { status: 400 });
}
- // 3. PDF-Datensätze aus DB löschen
- await prisma.pdfDatei.deleteMany({
- where: { anmeldungId: id }
- });
-
- // 4. Anmeldung löschen
await prisma.anmeldung.delete({
where: { id }
});
- return json({ success: true, message: 'Anmeldung erfolgreich gelöscht' });
+ return json({ success: true });
} catch (error) {
console.error('Fehler beim Löschen der Anmeldung:', error);
- return json({ error: 'Löschen fehlgeschlagen' }, { status: 500 });
+ return json({ error: 'Fehler beim Löschen der Anmeldung' }, { status: 500 });
}
-};
\ No newline at end of file
+}
+
+// Hilfsfunktion: Prisma Status zu Frontend Status
+function mapPrismaStatusToFrontend(prismaStatus) {
+ const statusMap = {
+ 'OFFEN': 'pending',
+ 'BEARBEITUNG': 'processing',
+ 'ANGENOMMEN': 'accepted',
+ 'ABGELEHNT': 'rejected'
+ };
+
+ return statusMap[prismaStatus] || 'pending';
+}
+
+// Hilfsfunktion: Frontend Status zu Prisma Status
+function mapFrontendStatusToPrisma(frontendStatus) {
+ const statusMap = {
+ 'pending': 'OFFEN',
+ 'processing': 'BEARBEITUNG',
+ 'accepted': 'ANGENOMMEN',
+ 'rejected': 'ABGELEHNT'
+ };
+
+ return statusMap[frontendStatus] || 'OFFEN';
+}
\ No newline at end of file