diff --git a/app/decorators/person_decorator.rb b/app/decorators/person_decorator.rb index 82b867eb0..eca000090 100644 --- a/app/decorators/person_decorator.rb +++ b/app/decorators/person_decorator.rb @@ -25,12 +25,52 @@ def default_display_image "missing.png" end + def facilitator_since_date + facilitator_affiliations = affiliations.where("title LIKE ?", "%Facilitator%") + facilitator_affiliations.minimum(:start_date) || member_since + end + + def affiliated_since_date + affiliations.minimum(:start_date) + end + + def affiliated_end_date + return nil if affiliations.active.exists? + affiliations.maximum(:end_date) + end + + def facilitator_end_date + facilitator_affiliations = affiliations.where("title LIKE ?", "%Facilitator%") + return nil if facilitator_affiliations.active.exists? + facilitator_affiliations.maximum(:end_date) + end + def member_since_year - member_since ? member_since.year : nil + facilitator_since_date&.year + end + + def member_since_earlier_than_facilitator_affiliations? + earliest_facilitator = affiliations.where("title LIKE ?", "%Facilitator%").minimum(:start_date) + member_since.present? && earliest_facilitator.present? && member_since.beginning_of_month < earliest_facilitator.beginning_of_month + end + + def member_since_earlier_than_all_affiliations? + earliest = affiliations.minimum(:start_date) + member_since.present? && earliest.present? && member_since.beginning_of_month < earliest.beginning_of_month + end + + def member_since_differs_from_facilitator_affiliations? + earliest_facilitator = affiliations.where("title LIKE ?", "%Facilitator%").minimum(:start_date) + member_since.present? && earliest_facilitator.present? && member_since.beginning_of_month != earliest_facilitator.beginning_of_month + end + + def member_since_differs_from_all_affiliations? + earliest = affiliations.minimum(:start_date) + member_since.present? && earliest.present? && member_since.beginning_of_month != earliest.beginning_of_month end def badges - earliest = affiliations.minimum(:start_date) || member_since + earliest = facilitator_since_date years = earliest ? (Time.zone.now.year - earliest.year) : nil badges = [] badges << badge("Legacy Facilitator (10+ years)", :legacy_facilitator) if years && years >= 10 @@ -43,6 +83,9 @@ def badges badges << badge("Story Author", :stories) if user&.stories_as_creator&.any? badges << badge("Sector Leader", :sectors) if sectorable_items.where(is_leader: true).any? badges << badge("Blog Contributor", :blog_contributor) if blog_contributor? + if affiliated_since_date.present? && affiliated_since_date != facilitator_since_date + badges << badge("Affiliated since #{affiliated_since_date.strftime('%B %Y')}", :affiliated_person) + end badges end diff --git a/app/models/organization.rb b/app/models/organization.rb index c6b6e6489..f0ebe782f 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -56,7 +56,11 @@ class Organization < ApplicationRecord # Scopes # See TagFilterable, Trendable, WindowsTypeFilterable - scope :active, -> { joins(:organization_status).where(organization_statuses: { name: "Active" }) } + scope :active, -> { + status_active = joins(:organization_status).where(organization_statuses: { name: "Active" }) + affiliation_active = where(id: Affiliation.active.select(:organization_id)) + status_active.or(affiliation_active) + } scope :address, ->(address) do return all if address.blank? terms = address.to_s.strip.split(/[\s,]+/).reject(&:blank?) @@ -141,7 +145,8 @@ def organization_locality end def published? # needed for my_bookmarks - organization_status&.name == "Active" + return true if organization_status&.name == "Active" + affiliations.active.exists? end def sector_list diff --git a/app/views/organizations/_form.html.erb b/app/views/organizations/_form.html.erb index 0196355d1..ad7d49cc8 100644 --- a/app/views/organizations/_form.html.erb +++ b/app/views/organizations/_form.html.erb @@ -72,7 +72,61 @@ + <% org_earliest_aff = f.object.persisted? ? f.object.affiliations.minimum(:start_date) : nil %> + <% org_aff_ended = f.object.persisted? && f.object.affiliations.any? && !f.object.affiliations.active.exists? %> + <% org_latest_end = f.object.persisted? ? f.object.affiliations.maximum(:end_date) : nil %> + <% org_end_date = org_aff_ended ? org_latest_end : f.object.end_date %>
+ <% if allowed_to?(:manage?, Organization) %> +
+
+ + +
+ <% if org_aff_ended || (f.object.end_date.present? && !has_affiliations) %> + + <% end %> + + <%= (org_earliest_aff || f.object.start_date)&.strftime('%b %Y') || "—" %><%= " – #{org_end_date.strftime('%b %Y')}" if org_end_date.present? %> + + <% if has_affiliations && org_earliest_aff.nil? %> + + <% elsif f.object.start_date.present? && org_earliest_aff.present? && f.object.start_date.beginning_of_month != org_earliest_aff.beginning_of_month %> +

