diff --git a/app/decorators/organization_decorator.rb b/app/decorators/organization_decorator.rb index ea2efce6b..97a0c134e 100644 --- a/app/decorators/organization_decorator.rb +++ b/app/decorators/organization_decorator.rb @@ -44,6 +44,18 @@ def program_status_badge(status = object.program_status) class: "inline-flex shrink-0 items-center justify-center w-5 h-5 rounded-full border text-xs font-semibold #{self.class.program_status_classes(status)}") end + # The org form's type dropdown offers Organization::AGENCY_TYPES. A record may + # still hold a value that is no longer offered (e.g. the pre-rename legacy label + # "Other (please specify below)"); rendered as-is the select finds no match and + # an untouched save would silently reclassify the org as the first option. Fold + # any unrecognized non-blank value into the catch-all "Other". Blank stays blank. + def agency_type_option + return object.agency_type if object.agency_type.blank? + return object.agency_type if Organization::AGENCY_TYPES.include?(object.agency_type) + + Organization::AGENCY_TYPE_OTHER + end + def detail(length: nil) length ? description&.truncate(length) : description end diff --git a/app/models/organization.rb b/app/models/organization.rb index b1ed44097..fb579164c 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -31,6 +31,14 @@ class Organization < ApplicationRecord saver: { quality: 80 } end + # The organization classifications offered by the org form and the registration + # form's "Organization Type" question, in display order. "Other" is the generic + # catch-all; any stored value not in this list (e.g. a legacy label like the + # pre-rename "Other (please specify below)") is folded into it for display so an + # unmatched select can't silently save as the first option. + AGENCY_TYPE_OTHER = "Other" + AGENCY_TYPES = [ "501c3/nonprofit", "For-profit", "Government agency", AGENCY_TYPE_OTHER ].freeze + # Validations validates :logo, content_type: %w[image/png image/jpeg image/webp], diff --git a/app/services/event_registration_services/public_registration.rb b/app/services/event_registration_services/public_registration.rb index fa1110dff..0930c1b8e 100644 --- a/app/services/event_registration_services/public_registration.rb +++ b/app/services/event_registration_services/public_registration.rb @@ -187,7 +187,24 @@ def sync_person_profile(person) def sync_organization_profile(organization) apply_value(organization, :website_url, field_value("agency_website")) - apply_value(organization, :agency_type, field_value("agency_type")) + sync_agency_type(organization) + end + + # The "Organization Type" answer folds an "Other" choice's free text in as + # "Other: " (a specify option). Split the option label from the typed + # text: the label drives agency_type and the stripped text fills + # agency_type_other, which is cleared for the non-"Other" classifications so + # no stale free text lingers. Follows the same latest-wins / never-clobber-on- + # blank contract as apply_value. + def sync_agency_type(organization) + raw = field_value("agency_type")&.strip + return if raw.blank? + + label, _separator, specified = raw.partition(":") + label = label.strip + return if label.blank? + other_text = FormField.other_option?(label) ? specified.strip.presence : nil + organization.update!(agency_type: label, agency_type_other: other_text) end # Write value onto attribute when a non-blank value was submitted, overwriting diff --git a/app/services/form_builder_service.rb b/app/services/form_builder_service.rb index daa33f572..5144d3dfa 100644 --- a/app/services/form_builder_service.rb +++ b/app/services/form_builder_service.rb @@ -406,10 +406,7 @@ def build_person_contact_info_fields(form, position) key: "agency_website", group: "person_contact_info", required: false) position = add_field(form, position, "Organization Type", :single_select_radio, key: "agency_type", group: "person_contact_info", required: false, - options: [ - "501c3/nonprofit", "For-profit", "Government agency", - "Other (please specify below)" - ]) + options: Organization::AGENCY_TYPES) position = add_field(form, position, "Organization Street Address", :free_form_input_one_line, key: "agency_street", group: "person_contact_info", required: false) position = add_field(form, position, "Organization City", :free_form_input_one_line, diff --git a/app/views/organizations/_form.html.erb b/app/views/organizations/_form.html.erb index 76b865bde..855477e16 100644 --- a/app/views/organizations/_form.html.erb +++ b/app/views/organizations/_form.html.erb @@ -105,19 +105,15 @@
+ <% agency_type_option = f.object.decorate.agency_type_option %> <%= f.input :agency_type, label: "Organization Type", as: :select, required: true, - collection: [ - "501c3/nonprofit", - "For-profit", - "Government agency", - "Other (please specify below)" - ], - selected: f.object.agency_type, + collection: Organization::AGENCY_TYPES, + selected: agency_type_option, input_html: { - value: f.object.agency_type, + value: agency_type_option, class: "rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" } %> <%= f.input :agency_type_other, diff --git a/spec/decorators/organization_decorator_spec.rb b/spec/decorators/organization_decorator_spec.rb index 3b487d222..638447c9b 100644 --- a/spec/decorators/organization_decorator_spec.rb +++ b/spec/decorators/organization_decorator_spec.rb @@ -41,4 +41,21 @@ expect(organization.decorate.program_status_badge(nil)).to be_nil end end + + describe "#agency_type_option" do + it "returns a recognized type unchanged" do + organization = create(:organization, agency_type: "For-profit") + expect(organization.decorate.agency_type_option).to eq("For-profit") + end + + it "folds a legacy 'Other' label into the catch-all 'Other'" do + organization = create(:organization, agency_type: "Other (please specify below)") + expect(organization.decorate.agency_type_option).to eq("Other") + end + + it "leaves a blank value blank" do + organization = create(:organization, agency_type: "") + expect(organization.decorate.agency_type_option).to eq("") + end + end end diff --git a/spec/services/event_registration_services/public_registration_spec.rb b/spec/services/event_registration_services/public_registration_spec.rb index cca9cdd20..76ec84f6f 100644 --- a/spec/services/event_registration_services/public_registration_spec.rb +++ b/spec/services/event_registration_services/public_registration_spec.rb @@ -315,6 +315,59 @@ def register_with_org(extra) end end + describe "organization type sync" do + let!(:organization) { create(:organization, name: "Helping Hands") } + + def register_with_agency_type(value) + params = base_form_params(first_name: "Sam", last_name: "Rowe", email: "sam@example.com").merge( + field_id(described_class::ORGANIZATION_NAME_IDENTIFIER) => "Helping Hands", + field_id("agency_type") => value + ) + described_class.call(event: event, form: form, form_params: params) + organization.reload + end + + it "folds an 'Other' answer into agency_type and the stripped free text into agency_type_other" do + register_with_agency_type("Other: Equine therapy") + + expect(organization.agency_type).to eq("Other") + expect(organization.agency_type_other).to eq("Equine therapy") + end + + it "stores the answer as 'Other: ' on the form submission, like other specify options" do + register_with_agency_type("Other: Equine therapy") + + answer = FormAnswer.joins(:form_field) + .find_by(form_fields: { field_identifier: "agency_type" }) + expect(answer.submitted_answer).to eq("Other: Equine therapy") + end + + it "stores a non-'Other' classification with no agency_type_other" do + register_with_agency_type("501c3/nonprofit") + + expect(organization.agency_type).to eq("501c3/nonprofit") + expect(organization.agency_type_other).to be_nil + end + + it "overwrites a previously stored type with the latest registrant's answer" do + organization.update!(agency_type: "501c3/nonprofit", agency_type_other: nil) + + register_with_agency_type("Other: Equine therapy") + + expect(organization.agency_type).to eq("Other") + expect(organization.agency_type_other).to eq("Equine therapy") + end + + it "clears a stale agency_type_other when the latest answer is no longer 'Other'" do + organization.update!(agency_type: "Other", agency_type_other: "Equine therapy") + + register_with_agency_type("Government agency") + + expect(organization.agency_type).to eq("Government agency") + expect(organization.agency_type_other).to be_nil + end + end + describe "matching an existing registrant by name" do it "matches a person stored under a nickname when the registrant types their legal first name" do existing = create(:person, first_name: "Bob", legal_first_name: "Robert", diff --git a/spec/services/form_builder_service_spec.rb b/spec/services/form_builder_service_spec.rb index b6717e268..ea67bd2aa 100644 --- a/spec/services/form_builder_service_spec.rb +++ b/spec/services/form_builder_service_spec.rb @@ -75,9 +75,14 @@ it "offers the agency type as the four organization classifications" do field = form.form_fields.find_by(field_identifier: "agency_type") expect(field.answer_options.pluck(:name)).to contain_exactly( - "501c3/nonprofit", "For-profit", "Government agency", "Other (please specify below)" + "501c3/nonprofit", "For-profit", "Government agency", "Other" ) end + + it "treats the agency type 'Other' as a specify option that reveals a free-text box" do + field = form.form_fields.find_by(field_identifier: "agency_type") + expect(field.specify_option_labels).to contain_exactly("Other") + end end context "scholarship section" do