Merges when both sides have a common tag or custom field now work
This commit is contained in:
parent
a45991ff15
commit
96c37e0af9
5 changed files with 217 additions and 41 deletions
|
@ -86,7 +86,7 @@ class SupportersController < ApplicationController
|
|||
requires(:supporter_ids).as_array
|
||||
}.when_valid{|params|
|
||||
params[:supporter][:nonprofit_id] = params[:nonprofit_id]
|
||||
MergeSupporters.selected(params[:supporter], params[:supporter_ids])
|
||||
MergeSupporters.selected(params[:supporter], params[:supporter_ids], params[:nonprofit_id], current_user.id)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1,39 +1,68 @@
|
|||
# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
|
||||
module MergeSupporters
|
||||
|
||||
# Given some supporter ids, merge them together into a new supporter
|
||||
def self.selected(merged_data, supporter_ids)
|
||||
new_supporter = Psql.execute(Qexpr.new.insert(:supporters, merged_data).returning('*')).first
|
||||
# Update merged supporters as deleted
|
||||
Psql.execute(Qexpr.new.update(:supporters, {deleted: true}).where("id IN ($ids)", ids: supporter_ids))
|
||||
# Update all associated tables
|
||||
self.update_associations(supporter_ids, new_supporter['id'])
|
||||
return {json: new_supporter, status: :ok}
|
||||
end
|
||||
|
||||
# For supporters that have been merged, we want to update all their child tables to the new supporter_id
|
||||
def self.update_associations(old_supporter_ids, new_supporter_id)
|
||||
def self.update_associations(old_supporter_ids, new_supporter_id, np_id, profile_id)
|
||||
# The new supporter needs to have the following tables from the merged supporters:
|
||||
associations = [:activities, :donations, :recurring_donations, :offsite_payments, :payments, :tickets, :custom_field_joins, :tag_joins, :supporter_notes, :supporter_emails, :full_contact_infos]
|
||||
associations = [:activities, :donations, :recurring_donations, :offsite_payments, :payments, :tickets, :supporter_notes, :supporter_emails, :full_contact_infos]
|
||||
|
||||
associations.each do |table_name|
|
||||
Qx.update(table_name).set(supporter_id: new_supporter_id).where("supporter_id IN ($ids)", ids: old_supporter_ids).timestamps.execute
|
||||
end
|
||||
|
||||
old_supporters = Supporter.includes(:tag_joins).includes(:custom_field_joins).where('id in (?)', old_supporter_ids)
|
||||
old_tags = old_supporters.map{|i| i.tag_joins.map{|j| j.tag_master}}.flatten.uniq
|
||||
|
||||
#delete old tags
|
||||
InsertTagJoins.in_bulk(np_id, profile_id, old_supporter_ids,
|
||||
old_tags.map{|i| {tag_master_id: i.id, selected: false}})
|
||||
|
||||
|
||||
InsertTagJoins.in_bulk(np_id, profile_id, [new_supporter_id], old_tags.map{|i| {tag_master_id: i.id, selected: true}})
|
||||
|
||||
all_custom_field_joins = old_supporters.map{| i| i.custom_field_joins}.flatten
|
||||
group_joins_by_custom_field_master = all_custom_field_joins.group_by{|i| i.custom_field_master.id}
|
||||
one_custom_field_join_per_user = group_joins_by_custom_field_master.map{|k,v|
|
||||
v.sort_by{|i|
|
||||
i.created_at
|
||||
}.reverse.first}
|
||||
|
||||
#delete old supporter custom_field
|
||||
InsertCustomFieldJoins.in_bulk(np_id, old_supporter_ids, one_custom_field_join_per_user.map{|i| {
|
||||
custom_field_master_id: i.custom_field_master_id,
|
||||
value: ""
|
||||
}})
|
||||
|
||||
#insert new supporter custom field
|
||||
InsertCustomFieldJoins.in_bulk(np_id, [new_supporter_id], one_custom_field_join_per_user.map{|i| {
|
||||
custom_field_master_id: i.custom_field_master_id,
|
||||
value: i.value
|
||||
}})
|
||||
|
||||
# Update all deleted/merged supporters to record when and where they got merged
|
||||
Psql.execute(Qexpr.new.update(:supporters, {merged_at: Time.current, merged_into: new_supporter_id}).where("id IN ($ids)", ids: old_supporter_ids))
|
||||
# Removing any duplicate custom fields
|
||||
UpdateCustomFieldJoins.delete_dupes([new_supporter_id])
|
||||
# Removing any duplicate custom fields UpdateCustomFieldJoins.delete_dupes([new_supporter_id])
|
||||
end
|
||||
|
||||
def self.selected(merged_data, supporter_ids,np_id, profile_id)
|
||||
new_supporter = Supporter.new(merged_data)
|
||||
new_supporter.save!
|
||||
# Update merged supporters as deleted
|
||||
Psql.execute(Qexpr.new.update(:supporters, {deleted: true}).where("id IN ($ids)", ids: supporter_ids))
|
||||
# Update all associated tables
|
||||
self.update_associations(supporter_ids, new_supporter['id'],np_id, profile_id)
|
||||
return {json: new_supporter, status: :ok}
|
||||
end
|
||||
|
||||
|
||||
# Merge supporters for a nonprofit based on an array of groups of ids, generated from QuerySupporters.dupes_on_email or dupes_on_names
|
||||
def self.merge_by_id_groups(np_id, arr_of_ids)
|
||||
def self.merge_by_id_groups(np_id, arr_of_ids, profile_id)
|
||||
Qx.transaction do
|
||||
arr_of_ids.select{|arr| arr.count > 1}.each do |ids|
|
||||
# Get all column data from every supporter
|
||||
all_data = Psql.execute(
|
||||
Qexpr.new.from(:supporters)
|
||||
.select(:email, :name, :phone, :address, :city, :state_code, :zip_code, :organization, :latitude, :longitude, :country, :created_at)
|
||||
.select(:email, :name, :phone, :address, :city, :state_code, :zip_code, :organization, :country, :created_at)
|
||||
.where("id IN ($ids)", ids: ids)
|
||||
.order_by("created_at ASC")
|
||||
)
|
||||
|
@ -43,7 +72,7 @@
|
|||
acc
|
||||
end.merge({'nonprofit_id' => np_id})
|
||||
|
||||
MergeSupporters.selected(data, ids)
|
||||
MergeSupporters.selected(data, ids, np_id, profile_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -116,29 +116,29 @@ module QuerySupporters
|
|||
return { data: supps }
|
||||
end
|
||||
|
||||
# Given a list of supporters, you may want to remove duplicates from those supporters.
|
||||
# @param [Enumerable[Supporter]] supporters
|
||||
def self._remove_dupes_on_a_list_of_supporters(supporters, np_id)
|
||||
|
||||
new_supporters =supporters.clone.to_a
|
||||
|
||||
QuerySupporters.dupes_on_name_and_email(np_id).each{|duplicates|
|
||||
matched_in_group = false
|
||||
duplicates.each{|i|
|
||||
supporter = new_supporters.find{|s| s.id == i}
|
||||
if (supporter)
|
||||
if (matched_in_group)
|
||||
new_supporters.delete(supporter)
|
||||
else
|
||||
matched_in_group = true
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new_supporters
|
||||
end
|
||||
# # Given a list of supporters, you may want to remove duplicates from those supporters.
|
||||
# # @param [Enumerable[Supporter]] supporters
|
||||
# def self._remove_dupes_on_a_list_of_supporters(supporters, np_id)
|
||||
#
|
||||
# new_supporters =supporters.clone.to_a
|
||||
#
|
||||
# QuerySupporters.dupes_on_name_and_email(np_id).each{|duplicates|
|
||||
# matched_in_group = false
|
||||
# duplicates.each{|i|
|
||||
# supporter = new_supporters.find{|s| s.id == i}
|
||||
# if (supporter)
|
||||
# if (matched_in_group)
|
||||
# new_supporters.delete(supporter)
|
||||
# else
|
||||
# matched_in_group = true
|
||||
# end
|
||||
# end
|
||||
# }
|
||||
#
|
||||
# }
|
||||
#
|
||||
# return new_supporters
|
||||
# end
|
||||
|
||||
|
||||
# Perform all filters and search for /nonprofits/id/supporters dashboard and export
|
||||
|
|
5
spec/factories/supporter_notes.rb
Normal file
5
spec/factories/supporter_notes.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
FactoryBot.define do
|
||||
factory :supporter_note do
|
||||
|
||||
end
|
||||
end
|
142
spec/lib/merge_supporters_spec.rb
Normal file
142
spec/lib/merge_supporters_spec.rb
Normal file
|
@ -0,0 +1,142 @@
|
|||
require "rails_helper"
|
||||
|
||||
describe MergeSupporters do
|
||||
describe '.update_associations' do
|
||||
|
||||
around(:each){|e|
|
||||
Timecop.freeze(2020, 3, 4) do
|
||||
e.run
|
||||
end
|
||||
}
|
||||
#one unique tag for 1
|
||||
# one unique tag for 2
|
||||
# one common tag on both
|
||||
#
|
||||
# one unique custom field on 1
|
||||
# one unique custom field on 2
|
||||
# one common field on both and keep the value of the most common
|
||||
|
||||
let(:np) {force_create(:nonprofit)}
|
||||
let(:old_supporter1) { force_create(:supporter, nonprofit: np)}
|
||||
let(:old_supporter2) {force_create(:supporter, nonprofit:np )}
|
||||
let(:new_supporter) {force_create(:supporter, nonprofit:np)}
|
||||
|
||||
let(:supporter_note) {force_create(:supporter_note, supporter: old_supporter1, content: "feoatheoiath")}
|
||||
|
||||
let(:tag_master) {force_create(:tag_master, nonprofit: np, name: 'something')}
|
||||
let(:tag_master2) {force_create(:tag_master, nonprofit: np, name: 'something2')}
|
||||
let(:tag_master3) {force_create(:tag_master, nonprofit: np, name: 'something3')}
|
||||
|
||||
let(:tag_on_1) {force_create(:tag_join, tag_master: tag_master, supporter_id: old_supporter1.id)}
|
||||
let(:tag_on_2) {force_create(:tag_join, tag_master: tag_master2, supporter_id: old_supporter2.id)}
|
||||
let(:tag_on_both) {[old_supporter1, old_supporter2].each{|i| force_create(:tag_join, tag_master:tag_master3, supporter_id:i.id)}}
|
||||
|
||||
let(:custom_field_master) {force_create(:custom_field_master, nonprofit: np, name: 'cfm1')}
|
||||
let(:custom_field_master2) {force_create(:custom_field_master, nonprofit: np, name: 'cfm2')}
|
||||
let(:custom_field_master3) {force_create(:custom_field_master, nonprofit: np, name: 'cfm3')}
|
||||
|
||||
let(:cfj_on_1) { force_create(:custom_field_join, supporter:old_supporter1, custom_field_master: custom_field_master, value: 'cfj_on_1')}
|
||||
let(:cfj_on_2) { force_create(:custom_field_join, supporter:old_supporter2, custom_field_master: custom_field_master2, value: 'cfj_on_2')}
|
||||
let(:cfj_on_3) {force_create(:custom_field_join, supporter:old_supporter1, custom_field_master: custom_field_master3, value: 'old_cfj')}
|
||||
let(:cfj_on_4) {force_create(:custom_field_join, supporter:old_supporter2, custom_field_master: custom_field_master3, value: 'new_cfj', created_at: Time.now + 1.day)}
|
||||
|
||||
let(:profile) {force_create(:profile)}
|
||||
|
||||
before(:each){
|
||||
np
|
||||
old_supporter1
|
||||
old_supporter2
|
||||
new_supporter
|
||||
supporter_note
|
||||
|
||||
}
|
||||
|
||||
it 'merges everything properly with tags and cfjs on both' do
|
||||
tag_on_1
|
||||
tag_on_2
|
||||
tag_on_both
|
||||
cfj_on_1
|
||||
cfj_on_2
|
||||
cfj_on_3
|
||||
cfj_on_4
|
||||
MergeSupporters.update_associations([old_supporter1.id, old_supporter2.id], new_supporter.id, np.id, profile.id)
|
||||
old_supporter1.reload
|
||||
old_supporter2.reload
|
||||
expect(old_supporter1.tag_joins.count).to eq 0
|
||||
expect(old_supporter2.tag_joins.count).to eq 0
|
||||
expect(new_supporter.tag_joins.count).to eq 3
|
||||
expect(new_supporter.tag_joins.map{|i| i.tag_master}).to contain_exactly(tag_master, tag_master2, tag_master3)
|
||||
|
||||
expect(old_supporter1.custom_field_joins.count).to eq 0
|
||||
expect(old_supporter2.custom_field_joins.count).to eq 0
|
||||
expect(new_supporter.custom_field_joins.count).to eq 3
|
||||
expect(new_supporter.custom_field_joins.map{|i| i.custom_field_master}).to contain_exactly(custom_field_master, custom_field_master2, custom_field_master3)
|
||||
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master}.value).to eq cfj_on_1.value
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master2}.value).to eq cfj_on_2.value
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master3}.value).to eq cfj_on_4.value
|
||||
|
||||
expect(new_supporter.supporter_notes.first.id).to eq supporter_note.id
|
||||
end
|
||||
|
||||
it 'merges with tags and cfjs on first' do
|
||||
tag_on_1
|
||||
cfj_on_1
|
||||
cfj_on_3
|
||||
MergeSupporters.update_associations([old_supporter1.id, old_supporter2.id], new_supporter.id, np.id, profile.id)
|
||||
old_supporter1.reload
|
||||
old_supporter2.reload
|
||||
expect(old_supporter1.tag_joins.count).to eq 0
|
||||
expect(old_supporter2.tag_joins.count).to eq 0
|
||||
expect(new_supporter.tag_joins.count).to eq 1
|
||||
expect(new_supporter.tag_joins.map{|i| i.tag_master}).to contain_exactly(tag_master)
|
||||
|
||||
expect(old_supporter1.custom_field_joins.count).to eq 0
|
||||
expect(old_supporter2.custom_field_joins.count).to eq 0
|
||||
expect(new_supporter.custom_field_joins.count).to eq 2
|
||||
expect(new_supporter.custom_field_joins.map{|i| i.custom_field_master}).to contain_exactly(custom_field_master, custom_field_master3)
|
||||
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master}.value).to eq cfj_on_1.value
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master3}.value).to eq cfj_on_3.value
|
||||
|
||||
expect(new_supporter.supporter_notes.first.id).to eq supporter_note.id
|
||||
end
|
||||
|
||||
it 'merges with tags and cfjs on second' do
|
||||
tag_on_2
|
||||
cfj_on_2
|
||||
cfj_on_4
|
||||
MergeSupporters.update_associations([old_supporter1.id, old_supporter2.id], new_supporter.id, np.id, profile.id)
|
||||
old_supporter1.reload
|
||||
old_supporter2.reload
|
||||
expect(old_supporter1.tag_joins.count).to eq 0
|
||||
expect(old_supporter2.tag_joins.count).to eq 0
|
||||
expect(new_supporter.tag_joins.count).to eq 1
|
||||
expect(new_supporter.tag_joins.map{|i| i.tag_master}).to contain_exactly(tag_master2)
|
||||
|
||||
expect(old_supporter1.custom_field_joins.count).to eq 0
|
||||
expect(old_supporter2.custom_field_joins.count).to eq 0
|
||||
expect(new_supporter.custom_field_joins.count).to eq 2
|
||||
expect(new_supporter.custom_field_joins.map{|i| i.custom_field_master}).to contain_exactly(custom_field_master2, custom_field_master3)
|
||||
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master2}.value).to eq cfj_on_2.value
|
||||
expect(new_supporter.custom_field_joins.find{|i| i.custom_field_master == custom_field_master3}.value).to eq cfj_on_4.value
|
||||
expect(new_supporter.supporter_notes.first.id).to eq supporter_note.id
|
||||
end
|
||||
|
||||
it 'merges with tags and cfjs on neighter' do
|
||||
MergeSupporters.update_associations([old_supporter1.id, old_supporter2.id], new_supporter.id, np.id, profile.id)
|
||||
old_supporter1.reload
|
||||
old_supporter2.reload
|
||||
expect(old_supporter1.tag_joins.count).to eq 0
|
||||
expect(old_supporter2.tag_joins.count).to eq 0
|
||||
expect(new_supporter.tag_joins.count).to eq 0
|
||||
|
||||
|
||||
expect(old_supporter1.custom_field_joins.count).to eq 0
|
||||
expect(old_supporter2.custom_field_joins.count).to eq 0
|
||||
expect(new_supporter.custom_field_joins.count).to eq 0
|
||||
expect(new_supporter.supporter_notes.first.id).to eq supporter_note.id
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue