Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions app/controllers/admin/deduplicate_patients_controller.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
class Admin::DeduplicatePatientsController < AdminController
skip_before_action :verify_authenticity_token
before_action :set_filter_options, only: [:show], if: :filter_enabled?
DUPLICATE_LIMIT = 250

def show
facilities = current_admin.accessible_facilities(:manage)
authorize { facilities.any? }

# Apply facility filter if selected and feature flag is enabled
filtered_facilities = if filter_enabled? && @selected_facility.present?
facilities.where(id: @selected_facility.id)
elsif filter_enabled? && @selected_district.present?
facilities.where(id: @selected_district.facilities)
else
facilities
end

# Scoping by facilities is costly for users who have a lot of facilities
duplicate_patient_ids = if current_admin.accessible_organizations(:manage).any?
use_all_organizations = current_admin.accessible_organizations(:manage).any? &&
(!filter_enabled? || (!@selected_district.present? && !@selected_facility.present?))

duplicate_patient_ids = if use_all_organizations
PatientDeduplication::Strategies.identifier_excluding_full_name_match(limit: DUPLICATE_LIMIT)
else
PatientDeduplication::Strategies.identifier_excluding_full_name_match_for_facilities(
limit: DUPLICATE_LIMIT,
facilities: facilities
facilities: filtered_facilities
)
end

Expand Down Expand Up @@ -44,4 +57,42 @@ def can_admin_deduplicate_patients?(patients)
.where(id: patients.pluck(:assigned_facility_id))
.any?
end

private

def set_filter_options
@accessible_facilities = current_admin.accessible_facilities(:manage)
populate_districts
set_selected_district
populate_facilities
set_selected_facility
end

def populate_districts
@districts = Region.district_regions
.joins("INNER JOIN regions facility_region ON regions.path @> facility_region.path")
.where("facility_region.source_id" => @accessible_facilities.map(&:id))
.distinct(:slug)
.order(:name)
end

def set_selected_district
@selected_district = if params[:district_slug].present?
@districts.find_by(slug: params[:district_slug])
elsif @districts.present?
@districts.first
end
end

def populate_facilities
@facilities = @accessible_facilities.where(id: @selected_district&.facilities).order(:name)
end

def set_selected_facility
@selected_facility = @facilities.find_by(id: params[:facility_id]) if params[:facility_id].present?
end

def filter_enabled?
Flipper.enabled?(:patient_deduplication_filter, current_admin)
end
end
34 changes: 34 additions & 0 deletions app/views/admin/deduplicate_patients/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,38 @@
<h1 class="page-title mb-3">Merge duplicate patients</h1>

<% if Flipper.enabled?(:patient_deduplication_filter, current_admin) %>
<%= bootstrap_form_with(url: admin_deduplication_path, method: :get, layout: :horizontal, class: "mb-4") do |form| %>
<% html_select_options = { onchange: "this.form.submit();" }
searchable_select_options = html_select_options.merge(class: "selectpicker", data: {live_search: true}) %>
<div class="form-row">
<div id="district-selector" class="form-group col-md-4">
<%= form.select :district_slug,
@districts.order(:name).map { |district| [district.name, district.slug] },
{
hide_label: true,
selected: @selected_district&.slug,
wrapper: false
},
searchable_select_options
%>
</div>
<div id="facility-selector" class="form-group col-md-4">
<%= form.select :facility_id,
@facilities.order(:name).map { |facility| [facility.label_with_district, facility.id] },
{
hide_label: true,
include_blank: "All facilities",
selected: @selected_facility&.id,
wrapper: false
},
searchable_select_options
%>
</div>
</div>
<%= form.submit "Filter", style: "display: none" %>
<% end %>
<% end %>

<div class="card">
<h3>
<% if @duplicate_count == Admin::DeduplicatePatientsController::DUPLICATE_LIMIT %>
Expand Down
119 changes: 117 additions & 2 deletions spec/controllers/admin/deduplicate_patients_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "rails_helper"

RSpec.describe Admin::DeduplicatePatientsController, type: :controller do
context "#show" do
describe "#show" do
it "shows patients accessible by the user" do
patient = create(:patient, full_name: "Patient one")
patient_passport_id = patient.business_identifiers.first.identifier
Expand Down Expand Up @@ -40,9 +40,124 @@
expect(response.status).to eq(302)
expect(flash[:alert]).to eq("You are not authorized to perform this action.")
end

