diff --git a/app/models/affiliation.rb b/app/models/affiliation.rb index b35069b5de..2bd884dcc2 100644 --- a/app/models/affiliation.rb +++ b/app/models/affiliation.rb @@ -15,7 +15,7 @@ class Affiliation < ApplicationRecord # Not flagged inactive and not past its end date. Includes affiliations whose # start_date is still in the future (e.g. a Facilitator affiliation dated to an - # upcoming training's month) — they are "pending" but counted here. + # upcoming training) — they are "pending" but counted here. scope :active_or_pending, -> { where(inactive: false) .where("affiliations.end_date IS NULL OR affiliations.end_date >= ?", Date.current) diff --git a/app/models/event_registration.rb b/app/models/event_registration.rb index bd1a50f2da..ea0461428a 100644 --- a/app/models/event_registration.rb +++ b/app/models/event_registration.rb @@ -743,13 +743,15 @@ def sync_attendance_status_to_days! end # Program status(es) for THIS registration only: classify each organization - # linked to the registration as of the training date (the 1st of the event's - # month), excluding the registrant's own facilitator affiliation to that org so - # the status reflects whether the *org* was already a facilitator program when - # they joined. Distinct, so one linked org shows one badge — unlike the - # registrant-wide rollup, this ignores affiliations to other organizations. + # linked to the registration as of the training date, excluding the registrant's + # own facilitator affiliation to that org so the status reflects whether the *org* + # was already a facilitator program when they joined. Using the actual training + # date (not the 1st of its month) means a facilitator affiliation started earlier + # that same month still counts toward the org's activity. Distinct, so one linked + # org shows one badge — unlike the registrant-wide rollup, this ignores + # affiliations to other organizations. def program_statuses - reference_date = (event&.start_date&.to_date || Date.current).beginning_of_month + reference_date = event&.start_date&.to_date || Date.current organizations.filter_map do |organization| own = registrant.affiliations.find { |affiliation| affiliation.organization_id == organization.id && affiliation.facilitator? } organization.facilitator_status_on(reference_date, excluding_affiliation_id: own&.id) diff --git a/app/services/affiliation_services/create_from_registration.rb b/app/services/affiliation_services/create_from_registration.rb index 28cbf1fb2f..839046b041 100644 --- a/app/services/affiliation_services/create_from_registration.rb +++ b/app/services/affiliation_services/create_from_registration.rb @@ -16,11 +16,11 @@ module AffiliationServices # facilitator-ish job title that isn't exactly "Facilitator" (e.g. "Lead # Facilitator") still gets its own standing "Facilitator" affiliation alongside it. # - # Start dates: the facilitator affiliation begins on the first day of the - # training's month (that's when they become a facilitator). The job affiliation - # is left without a start date — we don't know when the person began that role - # (they may have been with the org for years before this training), and dating it - # to registration would misrepresent that. + # Start dates: the facilitator affiliation begins on the training date itself + # (that's when they become a facilitator). The job affiliation is left without a + # start date — we don't know when the person began that role (they may have been + # with the org for years before this training), and dating it to registration + # would misrepresent that. class CreateFromRegistration def self.call(person:, organization:, job_title: nil, training_date: nil, organization_address: nil) new(person:, organization:, job_title:, training_date:, organization_address:).call @@ -82,7 +82,7 @@ def create_affiliation(title, start_date:) end def facilitator_start_date - (@training_date || Date.current).to_date.beginning_of_month + (@training_date || Date.current).to_date end def active_or_pending_affiliations_with_title(title) diff --git a/app/views/affiliations/_fields.html.erb b/app/views/affiliations/_fields.html.erb index 17073012a7..9c3b19b3b5 100644 --- a/app/views/affiliations/_fields.html.erb +++ b/app/views/affiliations/_fields.html.erb @@ -69,7 +69,7 @@ label_html: { class: "block text-sm font-medium text-gray-700 mb-1" }, input_html: { type: "date", - value: (f.object.start_date || (Date.current.beginning_of_month unless f.object.persisted?))&.strftime("%Y-%m-%d"), + value: (f.object.start_date || (Date.current unless f.object.persisted?))&.strftime("%Y-%m-%d"), class: "rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500 text-sm", data: { action: "change->affiliation-dates#recalculate" } } %> diff --git a/spec/models/event_registration_spec.rb b/spec/models/event_registration_spec.rb index 3efd911b00..70426d6682 100644 --- a/spec/models/event_registration_spec.rb +++ b/spec/models/event_registration_spec.rb @@ -1134,6 +1134,19 @@ def registration_for(person) expect(registration.reload.program_statuses).to eq([ :ongoing ]) end + + it "counts a facilitator affiliation started earlier the same month as the training" do + event = create(:event, start_date: Date.new(2026, 6, 20)) + reg = create(:event_registration, event: event) + create(:event_registration_organization, event_registration: reg, organization: linked_org) + # Earlier that same month, before the training date — still counts as ongoing. + create(:affiliation, organization: linked_org, title: "Facilitator", + start_date: Date.new(2026, 6, 5), end_date: nil) + create(:affiliation, organization: linked_org, person: reg.registrant, + title: "Facilitator", start_date: Date.new(2026, 6, 20)) + + expect(reg.reload.program_statuses).to eq([ :ongoing ]) + end end describe "onboarding checklist" do diff --git a/spec/services/affiliation_services/create_from_registration_spec.rb b/spec/services/affiliation_services/create_from_registration_spec.rb index 7ab64c326b..e17775c34f 100644 --- a/spec/services/affiliation_services/create_from_registration_spec.rb +++ b/spec/services/affiliation_services/create_from_registration_spec.rb @@ -144,18 +144,18 @@ def titles expect(job.start_date).to be_nil end - it "starts the facilitator affiliation on the first day of the training's month" do + it "starts the facilitator affiliation on the training date" do described_class.call(person: person, organization: organization, job_title: "Counselor", training_date: Date.new(2026, 9, 17)) facilitator = person.affiliations.find_by(organization: organization, title: "Facilitator") - expect(facilitator.start_date).to eq(Date.new(2026, 9, 1)) + expect(facilitator.start_date).to eq(Date.new(2026, 9, 17)) end - it "falls back to the current month for the facilitator affiliation when no training date is given" do + it "falls back to today for the facilitator affiliation when no training date is given" do described_class.call(person: person, organization: organization, job_title: nil) facilitator = person.affiliations.find_by(organization: organization, title: "Facilitator") - expect(facilitator.start_date).to eq(Date.current.beginning_of_month) + expect(facilitator.start_date).to eq(Date.current) end end diff --git a/spec/views/organizations/edit.html.erb_spec.rb b/spec/views/organizations/edit.html.erb_spec.rb index c706dd03a3..807628c2f7 100644 --- a/spec/views/organizations/edit.html.erb_spec.rb +++ b/spec/views/organizations/edit.html.erb_spec.rb @@ -75,11 +75,11 @@ def org_with_status(name) end describe "new affiliation defaults" do - it "defaults the start date to the first of the current month and leaves primary contact unchecked" do + it "defaults the start date to today and leaves primary contact unchecked" do organization.affiliations.build render assert_select "input[name*='start_date'][value=?]", - Date.current.beginning_of_month.strftime("%Y-%m-%d") + Date.current.strftime("%Y-%m-%d") assert_select "input[type=checkbox][name*='primary_contact']" assert_select "input[type=checkbox][name*='primary_contact'][checked]", false end