diff --git a/app/models/location.rb b/app/models/location.rb index 19cf5e211..6f2963e39 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -121,6 +121,19 @@ class Location < ApplicationRecord scope :visible, -> { where(discarded_at: nil) } + scope :duplicate_sets, lambda { + scope = visible + .group(*DUPLICATE_LOCATION_ATTRIBUTES) + .where.not(scheme_id: nil) + .where.not(postcode: nil) + .where.not(mobility_type: nil) + .having( + "COUNT(*) > 1", + ) + scope.pluck("ARRAY_AGG(id)") + } + + DUPLICATE_LOCATION_ATTRIBUTES = %w[scheme_id postcode mobility_type].freeze LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h enum local_authorities: LOCAL_AUTHORITIES diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 4856c5662..2c309c849 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -831,6 +831,71 @@ RSpec.describe Location, type: :model do expect(described_class.active.count).to eq(2) end end + + context "when getting list of duplicate locations" do + let!(:scheme) { create(:scheme) } + let!(:location) { create(:location, postcode: "AB1 2CD", mobility_type: "M", scheme:) } + let!(:duplicate_location) { create(:location, postcode: "AB1 2CD", mobility_type: "M", scheme:) } + let(:duplicate_sets) { described_class.duplicate_sets } + + it "returns a list of duplicates for the same scheme" do + expect(duplicate_sets.count).to eq(1) + expect(duplicate_sets.first).to contain_exactly(location.id, duplicate_location.id) + end + + context "when there is a deleted duplicate location" do + before do + create(:location, postcode: "AB1 2CD", mobility_type: "M", discarded_at: Time.zone.now, scheme:) + end + + it "does not return the deleted location as a duplicate" do + expect(duplicate_sets.count).to eq(1) + expect(duplicate_sets.first).to contain_exactly(location.id, duplicate_location.id) + end + end + + context "when there is a location with a different postcode" do + before do + create(:location, postcode: "A1 1AB", mobility_type: "M", scheme:) + end + + it "does not return a location with a different postcode as a duplicate" do + expect(duplicate_sets.count).to eq(1) + expect(duplicate_sets.first).to contain_exactly(location.id, duplicate_location.id) + end + end + + context "when there is a location with a different mobility_type" do + before do + create(:location, postcode: "AB1 2CD", mobility_type: "A", scheme:) + end + + it "does not return a location with a different mobility_type as a duplicate" do + expect(duplicate_sets.count).to eq(1) + expect(duplicate_sets.first).to contain_exactly(location.id, duplicate_location.id) + end + end + + context "when there is a location with a different scheme" do + before do + create(:location, postcode: "AB1 2CD", mobility_type: "M") + end + + it "does not return a location with a different scheme as a duplicate" do + expect(duplicate_sets.count).to eq(1) + expect(duplicate_sets.first).to contain_exactly(location.id, duplicate_location.id) + end + end + + context "when there is a location with nil values for duplicate check fields" do + let!(:location) { build(:location, postcode: nil, mobility_type: nil, scheme:).save(validate: false) } + let!(:duplicate_location) { build(:location, postcode: nil, mobility_type: nil, scheme:).save(validate: false) } + + it "does not return a location with nil values as a duplicate" do + expect(duplicate_sets).to be_empty + end + end + end end describe "status" do