Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
### When Working on Inward / Inpatient Module
- [Inward Navigation & Reference](developer_docs/navigation/inward_navigation.md) - Pages, controllers, workflow, open issues

### When Adding a New Privilege
- [Privilege System Guide](developer_docs/security/privilege-system.md) - **All 3 steps required**: enum value + `getCategory()` case + `UserPrivilageController` tree node. Adding only the enum is NOT sufficient — the privilege will be invisible in the admin UI. This was missed for `InpatientClinicalDischarge` (PR #19658, issue #19677).

### When Committing Code
- [Commit Conventions](developer_docs/git/commit-conventions.md) - Message format

Expand Down
16 changes: 14 additions & 2 deletions developer_docs/security/privilege-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,23 @@ HMIS relies on enum-driven privileges to gate sensitive UI flows. Follow this gu
4. **Coordinate with administrators** to assign the new privilege to appropriate roles in production.

### Adding a New Privilege

> 🚨 **All three steps below are mandatory.** Adding only the enum value is NOT sufficient — the privilege will silently never appear in the admin UI, so administrators can never grant it. This was missed for `InpatientClinicalDischarge` in PR #19658, reported in issue #19677.

Follow this checklist whenever a new privilege is required:

**Step 1 — Declare it in `Privileges.java`**
- Inspect `src/main/java/com/divudi/core/data/Privileges.java` and **reuse an existing privilege** if one with matching behaviour already exists. For backward compatibility, *never* rename or edit legacy enum values even if they contain spelling or convention issues.
- If a new entry is necessary, add it to the most relevant section of the enum, keeping the alphabetical or functional grouping that already exists.
- Update the admin privilege maintenance page `src/main/webapp/admin/users/user_privileges.xhtml` so the new privilege can be assigned through the UI.
- Extend the `UserPrivilageController` implementation—specifically the `createPrivilegeHolderTreeNodes()` method—to place the new privilege in the appropriate branch of the tree structure so it renders correctly in the privileges UI.
- Add a `case YourPrivilege:` entry in the `getCategory()` method in the same file so the privilege maps to the correct module.

**Step 2 — Register it in `UserPrivilageController.java`**
- Extend the `UserPrivilageController` implementation—specifically the method that builds the privilege tree—to place the new privilege as a `DefaultTreeNode` in the appropriate branch. Without this step the privilege **will not appear** in the admin UI at `/admin/users/user_privileges.xhtml` and can never be assigned.
- Example: `new DefaultTreeNode(new PrivilegeHolder(Privileges.YourPrivilege, "Display Label"), parentNode);`

**Step 3 — Guard the UI**
- Reference the privilege from XHTML using `rendered="#{webUserController.hasPrivilege('YourPrivilege')}"`.
- Update the admin privilege maintenance page `src/main/webapp/admin/users/user_privileges.xhtml` if static privilege rows are used (tree-based pages handle this automatically via Step 2).

## Testing Checklist
- Log in with an account that lacks the privilege to confirm the UI element stays hidden or disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ private TreeNode<PrivilegeHolder> createPrivilegeHolderTreeNodes() {

TreeNode inwardClinicalNode = new DefaultTreeNode(new PrivilegeHolder(null, "Clinical"), inwardNode);
new DefaultTreeNode(new PrivilegeHolder(Privileges.InpatientClinicalAssessment, "Clinical Notes / Assessments"), inwardClinicalNode);
new DefaultTreeNode(new PrivilegeHolder(Privileges.InpatientClinicalDischarge, "Clinical Discharge"), inwardClinicalNode);

TreeNode additionalPrivilegesNode = new DefaultTreeNode(new PrivilegeHolder(null, "Additional Privileges"), inwardNode);
new DefaultTreeNode(new PrivilegeHolder(Privileges.InwardAdditionalPrivilages, "Additional Privilege Menu"), additionalPrivilegesNode);
Expand Down
Loading