Projektleiter-Rolle, Papierkorb, neue Login-Seite und Benutzerverwaltung
- Neue Rolle "Projektleiter": Projekte anlegen und löschen nur noch für Projektleiter und Administratoren (serverseitig in /save-week geprüft) - Papierkorb: gelöschte Projekte 30 Tage durch Admins wiederherstellbar - Benutzerverwaltung: Aktionen im Drei-Punkte-Menü mit Rückfrage im Menü, getrennte Felder für Vor- und Nachname, E-Mail-Vorschlag v.nachname@rauschenberg-ing.de, Benutzername = Anfang der E-Mail, Person-ID aus dem Kürzel - Endgültiges Löschen eines Benutzers entfernt ihn auch aus den Projekten und Aufgaben der aktuellen Sitzung; Dateien bleiben als "Gelöschter Benutzer" - Login-Seite neu (Design "Kalenderwoche") mit Logo als Wasserzeichen; Anmeldung ohne Groß-/Kleinschreibung, auch mit voller E-Mail-Adresse Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93505e0e48
commit
f4fb3c550b
13 files changed
+1214
-116
No files matched your search
@@ -423,10 +423,21 @@ on the JSON endpoints.
|
||||
forces `must_change_password=1`, deletes all sessions of that user (forces
|
||||
re-login), returns the plaintext once as `generatedPassword`.
|
||||
- `DELETE /admin/users/:id[?hard=1]` → Admin. Default is a soft-delete
|
||||
(`active=0` + delete sessions); `?hard=1` permanently deletes the row, but
|
||||
only if the user is referenced nowhere in `project_files.uploaded_by_user_id`
|
||||
/ `project_file_reviewers.user_id` / `project_file_comments.user_id` (else
|
||||
409). An admin can never delete their own account (soft or hard).
|
||||
(`active=0` + delete sessions). `?hard=1` (`loescheBenutzerEndgueltig()`,
|
||||
one transaction) permanently deletes the account **and, on Erik's request,
|
||||
removes the person from the projects and thereby the Auslastung — but only
|
||||
in the current session (latest real week) and the Planung sandbox**; older
|
||||
sessions are history and stay untouched. Removed there: `project_assign`,
|
||||
`plan_todo_assignees`, `plan_people`, `week_people`/`_roles`,
|
||||
`attendance`, matched via `personenKennungenInWoche()` = `users.person_id`
|
||||
plus every id in that week's `week_people` with the same full name
|
||||
(legacy short ids like `p_ap`). Those weeks get a new `updated_at`, so a
|
||||
stale browser gets a 409 instead of writing the person back. Files are no
|
||||
longer a blocker: uploads/comments stay and show as "Gelöschter Benutzer"
|
||||
(`COALESCE(..., 'Gelöschter Benutzer')` in the file queries), the person's
|
||||
review assignments are deleted and pending files' status recomputed.
|
||||
Response includes counts (`zuordnungen`, `aufgaben`, `pruefauftraege`).
|
||||
An admin can never delete their own account (soft or hard).
|
||||
- `GET /users` → Auth (not admin-only). Lists active users
|
||||
(`id, username, displayName, email, personId, department, initials, roles`)
|
||||
— used both by the reviewer-picker in the file upload UI and by the
|
||||
@@ -1176,6 +1187,39 @@ Phase eine Aufgabe in `project.plan.todos`.
|
||||
180.000 € rechnerisch 172 Arbeitstage für eine Person, und der Plan lief
|
||||
über Jahre.
|
||||
|
||||
### Projekte anlegen/löschen — Rolle „Projektleiter" und Papierkorb
|
||||
|
||||
- **Projekte anlegen und aus der Projektliste löschen dürfen nur
|
||||
`projektleiter` und `admin`** (`users.role`; `VALID_ROLES` enthält jetzt
|
||||
`admin|member|projektleiter|kiosk`). `projektleiter` ist sonst ein
|
||||
gewöhnliches Mitglied — nicht zu verwechseln mit der Fachrolle
|
||||
`Projektleitung` (`user_roles`), die keine Berechtigung verleiht. Vergeben
|
||||
in Admin → Benutzerverwaltung. Bearbeiten bestehender Projekte bleibt
|
||||
jedem angemeldeten Konto erlaubt.
|
||||
- **Serverseitig durchgesetzt**, nicht nur UI: Da `/save-week` immer die ganze
|
||||
Woche schreibt, vergleicht `projektlisteAenderungen()` (`server.js`) den
|
||||
eingehenden mit dem gespeicherten Stand (Abgleich per `id` oder
|
||||
`projectUid`) und liefert neue und fehlende Projekte; ohne
|
||||
`darfProjektlisteAendern(req.user)` → 403 vor jedem Schreiben. Die
|
||||
Planung-Sandbox ist ausgenommen. Clientseitig blendet
|
||||
`darfProjektlisteAendern()` (`js/auth.js`) den Löschen-Knopf in
|
||||
`renderBoardRow()` aus, `renderUserBar()` alle Elemente mit der Klasse
|
||||
`.nur-projektleitung` (die beiden „+ Projekt"-Knöpfe), und
|
||||
`openNewProjWizard()` weist zusätzlich ab.
|
||||
- **Papierkorb** (`deleted_projects`: Schnappschuss des kompletten
|
||||
Projekt-Objekts als JSON, wer/wann/aus welcher Sitzung). Nach dem
|
||||
erfolgreichen Speichern legt `legeInPapierkorb()` die gelöschten Projekte
|
||||
dort ab. `purgeGeloeschteProjekte()` entfernt Einträge nach
|
||||
`GELOESCHTE_PROJEKTE_TAGE` (30) — beim Start und stündlich.
|
||||
Admin → **Papierkorb** (`loadAdminPapierkorb()`, `js/admin/admin.js`):
|
||||
`GET /admin/deleted-projects`, `POST /admin/deleted-projects/:id/restore`
|
||||
(setzt das Projekt ans Ende seiner Gruppe in der **jüngsten echten
|
||||
Sitzung**, leert den Sitzungskommentar, wenn es aus einer älteren Sitzung
|
||||
stammt; 409 bei Nummern-Kollision), `DELETE /admin/deleted-projects/:id`
|
||||
(sofort endgültig) — alle `requireRole('admin')`. Dateien/Protokolle
|
||||
hängen an `project_nr` und werden beim Löschen nie angefasst. Nicht in
|
||||
`IMPORT_TABLES` — ein DB-Import lässt den Papierkorb unberührt.
|
||||
|
||||
### Auftragsstatus — von der Anfrage zum Projekt
|
||||
|
||||
`projects.auftragsstatus` (`anfrage|angebot|beauftragt|abgeschlossen`, default
|
||||
@@ -2490,6 +2534,23 @@ gleichzeitig bearbeiten.
|
||||
`+ Neues Projekt`, `Wandtafel` (the former `Show Mode` entry — see
|
||||
"Wandtafel" below).
|
||||
|
||||
### Login-Seite (`#loginOverlay`)
|
||||
|
||||
Full-screen page, not a dialog: design "Kalenderwoche" (Erik's pick B from
|
||||
three Taste-Skill drafts). The logo is a large, pale watermark across the
|
||||
whole page (`.login-wasserzeichen`, `/logo.png` converted from `logo.ico`;
|
||||
Erik's pick E from a second comparison of logo placements) — deliberately
|
||||
no small logo. Left: heading and form; right: the current ISO calendar week as a large
|
||||
number with Mo-Fr and today highlighted (`renderLoginWoche()` in
|
||||
`js/auth.js`, called by `showLoginOverlay()`) — the app thinks in weekly
|
||||
sessions, so the week is what you see before logging in. Form has inline
|
||||
errors (`.login-feld.hat-fehler`, `#loginError`), a loading state on the
|
||||
button, "Anzeigen" for the password and a caps-lock hint
|
||||
(`verdrahteLoginHilfen()`). Styles: "LOGIN-SEITE" block at the end of
|
||||
`styles.css`, app tokens plus `--login-zahl`/`--login-jahr`. The number uses
|
||||
the web font Outfit (same Google-Fonts link as the Wandtafel, falls back to
|
||||
the system font offline). `z-index: 400`, above `.modal-bg` (300).
|
||||
|
||||
### Admin (tab `admin`, `js/admin/admin.js`)
|
||||
|
||||
- Everything only admins may do, in one place. Split into **sections, one
|
||||
@@ -2514,7 +2575,40 @@ gleichzeitig bearbeiten.
|
||||
`renderAdminArea()` renders nothing for them.
|
||||
- Sections:
|
||||
- **Benutzerverwaltung** — accounts, roles, weekly hours, absences
|
||||
(`js/admin-users.js`, `renderAdminUsers()`); reloaded from the server
|
||||
(`js/admin-users.js`, `renderAdminUsers()`). Row actions: "Bearbeiten"
|
||||
stays a visible button, everything else sits behind a "⋯" menu
|
||||
(`openUserActionMenu()`, Erik's pick "C" from a three-way comparison):
|
||||
Passwort zurücksetzen, De-/Aktivieren, and — only for deactivated
|
||||
accounts — Endgültig löschen. Irreversible actions confirm inside the
|
||||
menu instead of `confirm()`; the action functions take an
|
||||
`ohneRueckfrage` flag for that. The menu is appended to `<body>` with
|
||||
`position: fixed` (the table's horizontally scrolling frame would clip
|
||||
it) and flips upward when there is no room below.
|
||||
**Vor- und Nachname** are separate fields and columns
|
||||
(`users.first_name`/`last_name`, migration `migrateUsersVorNachname()`);
|
||||
`display_name` stays and is always written as "Vorname Nachname" by
|
||||
`POST`/`PATCH /admin/users*` — every other place still reads
|
||||
`display_name`; existing accounts got Vor-/Nachname once from
|
||||
`display_name` (first word / rest). **The username is the local part of
|
||||
the e-mail address** (`e.dunin`) — Erik first wanted "Vorname Nachname",
|
||||
then reverted to this before it ever reached the real database, so
|
||||
existing usernames were never renamed. In the new-user dialog the
|
||||
username follows the e-mail field (`benutzernameAusEmail()`), which
|
||||
itself follows Vor-/Nachname; each field stops following once edited by
|
||||
hand. **Person-IDs of new accounts come from the Kürzel** (`p_mk`,
|
||||
`personIdAusKuerzel()`), with a digit appended if taken (`p_mk2`) — the
|
||||
scheme every existing account and its project data already use. Before,
|
||||
the dialog derived them from the full name (`p_miriam_k_rner`), which
|
||||
split one person into two ids (account vs. project data).
|
||||
`/login` matches `COLLATE NOCASE` and also accepts the full
|
||||
address (everything from "@" is dropped); `POST /admin/users` checks
|
||||
uniqueness case-insensitively (409).
|
||||
The e-mail field
|
||||
pre-fills `v.nachname@rauschenberg-ing.de` from Vor-/Nachname
|
||||
(`emailVorschlag()`/`EMAIL_DOMAIN`, `js/utils.js`; umlauts transliterated)
|
||||
while not edited by hand — same pattern as Person-ID/Kürzel; the edit
|
||||
dialog and Einstellungen → Mein Konto only show it as a placeholder and
|
||||
never overwrite a stored address; reloaded from the server
|
||||
every time the section is entered, since the list changes outside this
|
||||
browser and the other masks compute with it (weekly hours → Auslastung).
|
||||
Marked `wide: true` in `ADMIN_SECTIONS`, which lifts the grid's width cap
|
||||
|
||||
Reference in new issue
Block a user