Add a registration deduper + fix polymorphic reassignment in the deduper engine - #2467
Conversation
| # A polymorphic `has_many … as:` (allocations, comments) is scoped by its | ||
| # `*_type` column too, so a merge only moves this model's own rows and can't | ||
| # steal another type's rows that happen to share the deleted record's id. | ||
| type_column = assoc.type.to_s if assoc.options[:as] |
There was a problem hiding this comment.
🤖 From Claude: Load-bearing behavior change for all five deduper models, not just registrations: polymorphic has_many … as: children (allocations, comments, bookmarks) were previously reassigned by FK id alone. This adds the *_type scope so a merge can only touch this model's own rows — worth a close look since it changes existing people/org/workshop merges too.
|
🤖 From Claude: What happens to associated records on a registration merge (D = deleted, K = kept) Short version: nothing with money attached is lost — it all reassigns to K. Everything moves to the kept registration
Only exact duplicates collapse (no money involved)
One caveat — cross-registrant scholarship mismatch |
|
🤖 From Claude: Open question — should the cross-registrant scholarship mismatch be fixed automatically? When merging registrations with different registrants, I could re-point any moved scholarship's Trade-off: it silently changes who a scholarship is credited to, which the People merge would otherwise do explicitly. Happy to implement it either way — flagging the decision. |
cdbde5e to
13f1635
Compare
Facilitators end up with the same person registered for one event under two different people records (a duplicate person, a re-typed sign-up). Wire EventRegistration into the existing config-driven deduper so admins can find and merge those without a developer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merging reassigned a polymorphic `has_many … as:` child (allocations, comments, bookmarks) by foreign-key id alone, so it could move — and corrupt — another type's rows that happened to share the deleted record's id. Scope every polymorphic join by its `*_type` column so a merge only ever touches this model's own rows. Surfaced by the new registration deduper: EventRegistration's financial allocations and comments are both polymorphic children that must combine onto the kept registration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13f1635 to
e996fea
Compare
jmilljr24
left a comment
There was a problem hiding this comment.
This worked really well!
Two comments, questions.
- Is there an intended way to get to the deduper from the event management pages? I only found the link from event-reg index which isn't the primary flow for events. I wasn't sure how surfaced you wanted this feature.
- I could be mistaken but it didn't seem like form submissions were carried over from the deleted. Could be intentional, just didn't want that to slip through if the intent was to keep them.
|
Justin noted the deduper was only reachable from the global registrations index, not the event-management flow admins actually work in. Add a "Dedupe registrations" bulk action on the event registrants page that scopes the suggested duplicates to that event and returns the eyebrow to its registrants list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🤖 From Claude: Thanks Justin — both answered: 1. Getting to it from event management — good call, it was only on the global registrations index. Just pushed a "Dedupe registrations" entry to the Bulk actions menu on an event's registrants page. From there it's scoped to that one event (the suggested-duplicate groups only show that event's registrations) and the eyebrow returns to the event's registrants list. The global index link stays for the cross-event view. 2. Form submissions — intentional, and nothing is lost. A |
A registration merge already carries the deleted reg's scholarship and CE registration onto the keeper, but a scholarship kept crediting the deleted registrant — so its recipient no longer matched its allocation (the two registrations have different registrants by definition). Add an after_merge hook to Dedupable and use it to re-credit any scholarship now on the keeper to the keeper's registrant. CE delegates its registrant to the registration, so it follows automatically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@jmilljr24 just an update -- i'm not going to submit the 2nd pr bc i don't think it's needed. Submissions are not tied to Registrations, they are just tied to the Person, so a Submission is only shown on the Registrants table if its parent Person is the Registration's parent Person. If someone dedupes registrations that have two different parent people, then the new single reg will still only show one submission. Deduping those two people will then move the form into that reg's row for visibility. |
Sounds good. Thanks for checking on it. |
🤖 suggested review level: 5 Inspect 🔬 new feature on shared machinery plus a correctness fix to
ModelDeduperthat changes merge behavior for all five deduper modelsLets admins find and merge duplicate registrations — the same person signed up for one event under two different people records (often a re-typed name) — without a developer. Along the way it fixes a latent data-corruption bug in the shared merge engine.
Registration deduper
(registrant_id, event_id)unique index blocks one person registering for the same event twice, so a duplicate registration only happens when a real person is registered under two differentPersonrecords. This surfaces and consolidates those.Dedupablededuper (same as people/orgs/workshops) — no new merge engine, views, policy, or Stimulus.EventRegistrationServices::DuplicateFinder— clusters same-event registrations whose registrants share a name (nickname/legal-variant aware), email/email_2, or FileMaker code; flags mismatched FileMaker codes as a caution.Engine fix (affects all deduper models)
merge_joinreassigned a polymorphichas_many … as:child (allocations, comments, bookmarks) by foreign-key id alone, so it could move — and corrupt — another type's rows that happened to share the deleted record's id.*_typecolumn, so a merge only ever touches this model's own rows. Counts/preview/merge all go through onejoin_referenceshelper.model_deduper_spec): a same-id row of a different type is left untouched.Notes for the reviewer
allocationsandcommentsnow combine onto the kept registration correctly (the reason the fix was needed).after_mergehook re-credits any moved scholarship to the kept registrant so its recipient matches its allocation (they differ only because a duplicate is a second registrant record). Runs inside the merge transaction.ModelDeduper'sunhandled_referencessafety net still blocks (and explains) any child it genuinely can't move.dedupable_spec.rb(surfacing, preview, merge/reassign, authorization) + the engine regression test.registrationarea).🤖 Generated with Claude Code