context "when patient_deduplication_filter feature flag is enabled" do
let(:organization) { create(:organization) }
let(:facility_group) { create(:facility_group, organization: organization) }
let(:district) { facility_group.region }
let(:facility1) { create(:facility, facility_group: facility_group) }
let(:facility2) { create(:facility, facility_group: facility_group) }
let(:admin) { create(:admin, :manager, :with_access, resource: organization) }

before do
sign_in(admin.email_authentication)
Flipper.enable(:patient_deduplication_filter, admin)
end

it "sets filter options when feature flag is enabled" do
facility1
get :show

expect(assigns(:districts)).to be_present
expect(assigns(:selected_district)).to be_present
end

it "populates accessible districts" do
facility1
get :show

expect(assigns(:districts)).to include(district)
end

it "filters patients by selected district" do
patient1 = create(:patient, full_name: "Patient one", assigned_facility: facility1)
patient1_passport_id = patient1.business_identifiers.first.identifier

patient1_dup = create(:patient, full_name: "Patient one dup", assigned_facility: facility1)
patient1_dup.business_identifiers.first.update(identifier: patient1_passport_id)

other_facility_group = create(:facility_group, organization: organization)
other_facility = create(:facility, facility_group: other_facility_group)

patient2 = create(:patient, full_name: "Patient two", assigned_facility: other_facility)
patient2_passport_id = patient2.business_identifiers.first.identifier

patient2_dup = create(:patient, full_name: "Patient two dup", assigned_facility: other_facility)
patient2_dup.business_identifiers.first.update(identifier: patient2_passport_id)

get :show, params: {district_slug: district.slug}

expect(assigns(:patients)).to include(patient1, patient1_dup)
expect(assigns(:patients)).not_to include(patient2, patient2_dup)
end

it "filters patients by selected facility" do
patient1 = create(:patient, full_name: "Patient one", assigned_facility: facility1)
patient1_passport_id = patient1.business_identifiers.first.identifier

patient1_dup = create(:patient, full_name: "Patient one dup", assigned_facility: facility1)
patient1_dup.business_identifiers.first.update(identifier: patient1_passport_id)

patient2 = create(:patient, full_name: "Patient two", assigned_facility: facility2)
patient2_passport_id = patient2.business_identifiers.first.identifier

patient2_dup = create(:patient, full_name: "Patient two dup", assigned_facility: facility2)
patient2_dup.business_identifiers.first.update(identifier: patient2_passport_id)

get :show, params: {district_slug: district.slug, facility_id: facility1.id}

expect(assigns(:patients)).to include(patient1, patient1_dup)
expect(assigns(:patients)).not_to include(patient2, patient2_dup)
end

it "populates facilities for the selected district" do
facility1
facility2
get :show, params: {district_slug: district.slug}

expect(assigns(:facilities)).to include(facility1, facility2)
end

it "sets the selected facility when facility_id param is present" do
facility1
get :show, params: {district_slug: district.slug, facility_id: facility1.id}

expect(assigns(:selected_facility)).to eq(facility1)
end
end

context "when patient_deduplication_filter feature flag is disabled" do
let(:admin) { create(:admin, :manager, :with_access, resource: create(:facility)) }

before do
sign_in(admin.email_authentication)
Flipper.disable(:patient_deduplication_filter)
end

it "does not set filter options" do
get :show

expect(assigns(:districts)).to be_nil
expect(assigns(:selected_district)).to be_nil
expect(assigns(:facilities)).to be_nil
end

it "shows all accessible duplicate patients without filtering" do
facility = admin.accessible_facilities(:manage).first
patient = create(:patient, full_name: "Patient one", assigned_facility: facility)
patient_passport_id = patient.business_identifiers.first.identifier

patient_dup = create(:patient, full_name: "Patient one dup", assigned_facility: facility)
patient_dup.business_identifiers.first.update(identifier: patient_passport_id)

get :show

expect(assigns(:patients)).to contain_exactly(patient, patient_dup)
end
end
end

context "#merge" do
describe "#merge" do
it "returns unauthorized when none of the patient IDs is accessible by the user" do
patients = [create(:patient, full_name: "Patient one"), create(:patient, full_name: "Patient two")]
admin = create(:admin, :manager, :with_access, resource: create(:facility))
Expand Down