Skip to content
Open
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
36 changes: 28 additions & 8 deletions app/services/event_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ def scholarship_recipient_count
HEADER_SECTOR_KEY = "sector".freeze

# Active registrants who requested a scholarship for this event, as Person
# records sorted by display name. Sectors, age-range tags, and affiliations are
# preloaded for the recipients page header; their application answers appear
# below it.
# records sorted by display name. Sectors, age-range tags, and affiliations β†’
# organization β†’ addresses are preloaded for the recipients page header (job
# title, program org, and its city/state add no per-row queries); their
# application answers appear below it.
def scholarship_applicants
@scholarship_applicants ||= Person
.where(id: scholarship_applicant_ids)
.includes(:sectors, { categories: :category_type },
{ categorizable_items: { category: :category_type } },
{ affiliations: :organization })
{ affiliations: { organization: :addresses } })
.sort_by(&:name)
end

Expand Down Expand Up @@ -99,14 +100,18 @@ def header_answer_key(identifier)

# Shout outs for the recipients page: each active registrant the admin flagged
# for a shout-out who also has shout-out text on their profile, paired with that
# text, their first active affiliated organization (if any), and their primary
# sector / age group (from their profile) for the parenthetical after their name.
# Flagged registrants with blank shout-out text are omitted; org/sector/age are optional.
# text, the organization to credit, and their primary sector / age group (from
# their profile) for the parenthetical after their name. The credited org
# prefers their real job, then their AWBW Facilitator role, then the org
# snapshotted on their registration for this event. Flagged registrants with
# blank shout-out text are omitted; org/sector/age are optional.
def shoutouts
@shoutouts ||= shoutout_registrants.filter_map do |person|
text = person.shoutout_text.to_s.strip.presence
next unless text
organization = person.affiliations.reject(&:inactive?).filter_map(&:organization).first
organization = person.job_affiliation&.organization ||
person.facilitator_affiliation&.organization ||
registration_org_by_registrant[person.id]
Shoutout.new(
recipient: person,
organization: organization,
Expand All @@ -117,6 +122,21 @@ def shoutouts
end
end

# The organization snapshotted on each shout-out registrant's registration for
# this event (their active affiliations at registration time), keyed by Person
# id. The last fallback for the shout-out credit when a recipient currently
# holds neither a job nor a facilitator affiliation. Preloaded so the shout-out
# list adds no per-row queries.
def registration_org_by_registrant
@registration_org_by_registrant ||= active_registrations
.where(registrant_id: shoutout_registrant_ids)
.includes(event_registration_organizations: :organization)
.each_with_object({}) do |registration, map|
org = registration.event_registration_organizations.filter_map(&:organization).first
map[registration.registrant_id] ||= org if org
end
end

# The scholarship record per recipient, keyed by Person id β€” lets the roster
# decide whether to flag a registrant as a scholarship recipient. First
# scholarship wins if a person has several.
Expand Down
29 changes: 16 additions & 13 deletions app/views/events/recipients.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

<% answers_by_applicant = @dashboard.scholarship_answers_by_applicant %>
<% header_answers = @dashboard.header_answers_by_applicant %>
<% event_date = @event.start_date %>

<div class="max-w-7xl mx-auto bg-white border border-gray-200 rounded-xl shadow p-4 sm:p-6">
<%# Top bar: back link + sub-nav, matching the other event pages %>
Expand Down Expand Up @@ -61,11 +60,12 @@
<%# Prefer the registrant's form answers; fall back to their profile data. %>
<% sector_text = resolve_answer_text(sector_answer&.form_field, sector_answer&.submitted_answer).presence || person.sectors.map(&:name).join(", ").presence %>
<% age_group_text = resolve_answer_text(age_group_answer&.form_field, age_group_answer&.submitted_answer).presence || person.categories.select { |c| c.category_type&.name == "AgeRange" }.map(&:name).join(", ").presence %>
<%# The affiliation active at the time of the event, excluding facilitator roles. %>
<% recipient_affiliations = person.affiliations.select { |a|
!a.facilitator? &&
(event_date.blank? || ((a.start_date.nil? || a.start_date <= event_date) && (a.end_date.nil? || a.end_date >= event_date)))
} %>
<%# Program org + its city/state come from the recipient's AWBW Facilitator
affiliation; the job title comes from their real-job (non-facilitator)
affiliation. See Person#facilitator_organization / #job_affiliation. %>
<% program_org = person.facilitator_organization %>
<% job_title = person.job_affiliation&.title %>
<% program_location = program_org&.program_location %>

<div id="<%= "participant-#{participant_slug}" if participant_slug %>"
class="scroll-mt-24 bg-white border <%= DomainTheme.border_class_for(:scholarships) %> rounded-xl shadow-sm overflow-hidden break-inside-avoid"
Expand Down Expand Up @@ -107,21 +107,24 @@
<div class="min-w-0">
<%= link_to person.name, person_path(person), data: { turbo_frame: "_top" },
class: "text-lg font-bold text-gray-900 hover:underline" %>
<% recipient_affiliations.each do |affiliation| %>
<% if program_org || job_title.present? %>
<div class="text-sm text-gray-600 leading-tight">
<% if affiliation.organization %>
<% if allowed_to?(:show?, affiliation.organization) %>
<%= link_to affiliation.organization.name, organization_path(affiliation.organization),
<% if program_org %>
<% if allowed_to?(:show?, program_org) %>
<%= link_to program_org.name, organization_path(program_org),
data: { turbo_frame: "_top" },
class: "font-medium text-gray-700 hover:text-gray-900 hover:underline" %>
<% else %>
<span class="font-medium text-gray-700"><%= affiliation.organization.name %></span>
<span class="font-medium text-gray-700"><%= program_org.name %></span>
<% end %>
<% end %>
<% if affiliation.organization && affiliation.title.present? %><span class="text-gray-300">Β·</span><% end %>
<% if affiliation.title.present? %><span><%= affiliation.title %></span><% end %>
<% if program_org && job_title.present? %><span class="text-gray-300">Β·</span><% end %>
<% if job_title.present? %><span><%= job_title %></span><% end %>
</div>
<% end %>
<% if program_location.present? %>
<div class="text-sm text-gray-500 leading-tight"><%= program_location %></div>
<% end %>
</div>

<%# Serves / Ages β€” parallel with the name %>
Expand Down
5 changes: 3 additions & 2 deletions app/views/events/staff.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
<div class="flex flex-wrap justify-center gap-6">
<% @event_staffs.each do |event_staff| %>
<% person = event_staff.person.decorate %>
<% active_affiliation = person.affiliations.find { |a| !a.inactive? } %>
<% org = active_affiliation&.organization %>
<%# The staffer's real-job organization, falling back to the org they
facilitate for when they have no separate job affiliation. %>
<% org = person.job_affiliation&.organization || person.facilitator_organization %>
<% bio = event_staff.public_bio %>
<%# Primary sector first (starred, darker chip), then the rest alphabetically β€”
the same treatment as the recipients page. %>
Expand Down
42 changes: 34 additions & 8 deletions spec/requests/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,27 @@ def submit_agency_name(name)
expect(response.body).to include("Attending")
end

it "shows the staffer's real-job organization on their card" do
job_org = create(:organization, name: "County Health")
create(:affiliation, person: staff_member, organization: job_org, title: "Social Worker")
create(:affiliation, person: staff_member, organization: job_org, title: "Facilitator")
create(:event_staff, event: public_event, person: staff_member)

get staff_event_path(public_event)

expect(response.body).to include("County Health")
end

it "falls back to the facilitator org when the staffer has no separate job affiliation" do
facilitator_org = create(:organization, name: "Prevail")
create(:affiliation, person: staff_member, organization: facilitator_org, title: "Facilitator")
create(:event_staff, event: public_event, person: staff_member)

get staff_event_path(public_event)

expect(response.body).to include("Prevail")
end

context "when the event has ended" do
let(:ended_event) { create(:event, :published, :ended) }

Expand Down Expand Up @@ -1505,19 +1526,24 @@ def submit_agency_name(name)
expect(response.body).to include("Mark tasks complete")
end

it "shows the non-facilitator affiliation's title and linked organization, excluding facilitator roles" do
org = create(:organization, name: "Safe Harbor of Sheboygan")
create(:affiliation, person: applicant, organization: org,
title: "Prevention, Education, and Outreach Specialist", start_date: 1.year.ago)
create(:affiliation, person: applicant, organization: create(:organization, name: "Facilitator Org"),
it "shows the program org (from the facilitator affiliation) with its city/state and the real-job title" do
# A recipient holds two affiliations to the same org: their real job and
# their AWBW Facilitator role. The header shows the program org + its
# city/state (facilitator side) and the job title (real-job side).
program_org = create(:organization, name: "Safe Harbor of Sheboygan")
create(:address, addressable: program_org, city: "Sheboygan", state: "WI")
create(:affiliation, person: applicant, organization: program_org,
title: "Facilitator", start_date: 1.year.ago)
create(:affiliation, person: applicant, organization: program_org,
title: "Prevention, Education, and Outreach Specialist", start_date: 1.year.ago)

get recipients_event_path(event)

expect(response.body).to include("Prevention, Education, and Outreach Specialist")
expect(response.body).to include("Safe Harbor of Sheboygan")
expect(response.body).to include(organization_path(org))
expect(response.body).not_to include("Facilitator Org")
expect(response.body).to include(organization_path(program_org))
expect(response.body).to include("Sheboygan, WI")
expect(response.body).to include("Prevention, Education, and Outreach Specialist")
expect(response.body).not_to include("Facilitator")
end

it "excludes registrants who did not request a scholarship" do
Expand Down
19 changes: 19 additions & 0 deletions spec/services/event_dashboard_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,25 @@ def opt_in(person, text:)
expect(shoutout.organization).to eq(org)
end

it "prefers the real-job affiliation's org over the AWBW Facilitator role" do
job_org = create(:organization, name: "County Health")
opt_in(embedded_applicant, text: "Honored to serve.")
create(:affiliation, person: embedded_applicant, organization: org, title: "Facilitator")
create(:affiliation, person: embedded_applicant, organization: job_org, title: "Social Worker")

shoutout = dashboard.shoutouts.find { |s| s.recipient == embedded_applicant }
expect(shoutout.organization).to eq(job_org)
end

it "falls back to the registration's snapshotted org when the recipient has no affiliations" do
opt_in(separate_applicant, text: "Art has been my way through.")
registration = EventRegistration.find_by(event: event, registrant: separate_applicant)
create(:event_registration_organization, event_registration: registration, organization: org)

shoutout = dashboard.shoutouts.find { |s| s.recipient == separate_applicant }
expect(shoutout.organization).to eq(org)
end

it "exposes the registrant's primary sector and age group" do
age_range = create(:category_type, name: "AgeRange")
embedded_applicant.sectorable_items.create!(sector: create(:sector, name: "Sexual Assault"), is_primary: true)
Expand Down