Skip to content

CE cutover: reroute intake/display, drop ce_* columns (PR 2)#1917

Open
maebeale wants to merge 7 commits into
maebeale/continuing-education-modelfrom
maebeale/continuing-education-cutover
Open

CE cutover: reroute intake/display, drop ce_* columns (PR 2)#1917
maebeale wants to merge 7 commits into
maebeale/continuing-education-modelfrom
maebeale/continuing-education-cutover

Conversation

@maebeale

Copy link
Copy Markdown
Collaborator

🤖 PR, suggested 👤 review level: 🔬 Inspect — reroutes CE intake/display onto the new models and drops the old ce_* columns

Stacked on #1916 (PR 1: the models). Review/merge that first; this branch targets PR 1's branch and will be rebased onto main after it merges.

What is the goal of this PR and why is this important?

PR 1 added ProfessionalLicense + ContinuingEducationRegistration but left the registration form, callout, and read sites on the old flat EventRegistration#ce_* columns. This PR completes the cutover and removes those columns.

How did you approach the change?

  • EventRegistration aggregatorsce_requested?, ce_hours_total, ce_amount_owed_cents, ce_license_provided?, ce_paid_in_full? over the CE registrations; ce_status filter scope.
  • Intake — the CE-interest form answer becomes a plain Yes/No opt-in (hours now come from the event); opting in creates a CE registration against a found-or-created license.
  • Public callout — plain license-number form (full-page POST) so registrants can supply their number.
  • Read sites — magic-ticket card, reminders, onboarding, CSV rerouted; #ce_hours page + card gated on ce_hours_eligible.
  • Registrants indexce_status dropdown (All / Needs license / Requested / Paid / Issued / Unawarded).
  • Drop the ce_credit_requested / ce_hours_requested / ce_license_number columns + dead methods.

Anything else to add?

All CE data is test data, so this is a clean cutover (no backfill).

maebeale and others added 6 commits June 25, 2026 12:06
Aggregate CE data across a registration's continuing_education_registrations
(requested?, hours total, amount owed, license provided, paid in full) so read
sites can move off the flat ce_* columns. Add a ce_status scope (needs_license
+ the CE statuses) wired into search_by_params and the registrants roster, and
preload the CE association in the roster/onboarding/index actions.

Part of PR 2 (intake + read-site cutover).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Intake: the CE-interest answer is a plain Yes/No opt-in (hours come from the
  event); opting in creates a ContinuingEducationRegistration against a license
  found-or-created from the ce_license_number answer.
- Read sites (magic-ticket card, reminder filter, onboarding, CSV) read the new
  EventRegistration aggregators; #ce_hours page + CE card gate on ce_hours_eligible.
- Event form gains ce_hours_eligible + ce_hours inputs.
- Drop the obsolete "Hours not provided" reminder filter and the CE hours
  specify box (FIELD_SPECIFY_OPTION_PLACEHOLDERS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Registration form CE section posts under a `ce` namespace; the controller
  reconciles it into a CE registration + professional license (editable hours).
- CE callout shows the registration's CE status and a plain license-number form
  (full-page POST) that finds-or-creates the license and mirrors a FormAnswer.
- Registrants roster gains a CE-status dropdown.
- Stimulus live-cost calc handles fractional hours.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Migration removes ce_credit_requested/ce_hours_requested/ce_license_number
  (reversible) and the dead constant/validation come off EventRegistration.
- Seeds mark the facilitator/trauma trainings CE-eligible with hours and create
  ContinuingEducationRegistration + ProfessionalLicense records (a mix of known
  and placeholder licenses, one issued) in place of the old flag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the CE specs (model, services, requests, system) to drive the new
ContinuingEducationRegistration/ProfessionalLicense flow instead of the dropped
columns, add request specs for the public callout license POST and the
registrants ce_status filter, and re-gate the #ce_hours page tests on
ce_hours_eligible. EventRegistration CE aggregators now read in-memory built
records so the unsaved sample-ticket preview renders a CE card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CE-interest question no longer reveals a "How many CE hours?" box (hours
come from the event), so guard that its "Yes" is now a plain choice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# removes any CE registrations; otherwise the registrant's (single) CE
# registration is found-or-built against the licence for the typed number, with
# the editable hours applied.
def reconcile_ce_registration

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: The admin CE section posts under a separate ce namespace and is reconciled here rather than via accepts_nested_attributes_for. Editing a license number through the CE registration means touching a separate ProfessionalLicense (find-or-create), which nested attributes handle fragilely; this keeps it explicit and covers the common single-license case (multi-license is managed elsewhere).

# Read CE registrations from the in-memory collection rather than the DB when
# it's already loaded or this registration isn't persisted (e.g. the unsaved
# sample-ticket preview builds CE registrations without saving).
def ce_registrations_in_memory?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: The aggregators read the in-memory collection when the registration is a new_record? so the unsaved sample-ticket preview (which builds a CE registration without saving) still renders a populated CE card — otherwise exists? would query the DB with a nil id and report no CE.

}
# Filter by CE state. "needs_license" is derived (a CE registration sits on a
# placeholder license); the rest match a ContinuingEducationRegistration#status.
scope :ce_status, ->(value) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: needs_license is derived (a CE registration on a placeholder license, number IS NULL); the other options match a ContinuingEducationRegistration#status. EXISTS subqueries so a registration with several CE registrations matches if any one qualifies.

# registrant's (first) CE registration via a found-or-created license, mirrors
# it onto the registration's form answer, then returns to the callout. Plain
# full-page POST — no Turbo.
def update_ce_license

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Public license entry, authorized by the registration slug (the same bearer-token model as the other callouts). Plain full-page POST (no Turbo) — it sets the number on the first CE registration via find-or-create and mirrors it onto the form answer.

@maebeale maebeale marked this pull request as ready for review June 25, 2026 20:18
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