CE cutover: reroute intake/display, drop ce_* columns (PR 2)#1917
CE cutover: reroute intake/display, drop ce_* columns (PR 2)#1917maebeale wants to merge 7 commits into
Conversation
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 |
There was a problem hiding this comment.
🤖 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? |
There was a problem hiding this comment.
🤖 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) { |
There was a problem hiding this comment.
🤖 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 |
There was a problem hiding this comment.
🤖 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.
…inuing-education-cutover
🤖 PR, suggested 👤 review level: 🔬 Inspect — reroutes CE intake/display onto the new models and drops the old
ce_*columnsWhat is the goal of this PR and why is this important?
PR 1 added
ProfessionalLicense+ContinuingEducationRegistrationbut left the registration form, callout, and read sites on the old flatEventRegistration#ce_*columns. This PR completes the cutover and removes those columns.How did you approach the change?
EventRegistrationaggregators —ce_requested?,ce_hours_total,ce_amount_owed_cents,ce_license_provided?,ce_paid_in_full?over the CE registrations;ce_statusfilter scope.#ce_hourspage + card gated once_hours_eligible.ce_statusdropdown (All / Needs license / Requested / Paid / Issued / Unawarded).ce_credit_requested/ce_hours_requested/ce_license_numbercolumns + dead methods.Anything else to add?
All CE data is test data, so this is a clean cutover (no backfill).