Skip to content

Commit 92e4d25

Browse files
nbudinclaude
andcommitted
Handle SignupRankedChoice priority conflicts when merging profiles
When two user_con_profiles in the same convention are merged, transfer ranked choices by appending them after the winning profile's existing choices per state, rather than using update_all which would violate the unique constraint on (user_con_profile_id, state, priority). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 77b42c2 commit 92e4d25

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

app/services/merge_users_service.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class Result < CivilService::Result
1212
{ model: Order, field: :user_con_profile_id },
1313
{ model: Signup, field: :user_con_profile_id },
1414
{ model: SignupChange, field: :user_con_profile_id },
15-
{ model: SignupRankedChoice, field: :user_con_profile_id },
1615
{ model: SignupRequest, field: :user_con_profile_id },
1716
{ model: TeamMember, field: :user_con_profile_id },
1817
{ model: Ticket, field: :user_con_profile_id }
@@ -77,11 +76,25 @@ def merge_profile_if_losing(profile)
7776
model.where(field => profile.id).update_all(field => winning_profile_for_convention.id)
7877
end
7978

79+
merge_signup_ranked_choices(profile, winning_profile_for_convention)
80+
8081
profile.staff_positions.each { |staff_position| winning_profile_for_convention.staff_positions << staff_position }
8182

8283
profile.destroy!
8384
end
8485

86+
def merge_signup_ranked_choices(losing_profile, winning_profile)
87+
losing_choices_by_state =
88+
SignupRankedChoice.where(user_con_profile: losing_profile).order(:priority).group_by(&:state)
89+
90+
losing_choices_by_state.each do |state, choices|
91+
max_priority = SignupRankedChoice.where(user_con_profile: winning_profile, state: state).maximum(:priority) || 0
92+
choices.each_with_index do |choice, index|
93+
choice.update_columns(user_con_profile_id: winning_profile.id, priority: max_priority + index + 1)
94+
end
95+
end
96+
end
97+
8598
def merge_record_keeping_fields
8699
RECORD_KEEPING_FIELDS.each do |record_keeping_field|
87100
model = record_keeping_field[:model]

test/services/merge_users_service_test.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,64 @@ class MergeUsersServiceTest < ActiveSupport::TestCase
3737
assert_nil User.find_by(id: user2.id)
3838
end
3939

40+
it "transfers signup_ranked_choices from losing profile to winning profile" do
41+
profile1, profile2 = create_list(:user_con_profile, 2)
42+
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
43+
profile3 = create(:user_con_profile, convention: profile1.convention, user: profile2.user)
44+
45+
# profile1 is the losing profile for the shared convention
46+
# profile3 is the winning profile for the shared convention
47+
run1 = create(:run, event: create(:event, convention: profile1.convention))
48+
run2 = create(:run, event: create(:event, convention: profile1.convention))
49+
choice1 = create(:signup_ranked_choice, user_con_profile: profile1, target_run: run1, updated_by: profile1.user)
50+
choice2 = create(:signup_ranked_choice, user_con_profile: profile1, target_run: run2, updated_by: profile1.user)
51+
52+
MergeUsersService.new(
53+
user_ids: [user_id1, user_id2],
54+
winning_user_id: user_id1,
55+
winning_user_con_profile_ids_by_convention_id: {
56+
profile3.convention_id => profile3.id
57+
}
58+
).call!
59+
60+
assert_equal profile3.id, choice1.reload.user_con_profile_id
61+
assert_equal profile3.id, choice2.reload.user_con_profile_id
62+
assert_equal [1, 2], [choice1.reload.priority, choice2.reload.priority].sort
63+
end
64+
65+
it "transfers signup_ranked_choices when both profiles have choices" do
66+
profile1, profile2 = create_list(:user_con_profile, 2)
67+
user_id1, user_id2 = [profile1, profile2].map(&:user_id)
68+
profile3 = create(:user_con_profile, convention: profile1.convention, user: profile2.user)
69+
70+
run1 = create(:run, event: create(:event, convention: profile1.convention))
71+
run2 = create(:run, event: create(:event, convention: profile1.convention))
72+
run3 = create(:run, event: create(:event, convention: profile1.convention))
73+
74+
# losing profile has priorities 1, 2; winning profile already has priority 1
75+
winning_choice =
76+
create(:signup_ranked_choice, user_con_profile: profile3, target_run: run3, updated_by: profile3.user)
77+
losing_choice1 =
78+
create(:signup_ranked_choice, user_con_profile: profile1, target_run: run1, updated_by: profile1.user)
79+
losing_choice2 =
80+
create(:signup_ranked_choice, user_con_profile: profile1, target_run: run2, updated_by: profile1.user)
81+
82+
MergeUsersService.new(
83+
user_ids: [user_id1, user_id2],
84+
winning_user_id: user_id1,
85+
winning_user_con_profile_ids_by_convention_id: {
86+
profile3.convention_id => profile3.id
87+
}
88+
).call!
89+
90+
all_choices = SignupRankedChoice.where(user_con_profile: profile3).order(:priority)
91+
assert_equal 3, all_choices.count
92+
assert_equal [1, 2, 3], all_choices.map(&:priority)
93+
assert_includes all_choices, winning_choice.reload
94+
assert_includes all_choices, losing_choice1.reload
95+
assert_includes all_choices, losing_choice2.reload
96+
end
97+
4098
it "merges two profiles from the same convention" do
4199
profile1, profile2 = create_list(:user_con_profile, 2)
42100
user_id1, user_id2 = [profile1, profile2].map(&:user_id)

0 commit comments

Comments
 (0)