--- status: Accepted date: 2026-09-14 applyTo: - "Fuchs/Controllers/IntranetController.AdminUsers.cs" - "Fuchs/js/intranet/modules/fis.usr*.js" - "Fuchs/js/intranet/modules/fis.usr.scss" - "Fuchs_Database/dbo/Stored Procedures/fis_admin_createUserAccount.sql" - "Fuchs_Database/dbo/Stored Procedures/fis_admin_delUserAccount.sql" supersededBy: "" --- # 0020 — User management: wire up create/delete, welcome email on create, guard delete ## Context The `usr` module (see [0015](0015-split-admin-user-management-module.md)) let an admin list users, reset a password, change an email address, and manage group membership, but had no way to add a new account or remove one — the admin had to do that directly in the database. Two stored procedures for this already existed in `Fuchs_Database` but were completely unwired from any controller: `fis_admin_createUserAccount` and `fis_admin_delUserAccount`. Neither followed the module's established conventions: - `fis_admin_createUserAccount` took no `@authaccount` and performed **no** `intranet_admin` authorization check at all (every other write in this module checks `fis_admin_getUserAuth(@authaccount) > 1`, per [0014](0014-admin-user-management.md)'s dual auth axis), had no duplicate-email guard (`fis_admin_setUserAccount` does), and generated the initial password inline in the `INSERT` without ever returning it — so nothing calling it could email the new user their credentials. - `fis_admin_delUserAccount` likewise had no `intranet_admin` check, and nothing stopped an admin from deleting their own account. ## Decision - **`create` and `delete` are new `id` cases in `Do_Process_AdminUsers`**, behind the same `fds_sys > 4` gate as every other action in the module. - **`fis_admin_createUserAccount` was brought in line with its siblings** rather than reused via `fis_admin_setUserAccount`'s existing "insert when `@useraccount_id` is NULL" branch (which would have been the smaller diff): it gained `@authaccount` + the standard `fis_admin_getUserAuth(@authaccount) > 1` gate, the same duplicate-email check `fis_admin_setUserAccount` already has, and — mirroring `fis_admin_resetUserPassword`'s existing pattern — resolves `@password` (auto-generating one via `ocms_fn_generatePassword` when the admin leaves it blank) **before** encrypting it, and returns it in the result set as `newpassword` alongside the created row. `fis_admin_setUserAccount`'s insert branch was left alone (still reachable in principle, but has no caller) since changing its shared behavior risked the existing `email` action; a dedicated create proc with an explicit contract was judged clearer than overloading an update proc's edge case. - **New account creation always emails the credentials, never displays them** — identical handling to password reset ([0014](0014-admin-user-management.md)): `HandleLogin`-style `IComService.SendEmailAsync` (respecting `Fuchs:Email:OverrideRecipient`) sends name, email, and password via `IntranetController.BuildWelcomeEmail`; on delivery failure the admin gets an `IEventService.UserIssueAsync` notification per [0003](0003-interrupting-exceptions-notify-the-user.md), matching `pwdreset`'s existing `emailed` flag semantics. - **`fis_admin_delUserAccount` gained the same `intranet_admin` gate** (previously entirely missing — a real authorization hole once the proc is reachable from a controller) **and a self-delete guard** (`@authaccount <> @useraccount_id`), enforced at both the SQL layer (belt) and the controller (suspenders — returns 409 `cannot_delete_self` without a round-trip when the caller targets their own account). - **Frontend**: `fis.usr.js`'s base ribbon gained an always-visible "Neuer Benutzer" button (`createDlg`, a `$ocms.dlgform` collecting Vorname/Nachname/E-Mail required, Titel/Anrede/Mobil optional); the per-row ribbon gained "Benutzer löschen" (`deleteUser`), using the same `confirm(...)` guard pattern Banking's `fis.bam.js` uses before destructive actions (`smd`/`mbd`) rather than a modal dialog, since delete has no fields to collect. ## Consequences - `fis_admin_createUserAccount`'s signature changed (`@authaccount` added as the first parameter) and its result set gained a `newpassword` column; safe in-place change since it had no existing caller. - `fis_admin_delUserAccount` now silently no-ops (same "no-op instead of error" style as every other `fis_admin_*` proc in this module) for a caller without `intranet_admin >= 2` **or** who targets their own account — the controller's own `cannot_delete_self` check makes the self-delete case surface as a clear error to the admin instead of a silent no-op, but a caller lacking `intranet_admin` still only sees an unexplained failure, consistent with the accepted gap from 0014. - Delete is a soft-delete (`fis_admin_delUserAccount` already set `DateDeleted`/`UserDeleted` before this change) — deleted accounts simply stop appearing in `fis_admin_getUserAccountList` and can only be restored directly in the database; no "undo" is exposed in the UI. - The list view (`fis.usr.scss`) was restyled from a plain bordered `