From caa5ecf6fd9caccbc0c98d0556ba97b383a53ca0 Mon Sep 17 00:00:00 2001 From: Daniel Nottingham Date: Tue, 10 Mar 2026 07:52:40 -0300 Subject: [PATCH 1/2] Add filter by patient name --- .../api/v1/event_procedures_controller.rb | 3 +- app/models/event_procedure.rb | 1 + app/operations/event_procedures/list.rb | 5 ++ .../event_procedures/by_patient_query.rb | 16 ++++++ spec/operations/event_procedures/list_spec.rb | 14 ++++++ .../event_procedures/by_patient_query_spec.rb | 49 +++++++++++++++++++ .../api/v1/event_procedures_request_spec.rb | 20 ++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 app/queries/event_procedures/by_patient_query.rb create mode 100644 spec/queries/event_procedures/by_patient_query_spec.rb diff --git a/app/controllers/api/v1/event_procedures_controller.rb b/app/controllers/api/v1/event_procedures_controller.rb index 4862ebd..d62647a 100644 --- a/app/controllers/api/v1/event_procedures_controller.rb +++ b/app/controllers/api/v1/event_procedures_controller.rb @@ -96,7 +96,8 @@ def event_procedure_permitted_query_params :year, :paid, hospital: [:name], - health_insurance: [:name] + health_insurance: [:name], + patient: [:name] ).to_h end diff --git a/app/models/event_procedure.rb b/app/models/event_procedure.rb index 9708bc9..542cb68 100644 --- a/app/models/event_procedure.rb +++ b/app/models/event_procedure.rb @@ -27,6 +27,7 @@ class EventProcedure < ApplicationRecord scope :date_between, EventProcedures::ByDateBetween scope :by_hospital_name, EventProcedures::ByHospitalNameQuery scope :by_health_insurance_name, EventProcedures::ByHealthInsuranceNameQuery + scope :by_patient, EventProcedures::ByPatientQuery validates :date, presence: true validates :patient_service_number, presence: true diff --git a/app/operations/event_procedures/list.rb b/app/operations/event_procedures/list.rb index 81755e5..966acec 100644 --- a/app/operations/event_procedures/list.rb +++ b/app/operations/event_procedures/list.rb @@ -32,6 +32,7 @@ def apply_all_filters(query) query = apply_year_filter(query) query = apply_hospital_filter(query) query = apply_health_insurance_filter(query) + query = apply_patient_filter(query) query = apply_ids_filter(query) apply_paid_filter(query) end @@ -60,6 +61,10 @@ def apply_health_insurance_filter(query) end end + def apply_patient_filter(query) + params.dig(:patient, :name).present? ? query.by_patient(patient_name: params[:patient][:name]) : query + end + def apply_ids_filter(query) params[:ids].present? ? query.where(id: params[:ids]) : query end diff --git a/app/queries/event_procedures/by_patient_query.rb b/app/queries/event_procedures/by_patient_query.rb new file mode 100644 index 0000000..c17df3f --- /dev/null +++ b/app/queries/event_procedures/by_patient_query.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module EventProcedures + class ByPatientQuery < ApplicationQuery + attr_reader :patient_name, :relation + + def initialize(patient_name:, relation: EventProcedure) + @patient_name = patient_name + @relation = relation + end + + def call + relation.joins(:patient).where("patients.name ILIKE ?", "%#{patient_name}%") + end + end +end diff --git a/spec/operations/event_procedures/list_spec.rb b/spec/operations/event_procedures/list_spec.rb index 2050528..bb2be30 100644 --- a/spec/operations/event_procedures/list_spec.rb +++ b/spec/operations/event_procedures/list_spec.rb @@ -119,6 +119,20 @@ it { expect(result.event_procedures).to eq [EventProcedure.last] } end + context "when has filters by patient name" do + it "returns event_procedures for patients matching the name" do + user = create(:user) + joao = create(:patient, name: "João Silva", user: user) + maria = create(:patient, name: "Maria Souza", user: user) + ep_joao = create(:event_procedure, patient: joao, user: user) + _ep_maria = create(:event_procedure, patient: maria, user: user) + + result = described_class.result(scope: EventProcedure.all, params: { patient: { name: "João" } }) + + expect(result.event_procedures).to contain_exactly(ep_joao) + end + end + describe "event_procedures_unpaginated" do context "when there are event procedures outside of pagination" do let!(:event_procedures) { create_list(:event_procedure, 11) } diff --git a/spec/queries/event_procedures/by_patient_query_spec.rb b/spec/queries/event_procedures/by_patient_query_spec.rb new file mode 100644 index 0000000..029fce6 --- /dev/null +++ b/spec/queries/event_procedures/by_patient_query_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe EventProcedures::ByPatientQuery do + describe "#call" do + context "when a matching patient name is provided" do + it "returns event_procedures for patients matching the name" do + user = create(:user) + joao = create(:patient, name: "João Silva", user: user) + maria = create(:patient, name: "Maria Souza", user: user) + ep_joao = create(:event_procedure, user: user, patient: joao) + _ep_maria = create(:event_procedure, user: user, patient: maria) + + query = described_class.call(patient_name: "João") + + expect(query).to contain_exactly(ep_joao) + end + + it "performs case-insensitive partial matching" do + user = create(:user) + patient = create(:patient, name: "Carlos Alberto", user: user) + ep = create(:event_procedure, user: user, patient: patient) + + query = described_class.call(patient_name: "carlos") + + expect(query).to contain_exactly(ep) + end + end + + context "when patient name does not match any patient" do + it "returns an empty collection" do + user = create(:user) + patient = create(:patient, name: "Pedro Lima", user: user) + create(:event_procedure, user: user, patient: patient) + + query = described_class.call(patient_name: "Zélia") + + expect(query).to be_empty + end + end + + context "when keyword arguments are missing" do + it "raises an ArgumentError" do + expect { described_class.call }.to raise_error(ArgumentError, "missing keyword: :patient_name") + end + end + end +end diff --git a/spec/requests/api/v1/event_procedures_request_spec.rb b/spec/requests/api/v1/event_procedures_request_spec.rb index 1584dc1..ad00390 100644 --- a/spec/requests/api/v1/event_procedures_request_spec.rb +++ b/spec/requests/api/v1/event_procedures_request_spec.rb @@ -119,6 +119,26 @@ end end + context "when has filters by patient name" do + it "returns event_procedures for patients matching the name" do + joao = create(:patient, name: "João Silva", user: user) + maria = create(:patient, name: "Maria Souza", user: user) + create(:event_procedure, patient: joao, user_id: user.id) + create(:event_procedure, patient: maria, user_id: user.id) + + get path, params: { patient: { name: "João" } }, headers: headers + + expect(response.parsed_body["event_procedures"].length).to eq(1) + expect(response.parsed_body["event_procedures"].first["patient"]).to eq("João Silva") + end + + it "returns empty when no patient matches the name" do + get path, params: { patient: { name: "Zélia" } }, headers: headers + + expect(response.parsed_body["event_procedures"]).to be_empty + end + end + context "when has pagination via page and per_page" do before do procedure = create(:procedure, custom: true, user: user, amount_cents: 5000) From 424613c2753eadd60b577bfc27fcffe4c81faef0 Mon Sep 17 00:00:00 2001 From: Daniel Nottingham Date: Thu, 12 Mar 2026 11:47:55 -0300 Subject: [PATCH 2/2] fix filter by patient name --- app/queries/event_procedures/by_patient_query.rb | 2 +- db/migrate/20260312000000_enable_unaccent_extension.rb | 7 +++++++ db/schema.rb | 3 ++- spec/queries/event_procedures/by_patient_query_spec.rb | 10 ++++++++++ 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20260312000000_enable_unaccent_extension.rb diff --git a/app/queries/event_procedures/by_patient_query.rb b/app/queries/event_procedures/by_patient_query.rb index c17df3f..00eae98 100644 --- a/app/queries/event_procedures/by_patient_query.rb +++ b/app/queries/event_procedures/by_patient_query.rb @@ -10,7 +10,7 @@ def initialize(patient_name:, relation: EventProcedure) end def call - relation.joins(:patient).where("patients.name ILIKE ?", "%#{patient_name}%") + relation.joins(:patient).where("unaccent(patients.name) ILIKE unaccent(?)", "%#{patient_name}%") end end end diff --git a/db/migrate/20260312000000_enable_unaccent_extension.rb b/db/migrate/20260312000000_enable_unaccent_extension.rb new file mode 100644 index 0000000..80c588a --- /dev/null +++ b/db/migrate/20260312000000_enable_unaccent_extension.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class EnableUnaccentExtension < ActiveRecord::Migration[7.1] + def change + enable_extension "unaccent" + end +end diff --git a/db/schema.rb b/db/schema.rb index bfc5525..d556783 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,11 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_12_20_153000) do +ActiveRecord::Schema[7.1].define(version: 2026_03_12_000000) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "plpgsql" + enable_extension "unaccent" create_table "cbhpm_procedures", force: :cascade do |t| t.bigint "cbhpm_id", null: false diff --git a/spec/queries/event_procedures/by_patient_query_spec.rb b/spec/queries/event_procedures/by_patient_query_spec.rb index 029fce6..96bbf74 100644 --- a/spec/queries/event_procedures/by_patient_query_spec.rb +++ b/spec/queries/event_procedures/by_patient_query_spec.rb @@ -26,6 +26,16 @@ expect(query).to contain_exactly(ep) end + + it "performs accent-insensitive matching" do + user = create(:user) + patient = create(:patient, name: "João Silva", user: user) + ep = create(:event_procedure, user: user, patient: patient) + + query = described_class.call(patient_name: "Joao") + + expect(query).to contain_exactly(ep) + end end context "when patient name does not match any patient" do