Skip to content

Fix: prevent empty Rx save from archiving ReRx'd med when not staged#145

Open
LiamStanziani wants to merge 3 commits into
MagentaHealth:release/2026-03-17from
openo-beta:bug/med-deleted-if-check-re-rx-without-saving-base-release-version
Open

Fix: prevent empty Rx save from archiving ReRx'd med when not staged#145
LiamStanziani wants to merge 3 commits into
MagentaHealth:release/2026-03-17from
openo-beta:bug/med-deleted-if-check-re-rx-without-saving-base-release-version

Conversation

@LiamStanziani
Copy link
Copy Markdown
Collaborator

@LiamStanziani LiamStanziani commented Jun 5, 2026

Summary

In the Medications (Rx) module, checking the ReRx box next to an existing
medication and then clicking Save or Save And Printwithout clicking
Stage Medication — caused the medication to drop off the active list, and
quietly persisted an empty prescription. This fix makes an unstaged save do
nothing harmful: Save And Print warns "No medications have been added" and
Save is a no-op, with no medication archived and no empty prescription
written. A server-side guard additionally ensures a ReRx'd medication is only
archived when a replacement was actually staged and saved.

Original PR openo-beta#2461

Problem

Checking the ReRx checkbox immediately parks the drug's id in the session's
reRxDrugIdList (via an AJAX call) and shows a confirmation box prompting the
user to click Stage Medication. Staging is what actually builds the new
prescription. If the user skipped staging and clicked Save:

  • The save submitted anyway. RxWriteScript2Action.saveDrug() ran its archival
    loop over every id in reRxDrugIdList, archiving the original medication
    (reason REPRESCRIBED) even though no replacement had been created. Archived
    drugs don't render in the active list, so the medication appeared deleted.
  • saveScript() always ran first, so an empty prescription row was persisted
    on every such save, and Save And Print then opened a print preview that
    errored on the empty script.

Neither outcome matched the expected behavior: an unstaged save should warn
(Save And Print) or do nothing (Save), and must never remove the medication.

Solution

Two layers, both required:

  1. Client gate — src/main/webapp/oscarRx/SearchDrug3.jsp

    • Added countStagedMedications(), which counts the staged drugs actually
      present in the prescription form (each staged drug renders a
      drugName_<rand> input; a ReRx box that was only checked does not).
    • updateSaveAllDrugsPrintCheckContinue() and
      updateSaveAllDrugsCheckContinue() now short-circuit when nothing is
      staged: Save And Print alerts "No medications have been added." and Save
      returns without submitting. This prevents the empty prescription and the
      erroring print preview, and never reaches the archival code in the empty
      case.
    • The ReRx selection is intentionally left intact, so after dismissing the
      warning (or the Save no-op) the box stays checked and the user can simply
      click Stage Medication to continue. This is safe because the checkbox
      is rendered from the server-side reRxDrugIdList on every load
      (ListDrugs.jsp), so the on-screen state always matches the session, and
      the server guard below ensures a parked id is never archived unless it was
      actually staged and saved.
  2. Server guard — RxWriteScript2Action.saveDrug()

    • While persisting the staged drugs, collect the original ids that were
      actually re-prescribed (each staged re-prescription carries the source
      drug's id in drugReferenceId, set by
      RxPrescriptionData.newPrescription(.., rePrescribe)).
    • The archival loop now archives an id from reRxDrugIdList only if it is in
      that set, with a null guard on the lookup. This covers the mixed case
      (stage one drug while another ReRx box is left checked but unstaged) and
      any stale parked id, so a medication is only ever archived when a
      replacement genuinely exists.

Verification

Reproduced and verified against the running app on a test patient:

  • ReRx checked, Save (no stage): no-op, ReRx box stays checked, no archival,
    no empty script — medication remains.
  • ReRx checked, Save And Print (no stage): "No medications have been added."
    warning, ReRx box stays checked, no archival, no empty script, no broken
    preview — medication remains.
  • ReRx → (warned/no-op while box stays checked) → Stage Medication → Save:
    new drug created and the original archived as REPRESCRIBED (normal flow
    unchanged).
  • Mixed case — stage one medication while another ReRx box is left checked but
    unstaged: only the staged medication is archived; the unstaged one is left
    untouched (server guard).

Summary by Sourcery

Prevent unstaged ReRx saves from archiving medications or creating empty prescriptions by adding client and server guards, and harden ReRx handling against malformed IDs.

Bug Fixes:

  • Ensure Save and Save And Print do not persist an empty prescription or archive medications when no drugs are staged for ReRx.
  • Prevent the ReRx archival loop from processing non-numeric or unstaged drug IDs that could silently remove active medications or break saves.

Enhancements:

  • Add a client-side check to detect when no medications are staged and block save actions with a user-facing warning.
  • Introduce server-side validation and filtering of ReRx drug IDs to align archival behavior with actually re-prescribed medications and tolerate malformed session data.

@LiamStanziani LiamStanziani self-assigned this Jun 5, 2026
@LiamStanziani LiamStanziani marked this pull request as ready for review June 5, 2026 23:49
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 5, 2026

Reviewer's Guide

Guards the Rx ReRx flow so that Save/Save And Print only operate when there are actually staged medications, and tightens server-side handling so only valid, truly re-prescribed source medications are archived and empty scripts are never created.

Sequence diagram for guarded ReRx Save and archival behavior

