Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit 8ba3e24

Browse files
holdenhinklepatrick-brown-oddballopticbob
authored
feat: re-add appoint search acceptance mode and reps_can_accept_any_r… (#27186)
* feat: re-add appoint search acceptance mode and reps_can_accept_any_request * fix: set explicit serializer type/id and resolve N+1 in OrganizationWithRepContext --------- Co-authored-by: Patrick Brown <patrick.brown@oddball.io> Co-authored-by: Josh Fike <josh.fike@oddball.io>
1 parent 92d6960 commit 8ba3e24

11 files changed

Lines changed: 609 additions & 5 deletions

File tree

modules/representation_management/app/controllers/representation_management/v0/original_entities_controller.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@ class OriginalEntitiesController < ApplicationController
99

1010
def index
1111
data = RepresentationManagement::OriginalEntityQuery.new(params[:query]).results
12+
orgs = data.select { |r| r.is_a?(Veteran::Service::Organization) }
13+
any_request_poas = if Flipper.enabled?(:accredited_representative_portal_individual_accept) && orgs.any?
14+
RepresentationManagement::OrganizationWithAcceptanceCheck.any_request_poas_for(orgs)
15+
else
16+
Set.new
17+
end
18+
1219
json_response = data.map do |record|
1320
if record.is_a?(Veteran::Service::Representative)
1421
RepresentationManagement::OriginalEntities::RepresentativeSerializer.new(record).serializable_hash
1522
elsif record.is_a?(Veteran::Service::Organization)
16-
RepresentationManagement::OriginalEntities::OrganizationSerializer.new(record).serializable_hash
23+
org = RepresentationManagement::OrganizationWithAcceptanceCheck.new(record, any_request_poas:)
24+
RepresentationManagement::OriginalEntities::OrganizationSerializer.new(org).serializable_hash
1725
end
1826
end
1927
render json: json_response
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
module RepresentationManagement
4+
class OrganizationWithAcceptanceCheck < SimpleDelegator
5+
def initialize(organization, any_request_poas:)
6+
super(organization)
7+
@any_request_poas = any_request_poas
8+
end
9+
10+
def reps_can_accept_any_request
11+
@any_request_poas.include?(__getobj__.poa)
12+
end
13+
14+
def self.any_request_poas_for(organizations)
15+
poas = organizations.map(&:poa)
16+
Veteran::Service::OrganizationRepresentative.active
17+
.where(organization_poa: poas, acceptance_mode: 'any_request')
18+
.distinct
19+
.pluck(:organization_poa)
20+
.to_set
21+
end
22+
end
23+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
module RepresentationManagement
4+
class OrganizationWithRepContext < SimpleDelegator
5+
def initialize(organization, accepting_poas:)
6+
super(organization)
7+
@accepting_poas = accepting_poas
8+
end
9+
10+
def can_accept_digital_poa_requests
11+
return false unless __getobj__.can_accept_digital_poa_requests
12+
13+
@accepting_poas.include?(__getobj__.poa)
14+
end
15+
16+
def self.accepting_poas_for(representative)
17+
Veteran::Service::OrganizationRepresentative.active
18+
.where(representative_id: representative.representative_id)
19+
.where.not(acceptance_mode: 'no_acceptance')
20+
.pluck(:organization_poa)
21+
.to_set
22+
end
23+
end
24+
end

modules/representation_management/app/serializers/representation_management/original_entities/organization_serializer.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ module OriginalEntities
55
class OrganizationSerializer
66
include JSONAPI::Serializer
77

8+
set_type :organization
9+
set_id :poa
10+
811
attributes :name, :address_line1, :address_line2, :address_line3, :address_type,
912
:city, :country_name, :country_code_iso3, :province, :international_postal_code, :state_code,
10-
:zip_code, :zip_suffix, :phone, :lat, :long, :can_accept_digital_poa_requests
13+
:zip_code, :zip_suffix, :phone, :lat, :long, :can_accept_digital_poa_requests,
14+
:reps_can_accept_any_request
1115
attribute :poa_code, &:poa
1216
end
1317
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
module RepresentationManagement
4+
module OriginalEntities
5+
class RepresentativeOrganizationSerializer
6+
include JSONAPI::Serializer
7+
8+
set_type :organization
9+
set_id :poa
10+
11+
attributes :name, :address_line1, :address_line2, :address_line3, :address_type,
12+
:city, :country_name, :country_code_iso3, :province, :international_postal_code, :state_code,
13+
:zip_code, :zip_suffix, :phone, :lat, :long, :can_accept_digital_poa_requests
14+
attribute :poa_code, &:poa
15+
end
16+
end
17+
end

modules/representation_management/app/serializers/representation_management/original_entities/representative_serializer.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,15 @@ class RepresentativeSerializer
1818
end
1919

2020
attribute :accredited_organizations do |object|
21-
RepresentationManagement::OriginalEntities::OrganizationSerializer.new(object.organizations)
21+
orgs = if Flipper.enabled?(:accredited_representative_portal_individual_accept)
22+
accepting_poas = RepresentationManagement::OrganizationWithRepContext.accepting_poas_for(object)
23+
object.organizations.map do |org|
24+
RepresentationManagement::OrganizationWithRepContext.new(org, accepting_poas:)
25+
end
26+
else
27+
object.organizations
28+
end
29+
RepresentationManagement::OriginalEntities::RepresentativeOrganizationSerializer.new(orgs)
2230
end
2331
end
2432
end
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe RepresentationManagement::OrganizationWithAcceptanceCheck do
6+
subject { described_class.new(organization, any_request_poas:) }
7+
8+
let(:organization) { create(:organization, poa: 'ABC', can_accept_digital_poa_requests: true) }
9+
let(:any_request_poas) { described_class.any_request_poas_for([organization]) }
10+
11+
describe '#can_accept_digital_poa_requests' do
12+
it 'delegates to the underlying organization' do
13+
expect(subject.can_accept_digital_poa_requests).to eq(organization.can_accept_digital_poa_requests)
14+
end
15+
end
16+
17+
describe '#reps_can_accept_any_request' do
18+
let(:representative) { create(:representative, representative_id: '12345') }
19+
20+
context 'when an active organization_representative has acceptance_mode any_request' do
21+
before do
22+
create(:veteran_organization_representative,
23+
representative:,
24+
organization:,
25+
acceptance_mode: 'any_request')
26+
end
27+
28+
it 'returns true' do
29+
expect(subject.reps_can_accept_any_request).to be true
30+
end
31+
end
32+
33+
context 'when an active organization_representative has acceptance_mode self_only' do
34+
before do
35+
create(:veteran_organization_representative,
36+
representative:,
37+
organization:,
38+
acceptance_mode: 'self_only')
39+
end
40+
41+
it 'returns false' do
42+
expect(subject.reps_can_accept_any_request).to be false
43+
end
44+
end
45+
46+
context 'when no organization_representative records exist' do
47+
it 'returns false' do
48+
expect(subject.reps_can_accept_any_request).to be false
49+
end
50+
end
51+
52+
context 'when the only any_request organization_representative is deactivated' do
53+
before do
54+
create(:veteran_organization_representative,
55+
representative:,
56+
organization:,
57+
acceptance_mode: 'any_request',
58+
deactivated_at: Time.current)
59+
end
60+
61+
it 'returns false' do
62+
expect(subject.reps_can_accept_any_request).to be false
63+
end
64+
end
65+
end
66+
67+
describe '.any_request_poas_for' do
68+
let(:representative) { create(:representative, representative_id: '12345') }
69+
70+
it 'returns a set of POAs with active any_request reps' do
71+
create(:veteran_organization_representative,
72+
representative:,
73+
organization:,
74+
acceptance_mode: 'any_request')
75+
76+
result = described_class.any_request_poas_for([organization])
77+
expect(result).to be_a(Set)
78+
expect(result).to include('ABC')
79+
end
80+
81+
it 'excludes self_only reps' do
82+
create(:veteran_organization_representative,
83+
representative:,
84+
organization:,
85+
acceptance_mode: 'self_only')
86+
87+
result = described_class.any_request_poas_for([organization])
88+
expect(result).not_to include('ABC')
89+
end
90+
91+
it 'excludes no_acceptance reps' do
92+
create(:veteran_organization_representative,
93+
representative:,
94+
organization:,
95+
acceptance_mode: 'no_acceptance')
96+
97+
result = described_class.any_request_poas_for([organization])
98+
expect(result).not_to include('ABC')
99+
end
100+
101+
it 'excludes deactivated reps' do
102+
create(:veteran_organization_representative,
103+
representative:,
104+
organization:,
105+
acceptance_mode: 'any_request',
106+
deactivated_at: Time.current)
107+
108+
result = described_class.any_request_poas_for([organization])
109+
expect(result).not_to include('ABC')
110+
end
111+
end
112+
113+
it 'delegates other methods to the organization' do
114+
expect(subject.poa).to eq('ABC')
115+
expect(subject.name).to eq(organization.name)
116+
end
117+
end
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe RepresentationManagement::OrganizationWithRepContext do
6+
subject { described_class.new(organization, accepting_poas:) }
7+
8+
let(:organization) { create(:organization, poa: 'ABC', can_accept_digital_poa_requests: true) }
9+
let(:representative) { create(:representative, representative_id: '12345') }
10+
let(:accepting_poas) { described_class.accepting_poas_for(representative) }
11+
12+
describe '#can_accept_digital_poa_requests' do
13+
context 'when the organization cannot accept digital POA requests' do
14+
let(:organization) { create(:organization, poa: 'ABC', can_accept_digital_poa_requests: false) }
15+
16+
it 'returns false' do
17+
expect(subject.can_accept_digital_poa_requests).to be false
18+
end
19+
end
20+
21+
context 'when the organization can accept digital POA requests' do
22+
context 'when no organization_representative record exists' do
23+
it 'returns false' do
24+
expect(subject.can_accept_digital_poa_requests).to be false
25+
end
26+
end
27+
28+
context 'when the organization_representative has acceptance_mode any_request' do
29+
before do
30+
create(:veteran_organization_representative,
31+
representative:,
32+
organization:,
33+
acceptance_mode: 'any_request')
34+
end
35+
36+
it 'returns true' do
37+
expect(subject.can_accept_digital_poa_requests).to be true
38+
end
39+
end
40+
41+
context 'when the organization_representative has acceptance_mode self_only' do
42+
before do
43+
create(:veteran_organization_representative,
44+
representative:,
45+
organization:,
46+
acceptance_mode: 'self_only')
47+
end
48+
49+
it 'returns true' do
50+
expect(subject.can_accept_digital_poa_requests).to be true
51+
end
52+
end
53+
54+
context 'when the organization_representative has acceptance_mode no_acceptance' do
55+
before do
56+
create(:veteran_organization_representative,
57+
representative:,
58+
organization:,
59+
acceptance_mode: 'no_acceptance')
60+
end
61+
62+
it 'returns false' do
63+
expect(subject.can_accept_digital_poa_requests).to be false
64+
end
65+
end
66+
67+
context 'when a different rep has an active any_request record for the same org' do
68+
before do
69+
other_rep = create(:representative, representative_id: '99999')
70+
create(:veteran_organization_representative,
71+
representative: other_rep,
72+
organization:,
73+
acceptance_mode: 'any_request')
74+
end
75+
76+
it 'returns false' do
77+
expect(subject.can_accept_digital_poa_requests).to be false
78+
end
79+
end
80+
81+
context 'when the organization_representative is deactivated' do
82+
before do
83+
create(:veteran_organization_representative,
84+
representative:,
85+
organization:,
86+
acceptance_mode: 'any_request',
87+
deactivated_at: Time.current)
88+
end
89+
90+
it 'returns false' do
91+
expect(subject.can_accept_digital_poa_requests).to be false
92+
end
93+
end
94+
end
95+
end
96+
97+
describe '.accepting_poas_for' do
98+
it 'returns a set of POAs with active non-no_acceptance reps' do
99+
create(:veteran_organization_representative,
100+
representative:,
101+
organization:,
102+
acceptance_mode: 'any_request')
103+
104+
result = described_class.accepting_poas_for(representative)
105+
expect(result).to be_a(Set)
106+
expect(result).to include('ABC')
107+
end
108+
109+
it 'excludes no_acceptance reps' do
110+
create(:veteran_organization_representative,
111+
representative:,
112+
organization:,
113+
acceptance_mode: 'no_acceptance')
114+
115+
result = described_class.accepting_poas_for(representative)
116+
expect(result).not_to include('ABC')
117+
end
118+
119+
it 'excludes deactivated reps' do
120+
create(:veteran_organization_representative,
121+
representative:,
122+
organization:,
123+
acceptance_mode: 'any_request',
124+
deactivated_at: Time.current)
125+
126+
result = described_class.accepting_poas_for(representative)
127+
expect(result).not_to include('ABC')
128+
end
129+
end
130+
131+
it 'delegates other methods to the organization' do
132+
expect(subject.poa).to eq('ABC')
133+
expect(subject.name).to eq(organization.name)
134+
end
135+
end

0 commit comments

Comments
 (0)