From 26c57f2e5521617b23365622b8e69f4e0b4a21cb Mon Sep 17 00:00:00 2001 From: Dr M H B Ariyaratne Date: Thu, 11 Jun 2026 16:12:19 +0530 Subject: [PATCH 1/4] @ fix: discharge notification navigation, removal, filters, real-time push, and UI display - Navigate to admission profile when clicking discharge notifications (PatientRoom/PatientEncounter) - Allow removal of PatientRoom/PatientEncounter-based notifications - Fix NPE in cancel filter for discharge notifications (clear + filter methods) - Fix today filter date comparison (use midnight truncation instead of equals) - Add WebSocket listener to user_notifications.xhtml for real-time notification list updates - Add Room Discharge and Patient Discharge type badges to notification list Closes #21389 Closes #21390 Closes #21391 Co-Authored-By: Claude @ --- .../common/UserNotificationController.java | 45 ++++++++++++++++--- .../Notification/user_notifications.xhtml | 34 ++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/divudi/bean/common/UserNotificationController.java b/src/main/java/com/divudi/bean/common/UserNotificationController.java index 947a2b500d..904f892fe4 100644 --- a/src/main/java/com/divudi/bean/common/UserNotificationController.java +++ b/src/main/java/com/divudi/bean/common/UserNotificationController.java @@ -20,6 +20,8 @@ import com.divudi.core.entity.Department; import com.divudi.core.entity.UserNotification; import com.divudi.core.entity.Notification; +import com.divudi.bean.inward.BhtSummeryController; +import com.divudi.core.entity.inward.PatientRoom; import com.divudi.core.entity.PatientEncounter; import com.divudi.core.entity.Sms; import com.divudi.core.entity.WebUser; @@ -82,6 +84,8 @@ public class UserNotificationController implements Serializable { SmsManagerEjb smsManager; @Inject NotificationPushService notificationPushService; + @Inject + BhtSummeryController bhtSummeryController; private Date date; private boolean todayNotification; private boolean seenedNotifiaction; @@ -153,7 +157,7 @@ public void clearNotificationsByCriteria() { return; } for (UserNotification un : items) { - if (un.getNotification().getBill().isCancelled()) { + if (un.getNotification().getBill() != null && un.getNotification().getBill().isCancelled()) { un.setRetired(true); un.setRetiredAt(new Date()); getFacade().edit(un); @@ -220,6 +224,14 @@ public void filterNotificationsByCriteria() { if (items == null) { return; } + // Compare dates ignoring time (use java.util.Calendar to truncate) + java.util.Calendar todayCal = java.util.Calendar.getInstance(); + todayCal.set(java.util.Calendar.HOUR_OF_DAY, 0); + todayCal.set(java.util.Calendar.MINUTE, 0); + todayCal.set(java.util.Calendar.SECOND, 0); + todayCal.set(java.util.Calendar.MILLISECOND, 0); + Date todayMidnight = todayCal.getTime(); + Iterator iterator = items.iterator(); while (iterator.hasNext()) { UserNotification notification = iterator.next(); @@ -227,7 +239,8 @@ public void filterNotificationsByCriteria() { continue; } - if (!notification.getNotification().getCreatedAt().equals(getDate())) { + Date createdAt = notification.getNotification().getCreatedAt(); + if (createdAt == null || createdAt.before(todayMidnight)) { iterator.remove(); } } @@ -260,6 +273,11 @@ public void filterNotificationsByCriteria() { if (notification.getNotification() == null) { continue; } + // Skip notifications without bills (e.g., discharge notifications) — they can't be "cancelled" + if (notification.getNotification().getBill() == null) { + iterator.remove(); + continue; + } if (!notification.getNotification().getBill().isCancelled()) { iterator.remove(); @@ -348,7 +366,9 @@ public void removeUserNotification(UserNotification un) { JsfUtil.addErrorMessage("You can't Access On Current Department !"); return; } - if (un.getNotification().getBill() == null) { + // Allow removal of both bill-based and PatientRoom-based notifications + if (un.getNotification().getBill() == null && un.getNotification().getPatientRoom() == null + && un.getNotification().getPatientEncounter() == null) { return; } un.setRetired(true); @@ -416,6 +436,22 @@ public String navigateToCurrentNotificationRequest(UserNotification un) { un.setSeen(true); getFacade().edit(un); + // Handle PatientRoom-based (discharge) notifications + if (un.getNotification().getPatientRoom() != null) { + PatientRoom pr = un.getNotification().getPatientRoom(); + if (pr.getPatientEncounter() != null) { + bhtSummeryController.setPatientEncounter(pr.getPatientEncounter()); + return bhtSummeryController.navigateToInpatientProfile(); + } + return ""; + } + + // Handle PatientEncounter-based notifications + if (un.getNotification().getPatientEncounter() != null) { + bhtSummeryController.setPatientEncounter(un.getNotification().getPatientEncounter()); + return bhtSummeryController.navigateToInpatientProfile(); + } + if (un.getNotification().getBill() == null) { return ""; } @@ -444,9 +480,6 @@ public String navigateToCurrentNotificationRequest(UserNotification un) { JsfUtil.addErrorMessage("You can't Access On Current Department !"); return ""; } - if (un.getNotification().getBill() == null) { - return ""; - } Bill bill = un.getNotification().getBill(); BillTypeAtomic type = bill.getBillTypeAtomic(); switch (type) { diff --git a/src/main/webapp/Notification/user_notifications.xhtml b/src/main/webapp/Notification/user_notifications.xhtml index f6be7656ec..286ca8f482 100644 --- a/src/main/webapp/Notification/user_notifications.xhtml +++ b/src/main/webapp/Notification/user_notifications.xhtml @@ -9,6 +9,19 @@ + + + + @@ -108,6 +121,27 @@ + + +
+
+ +
Room Discharge
+
#{un.notification.patientRoom.roomFacilityCharge.name}
+
+
+
+ + +
+
+ +
Patient Discharge
+
#{un.notification.patientEncounter.bhtNo}
+
+
+
+
From 4987619c2d6e6a21e01e0a08c31898c5e4418f65 Mon Sep 17 00:00:00 2001 From: Dr M H B Ariyaratne Date: Thu, 11 Jun 2026 16:32:53 +0530 Subject: [PATCH 2/4] @ fix: replace f:websocket with OmniFaces o:socket in notification page Co-Authored-By: Claude @ --- .../webapp/Notification/user_notifications.xhtml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/webapp/Notification/user_notifications.xhtml b/src/main/webapp/Notification/user_notifications.xhtml index 286ca8f482..e5f90970f4 100644 --- a/src/main/webapp/Notification/user_notifications.xhtml +++ b/src/main/webapp/Notification/user_notifications.xhtml @@ -4,21 +4,22 @@ xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui" xmlns:h="http://xmlns.jcp.org/jsf/html" - xmlns:f="http://xmlns.jcp.org/jsf/core"> + xmlns:f="http://xmlns.jcp.org/jsf/core" + xmlns:o="http://omnifaces.org/ui"> - + From b1c067189466660751b4189a218725e4107f2889 Mon Sep 17 00:00:00 2001 From: buddhika Date: Fri, 12 Jun 2026 04:10:52 +0530 Subject: [PATCH 3/4] fix(notifications): make delete work for discharge notifications, rework filters, add clear/restore - removeUserNotification: PatientEncounter-based (clinical/final discharge) notifications silently returned without removing; now removable. Null-safe department check (avoids NPE on unmatched bill types) and sets retiredAt/retirer. - Filters reworked: previously pruned the already-loaded 20-item list in memory (compounding, not reversible, contradictory checkboxes). Now a fresh JPQL query per filter: Today Only, Seen (All/Unseen/Seen), Status (All/Pending/Completed), Cancelled Bills Only. - Clear button now clears exactly what is listed (filter first, then clear), with explicit confirmation and cleared count; previously it only acted on checked criteria and did nothing when none were checked. - New Show Cleared filter with per-row Restore button so cleared notifications remain retrievable; Clear is guarded while viewing cleared items. - Push refresh and row removal refill via the filter-aware query so active filters are preserved; filters reset when navigating to the page. Refs #21389 #21390 #21391 Co-Authored-By: Claude Fable 5 --- .../common/UserNotificationController.java | 276 +++++++----------- .../Notification/user_notifications.xhtml | 53 +++- 2 files changed, 156 insertions(+), 173 deletions(-) diff --git a/src/main/java/com/divudi/bean/common/UserNotificationController.java b/src/main/java/com/divudi/bean/common/UserNotificationController.java index 904f892fe4..be1afbd076 100644 --- a/src/main/java/com/divudi/bean/common/UserNotificationController.java +++ b/src/main/java/com/divudi/bean/common/UserNotificationController.java @@ -32,7 +32,6 @@ import java.io.Serializable; import java.util.Date; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import javax.ejb.EJB; @@ -87,17 +86,27 @@ public class UserNotificationController implements Serializable { @Inject BhtSummeryController bhtSummeryController; private Date date; + // Notification list filters private boolean todayNotification; - private boolean seenedNotifiaction; - private boolean completedNotification; - private boolean notCompeletedNotifiaction; + private String seenFilter = "ALL"; // ALL | SEEN | UNSEEN + private String completionFilter = "ALL"; // ALL | COMPLETED | PENDING private boolean canceldRequests; + private boolean showCleared; // list previously cleared (retired) notifications for restoring public String navigateToRecivedNotification() { + resetFilters(); fillLoggedUserNotifications(); return "/Notification/user_notifications?faces-redirect=true"; } + public void resetFilters() { + todayNotification = false; + canceldRequests = false; + showCleared = false; + seenFilter = "ALL"; + completionFilter = "ALL"; + } + public int getUnseenCount() { if (sessionController == null || sessionController.getLoggedUser() == null) { return 0; @@ -123,167 +132,103 @@ public String navigateToSentNotification() { return "/Notification/sent_notifications"; } + /** + * Retires (clears) every notification currently listed. The user filters + * first, then clears, so what is removed is exactly what is on screen. + * Cleared notifications remain in the database and can be viewed and + * restored through the "Show Cleared" filter. + */ public void clearNotificationsByCriteria() { - if (seenedNotifiaction) { - if (items == null) { - return; - } - for (UserNotification un : items) { - if (un.isSeen()) { - un.setRetired(true); - un.setRetiredAt(new Date()); - getFacade().edit(un); - } - } - fillLoggedUserNotifications(); + if (showCleared) { + JsfUtil.addErrorMessage("These notifications are already cleared. Use Restore to bring one back."); + return; } - - if (completedNotification) { - if (items == null) { - return; - } - for (UserNotification un : items) { - if (un.getNotification().isCompleted()) { - un.setRetired(true); - un.setRetiredAt(new Date()); - getFacade().edit(un); - } - } - fillLoggedUserNotifications(); + if (items == null || items.isEmpty()) { + JsfUtil.addErrorMessage("No notifications listed to clear"); + return; } - - if (canceldRequests) { - if (items == null) { - return; - } - for (UserNotification un : items) { - if (un.getNotification().getBill() != null && un.getNotification().getBill().isCancelled()) { - un.setRetired(true); - un.setRetiredAt(new Date()); - getFacade().edit(un); - } - } - fillLoggedUserNotifications(); + int clearedCount = 0; + for (UserNotification un : items) { + un.setRetired(true); + un.setRetiredAt(new Date()); + un.setRetirer(sessionController.getLoggedUser()); + getFacade().edit(un); + clearedCount++; } + JsfUtil.addSuccessMessage(clearedCount + " notification(s) cleared"); + filterNotificationsByCriteria(); + } - if (notCompeletedNotifiaction) { - if (items == null) { - return; - } - for (UserNotification un : items) { - if (!un.getNotification().isCompleted()) { - un.setRetired(true); - un.setRetiredAt(new Date()); - getFacade().edit(un); - } - } - fillLoggedUserNotifications(); - + /** + * Brings back a previously cleared (retired) notification so it appears + * in the normal list again. + */ + public void restoreUserNotification(UserNotification un) { + if (un == null || un.getId() == null) { + JsfUtil.addErrorMessage("Nothing to restore !"); + return; } - + un.setRetired(false); + un.setRetiredAt(null); + un.setRetirer(null); + un.setRetireComments(null); + getFacade().edit(un); + JsfUtil.addSuccessMessage("Notification restored"); + filterNotificationsByCriteria(); } + /** + * Reloads the notification list from the database applying the selected + * filter criteria. Always queries fresh so changing or removing criteria + * works as expected. + */ public void filterNotificationsByCriteria() { - if (seenedNotifiaction) { - if (items == null) { - return; - } - Iterator iterator = items.iterator(); - while (iterator.hasNext()) { - UserNotification notification = iterator.next(); - if (notification.getNotification() == null) { - continue; - } - - if (!notification.isSeen()) { - iterator.remove(); - } - } - } - - if (completedNotification) { - if (items == null) { - return; - } - - Iterator iterator = items.iterator(); - while (iterator.hasNext()) { - UserNotification notification = iterator.next(); - if (notification.getNotification() == null) { - continue; - } - - if (!notification.getNotification().isCompleted()) { - iterator.remove(); - } - } - + if (sessionController == null || sessionController.getLoggedUser() == null) { + items = null; + return; } + StringBuilder jpql = new StringBuilder("select un " + + " from UserNotification un " + + " where un.webUser=:wu " + + " and un.retired=:ret "); + Map m = new HashMap<>(); + m.put("wu", sessionController.getLoggedUser()); + m.put("ret", showCleared); if (todayNotification) { - if (items == null) { - return; - } - // Compare dates ignoring time (use java.util.Calendar to truncate) java.util.Calendar todayCal = java.util.Calendar.getInstance(); todayCal.set(java.util.Calendar.HOUR_OF_DAY, 0); todayCal.set(java.util.Calendar.MINUTE, 0); todayCal.set(java.util.Calendar.SECOND, 0); todayCal.set(java.util.Calendar.MILLISECOND, 0); - Date todayMidnight = todayCal.getTime(); - - Iterator iterator = items.iterator(); - while (iterator.hasNext()) { - UserNotification notification = iterator.next(); - if (notification.getNotification() == null) { - continue; - } - - Date createdAt = notification.getNotification().getCreatedAt(); - if (createdAt == null || createdAt.before(todayMidnight)) { - iterator.remove(); - } - } + jpql.append(" and un.notification.createdAt >= :fromDate "); + m.put("fromDate", todayCal.getTime()); } - if (notCompeletedNotifiaction) { - if (items == null) { - return; - } - Iterator iterator = items.iterator(); - while (iterator.hasNext()) { - UserNotification notification = iterator.next(); - if (notification.getNotification() == null) { - continue; - } + if ("SEEN".equals(seenFilter)) { + jpql.append(" and un.seen=:seen "); + m.put("seen", true); + } else if ("UNSEEN".equals(seenFilter)) { + jpql.append(" and un.seen=:seen "); + m.put("seen", false); + } - if (notification.getNotification().isCompleted()) { - iterator.remove(); - } - } + if ("COMPLETED".equals(completionFilter)) { + jpql.append(" and un.notification.completed=:com "); + m.put("com", true); + } else if ("PENDING".equals(completionFilter)) { + jpql.append(" and un.notification.completed=:com "); + m.put("com", false); } if (canceldRequests) { - if (items == null) { - return; - } - Iterator iterator = items.iterator(); - while (iterator.hasNext()) { - UserNotification notification = iterator.next(); - if (notification.getNotification() == null) { - continue; - } - // Skip notifications without bills (e.g., discharge notifications) — they can't be "cancelled" - if (notification.getNotification().getBill() == null) { - iterator.remove(); - continue; - } - - if (!notification.getNotification().getBill().isCancelled()) { - iterator.remove(); - } - } + jpql.append(" and un.notification.bill is not null " + + " and un.notification.bill.cancelled=:can "); + m.put("can", true); } + + jpql.append(" order by un.id desc"); + items = getFacade().findByJpql(jpql.toString(), m, 100); } public void save(UserNotification userNotification) { @@ -336,6 +281,10 @@ public void userNotificationRequestComplete() { } public void removeUserNotification(UserNotification un) { + if (un == null || un.getNotification() == null) { + JsfUtil.addErrorMessage("Nothing to remove !"); + return; + } Department todept = null; Notification n = un.getNotification(); if (n.getBill() != null) { @@ -357,23 +306,24 @@ public void removeUserNotification(UserNotification un) { break; } } else if (n.getPatientRoom() != null) { - todept = n.getPatientRoom().getRoomFacilityCharge().getDepartment(); - } else { + if (n.getPatientRoom().getRoomFacilityCharge() != null) { + todept = n.getPatientRoom().getRoomFacilityCharge().getDepartment(); + } + } else if (n.getPatientEncounter() == null) { + // Not a bill, room or encounter based notification - nothing we know how to remove return; } - - if (!todept.equals(sessionController.getLoggedUser().getDepartment())) { + // PatientEncounter-based (clinical/final discharge) notifications have no + // owning department; the notification already belongs to the logged user. + if (todept != null && !todept.equals(sessionController.getLoggedUser().getDepartment())) { JsfUtil.addErrorMessage("You can't Access On Current Department !"); return; } - // Allow removal of both bill-based and PatientRoom-based notifications - if (un.getNotification().getBill() == null && un.getNotification().getPatientRoom() == null - && un.getNotification().getPatientEncounter() == null) { - return; - } un.setRetired(true); + un.setRetiredAt(new Date()); + un.setRetirer(sessionController.getLoggedUser()); getFacade().edit(un); - fillLoggedUserNotifications(); + filterNotificationsByCriteria(); } private UserNotificationFacade getEjbFacade() { @@ -693,28 +643,28 @@ public void setTodayNotification(boolean todayNotification) { this.todayNotification = todayNotification; } - public boolean isSeenedNotifiaction() { - return seenedNotifiaction; + public String getSeenFilter() { + return seenFilter; } - public void setSeenedNotifiaction(boolean seenedNotifiaction) { - this.seenedNotifiaction = seenedNotifiaction; + public void setSeenFilter(String seenFilter) { + this.seenFilter = seenFilter; } - public boolean isCompletedNotification() { - return completedNotification; + public String getCompletionFilter() { + return completionFilter; } - public void setCompletedNotification(boolean completedNotification) { - this.completedNotification = completedNotification; + public void setCompletionFilter(String completionFilter) { + this.completionFilter = completionFilter; } - public boolean isNotCompeletedNotifiaction() { - return notCompeletedNotifiaction; + public boolean isShowCleared() { + return showCleared; } - public void setNotCompeletedNotifiaction(boolean notCompeletedNotifiaction) { - this.notCompeletedNotifiaction = notCompeletedNotifiaction; + public void setShowCleared(boolean showCleared) { + this.showCleared = showCleared; } public Date getDate() { diff --git a/src/main/webapp/Notification/user_notifications.xhtml b/src/main/webapp/Notification/user_notifications.xhtml index e5f90970f4..feb24f6709 100644 --- a/src/main/webapp/Notification/user_notifications.xhtml +++ b/src/main/webapp/Notification/user_notifications.xhtml @@ -17,7 +17,7 @@ rendered="#{sessionController.loggedUser != null}" /> + action="#{userNotificationController.filterNotificationsByCriteria}" />