Skip to content

ROCK-8575: Revert Medication Manager to Person-only reads#230

Open
jwakefield-secc wants to merge 3 commits into
masterfrom
ROCK-8575/medication-manager-person-only-reads
Open

ROCK-8575: Revert Medication Manager to Person-only reads#230
jwakefield-secc wants to merge 3 commits into
masterfrom
ROCK-8575/medication-manager-person-only-reads

Conversation

@jwakefield-secc
Copy link
Copy Markdown
Contributor

Summary

  • Roll back the GroupMember snapshot direction in the Medication Dispense grid. Nothing populates GroupMember.Medications in prod, which was causing ~6,300 active campers to render blank in the grid. The dispense grid now reads exclusively from Person.Medications, matching every other medication consumer (camp report stored proc, kiosk labels, MyEvents family info block, leader notifications).
  • Manage Medications modal opens against the Person master. Edit and Add write to the master, Toggle Active flips MedicationActive on the master. Delete is removed entirely — staff use the Active toggle to deactivate instead.
  • Inactive-but-distributed-today rows stay visible (grayed out, "Inactive" badge, Dispense disabled) so the same-day audit trail isn't lost when a med is deactivated after distribution. Filter is per-(person, medicine, schedule) so a Breakfast distribution doesn't leak Lunch and Dinner rows into the grid for the same med.
  • All MedicationActive filtering elsewhere (stored proc, kiosk labels, family info block, leader notifications) is unchanged and continues to work correctly against Person.

What's removed (dead code cleanup)

  • qrySnapshot LINQ branch, groupMemberMatrixAttributeIds, groupMemberMatrixValuesWithItems
  • SourcePerson / SourceSnapshot constants
  • IsSnapshotMatrix method
  • btnDeleteMed_Click handler
  • hfManageMedsSource hidden field, ltManageMedsSourceBadge literal, <Rock:DeleteField> column
  • Defense guards in Toggle / Edit / Add handlers
  • source parameter on the modal configure helper (renamed to ConfigureManageMedsModal)

Performance note

The inactive-but-distributed check uses a HashSet<string> keyed by (PersonId | MatrixItemId | ScheduleGuid) built once before the per-camper loop. O(1) lookup; safe for heavy-load Camp Freedom days (~40 meds × 200 campers × ~1000 daily distributions).

Behavioral implications worth flagging to camp ops

  • Edits to medications via the Medication Manager affect the parent's master record globally. Adding a "Tylenol — as needed" entry for one camp will appear on the camper's master and at every other event they're registered for. Use the Active toggle to deactivate camp-specific additions rather than relying on per-event isolation.
  • GroupMember.Medications snapshot infrastructure is now formally vestigial in prod. Cleanup of the auto-init Medications attribute on camp GroupTypes is an optional follow-up (separate ticket).

Files changed

.../Reporting/NextGen/MedicationDispense.ascx       (markup)
.../Reporting/NextGen/MedicationDispense.ascx.cs    (code)

Net -14 lines across the two files.

Test plan

  • Solution builds cleanly without warnings
  • Medication Dispense grid renders meds for a camper with populated Person.Medications (no blank rows)
  • Add a medication via the Manage Medications modal → confirm it appears on the camper's Person.Medications
  • Edit an existing medication → confirm changes persist on the Person master
  • Toggle Active/Inactive → confirm MedicationActive flips on the Person matrix item
  • Confirm no Delete button is visible anywhere in the modal
  • Distribute a med, then mark inactive on the master → row stays visible, grayed out, "Inactive" badge, Dispense disabled
  • Multi-schedule med: distribute Breakfast, mark inactive → only Breakfast row stays; Lunch and Dinner are hidden
  • "Hide Distributed" toggle still hides all distributed rows including the new inactive-but-dispensed ones
  • Camp Excel report (stored proc) still works unchanged
  • Kiosk medication labels still print correctly at check-in
  • MyEvents family info block still displays correctly

🤖 Generated with Claude Code

jwakefield-secc and others added 3 commits May 18, 2026 14:29
Adds a WorkflowTrigger on AttributeValue PostSave (scoped by qualifier to the
MedicationActive attribute id) that fires a transient workflow whose only
action is the new PropagateMedicationActiveState ActionComponent. The
component reads the saved AV's parent matrix item, confirms it belongs to a
Person master matrix, and writes the new active value to every snapshot
matrix item the same person has that matches by composite key
(Medication + Instructions + Schedule).

Covers all surfaces that can change MedicationActive on the master
(dispense modal, /myevents workflow 570, Person profile attribute matrix
editor, REST, direct SQL) without modifying Rock core or workflow 570.
Snapshot writes do not re-trigger because they fail the IsMasterMatrix
check -- automatic loop prevention without a recursion guard.

Kill switch:
  UPDATE WorkflowTrigger SET IsActive = 0
  WHERE [Guid] = 'CC5A8B9C-1A2B-4C3D-9E5F-6A7B8C9D0E1F'

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No behavior change. Drops redundant XML doc paragraphs and per-step
"Args:" comments in the migration. Keeps the kill-switch instruction
and non-obvious "why" notes (EntityType pinning, EntityTypeQualifier
scoping, snapshot-write loop prevention, Schedule order-sensitivity).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roll back the GroupMember snapshot direction in the Medication Dispense
grid. Nothing populates GroupMember.Medications in prod, which was
causing ~6,300 campers to render blank in the grid. Every other
medication consumer (camp report stored proc, kiosk labels, MyEvents
family info block, leader notifications) already reads Person.Medications.

- Strip the qrySnapshot LINQ branch and groupMemberMatrixAttributeIds.
  Grid reads exclusively from Person.Medications.
- Manage Medications modal opens against the Person master; Edit and Add
  write to the master, Toggle Active flips MedicationActive on the master.
- Remove Delete entirely (column, handler, defense guards). Staff use the
  Active toggle to deactivate instead.
- Drop dead source-aware plumbing: SourcePerson/SourceSnapshot constants,
  IsSnapshotMatrix method, hfManageMedsSource hidden field, badge literal,
  source parameter on the configure helper.

UX add: inactive-but-distributed-today rows stay visible, grayed out
with an "Inactive" badge and Dispense disabled, so same-day audit trail
is preserved. Filter is per-(person, medicine, schedule) so distributing
breakfast and then deactivating the med doesn't leak lunch and dinner
rows into the grid.

Perf: index today's distribution notes into a HashSet keyed by
(PersonId | MatrixItemId | ScheduleGuid) for O(1) lookup during the
inactive filter, avoiding a per-medicine linear scan on heavy-load days.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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