diff --git a/app/controllers/support/cases/evaluation_due_dates_controller.rb b/app/controllers/support/cases/evaluation_due_dates_controller.rb
new file mode 100644
index 000000000..855470b6f
--- /dev/null
+++ b/app/controllers/support/cases/evaluation_due_dates_controller.rb
@@ -0,0 +1,38 @@
+module Support
+ class Cases::EvaluationDueDatesController < Cases::ApplicationController
+ before_action :set_current_case
+ before_action { @back_url = support_case_path(@current_case, anchor: "tasklist") }
+
+ include HasDateParams
+ def edit
+ @case_evaluation_due_date_form = CaseEvaluationDueDateForm.new(**current_case.to_h)
+ end
+
+ def update
+ @case_evaluation_due_date_form = CaseEvaluationDueDateForm.from_validation(validation)
+
+ if validation.success?
+ @current_case.update!(@case_evaluation_due_date_form.to_h)
+ redirect_to @back_url
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def set_current_case
+ @current_case = Support::Case.find(params[:case_id])
+ end
+
+ def validation
+ @validation ||= CaseEvaluationDueDateFormSchema.new.call(**evaluation_due_date_params)
+ end
+
+ def evaluation_due_date_params
+ form_params = params.require(:case_evaluation_due_date_form).permit
+ form_params[:evaluation_due_date] = date_param(:case_evaluation_due_date_form, :evaluation_due_date)
+ form_params
+ end
+ end
+end
diff --git a/app/forms/support/case_evaluation_due_date_form.rb b/app/forms/support/case_evaluation_due_date_form.rb
new file mode 100644
index 000000000..a17629528
--- /dev/null
+++ b/app/forms/support/case_evaluation_due_date_form.rb
@@ -0,0 +1,5 @@
+module Support
+ class CaseEvaluationDueDateForm < Form
+ option :evaluation_due_date, Types::DateField, optional: true
+ end
+end
diff --git a/app/forms/support/case_evaluation_due_date_form_schema.rb b/app/forms/support/case_evaluation_due_date_form_schema.rb
new file mode 100644
index 000000000..834dd5bd9
--- /dev/null
+++ b/app/forms/support/case_evaluation_due_date_form_schema.rb
@@ -0,0 +1,26 @@
+module Support
+ class CaseEvaluationDueDateFormSchema < ::Support::Schema
+ config.messages.top_namespace = :case_evaluation_due_date_form
+
+ params do
+ required(:evaluation_due_date).value(:hash)
+ end
+
+ rule(:evaluation_due_date) do
+ key.failure(:missing) if value.values.all?(&:blank?)
+ end
+
+ rule :evaluation_due_date do
+ key.failure(:invalid) unless value.values.all?(&:blank?) || hash_to_date.call(value)
+ end
+
+ rule :evaluation_due_date do
+ if value.present?
+ date = hash_to_date.call(value)
+ if date && date <= Time.zone.today
+ key.failure(:must_be_in_the_future)
+ end
+ end
+ end
+ end
+end
diff --git a/app/views/support/cases/evaluation_due_dates/edit.html.erb b/app/views/support/cases/evaluation_due_dates/edit.html.erb
new file mode 100644
index 000000000..0df2aca94
--- /dev/null
+++ b/app/views/support/cases/evaluation_due_dates/edit.html.erb
@@ -0,0 +1,12 @@
+<%= render partial: "support/cases/components/case_header", locals: { current_case: @current_case } %>
+
<%= I18n.t("support.cases.evaluation_due_date.header") %>
+
+<%= form_with model: @case_evaluation_due_date_form, scope: :case_evaluation_due_date_form, url: support_case_evaluation_due_dates_path(@current_case), method: :patch do |form| %>
+ <%= form.govuk_error_summary %>
+ <%= form.govuk_date_field :evaluation_due_date, legend: { text: I18n.t("support.cases.evaluation_due_date.date.label"), }, hint: { text: I18n.t("support.cases.evaluation_due_date.date.hint") } %>
+
+
+ <%= form.submit I18n.t("support.cases.evaluation_due_date.submit"), class: "govuk-button" %>
+ <%= link_to I18n.t("support.cases.evaluation_due_date.cancel"), @back_url, class: "govuk-link govuk-link--no-visited-state" %>
+
+<% end %>
diff --git a/app/views/support/cases/show/_tasklist.html.erb b/app/views/support/cases/show/_tasklist.html.erb
index c0058c74f..c79ea52e1 100644
--- a/app/views/support/cases/show/_tasklist.html.erb
+++ b/app/views/support/cases/show/_tasklist.html.erb
@@ -9,7 +9,7 @@
<%= govuk_task_list(id_prefix: "complete-evaluation") do |task_list|
task_list.with_item(title: I18n.t("support.case.label.tasklist.item.add_evaluators"), href: '#', status: govuk_tag(text: I18n.t("support.case.label.tasklist.status.to_do")))
- task_list.with_item(title: I18n.t("support.case.label.tasklist.item.set_due_date"), href: '#', status: govuk_tag(text: I18n.t("support.case.label.tasklist.status.to_do")))
+ task_list.with_item(title: I18n.t("support.case.label.tasklist.item.set_due_date"), href: edit_support_case_evaluation_due_dates_path(@current_case), status: @current_case.evaluation_due_date ? govuk_tag(text: I18n.t("support.case.label.tasklist.status.complete"), colour: "green") : govuk_tag(text: I18n.t("support.case.label.tasklist.status.to_do")))
task_list.with_item(title: I18n.t("support.case.label.tasklist.item.upload_documents"), href: '#', status: govuk_tag(text: I18n.t("support.case.label.tasklist.status.to_do")))
task_list.with_item(title: I18n.t("support.case.label.tasklist.item.email_evaluators")) do | item |
item.with_status(text: I18n.t("support.case.label.tasklist.status.cannot_start"), cannot_start_yet: true)
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 245e39612..fb06d85c5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1773,6 +1773,13 @@ en:
cases:
my_cases:
header_link: My cases
+ evaluation_due_date:
+ header: Set due date
+ date:
+ label: When does the evaluation need to be completed by?
+ hint: For example, 27 3 2025
+ submit: Continue
+ cancel: Cancel
emails:
attachments:
attach_files: Attach files
diff --git a/config/locales/validation/support/en.yml b/config/locales/validation/support/en.yml
index 453ff07cb..4105b6ac2 100644
--- a/config/locales/validation/support/en.yml
+++ b/config/locales/validation/support/en.yml
@@ -290,3 +290,13 @@ en:
infected: One or more of the files you uploaded contained a virus and have been rejected
incorrect_file_type: One or more of the files you uploaded was an incorrect file type
+ case_evaluation_due_date_form:
+ rules:
+ evaluation_due_date: ""
+ errors:
+ rules:
+ evaluation_due_date:
+ missing: Enter valid evaluation due date
+ invalid: Enter valid evaluation due date
+ must_be_in_the_future: Evaluation due date must be in the future
+
diff --git a/config/routes.rb b/config/routes.rb
index f7347ef9e..091660488 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -235,6 +235,7 @@
resources :contracts, only: %i[edit update]
resources :additional_contacts
resources :evaluators, except: %i[show]
+ resource :evaluation_due_dates, only: %i[edit update]
resource :email, only: %i[create] do
scope module: :emails do
resources :content, only: %i[show], param: :template
diff --git a/db/migrate/20241126122758_add_evaluation_due_date_to_support_cases.rb b/db/migrate/20241126122758_add_evaluation_due_date_to_support_cases.rb
new file mode 100644
index 000000000..8c7bf6090
--- /dev/null
+++ b/db/migrate/20241126122758_add_evaluation_due_date_to_support_cases.rb
@@ -0,0 +1,5 @@
+class AddEvaluationDueDateToSupportCases < ActiveRecord::Migration[7.2]
+ def change
+ add_column :support_cases, :evaluation_due_date, :date
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 99e48e973..09646b58e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2024_11_27_115728) do
+ActiveRecord::Schema[7.2].define(version: 2024_11_26_122758) do
create_sequence "evaluation_refs"
create_sequence "framework_refs"
@@ -623,6 +623,7 @@
t.string "project"
t.string "other_school_urns", default: [], array: true
t.boolean "is_evaluator", default: false
+ t.date "evaluation_due_date"
t.index ["category_id"], name: "index_support_cases_on_category_id"
t.index ["existing_contract_id"], name: "index_support_cases_on_existing_contract_id"
t.index ["new_contract_id"], name: "index_support_cases_on_new_contract_id"
diff --git a/spec/features/support/agent_can_add_evaluation_due_date_spec.rb b/spec/features/support/agent_can_add_evaluation_due_date_spec.rb
new file mode 100644
index 000000000..79a537cd3
--- /dev/null
+++ b/spec/features/support/agent_can_add_evaluation_due_date_spec.rb
@@ -0,0 +1,28 @@
+require "rails_helper"
+
+describe "Edit evaluation due date", :js do
+ include_context "with an agent"
+
+ let(:support_case) { create(:support_case) }
+
+ specify "Update evaluation due date evaluators" do
+ visit edit_support_case_evaluation_due_dates_path(case_id: support_case)
+
+ expect(page).to have_text("Set due date")
+ expect(page).to have_text("When does the evaluation need to be completed by?")
+
+ fill_in "Day", with: ""
+ fill_in "Month", with: ""
+ fill_in "Year", with: ""
+ click_button "Continue"
+
+ expect(page).to have_text("Enter valid evaluation due date")
+
+ fill_in "Day", with: Date.yesterday.day
+ fill_in "Month", with: Date.yesterday.month
+ fill_in "Year", with: Date.yesterday.year
+ click_button "Continue"
+
+ expect(page).to have_text("Evaluation due date must be in the future")
+ end
+end
diff --git a/spec/models/support/case_spec.rb b/spec/models/support/case_spec.rb
index eda6d2af1..ac1912a4d 100644
--- a/spec/models/support/case_spec.rb
+++ b/spec/models/support/case_spec.rb
@@ -59,7 +59,7 @@
describe "#to_csv" do
it "includes headers" do
expect(described_class.to_csv).to eql(
- "id,ref,category_id,request_text,support_level,status,state,created_at,updated_at,agent_id,first_name,last_name,email,phone_number,source,organisation_id,existing_contract_id,new_contract_id,procurement_id,savings_status,savings_estimate_method,savings_actual_method,savings_estimate,savings_actual,action_required,organisation_type,value,closure_reason,extension_number,other_category,other_query,procurement_amount,confidence_level,special_requirements,query_id,exit_survey_sent,detected_category_id,creation_source,user_selected_category,created_by_id,procurement_stage_id,initial_request_text,with_school,next_key_date,next_key_date_description,discovery_method,discovery_method_other_text,project,other_school_urns,is_evaluator\n",
+ "id,ref,category_id,request_text,support_level,status,state,created_at,updated_at,agent_id,first_name,last_name,email,phone_number,source,organisation_id,existing_contract_id,new_contract_id,procurement_id,savings_status,savings_estimate_method,savings_actual_method,savings_estimate,savings_actual,action_required,organisation_type,value,closure_reason,extension_number,other_category,other_query,procurement_amount,confidence_level,special_requirements,query_id,exit_survey_sent,detected_category_id,creation_source,user_selected_category,created_by_id,procurement_stage_id,initial_request_text,with_school,next_key_date,next_key_date_description,discovery_method,discovery_method_other_text,project,other_school_urns,is_evaluator,evaluation_due_date\n",
)
end
end