diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb
new file mode 100644
index 000000000..6a8a77667
--- /dev/null
+++ b/app/controllers/organizations_controller.rb
@@ -0,0 +1,71 @@
+class OrganizationsController < ApplicationController
+ before_action :authenticate_user!, except: [ :index, :show ]
+ before_action :set_organization, only: [ :show, :edit, :update, :destroy, :annual_evaluations ]
+
+ def index
+ @organizations = Organization.all
+ end
+
+ def show
+ end
+
+ def new
+ @organization = Organization.new
+ end
+
+ def edit
+ end
+
+ def create
+ @organization = Organization.new(organization_params)
+
+ if @organization.save
+ redirect_to @organization, notice: "Organization was successfully created."
+ else
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ if @organization.update(organization_params)
+ redirect_to @organization, notice: "Organization was successfully updated."
+ else
+ render :edit, status: :unprocessable_entity
+ end
+ end
+
+ def destroy
+ @organization.destroy!
+ redirect_to organizations_url, notice: "Organization was successfully destroyed."
+ end
+
+ def annual_evaluations
+ @year = params[:year]&.to_i || Date.current.year
+ @aggregated_responses = @organization.aggregated_annual_evaluation_responses(@year)
+
+ # Get available years that have evaluations
+ all_evaluations = @organization.reports
+ .joins(form: :form_builder)
+ .where(form_builders: { name: "Annual Evaluation" })
+ .distinct
+
+ @available_years = all_evaluations.pluck(:created_at).map(&:year).uniq.sort.reverse
+ end
+
+ private
+
+ def set_organization
+ @organization = Organization.find(params[:id])
+ end
+
+ def organization_params
+ params.require(:organization).permit(
+ :name, :description, :website_url, :start_date, :end_date,
+ :agency_type, :agency_type_other, :mission_vision_values,
+ :internal_id, :filemaker_code, :inactive, :notes,
+ :project_status_id, :windows_type_id, :location_id,
+ addresses_attributes: [ :id, :street_address, :city, :state, :zip_code, :country, :_destroy ],
+ sectorable_items_attributes: [ :id, :sector_id, :_destroy ]
+ )
+ end
+end
diff --git a/app/models/organization.rb b/app/models/organization.rb
new file mode 100644
index 000000000..721f48b53
--- /dev/null
+++ b/app/models/organization.rb
@@ -0,0 +1,42 @@
+class Organization < Project
+ # Organization is an alias for Project
+ # This provides a semantic interface for organization-specific functionality
+
+ # Get all annual evaluation responses for a specific year
+ def annual_evaluations_for_year(year)
+ return Report.none unless year.present?
+
+ start_date = Date.new(year.to_i, 1, 1)
+ end_date = Date.new(year.to_i, 12, 31).end_of_day
+
+ reports
+ .joins(form: :form_builder)
+ .where(form_builders: { name: "Annual Evaluation" })
+ .where(created_at: start_date..end_date)
+ .distinct
+ end
+
+ # Get aggregated responses by form field for a year
+ def aggregated_annual_evaluation_responses(year)
+ evaluations = annual_evaluations_for_year(year)
+ return {} if evaluations.empty?
+
+ form_builder = FormBuilder.find_by(name: "Annual Evaluation")
+ return {} unless form_builder
+
+ form = form_builder.forms.first
+ return {} unless form
+
+ # Group responses by form field
+ form.form_fields.active.order(position: :desc).map do |field|
+ responses = ReportFormFieldAnswer
+ .where(report_id: evaluations.pluck(:id), form_field_id: field.id)
+ .includes(:answer_option, report: :user)
+
+ {
+ form_field: field,
+ responses: responses
+ }
+ end
+ end
+end
diff --git a/app/views/organizations/_form.html.erb b/app/views/organizations/_form.html.erb
new file mode 100644
index 000000000..94a95ca0c
--- /dev/null
+++ b/app/views/organizations/_form.html.erb
@@ -0,0 +1,66 @@
+<%= form_with(model: organization, local: true) do |form| %>
+ <% if organization.errors.any? %>
+
+
<%= pluralize(organization.errors.count, "error") %> prohibited this organization from being saved:
+
+ <% organization.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+
+ <%= form.label :name, class: "form-label" %>
+ <%= form.text_field :name, class: "form-control", required: true %>
+
+
+
+ <%= form.label :description, class: "form-label" %>
+ <%= form.text_area :description, class: "form-control", rows: 5 %>
+
+
+
+ <%= form.label :website_url, "Website", class: "form-label" %>
+ <%= form.url_field :website_url, class: "form-control" %>
+
+
+
+
+
+ <%= form.label :start_date, class: "form-label" %>
+ <%= form.date_field :start_date, class: "form-control" %>
+
+
+
+
+ <%= form.label :end_date, class: "form-label" %>
+ <%= form.date_field :end_date, class: "form-control" %>
+
+
+
+
+
+ <%= form.label :project_status_id, "Status", class: "form-label" %>
+ <%= form.collection_select :project_status_id, ProjectStatus.all, :id, :name, { prompt: "Select Status" }, { class: "form-control", required: true } %>
+
+
+
+ <%= form.label :agency_type, class: "form-label" %>
+ <%= form.text_field :agency_type, class: "form-control" %>
+
+
+
+ <%= form.label :notes, class: "form-label" %>
+ <%= form.text_area :notes, class: "form-control", rows: 3 %>
+
+
+
+ <%= form.check_box :inactive, class: "form-check-input" %>
+ <%= form.label :inactive, class: "form-check-label ms-2" %>
+
+
+
+ <%= form.submit "Save Organization", class: "btn btn-primary" %>
+
+<% end %>
diff --git a/app/views/organizations/annual_evaluations.html.erb b/app/views/organizations/annual_evaluations.html.erb
new file mode 100644
index 000000000..61bd4be83
--- /dev/null
+++ b/app/views/organizations/annual_evaluations.html.erb
@@ -0,0 +1,108 @@
+
+
+
+
Annual Evaluations - <%= @organization.name %>
+
+ <% if @available_years.any? %>
+
+ <%= form_with url: annual_evaluations_organization_path(@organization), method: :get, local: true do |form| %>
+ <%= form.label :year, "Select Year:", class: "form-label" %>
+ <%= form.select :year, @available_years.map { |y| [y, y] }, { selected: @year }, { class: "form-select", style: "max-width: 200px;", onchange: "this.form.submit();" } %>
+ <% end %>
+
+ <% end %>
+
+ <% if @aggregated_responses.present? %>
+
+
+
Responses for <%= @year %>
+
+ Total Evaluations: <%= @organization.annual_evaluations_for_year(@year).count %>
+
+
+
+
+ <% @aggregated_responses.each do |data| %>
+ <% field = data[:form_field] %>
+ <% responses = data[:responses] %>
+
+
+
+
+
+ <% if responses.any? %>
+ <% if field.answer_type&.to_s&.include?("multiple_choice") %>
+ <%# Multiple choice - show aggregated counts %>
+
+
Response Summary:
+
+ <% response_counts = responses.group_by { |r| r.answer_option&.name || r.response }.transform_values(&:count) %>
+ <% response_counts.each do |answer, count| %>
+ -
+ <%= answer %>
+ <%= count %>
+
+ <% end %>
+
+
+ <% else %>
+ <%# Free form responses - show all responses %>
+
+
+
+
+ | Respondent |
+ Response |
+ Submitted |
+
+
+
+ <% responses.each do |response| %>
+
+ |
+ <%= response.report.user.full_name %>
+ |
+
+ <%= simple_format(response.response) %>
+ |
+
+ <%= response.report.created_at.strftime("%m/%d/%Y") %>
+ |
+
+ <% end %>
+
+
+
+ <% end %>
+ <% else %>
+
No responses for this question.
+ <% end %>
+
+
+ <% end %>
+ <% else %>
+
+
No Annual Evaluations Found
+
There are no annual evaluation responses for <%= @year %> yet.
+ <% if @available_years.empty? %>
+
No evaluations have been submitted for this organization.
+ <% end %>
+
+ <% end %>
+
+
+ <%= link_to "Back to Organization", @organization, class: "btn btn-secondary" %>
+
+
+
+
diff --git a/app/views/organizations/edit.html.erb b/app/views/organizations/edit.html.erb
new file mode 100644
index 000000000..f5cf195e9
--- /dev/null
+++ b/app/views/organizations/edit.html.erb
@@ -0,0 +1,12 @@
+
+
+
+
Edit Organization
+
+ <%= render "form", organization: @organization %>
+
+ <%= link_to "Show", @organization, class: "btn btn-secondary mt-3" %>
+ <%= link_to "Back to Organizations", organizations_path, class: "btn btn-secondary mt-3" %>
+
+
+
diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb
new file mode 100644
index 000000000..698c3933c
--- /dev/null
+++ b/app/views/organizations/index.html.erb
@@ -0,0 +1,41 @@
+
+
+
+
Organizations
+
Organizations in the AWBW network.
+
+ <% if user_signed_in? && current_user.super_user? %>
+ <%= link_to "New Organization", new_organization_path, class: "btn btn-primary mb-3" %>
+ <% end %>
+
+
+
+
+
+ | Name |
+ Status |
+ Start Date |
+ Actions |
+
+
+
+ <% @organizations.each do |organization| %>
+
+ | <%= link_to organization.name, organization %> |
+ <%= organization.project_status&.name %> |
+ <%= organization.start_date&.strftime("%m/%d/%Y") %> |
+
+ <%= link_to "View", organization, class: "btn btn-sm btn-secondary" %>
+ <%= link_to "Annual Evaluations", annual_evaluations_organization_path(organization), class: "btn btn-sm btn-info" %>
+ <% if user_signed_in? && current_user.super_user? %>
+ <%= link_to "Edit", edit_organization_path(organization), class: "btn btn-sm btn-warning" %>
+ <% end %>
+ |
+
+ <% end %>
+
+
+
+
+
+
diff --git a/app/views/organizations/new.html.erb b/app/views/organizations/new.html.erb
new file mode 100644
index 000000000..ec06524c2
--- /dev/null
+++ b/app/views/organizations/new.html.erb
@@ -0,0 +1,11 @@
+
+
+
+
New Organization
+
+ <%= render "form", organization: @organization %>
+
+ <%= link_to "Back to Organizations", organizations_path, class: "btn btn-secondary mt-3" %>
+
+
+
diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb
new file mode 100644
index 000000000..f42fb1058
--- /dev/null
+++ b/app/views/organizations/show.html.erb
@@ -0,0 +1,36 @@
+
+
+
+
<%= @organization.name %>
+
+
+
+
Organization Details
+
+ <% if @organization.description.present? %>
+
Description: <%= simple_format(@organization.description) %>
+ <% end %>
+
+ <% if @organization.website_url.present? %>
+
Website: <%= link_to @organization.website_url, @organization.website_url, target: "_blank" %>
+ <% end %>
+
+ <% if @organization.start_date.present? %>
+
Start Date: <%= @organization.start_date.strftime("%m/%d/%Y") %>
+ <% end %>
+
+
Status: <%= @organization.project_status&.name %>
+
+
+
+
+ <%= link_to "Annual Evaluations", annual_evaluations_organization_path(@organization), class: "btn btn-primary" %>
+ <% if user_signed_in? && current_user.super_user? %>
+ <%= link_to "Edit", edit_organization_path(@organization), class: "btn btn-warning" %>
+ <%= button_to "Delete", @organization, method: :delete, data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, class: "btn btn-danger" %>
+ <% end %>
+ <%= link_to "Back to Organizations", organizations_path, class: "btn btn-secondary" %>
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index a888cd316..5a6156be3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -59,7 +59,11 @@
resources :facilitators
resources :faqs
resources :notifications, only: [ :index, :show ]
- resources :organizations
+ resources :organizations do
+ member do
+ get :annual_evaluations
+ end
+ end
resources :projects
resources :project_statuses
resources :project_users
diff --git a/db/seeds.rb b/db/seeds.rb
index d5298dca5..b1ffc4705 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -154,3 +154,6 @@
metadata.categories.find_or_create_by!(name: category_name)
end
end
+
+puts "Creating Annual Evaluation Form..."
+load Rails.root.join("db", "seeds", "annual_evaluation_form.rb")
diff --git a/db/seeds/annual_evaluation_form.rb b/db/seeds/annual_evaluation_form.rb
new file mode 100644
index 000000000..2978bd027
--- /dev/null
+++ b/db/seeds/annual_evaluation_form.rb
@@ -0,0 +1,208 @@
+# Annual Evaluation Form Seed
+# This creates the Form Builder and Form Fields for Annual Evaluations
+
+puts "Creating Annual Evaluation Form..."
+
+# Find or create WindowsType for combined (works for all types)
+combined_type = WindowsType.find_or_create_by!(name: "ADULT & CHILDREN COMBINED (FAMILY) WINDOWS") do |wt|
+ wt.legacy_id = 3
+ wt.short_name = "COMBINED"
+end
+
+# Create Form Builder for Annual Evaluation
+form_builder = FormBuilder.find_or_create_by!(name: "Annual Evaluation") do |fb|
+ fb.windows_type = combined_type
+ fb.description = "Annual Evaluation for Organizations"
+end
+
+# Create or update the Form
+form = form_builder.forms.first_or_create!(owner: form_builder)
+
+# Define form fields for Annual Evaluation
+# Position numbers are in reverse order (higher number displays first)
+form_fields_data = [
+ {
+ question: "Organization Name",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :text_alphanumeric,
+ position: 1000,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Name of your organization"
+ },
+ {
+ question: "Contact Person Name",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :text_alphanumeric,
+ position: 990,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Primary contact for this evaluation"
+ },
+ {
+ question: "Contact Email",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :text_alphanumeric,
+ position: 980,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Email address for contact person"
+ },
+ {
+ question: "Contact Phone Number",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :text_alphanumeric,
+ position: 970,
+ status: :active,
+ is_required: false,
+ instructional_hint: "Phone number for contact person"
+ },
+ {
+ question: "Total number of participants served this year",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :number_integer,
+ position: 960,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Approximate total number of individuals who participated in your programs"
+ },
+ {
+ question: "Number of facilitators trained or active this year",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :number_integer,
+ position: 950,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Total facilitators who led or were trained for workshops"
+ },
+ {
+ question: "Total number of workshops conducted this year",
+ answer_type: :free_form_input_one_line,
+ answer_datatype: :number_integer,
+ position: 940,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Total count of all workshops/sessions held"
+ },
+ {
+ question: "Primary populations served",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 930,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Describe the main groups/demographics you served (e.g., survivors of domestic violence, children in foster care, veterans)"
+ },
+ {
+ question: "Geographic area(s) served",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 920,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Cities, counties, or regions where services were provided"
+ },
+ {
+ question: "What were your organization's most significant accomplishments this year?",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 910,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Describe major achievements, milestones, or successes"
+ },
+ {
+ question: "What challenges did your organization face this year?",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 900,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Describe obstacles, difficulties, or areas that need improvement"
+ },
+ {
+ question: "How has participation in the AWBW network benefited your organization?",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 890,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Describe specific ways the AWBW network has supported your work"
+ },
+ {
+ question: "What resources or support from AWBW would be most helpful in the coming year?",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 880,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Identify needs, training opportunities, or resources that would benefit your organization"
+ },
+ {
+ question: "Participant feedback/success stories",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 870,
+ status: :active,
+ is_required: false,
+ instructional_hint: "Share any notable quotes, testimonials, or success stories from participants (optional)"
+ },
+ {
+ question: "Overall program quality rating",
+ answer_type: :multiple_choice_radio,
+ answer_datatype: :text_alphanumeric,
+ position: 860,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Rate the overall quality of your Windows programs this year"
+ },
+ {
+ question: "Plans for the coming year",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 850,
+ status: :active,
+ is_required: true,
+ instructional_hint: "Describe your organization's goals and plans for the upcoming year"
+ },
+ {
+ question: "Additional comments or information",
+ answer_type: :free_form_input_paragraph,
+ answer_datatype: :text_alphanumeric,
+ position: 840,
+ status: :active,
+ is_required: false,
+ instructional_hint: "Any other information you'd like to share (optional)"
+ }
+]
+
+# Create form fields
+form_fields_data.each do |field_data|
+ form_field = form.form_fields.find_or_initialize_by(question: field_data[:question])
+ form_field.assign_attributes(field_data)
+ form_field.save!
+ puts " Created/Updated form field: #{field_data[:question]}"
+end
+
+# Create answer options for the rating question
+rating_field = form.form_fields.find_by(question: "Overall program quality rating")
+if rating_field
+ rating_options = [
+ "Excellent",
+ "Very Good",
+ "Good",
+ "Fair",
+ "Needs Improvement"
+ ]
+
+ rating_options.each do |option_text|
+ answer_option = AnswerOption.find_or_create_by!(name: option_text)
+ rating_field.form_field_answer_options.find_or_create_by!(answer_option: answer_option)
+ puts " Added answer option: #{option_text}"
+ end
+end
+
+puts "Annual Evaluation Form created successfully!"
+puts "Form Builder ID: #{form_builder.id}"
+puts "Form ID: #{form.id}"
+puts "Total Form Fields: #{form.form_fields.count}"
diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb
new file mode 100644
index 000000000..4f8e713ae
--- /dev/null
+++ b/spec/factories/organizations.rb
@@ -0,0 +1,6 @@
+FactoryBot.define do
+ factory :organization, parent: :project, class: 'Organization' do
+ # Inherits all attributes from :project factory
+ # Can add organization-specific overrides here if needed
+ end
+end
diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb
new file mode 100644
index 000000000..82b3d95cd
--- /dev/null
+++ b/spec/models/organization_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+RSpec.describe Organization do
+ describe 'inheritance' do
+ it 'inherits from Project' do
+ expect(Organization.superclass).to eq(Project)
+ end
+
+ it 'uses the projects table' do
+ expect(Organization.table_name).to eq('projects')
+ end
+ end
+
+ describe '#annual_evaluations_for_year' do
+ let(:organization) { create(:project) }
+ let(:user) { create(:user) }
+ let(:form_builder) { create(:form_builder, name: "Annual Evaluation") }
+ let(:form) { create(:form, owner: form_builder) }
+
+ before do
+ create(:project_user, project: organization, user: user)
+ end
+
+ it 'returns empty relation when year is not provided' do
+ expect(organization.annual_evaluations_for_year(nil)).to eq(Report.none)
+ end
+
+ it 'returns reports for the specified year' do
+ report_2025 = create(:report, user: user, created_at: Date.new(2025, 6, 15))
+ report_2024 = create(:report, user: user, created_at: Date.new(2024, 6, 15))
+
+ # Mock the form_builder association
+ allow(Report).to receive(:joins).and_return(Report.where(id: [ report_2025.id, report_2024.id ]))
+
+ results = organization.annual_evaluations_for_year(2025)
+ expect(results).to include(report_2025)
+ expect(results).not_to include(report_2024)
+ end
+ end
+
+ describe '#aggregated_annual_evaluation_responses' do
+ let(:organization) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ create(:project_user, project: organization, user: user)
+ end
+
+ it 'returns empty hash when no evaluations exist' do
+ allow(organization).to receive(:annual_evaluations_for_year).and_return(Report.none)
+ expect(organization.aggregated_annual_evaluation_responses(2025)).to eq({})
+ end
+
+ it 'returns empty hash when form builder does not exist' do
+ allow(organization).to receive(:annual_evaluations_for_year).and_return(Report.all)
+ allow(FormBuilder).to receive(:find_by).with(name: "Annual Evaluation").and_return(nil)
+ expect(organization.aggregated_annual_evaluation_responses(2025)).to eq({})
+ end
+
+ it 'groups responses by form field' do
+ form_builder = create(:form_builder, name: "Annual Evaluation")
+ form = create(:form, owner: form_builder)
+ field = create(:form_field, form: form, question: "Test question", status: :active)
+ report = create(:report, user: user)
+
+ allow(organization).to receive(:annual_evaluations_for_year).and_return(Report.where(id: report.id))
+
+ result = organization.aggregated_annual_evaluation_responses(2025)
+ expect(result).to be_an(Array)
+ expect(result.first).to have_key(:form_field)
+ expect(result.first).to have_key(:responses)
+ end
+ end
+end
diff --git a/spec/requests/organizations_spec.rb b/spec/requests/organizations_spec.rb
new file mode 100644
index 000000000..31f9464ba
--- /dev/null
+++ b/spec/requests/organizations_spec.rb
@@ -0,0 +1,172 @@
+require "rails_helper"
+
+RSpec.describe "/organizations", type: :request do
+ let(:user) { create(:user) }
+ let(:admin) { create(:user, super_user: true) }
+
+ let!(:project_status) { create(:project_status, name: "Active") }
+
+ let(:valid_attributes) do
+ {
+ name: "Healing Through Art Organization",
+ description: "A community program supporting trauma-informed workshops.",
+ start_date: Date.today - 6.months,
+ end_date: Date.today + 6.months,
+ project_status_id: project_status.id,
+ inactive: false,
+ notes: "Runs bi-weekly at community centers."
+ }
+ end
+
+ let(:invalid_attributes) do
+ {
+ name: "", # required field missing
+ description: nil,
+ project_status_id: nil
+ }
+ end
+
+ before do
+ sign_in admin
+ end
+
+ describe "GET /index" do
+ it "renders a successful response" do
+ Organization.create!(valid_attributes)
+ get organizations_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /show" do
+ it "renders a successful response" do
+ organization = Organization.create!(valid_attributes)
+ get organization_url(organization)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /annual_evaluations" do
+ let(:organization) { Organization.create!(valid_attributes) }
+ let(:form_builder) { create(:form_builder, name: "Annual Evaluation") }
+ let(:form) { create(:form, owner: form_builder) }
+
+ before do
+ create(:project_user, project: organization, user: user)
+ end
+
+ it "renders a successful response" do
+ get annual_evaluations_organization_url(organization)
+ expect(response).to be_successful
+ end
+
+ it "accepts year parameter" do
+ get annual_evaluations_organization_url(organization, year: 2025)
+ expect(response).to be_successful
+ expect(assigns(:year)).to eq(2025)
+ end
+
+ it "defaults to current year when year parameter is not provided" do
+ get annual_evaluations_organization_url(organization)
+ expect(response).to be_successful
+ expect(assigns(:year)).to eq(Date.current.year)
+ end
+
+ it "assigns aggregated responses" do
+ get annual_evaluations_organization_url(organization, year: 2025)
+ expect(assigns(:aggregated_responses)).to be_present
+ end
+
+ it "assigns available years" do
+ get annual_evaluations_organization_url(organization)
+ expect(assigns(:available_years)).to be_an(Array)
+ end
+ end
+
+ describe "GET /new" do
+ it "renders a successful response" do
+ get new_organization_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /edit" do
+ it "renders a successful response" do
+ organization = Organization.create!(valid_attributes)
+ get edit_organization_url(organization)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "POST /create" do
+ context "with valid parameters" do
+ it "creates a new Organization" do
+ expect {
+ post organizations_url, params: { organization: valid_attributes }
+ }.to change(Organization, :count).by(1)
+ end
+
+ it "redirects to the created organization" do
+ post organizations_url, params: { organization: valid_attributes }
+ expect(response).to redirect_to(organization_url(Organization.last))
+ end
+ end
+
+ context "with invalid parameters" do
+ it "does not create a new Organization" do
+ expect {
+ post organizations_url, params: { organization: invalid_attributes }
+ }.not_to change(Organization, :count)
+ end
+
+ it "renders a response with 422 status" do
+ post organizations_url, params: { organization: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "PATCH /update" do
+ let(:organization) { Organization.create!(valid_attributes) }
+
+ context "with valid parameters" do
+ let(:new_attributes) do
+ { name: "Updated Organization Name" }
+ end
+
+ it "updates the requested organization" do
+ patch organization_url(organization), params: { organization: new_attributes }
+ organization.reload
+ expect(organization.name).to eq("Updated Organization Name")
+ end
+
+ it "redirects to the organization" do
+ patch organization_url(organization), params: { organization: new_attributes }
+ organization.reload
+ expect(response).to redirect_to(organization_url(organization))
+ end
+ end
+
+ context "with invalid parameters" do
+ it "renders a response with 422 status" do
+ patch organization_url(organization), params: { organization: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ it "destroys the requested organization" do
+ organization = Organization.create!(valid_attributes)
+ expect {
+ delete organization_url(organization)
+ }.to change(Organization, :count).by(-1)
+ end
+
+ it "redirects to the organizations list" do
+ organization = Organization.create!(valid_attributes)
+ delete organization_url(organization)
+ expect(response).to redirect_to(organizations_url)
+ end
+ end
+end
diff --git a/spec/routing/organizations_routing_spec.rb b/spec/routing/organizations_routing_spec.rb
new file mode 100644
index 000000000..5716909e1
--- /dev/null
+++ b/spec/routing/organizations_routing_spec.rb
@@ -0,0 +1,41 @@
+require "rails_helper"
+
+RSpec.describe OrganizationsController, type: :routing do
+ describe "routing" do
+ it "routes to #index" do
+ expect(get: "/organizations").to route_to("organizations#index")
+ end
+
+ it "routes to #new" do
+ expect(get: "/organizations/new").to route_to("organizations#new")
+ end
+
+ it "routes to #show" do
+ expect(get: "/organizations/1").to route_to("organizations#show", id: "1")
+ end
+
+ it "routes to #edit" do
+ expect(get: "/organizations/1/edit").to route_to("organizations#edit", id: "1")
+ end
+
+ it "routes to #create" do
+ expect(post: "/organizations").to route_to("organizations#create")
+ end
+
+ it "routes to #update via PUT" do
+ expect(put: "/organizations/1").to route_to("organizations#update", id: "1")
+ end
+
+ it "routes to #update via PATCH" do
+ expect(patch: "/organizations/1").to route_to("organizations#update", id: "1")
+ end
+
+ it "routes to #destroy" do
+ expect(delete: "/organizations/1").to route_to("organizations#destroy", id: "1")
+ end
+
+ it "routes to #annual_evaluations" do
+ expect(get: "/organizations/1/annual_evaluations").to route_to("organizations#annual_evaluations", id: "1")
+ end
+ end
+end