sequenceDiagram
    actor User
    participant Browser as Browser_JS
    participant RxAction as RxWriteScript2Action
    participant Bean as RxBean
    participant DrugDao

    User->>Browser: click SaveAndPrint
    Browser->>Browser: countStagedMedications()
    alt no staged medications
        Browser-->>User: alert("No medications have been added.")
        Browser-->>RxAction: (no request sent)
    else staged medications present
        Browser->>RxAction: updateSaveAllDrugs()
        RxAction->>Bean: getStashSize()
        alt stashSize == 0
            RxAction-->>Browser: savedScriptId = null
        else stashSize > 0
            RxAction->>RxAction: saveDrug(request)
            loop each stash item
                RxAction->>Bean: getStashItem(i)
                RxAction->>Bean: addRandomIdDrugIdPair()
                RxAction->>RxAction: collect drugReferenceId in represcribedOriginalIds
            end
            RxAction->>RxAction: iterate reRxDrugIdList
            loop each reRxDrugId
                RxAction->>RxAction: Integer.parseInt(reRxDrugId)
                alt id in represcribedOriginalIds
                    RxAction->>DrugDao: find(originalDrugId)
                    alt drug found
                        RxAction->>DrugDao: setArchived(true), setArchivedReason(REPRESCRIBED)
                    else drug null
                        RxAction-->>RxAction: continue
                    end
                else not represcribed or malformed
                    RxAction-->>RxAction: skip archival
                end
            end
            RxAction-->>Browser: savedScriptId
        end
    end
Loading

File-Level Changes

Change Details Files
Block Save/Save And Print in the Rx UI when no medications are staged, while keeping ReRx selections intact.
  • Add a countStagedMedications() helper that inspects the prescription form for drugName_ inputs to determine the number of staged medications.
  • Short-circuit Save And Print to show a 'No medications have been added.' alert if nothing is staged.
  • Short-circuit Save to be a no-op when nothing is staged, avoiding any form submission or archival path.
src/main/webapp/oscarRx/SearchDrug3.jsp
Prevent empty prescriptions and unintended archival on the server by only saving when there is staged data and only archiving source meds that were truly re-prescribed.
  • Change updateSaveAllDrugs() to skip saveDrug() when the stash is empty so a crafted request cannot create an empty script or trigger archival without staged meds.
  • Track the set of original drug IDs that were actually re-prescribed during saveDrug() using drugReferenceId from each staged item.
  • Modify the archival loop to parse and validate reRxDrugId entries, skip malformed IDs, and archive only drugs whose IDs are in the represcribed set and that still resolve from the DAO.
src/main/java/ca/openosp/openo/prescript/pageUtil/RxWriteScript2Action.java
Harden ReRx session handling so only valid numeric IDs are stored for later archival.
  • Validate reRxDrugId in updateReRxDrug() by parsing it as an integer before adding it to the session list.
  • Log and ignore non-numeric reRxDrugId values instead of letting them pollute the session and potentially break saveDrug().
src/main/java/ca/openosp/openo/prescript/pageUtil/RxWriteScript2Action.java

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The client/server coordination around detecting staged meds is brittle since both sides hard-code the drugName_ marker; consider centralizing this marker (e.g., via a shared constant, data attribute, or server-provided config) so future changes don't silently desync the gating logic.
  • When ignoring or skipping non-numeric reRxDrugId values, the logger.warn calls could include the offending value (with parameterized logging) to make it easier to diagnose polluted sessions or crafted requests in production.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The client/server coordination around detecting staged meds is brittle since both sides hard-code the `drugName_` marker; consider centralizing this marker (e.g., via a shared constant, data attribute, or server-provided config) so future changes don't silently desync the gating logic.
- When ignoring or skipping non-numeric `reRxDrugId` values, the `logger.warn` calls could include the offending value (with parameterized logging) to make it easier to diagnose polluted sessions or crafted requests in production.

## Individual Comments

### Comment 1
<location path="src/main/java/ca/openosp/openo/prescript/pageUtil/RxWriteScript2Action.java" line_range="1440-1449" />
<code_context>
         while (i.hasNext()) {

             String item = i.next();
+            int originalDrugId;
+            try {
+                originalDrugId = Integer.parseInt(item);
+            } catch (NumberFormatException e) {
+                // Defensive: skip any malformed entry from a polluted session
+                // rather than failing the whole save (#2453).
+                logger.warn("Skipping non-numeric reRxDrugId");
+                continue;
+            }
</code_context>
<issue_to_address>
**suggestion:** Improve diagnostic value of the non-numeric reRxDrugId warning.

This warning doesn’t include the offending `item` value, which will make production debugging harder. Please log the actual `item` (possibly truncated or sanitized) so operators can see which session value caused the parse failure.

```suggestion
            String item = i.next();
+            int originalDrugId;
+            try {
+                originalDrugId = Integer.parseInt(item);
+            } catch (NumberFormatException e) {
+                // Defensive: skip any malformed entry from a polluted session
+                // rather than failing the whole save (#2453).
+                String loggedItem = item;
+                if (loggedItem != null && loggedItem.length() > 128) {
+                    loggedItem = loggedItem.substring(0, 125) + "...";
+                }
+                logger.warn("Skipping non-numeric reRxDrugId value: '{}'", loggedItem);
+                continue;
+            }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@LiamStanziani LiamStanziani requested a review from lacarmen June 5, 2026 23:52
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.

1 participant