From 01e5febc00dc065179df5812e234bae40d7a4e3f Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Wed, 12 Aug 2026 11:41:11 -0400 Subject: [PATCH 1/3] Affiliations: default start to the actual date, not first of month The facilitator affiliation minted on registration was dated to the first of the training's month, which read as "affiliated since the month before" when a training landed early in a month. Start it on the training date itself instead. Likewise default the manual "+ Add Affiliation" row to today rather than the first of the current month. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/models/affiliation.rb | 2 +- .../affiliation_services/create_from_registration.rb | 12 ++++++------ app/views/affiliations/_fields.html.erb | 2 +- .../create_from_registration_spec.rb | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) 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/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/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 From 48b120c78f1908891944b26c69ded09956373b50 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Wed, 12 Aug 2026 11:42:02 -0400 Subject: [PATCH 2/3] Update org edit view spec for the new today default Co-Authored-By: Claude Opus 4.8 (1M context) --- spec/views/organizations/edit.html.erb_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 4a16be5b0a787a3ff21a66e4ec0f376a3a93efee Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Thu, 13 Aug 2026 19:48:47 -0400 Subject: [PATCH 3/3] Org program status: classify as of the exact training date Now that facilitator affiliations start on the training date rather than the first of its month, a facilitator affiliation started earlier that same month should still count toward the org's activity. Reference the actual training date instead of the 1st of the month. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/models/event_registration.rb | 14 ++++++++------ spec/models/event_registration_spec.rb | 13 +++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) 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/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