# Design

## Context

See proposal.md for scope. Settings has four dummy cards; profile has a single Save Changes button. User already stores position, passwordHash, role and isActive. SystemSetting is a singleton with plantName and legacy fields. Auth checks the current database account on each protected request; signed JWTs have no session revocation state. Report exports currently have generic titles without plantName. Notification publishing does not consult NotificationPreference.

## Goals / Non-Goals

**Goals:** Reuse existing layers, identity and schema; preserve transactional audit and account continuity; connect the current UI with limited changes listed for review.

**Non-Goals:** Session model/token-version changes, invitation email, forgot-password/admin password reset, notification preferences, timezone editing, new dependencies, schema cleanup or migration. No changes to inventory approval, notification delivery, maintenance or report calculations.

## Decisions

### API and ownership

Use settings.routes.ts, settings.openapi.ts, controller, validator and feature service(s), with DTOs in packages/shared/settings.ts. Reuse requireAuth, trustedOrigin, role checks, no-store responses, stable errors, pagination and existing password utilities. Backend role checks remain authoritative. Profile uses existing GET /auth/me and PATCH /settings/profile. Users use GET/POST /settings/users and PATCH /settings/users/:id; system uses GET/PATCH /settings/system. All paths are under /api/v1. Reject unrecognized writable fields rather than passing request objects to Prisma.

### Atomic profile save

PATCH /settings/profile accepts name (1-150), email (normalized lowercase, <=191), nullable position (<=150), and optional currentPassword/newPassword/confirmPassword. All password fields omitted means no password change; a partial group is invalid. Use the existing 8-128-character password policy and hash utility. Keep the single UI submit atomic rather than calling separate profile/password endpoints that could partially succeed. Verify password against the persisted hash; protect the final write against a concurrent password change, returning a conflict instead of overwriting it. Audit only non-secret profile fields and a passwordChanged boolean. Rate-limit password-bearing operations. Preserve the current session policy and document that password change does not revoke existing JWTs.

### User administration

Expose id, name, email, position, role, isActive, createdAt and updatedAt only. List accepts q, role, status=all|active|inactive, page and pageSize with a maximum of 100 and stable ordering. Initial password is entered by ADMIN at account creation and communicated outside the application; no email or compulsory-first-login state is implied. New users are active; roles remain ADMIN/SUPERVISOR/TECHNICIAN/VIEWER. Editing does not accept passwords. Normalize email and map unique violations to a field-level conflict, including races.

Use isActive rather than hard deletion to preserve history. Self-role changes and self-deactivation through administration are rejected. Serialize user-admin mutations on a consistent database lock (ensure and lock singleton SystemSetting id=1 before reading active administrators), then recheck actor activity/role and the last-admin invariant inside that transaction. Concurrent operations cannot remove all administrators. Audit commits with writes; do not use in-process locks. Ordinary profile saves update only allowlisted fields and cannot alter role/status. Re-enabling accounts preserves the existing behavior that unexpired JWTs can become valid again; no promise of session revocation is added.

### System configuration and reports

Expose only plantName. GET without row returns Pilot Plant Utama without side effects. PATCH upserts id=1, preserves all other fields, and audits old/new name. Existing timezone stays unchanged, including installations already configured differently. An absent row uses schema defaults. Legacy currency/autoApprovalRule and NotificationPreference remain stored and inactive in Settings.

Add plantName to report metadata using the same read snapshot as the report, with the same fallback. Report consumers receive this through report responses, not ADMIN-only system configuration. Excel and print/PDF headers render the metadata value as safe text. It is the current facility name, not a historical snapshot. Existing report metrics and authorization are unchanged. After saving the name, invalidate report queries in the current client; other clients obtain the name on their next normal fetch.

### Frontend

Use services/settings.service.ts with the existing HTTP helper and shared QueryClient; keep HTTP out of components. Keys include account identity and list filters. Reuse the auth query/provider on profile success so header name and email update. Clear private caches on account change according to current auth conventions. Preserve dirty inputs on background refetch and failure; Discard restores last server data and clears passwords. Use existing Skeleton, form controls and dialogs; pending buttons block duplicates and failures offer actionable messages.

Retain Settings layout. Add create/edit user dialog and confirmation for status changes. Replace Delete with deactivate/reactivate, show real role/status and self marker. Hide ADMIN sections for other roles and move to Profile if a role refresh removes access. Remove the Notifications section/component only after checking usages. Remove priority auto-approval control. No new theme, notification switches or timezone UI.

## Risks / Trade-offs

- Existing tokens remain valid after password change; reactivation can restore access with unexpired tokens -> explicitly preserve and document AUTH.md policy, do not label this logout-all.
- Singleton locking serializes rare administration writes -> short transactions and existing deadlock-retry conventions; validate real concurrent requests against disposable MySQL.
- Initial password is handled by ADMIN -> mask input, never return/store it as plaintext, no email integration or default shared password.
- Concurrent profile edits use last successful save for ordinary fields -> password changes require hash concurrency protection; broader optimistic editing is outside MVP.
- Legacy UI changes affect expectations -> list them for review before apply, preserve unrelated layout and functionality.

## Migration Plan

No Prisma schema or migration is planned. Deploy shared DTOs, backend and frontend together. Update AUTH.md and feature documentation for the new behavior. Regression-test auth, report exports, checkout approval and notification delivery. Rollback restores the prior application release; account/name changes remain valid with the prior schema. Do not reseed or reset application data. Any newly discovered need for schema changes requires user confirmation before implementation.