Organization start_date: <%= f.object.start_date.strftime("%b %Y") %>

+ <% end %> +
+
+ <% unless has_affiliations %> + <%= f.input :start_date, + label: false, + as: :string, + input_html: { + type: 'date', + value: f.object.start_date&.strftime('%Y-%m-%d'), + class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-2" + } %> + <% end %> +
+ <% end %> +
<%= f.input :agency_type, label: "Agency Type", @@ -89,106 +143,82 @@ value: f.object.agency_type, class: "rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" } %> + <%= f.input :agency_type_other, + label: "Agency Type Other", + hint: "Please specify if 'Other' selected", + input_html: { + class: "rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200" + } %>
- <%= f.input :agency_type_other, - label: "Agency Type Other", - hint: "Please specify if 'Other' selected", - input_html: { - class: "rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200" - } %> - <%= f.input :email, - label: "Email", - as: :email, - input_html: { - class: "rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" - } %> +
+ <%= f.input :email, + label: "Email", + as: :email, + input_html: { + class: "rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" + } %> + <% if f.object.website_url.present? && (URI.parse(f.object.website_url) rescue nil).nil? %> + <% website_warning = " + Invalid" %> + <% end %> + <%= f.input :website_url, + label: "Website URL #{website_warning}".html_safe, + as: :string, + hint: "Enter full URL: https:// or http://".html_safe, + wrapper_html: { id: "website-url-field" }, + input_html: { + class: "rounded-md #{"bg-red-200" if website_warning.present? } border-gray-300 + shadow-sm focus:ring-blue-500 focus:border-blue-500" + } %> +
- <% if f.object.website_url.present? && (URI.parse(f.object.website_url) rescue nil).nil? %> - <% website_warning = " - Invalid" %> + <% if allowed_to?(:manage?, Organization) %> +
+ <%= f.input :internal_id, + label: "Internal Organization ID", + input_html: { + class: "rounded-md border-gray-300 bg-blue-100 shadow-sm focus:ring-blue-500 focus:border-blue-500" + } %> +
<% end %> - <%= f.input :website_url, - label: "Website URL #{website_warning}".html_safe, - as: :string, - hint: "Enter full URL: https:// or http://".html_safe, - wrapper_html: { id: "website-url-field" }, + + <%= f.input :organization_status_id, + as: :select, + collection: @organization_statuses, + include_blank: true, + label: "Status", + required: true, input_html: { - class: "rounded-md #{"bg-red-200" if website_warning.present? } border-gray-300 - shadow-sm focus:ring-blue-500 focus:border-blue-500" + class: "rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200" } %>
- - -
- <% if allowed_to?(:manage?, Organization) %> -
+ <% if params[:admin] && allowed_to?(:manage?, Organization) %> +
+
+ <%= f.input :start_date, - label: "Affiliation start date", + label: false, as: :string, - hint: automanaged_notice.to_s.html_safe, input_html: { type: 'date', value: f.object.start_date&.strftime('%Y-%m-%d'), - disabled: has_affiliations, class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" } %>
-
+
+ <%= f.input :end_date, - label: "Affiliation end date", + label: false, as: :string, - hint: automanaged_notice.to_s.html_safe, input_html: { type: 'date', value: f.object.end_date&.strftime('%Y-%m-%d'), - disabled: has_affiliations, class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" } %>
- <% if allowed_to?(:manage?, Organization) %> -
- <%= f.input :organization_status_id, - as: :select, - collection: @organization_statuses, - include_blank: true, - label: "Status", - hint: automanaged_notice.to_s.html_safe, - required: true, - input_html: { - class: "rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-200" - } %> -
- <% else %> -
- Organization status: -
- <%= f.object.organization_status&.name %> - <% end %> -
- <%= f.input :internal_id, - label: "Internal Organization ID", - input_html: { - class: "rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500" - } %> -
- <% else %> -
-
- Affiliation start date: -
- <%= f.object.start_date&.strftime('%Y-%m-%d') %> - <%= automanaged_notice.to_s.html_safe %> -
-
-
- Affiliation end date: -
- <%= f.object.end_date&.strftime('%Y-%m-%d') %> - <%= automanaged_notice.to_s.html_safe %> -
- <% end %> -
+
+ <% end %>
diff --git a/app/views/organizations/organization_results.html.erb b/app/views/organizations/organization_results.html.erb index 4a2b5e670..02257ccd2 100644 --- a/app/views/organizations/organization_results.html.erb +++ b/app/views/organizations/organization_results.html.erb @@ -42,7 +42,7 @@ <% affiliated_since = @affiliated_since[organization.id] || organization.start_date %> - <%= affiliated_since&.year %> + <%= affiliated_since&.strftime('%b %Y') %> diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb index ef98d4c78..20d88ee95 100644 --- a/app/views/organizations/show.html.erb +++ b/app/views/organizations/show.html.erb @@ -1,5 +1,5 @@ -<% content_for(:page_bg_class, "admin-or-owner") %> -
+<% content_for(:page_bg_class, "admin-or-auth") %> +
@@ -52,11 +52,34 @@ <%= [address.locality, [address.city, address.state].compact_blank.join(", "), address.district].compact_blank.join(" · ") %>

<% end %> - <% if @organization.start_date.present? %> -

- Affiliated since - <%= @organization.start_date.year %> -

+ <% org_earliest_affiliation = @organization.affiliations.minimum(:start_date) %> + <% org_affiliated_since = org_earliest_affiliation || @organization.start_date %> + <% org_show_aff_ended = @organization.affiliations.any? && !@organization.affiliations.active.exists? %> + <% org_show_end_date = org_show_aff_ended ? @organization.affiliations.maximum(:end_date) : @organization.end_date %> + <% if org_affiliated_since.present? %> +
+

+ <% if org_show_aff_ended || org_show_end_date.present? %> + + <% end %> + Affiliated since + <%= org_affiliated_since.strftime("%b %Y") %><%= " – #{org_show_end_date.strftime('%b %Y')}" if org_show_end_date.present? %> + +

+ +
<% end %>
diff --git a/app/views/people/_form.html.erb b/app/views/people/_form.html.erb index 3156c4166..660d26d2d 100644 --- a/app/views/people/_form.html.erb +++ b/app/views/people/_form.html.erb @@ -12,23 +12,89 @@
<%= f.input :first_name, input_html: { value: f.object.first_name || @user&.first_name } %><%= f.input :last_name, input_html: { value: f.object.last_name || @user&.last_name } %><%= f.input :pronouns %>
-
-
- <%= f.input :date_of_birth, - as: :date, - discard_year: true, - order: [:month, :day], - prompt: { month: "Select Month", day: "Select Day" }, - wrapper_html: { class: "flex flex-wrap gap-2 items-end" }, - label_html: { class: "w-full" }, - input_html: { - class: "flex-1 rounded-md border-gray-300 shadow-sm - focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" - } %> + <% person = f.object.respond_to?(:object) ? f.object.object : f.object %> + <% decorated = person.decorate %> +

Important dates

+
+
+
+ + +
+ <% if decorated.affiliated_end_date %> + + <% end %> + <%= decorated.affiliated_since_date&.strftime("%b %Y") || "—" %><%= " – #{decorated.affiliated_end_date.strftime('%b %Y')}" if decorated.affiliated_end_date %> + <% if decorated.affiliated_since_date.nil? && person.affiliations.exists? %> + + <% elsif decorated.member_since_earlier_than_all_affiliations? %> +

⚠ Earlier date on file: <%= person.member_since.strftime("%b %Y") %>

+ <% elsif decorated.member_since_differs_from_all_affiliations? %> +

Different date on file: <%= person.member_since.strftime("%b %Y") %>

+ <% end %> +
+
+
+ + +
+ <% if decorated.facilitator_end_date %> + + <% end %> + <%= decorated.facilitator_since_date&.strftime("%b %Y") || "—" %><%= " – #{decorated.facilitator_end_date.strftime('%b %Y')}" if decorated.facilitator_end_date %> + <% if decorated.member_since_earlier_than_facilitator_affiliations? %> +

⚠ Earlier date on file: <%= person.member_since.strftime("%b %Y") %>

+ <% elsif decorated.member_since_differs_from_facilitator_affiliations? %> +

Different date on file: <%= person.member_since.strftime("%b %Y") %>

+ <% end %> +
+
+
+ <%= f.input :date_of_birth, + as: :date, + discard_year: true, + order: [:month, :day], + prompt: { month: "Select Month", day: "Select Day" }, + wrapper_html: { class: "flex flex-wrap gap-2 items-end" }, + label_html: { class: "w-full" }, + input_html: { + class: "flex-1 rounded-md border-gray-300 shadow-sm + focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" + } %> +
- -
- <% if allowed_to?(:manage?, Person) && params[:admin].present? %> + <% if allowed_to?(:manage?, Person) && params[:admin].present? %> +
<%= f.input :member_since, as: :date, discard_day: true, @@ -36,7 +102,7 @@ end_year: Time.current.year, prompt: { month: "Select Month", year: "Select Year" }, order: [:month, :year], - label: "Affiliated since", + label: "Member since (override)", hint: "Pick January if you only know the year", wrapper_html: { class: "flex flex-wrap gap-2 items-end" }, label_html: { class: "w-full" }, @@ -45,12 +111,8 @@ class: "flex-1 rounded-md border-gray-300 shadow-sm admin-only bg-blue-100 border-blue-200 focus:border-blue-500 focus:ring focus:ring-blue-200 focus:ring-opacity-50" } %> - <% else %> - - <%= f.object.member_since&.to_date&.strftime("%B %Y") || "—" %> -

Auto-managed by affiliations

- <% end %> -
+
+ <% end %>
diff --git a/app/views/people/show.html.erb b/app/views/people/show.html.erb index 12845ee3f..4c48b152b 100644 --- a/app/views/people/show.html.erb +++ b/app/views/people/show.html.erb @@ -54,11 +54,18 @@ (<%= @person.pronouns %>) <% end %> - <% if @person.member_since_year.present? && @person.profile_show_member_since? %> -

- Facilitator since - <%= @person.member_since_year %> -

+ <% if @person.profile_show_member_since? %> + <% if @person.facilitator_since_date.present? %> +

+ Facilitator since + <%= @person.facilitator_since_date.strftime("%B %Y") %> +

+ <% elsif @person.affiliated_since_date.present? %> +

+ Affiliated since + <%= @person.affiliated_since_date.strftime("%B %Y") %> +

+ <% end %> <% end %>
diff --git a/lib/domain_theme.rb b/lib/domain_theme.rb index 5f186d714..d188127fb 100644 --- a/lib/domain_theme.rb +++ b/lib/domain_theme.rb @@ -38,7 +38,8 @@ module DomainTheme seasoned_facilitator: :sky, new_facilitator: :green, spotlighted_facilitator: :pink, - blog_contributor: :orange + blog_contributor: :orange, + affiliated_person: :slate } def self.color_for(key) diff --git a/spec/views/page_bg_class_alignment_spec.rb b/spec/views/page_bg_class_alignment_spec.rb index 577d81901..7cd23dbff 100644 --- a/spec/views/page_bg_class_alignment_spec.rb +++ b/spec/views/page_bg_class_alignment_spec.rb @@ -73,10 +73,10 @@ "app/views/monthly_reports/edit.html.erb" => "admin-or-owner", "app/views/notifications/show.html.erb" => "admin-or-owner", + "app/views/organizations/show.html.erb" => "admin-or-auth", + "app/views/people/show.html.erb" => "admin-or-owner-or-authsearchable", "app/views/organizations/edit.html.erb" => "admin-or-owner", - "app/views/organizations/show.html.erb" => "admin-or-owner", "app/views/people/edit.html.erb" => "admin-or-owner", - "app/views/people/show.html.erb" => "admin-or-owner-or-authsearchable", "app/views/reports/show.html.erb" => "admin-or-owner-or-member", "app/views/workshop_logs/show.html.erb" => "admin-or-owner-or-member",