Skip to content

#13751 - Diagnosis date determined based on surveillance report#13803

Merged
raulbob merged 5 commits intodevelopmentfrom
bugfix-13751-diagnosis_date
Jan 27, 2026
Merged

#13751 - Diagnosis date determined based on surveillance report#13803
raulbob merged 5 commits intodevelopmentfrom
bugfix-13751-diagnosis_date

Conversation

@raulbob
Copy link
Copy Markdown
Contributor

@raulbob raulbob commented Jan 22, 2026

Fixes #13751

Summary by CodeRabbit

  • Bug Fixes

    • More reliable population of diagnosis dates on surveillance reports during message processing.
  • UI

    • Notifier panel now shows explicit diagnosis and notification dates, uses notifier change date as fallback, and handles missing dates gracefully.
  • Database

    • Schema upgrade (v606) to backfill historical diagnosis dates into surveillance reports from related records.

✏️ Tip: You can customize this high-level summary in your review settings.

- Also moved the logic from the UI view to the controller
@raulbob raulbob linked an issue Jan 22, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

API now sets surveillance report diagnosis dates from external messages during processing; a DB migration backfills existing reports from external messages; UI notifier was refactored to accept explicit diagnosis and notification dates instead of an oldest report DTO.

Changes

Cohort / File(s) Summary
API Processing Layer
sormas-api/.../externalmessage/processing/AbstractMessageProcessingFlowBase.java
Added protected helper updateSurveillanceReportAdditionalData(SurveillanceReportDto, ExternalMessageDto, CaseDataDto) and invoked it after creating and when finalizing surveillance reports to set dateOfDiagnosis from the external message.
Database Schema / Migration
sormas-backend/src/main/resources/sql/sormas_schema.sql
Added a migration that backfills surveillancereports.dateofdiagnosis from externalmessage.diagnosticdate where null, and inserts a schema version record (version 606).
UI Controller
sormas-ui/.../caze/notifier/CaseNotifierSideViewController.java
Added null-checks, computed dateOfDiagnosis via new getOldestDiagnosisDateFromReports(...) overloads, derived notificationDate, and updated construction call to the new CaseNotifierSideViewContent signature.
UI Component / View
sormas-ui/.../caze/notifier/CaseNotifierSideViewContent.java
Replaced SurveillanceReportDto oldestReport with Date dateOfDiagnosis, Date notificationDate fields and constructor; adjusted rendering and fallback logic to use explicit dates.

Sequence Diagram(s)

sequenceDiagram
  participant ExternalMsg as ExternalMessage Processor
  participant API as MessageProcessing API
  participant DB as Database
  participant UI as Notifier UI

  ExternalMsg->>API: ingest external message (includes diagnosticDate)
  API->>API: create/finalize SurveillanceReportDto
  API->>API: updateSurveillanceReportAdditionalData(survReport, externalMsg, case)
  API->>DB: persist SurveillanceReport (dateOfDiagnosis set)
  DB-->>API: persist confirmation
  UI->>DB: query case / surveillance reports
  DB-->>UI: return notificationDate, dateOfDiagnosis
  UI-->>User: render notifier with explicit dates
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • obinna-h-n
  • KarnaiahPesula

Poem

🐰 I hopped through logs and patched the trail,
Dates found their way from message to mail,
A migration nibble, a UI small tweak,
Now diagnosis shows clear, not meek,
Hooray — I bounce, my whiskers sleek!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the primary change: implementing diagnosis date from surveillance reports for Cases based on Doctor Declarations.
Description check ✅ Passed The description includes the required issue reference (Fixes #13751) as specified in the template, adequately referencing the linked issue.
Linked Issues check ✅ Passed The PR implements diagnosis date handling across API, database, and UI layers, fully addressing issue #13751's requirement to implement diagnosis date from Doctor Declarations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing diagnosis date handling as required by issue #13751; no extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java (1)

313-356: Bug: caze.setTherapy(therapy) is never reached.

The method modifies the therapy object but each branch has an early return statement, making line 355 (caze.setTherapy(therapy)) unreachable. The therapy changes are lost and never assigned back to the case.

Proposed fix
     private void updateTherapyBasedOnSelection(CaseDataDto caze, TreatmentOption selectedOption) {

         if (selectedOption == null) {
             return;
         }

         TherapyDto therapy = caze.getTherapy();

         if (therapy == null) {
             therapy = TherapyDto.build();
+            caze.setTherapy(therapy);
         }

         if (selectedOption.equals(TreatmentOption.YES)) {
             therapy.setTreatmentStarted(YesNoUnknown.YES);
             therapy.setTreatmentNotApplicable(false);
             if (therapy.getTreatmentStartDate() == null) {
                 therapy.setTreatmentStartDate(new java.util.Date());
             }
-            return;
+        } else if (selectedOption.equals(TreatmentOption.NO)) {
-        }
-
-        if (selectedOption.equals(TreatmentOption.NO)) {
             therapy.setTreatmentStarted(YesNoUnknown.NO);
             therapy.setTreatmentNotApplicable(false);
             therapy.setTreatmentStartDate(null);
-            return;
+        } else if (selectedOption.equals(TreatmentOption.NOT_APPLICABLE)) {
-        }
-
-        if (selectedOption.equals(TreatmentOption.NOT_APPLICABLE)) {
             therapy.setTreatmentNotApplicable(true);
             therapy.setTreatmentStarted(null);
             therapy.setTreatmentStartDate(null);
-            return;
+        } else if (selectedOption.equals(TreatmentOption.UNKNOWN)) {
-        }
-
-        if (selectedOption.equals(TreatmentOption.UNKNOWN)) {
             therapy.setTreatmentStarted(YesNoUnknown.UNKNOWN);
             therapy.setTreatmentNotApplicable(false);
             therapy.setTreatmentStartDate(null);
-            return;
         }
-
-        caze.setTherapy(therapy);
     }
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewContent.java (2)

61-77: Outdated Javadoc.

The Javadoc at lines 68-70 still references oldestReport parameter, but the constructor signature was changed to accept dateOfDiagnosis and notificationDate.

Proposed fix
     /**
      * Creates a new case notifier side view content.
      * 
      * `@param` caze
      *            the case data
      * `@param` notifier
      *            the notifier details
-     * `@param` oldestReport
-     *            the oldest surveillance report for the case
+     * `@param` dateOfDiagnosis
+     *            the date of diagnosis (may be null)
+     * `@param` notificationDate
+     *            the notification date (may be null)
      */
     public CaseNotifierSideViewContent(CaseDataDto caze, NotifierDto notifier, Date dateOfDiagnosis, Date notificationDate) {

149-161: Potential NPE on notifier.getAgentLastName().toUpperCase().

The condition at lines 149-150 uses ||, so the block is entered if either agentFirstName or agentLastName is non-null/non-blank. However, line 158 unconditionally calls toUpperCase() on getAgentLastName(), which will throw NPE if only agentFirstName is set.

Proposed fix
         if ((notifier.getAgentFirstName() != null && !notifier.getAgentFirstName().isBlank())
             || (notifier.getAgentLastName() != null && !notifier.getAgentLastName().isBlank())) {
             // Spacer before Reporting Agent label
             Label spacerAgent = new Label();
             spacerAgent.setHeight("0.1rem");
             addComponent(spacerAgent);
             Label reportingAgent = new Label(I18nProperties.getCaption(Captions.Notification_reportingAgent));
             CssStyles.style(reportingAgent, CssStyles.LABEL_BOLD, CssStyles.LABEL_BOTTOM_LINE);
             addComponent(reportingAgent);
-            Label agentNameLabel = new Label(notifier.getAgentFirstName() + " " + notifier.getAgentLastName().toUpperCase(Locale.ROOT));
+            String firstName = notifier.getAgentFirstName() != null ? notifier.getAgentFirstName() : "";
+            String lastName = notifier.getAgentLastName() != null ? notifier.getAgentLastName().toUpperCase(Locale.ROOT) : "";
+            Label agentNameLabel = new Label((firstName + " " + lastName).trim());
             CssStyles.style(agentNameLabel, CssStyles.LABEL_RELEVANT);
             addComponent(agentNameLabel);
         }
🤖 Fix all issues with AI agents
In `@sormas-backend/src/main/resources/sql/sormas_schema.sql`:
- Around line 15068-15073: The UPDATE currently pulls an arbitrary
em.diagnosticdate when multiple externalmessage rows match a surveillancereport
(surveillancereport_id), so change the update to deterministically pick a single
date (e.g., oldest or most recent) by joining against an aggregated subquery
that selects MIN(em.diagnosticdate) or MAX(em.diagnosticdate) grouped by
em.surveillancereport_id and only include rows where sr.dateofdiagnosis IS NULL;
reference the tables/columns surveillancereports sr, externalmessage em,
em.diagnosticdate and em.surveillancereport_id and replace the direct FROM em
with a join to that aggregated result so the same selection logic is applied
consistently for each sr.id.

In
`@sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java`:
- Around line 112-124: The inline comment in getLatestReport incorrectly says
"Filter to get the oldest report" while the code uses
.max(Comparator.comparing(SurveillanceReportDto::getReportDate)) to return the
latest report; update that comment (inside
CaseNotifierSideViewController.getLatestReport) to accurately state that it
selects the latest report (by report date) or remove the misleading comment
entirely so the behavior and comment match.
- Around line 57-70: The method in CaseNotifierSideViewController calls
getOldestReport(caze) twice and leaves an incomplete comment; fix by calling
getOldestReport(caze) once, store its result in a local variable (e.g.,
oldestReport) and use oldestReport.getReportDate() for notificationDate to avoid
duplicate DB queries, remove the stray comment "// find diagno", and then
construct and return the CaseNotifierSideViewContent(caze, notifier,
dateOfDiagnosis, notificationDate) using the cached oldestReport value; keep
references to getOldestDiagnosisDateFromReports and
FacadeProvider.getNotifierFacade() unchanged.
🧹 Nitpick comments (2)
sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractMessageProcessingFlowBase.java (1)

694-706: Unused caze parameter.

The caze parameter is declared but never used in the method body. If it's intended for future use or subclass overrides, consider documenting this in the Javadoc. Otherwise, remove it to avoid confusion.

Option 1: Remove unused parameter
-    protected void updateSurveillanceReportAdditionalData(SurveillanceReportDto surveillanceReport, ExternalMessageDto externalMessage, CaseDataDto caze) {
+    protected void updateSurveillanceReportAdditionalData(SurveillanceReportDto surveillanceReport, ExternalMessageDto externalMessage) {
         surveillanceReport.setDateOfDiagnosis(externalMessage.getDiagnosticDate());
     }

Note: This would also require updating the call site at line 665.

Option 2: Document the parameter for extensibility
     /** 
      * Updates the additional data of the surveillance report.
      * 
      * `@param` surveillanceReport
      *            the surveillance report
      * `@param` externalMessage
      *            the external message
      * `@param` caze
-     *            the case
+     *            the case (available for subclass overrides that may need case-specific logic)
      */
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java (1)

367-369: Empty TODO method - verify if implementation is needed.

The handleDiagnosticDateUpdate method is called at line 279 but does nothing. If the diagnosis date handling is intentionally delegated elsewhere (e.g., via surveillance reports), consider removing this method and its call site, or document why it's a no-op. Otherwise, this may indicate incomplete implementation of the PR objective.

Do you want me to help implement this method or open an issue to track this task?

Comment thread sormas-backend/src/main/resources/sql/sormas_schema.sql
@sormas-vitagroup
Copy link
Copy Markdown
Contributor

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java`:
- Around line 57-63: The code dereferences caze.getNotifier() in
CaseNotifierSideViewController when calling
FacadeProvider.getNotifierFacade().getByUuidAndTime(...), which can NPE because
CaseDataDto.notifier may be null; add a null guard matching the pattern used in
editNotifier(): check if caze.getNotifier() == null and handle it (e.g., throw
new IllegalArgumentException("Caze notifier is null") or return early) before
calling getUuid() or getVersionDate(), so the getByUuidAndTime call only
executes when notifier is non-null.
♻️ Duplicate comments (2)
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java (2)

68-68: Remove the stray // find diagno comment.

Proposed fix
-        // find diagno

122-124: Fix the misleading comment in getLatestReport.
The method returns the latest report (using max), but the comment says “oldest.”

Proposed fix
-        // Filter to get the oldest report
+        // Filter to get the latest report

@sormas-vitagroup
Copy link
Copy Markdown
Contributor

@sormas-vitagroup
Copy link
Copy Markdown
Contributor

1 similar comment
@sormas-vitagroup
Copy link
Copy Markdown
Contributor

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewContent.java (1)

58-68: Javadoc parameter mismatch with actual constructor signature.

The Javadoc at line 65-66 still documents @param oldestReport but the constructor now accepts Date dateOfDiagnosis and Date notificationDate parameters.

Proposed fix
     /**
      * Creates a new case notifier side view content.
      * 
      * `@param` caze
      *            the case data
      * `@param` notifier
      *            the notifier details
-     * `@param` oldestReport
-     *            the oldest surveillance report for the case
+     * `@param` dateOfDiagnosis
+     *            the date of diagnosis (may be null)
+     * `@param` notificationDate
+     *            the notification date (may be null)
      */
     public CaseNotifierSideViewContent(CaseDataDto caze, NotifierDto notifier, Date dateOfDiagnosis, Date notificationDate) {
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java (1)

104-107: Stale comment references non-existent method.

The comment mentions getDate() but the code uses getReportDate(). Consider updating or removing the comment.

Proposed fix
-        // Filter to get the oldest report
-        return reports.stream()
-            .min(Comparator.comparing(SurveillanceReportDto::getReportDate)) // Assuming getDate() returns the report date
+        return reports.stream()
+            .min(Comparator.comparing(SurveillanceReportDto::getReportDate))
             .orElse(null);
🤖 Fix all issues with AI agents
In
`@sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewContent.java`:
- Around line 121-125: The current fallback in CaseNotifierSideViewContent
(notificationDateField.setValue(...)) assumes notifier.getChangeDate() is
non-null and can throw an NPE; adjust the logic in the block that sets
notificationDateField so that if notificationDate is null you first check
notifier.getChangeDate() for null and only call
toInstant()/atZone()/toLocalDate() when changeDate is non-null, otherwise call
notificationDateField.setValue(null) (or another sensible default) to avoid the
NullPointerException.
🧹 Nitpick comments (1)
sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewController.java (1)

332-334: TODO placeholder for diagnostic date handling.

This method is documented as a "placeholder for future diagnostic date handling" but remains empty. Given the PR objective is to implement diagnosis date handling, verify if this is intentionally deferred or should be addressed now.

Would you like me to help implement the diagnostic date update logic, or should this be tracked as a separate issue?

Comment on lines +121 to +125
if (notificationDate != null) {
notificationDateField.setValue(notificationDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
} else {
notificationDateField.setValue(notifier.getChangeDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential NPE if notifier.getChangeDate() is null.

When notificationDate is null, the code falls back to notifier.getChangeDate().toInstant(). If changeDate is also null, this will throw a NullPointerException.

Proposed fix
         if (notificationDate != null) {
             notificationDateField.setValue(notificationDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
-        } else {
+        } else if (notifier.getChangeDate() != null) {
             notificationDateField.setValue(notifier.getChangeDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
         }
🤖 Prompt for AI Agents
In
`@sormas-ui/src/main/java/de/symeda/sormas/ui/caze/notifier/CaseNotifierSideViewContent.java`
around lines 121 - 125, The current fallback in CaseNotifierSideViewContent
(notificationDateField.setValue(...)) assumes notifier.getChangeDate() is
non-null and can throw an NPE; adjust the logic in the block that sets
notificationDateField so that if notificationDate is null you first check
notifier.getChangeDate() for null and only call
toInstant()/atZone()/toLocalDate() when changeDate is non-null, otherwise call
notificationDateField.setValue(null) (or another sensible default) to avoid the
NullPointerException.

@sormas-vitagroup
Copy link
Copy Markdown
Contributor

@raulbob raulbob merged commit cef2068 into development Jan 27, 2026
8 of 14 checks passed
@raulbob raulbob deleted the bugfix-13751-diagnosis_date branch January 27, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diagnosis date is not implemented - Case - Doctor Declaration

3 participants