diff --git a/app/components/performance_profile_banner/view.rb b/app/components/performance_profile_banner/view.rb
index 186df48822..06426a9493 100644
--- a/app/components/performance_profile_banner/view.rb
+++ b/app/components/performance_profile_banner/view.rb
@@ -15,13 +15,13 @@ def render?
end
def banner_heading_text
- "The #{previous_academic_cycle_label} ITT performance profile sign off due"
+ "The #{previous_academic_cycle_label} ITT performance profile sign off is due"
end
- delegate :label, :end_year, to: :previous_academic_cycle, prefix: true
+ delegate :label, :end_date_of_performance_profile, to: :previous_academic_cycle, prefix: true
def deadline_date
- "28 February #{previous_academic_cycle_end_year + 1}"
+ previous_academic_cycle.end_date_of_performance_profile.strftime(Date::DATE_FORMATS[:govuk])
end
private
diff --git a/app/models/academic_cycle.rb b/app/models/academic_cycle.rb
index 884fa6a839..c6d1901d78 100644
--- a/app/models/academic_cycle.rb
+++ b/app/models/academic_cycle.rb
@@ -78,6 +78,24 @@ def current?
(start_date.beginning_of_day..end_date.end_of_day).cover?(Time.zone.now)
end
+ def in_performance_profile_range?(date)
+ performance_profile_date_range.cover?(date)
+ end
+
+ def second_monday_of_january
+ Date.new(end_year + 1, 1, 1).next_week(:monday) + 7
+ end
+
+ def last_day_of_february
+ Date.new(end_year + 1, 2, -1)
+ end
+
+ alias_method :end_date_of_performance_profile, :last_day_of_february
+
+ def performance_profile_date_range
+ @performance_profile_date_range ||= second_monday_of_january..end_date_of_performance_profile
+ end
+
private
def start_date_before_end_date
diff --git a/app/services/determine_sign_off_period.rb b/app/services/determine_sign_off_period.rb
index 5dc4692f82..8fe8f18832 100644
--- a/app/services/determine_sign_off_period.rb
+++ b/app/services/determine_sign_off_period.rb
@@ -1,21 +1,31 @@
# frozen_string_literal: true
class DetermineSignOffPeriod
+ include ServicePattern
+
VALID_PERIODS = %i[census_period performance_period outside_period].freeze
- def self.call
+ def initialize(previous_academic_cycle: AcademicCycle.previous)
+ @previous_academic_cycle = previous_academic_cycle
+ end
+
+ def call
return Settings.sign_off_period.to_sym if valid_sign_off_period?
current_date = Time.zone.today
return :census_period if census_range.cover?(current_date)
- return :performance_period if in_performance_range?(current_date)
+ return :performance_period if in_performance_profile_range?(current_date)
:outside_period
end
- def self.valid_sign_off_period?
+private
+
+ attr_reader :previous_academic_cycle
+
+ def valid_sign_off_period?
return false if Settings.sign_off_period.blank?
return true if VALID_PERIODS.include?(Settings.sign_off_period.to_sym)
@@ -23,17 +33,12 @@ def self.valid_sign_off_period?
false
end
- def self.census_range
+ def census_range
start_date = Date.new(Time.zone.today.year, 9, 1) # 1st September
end_date = Date.new(Time.zone.today.year, 11, 7) # 7th November
start_date..end_date
end
- def self.in_performance_range?(date)
- jan_to_feb_range = Date.new(Time.zone.today.year, 1, 1)..Date.new(Time.zone.today.year, 2, 7)
- december_range = Date.new(Time.zone.today.year, 12, 1)..Date.new(Time.zone.today.year, 12, 31)
-
- jan_to_feb_range.cover?(date) || december_range.cover?(date)
- end
+ delegate :in_performance_profile_range?, to: :previous_academic_cycle
end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 213f2c0070..a676fa05ea 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -7,14 +7,14 @@
<%= canonical_tag %>
<%= tag.meta(name: "viewport", content: "width=device-width, initial-scale=1") %>
- <%= tag.meta(property: "og:image", content: image_path('govuk-opengraph-image.png')) %>
+ <%= tag.meta(property: "og:image", content: image_path("govuk-opengraph-image.png")) %>
<%= tag.meta(name: "theme-color", content: "#0b0c0c") %>
<%= tag.meta(name: "format-detection", content: "telephone=no") %>
- <%= favicon_link_tag image_path('favicon.ico'), type: nil, sizes: "48x48" %>
- <%= favicon_link_tag image_path('favicon.svg'), type: 'image/svg+xml', sizes: "any" %>
- <%= favicon_link_tag image_path('govuk-icon-mask.svg'), rel: 'mask-icon', color: "#0b0c0c", type: nil %>
- <%= favicon_link_tag image_path('govuk-icon-180.png'), rel: 'apple-touch-icon', type: nil %>
+ <%= favicon_link_tag image_path("favicon.ico"), type: nil, sizes: "48x48" %>
+ <%= favicon_link_tag image_path("favicon.svg"), type: "image/svg+xml", sizes: "any" %>
+ <%= favicon_link_tag image_path("govuk-icon-mask.svg"), rel: "mask-icon", color: "#0b0c0c", type: nil %>
+ <%= favicon_link_tag image_path("govuk-icon-180.png"), rel: "apple-touch-icon", type: nil %>
<%= stylesheet_link_tag "accessible-autocomplete/dist/accessible-autocomplete.min" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
@@ -68,7 +68,7 @@
{ name: "Home", url: root_path },
({ name: "Draft trainees", url: drafts_path, current: active_link_for("drafts", @trainee) } if can_view_drafts?),
{ name: "Registered trainees", url: trainees_path, current: active_link_for("trainees", @trainee) },
- ({ name: "Bulk updates", url: bulk_update_path, current: active_link_for("bulk") } if can_bulk_update? ),
+ ({ name: "Bulk updates", url: bulk_update_path, current: active_link_for("bulk") } if can_bulk_update?),
({ name: "Reports", url: reports_path, current: active_link_for("reports") } if can_view_reports?),
({ name: "Funding", url: funding_payment_schedule_path, current: active_link_for("funding") } if can_view_funding?),
({ name: current_user.organisation.name, url: organisations_path, align_right: true } if show_organisation_link?),
@@ -107,12 +107,13 @@
<% if FeatureService.enabled?(:maintenance_banner) %>
<%= govuk_notification_banner(title_text: "Important") do |banner|
- banner.heading(text: "Register will be unavailable on Wednesday 19 July from 5pm")
+ banner.heading(text: "Register will be unavailable on Wednesday 19 July from 5pm")
- content_tag(:p, "You will be able to use the service from 9am on Thursday 20 July 2023.", class: "govuk-body")
- end %>
+ content_tag(:p, "You will be able to use the service from 9am on Thursday 20 July 2023.", class: "govuk-body")
+ end %>
<% else %>
<%= render(YearChangeBanner::View.new) %>
+ <%= render(PerformanceProfileBanner::View.new(previous_academic_cycle: AcademicCycle.previous, sign_off_period: :performance_period, provider: @current_user.organisation)) if @current_user&.accredited_provider? && request.path == root_path %>
<% end %>
<%= render(FlashBanner::View.new(flash: flash, trainee: @trainee)) %>
diff --git a/config/settings/review.yml b/config/settings/review.yml
index f6ac122780..9ad4bf662a 100644
--- a/config/settings/review.yml
+++ b/config/settings/review.yml
@@ -27,3 +27,5 @@ pagination:
environment:
name: review
+
+sign_off_period: performance_period # census_period, performance_period, outside_period. See app/services/determine_sign_off_period.rb
diff --git a/spec/components/performance_profile_banner/view_spec.rb b/spec/components/performance_profile_banner/view_spec.rb
index cc2312cc01..8d501a8295 100644
--- a/spec/components/performance_profile_banner/view_spec.rb
+++ b/spec/components/performance_profile_banner/view_spec.rb
@@ -50,7 +50,7 @@
it "renders correctly" do
expect(@result).to have_css("#govuk-notification-banner-title", text: "Important")
- expect(@result).to have_css(".govuk-notification-banner__heading", text: "The #{previous_academic_cycle_label} ITT performance profile sign off due")
+ expect(@result).to have_css(".govuk-notification-banner__heading", text: "The #{previous_academic_cycle_label} ITT performance profile sign off is due")
end
it "renders the link correctly" do
diff --git a/spec/features/performance_profile_banner_spec.rb b/spec/features/performance_profile_banner_spec.rb
new file mode 100644
index 0000000000..58f413eb09
--- /dev/null
+++ b/spec/features/performance_profile_banner_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+feature "performance profile banner" do
+ context "within the performance profile date range" do
+ background do
+ Timecop.freeze(AcademicCycle.previous.performance_profile_date_range.to_a.sample)
+ end
+
+ context "not logged in" do
+ scenario "performance profile banner is not shown" do
+ given_i_am_not_logged_in
+ when_i_am_on_the_root_page
+ then_i_do_not_see_the_performance_profile_banner
+ end
+ end
+
+ context "logged in as system admin" do
+ scenario "performance profile banner is not shown" do
+ given_i_am_authenticated_as_system_admin
+ when_i_am_on_the_root_page
+ then_i_do_not_see_the_performance_profile_banner
+ end
+ end
+
+ context "accredited provider user" do
+ scenario "performance profile banner is shown" do
+ given_i_am_authenticated
+ when_i_am_on_the_root_page
+ then_i_can_see_the_performance_profile_banner
+ and_i_click_on("Sign off your performance profile")
+ and_i_am_on_the_sign_off_your_performance_profile_page
+ end
+ end
+
+ context "lead provider user" do
+ scenario "performance profile banner is not shown" do
+ given_i_am_authenticated_as_a_lead_partner_user
+ when_i_am_on_the_root_page
+ then_i_do_not_see_the_performance_profile_banner
+ end
+ end
+ end
+
+private
+
+ def when_i_am_on_the_root_page
+ visit "/"
+ end
+
+ def given_i_am_not_logged_in; end
+
+ def then_i_do_not_see_the_performance_profile_banner
+ expect(page).not_to have_css("#govuk-notification-banner-title", text: "Important")
+ expect(page).not_to have_css(".govuk-notification-banner__heading", text: "The #{previous_academic_cycle_label} ITT performance profile sign off is due")
+ end
+
+ def then_i_can_see_the_performance_profile_banner
+ expect(page).to have_css("#govuk-notification-banner-title", text: "Important")
+ expect(page).to have_css(".govuk-notification-banner__heading", text: "The #{previous_academic_cycle_label} ITT performance profile sign off is due")
+ end
+
+ def previous_academic_cycle_label
+ AcademicCycle.previous.label
+ end
+
+ def and_i_am_on_the_sign_off_your_performance_profile_page
+ expect(page).to have_current_path("/")
+ end
+
+ alias_method :and_i_click_on, :click_on
+end
diff --git a/spec/models/academic_cycle_spec.rb b/spec/models/academic_cycle_spec.rb
index e16ab40f1b..f6a854921b 100644
--- a/spec/models/academic_cycle_spec.rb
+++ b/spec/models/academic_cycle_spec.rb
@@ -3,7 +3,9 @@
require "rails_helper"
describe AcademicCycle do
- subject { build(:academic_cycle) }
+ let(:academic_cycle) { build(:academic_cycle) }
+
+ subject { academic_cycle }
before do
allow(Trainees::SetAcademicCycles).to receive(:call) # deactivate so it doesn't override factories
@@ -179,4 +181,30 @@
it { expect(subject).to eq(past_academic_year) }
end
end
+
+ describe "#in_performance_profile_range?" do
+ it "returns whether a date is in the performance profile range" do
+ expect(academic_cycle.in_performance_profile_range?(academic_cycle.second_monday_of_january)).to be true
+ expect(academic_cycle.in_performance_profile_range?(academic_cycle.last_day_of_february)).to be true
+ expect(academic_cycle.in_performance_profile_range?(academic_cycle.second_monday_of_january - 1.day)).to be false
+ expect(academic_cycle.in_performance_profile_range?(academic_cycle.last_day_of_february + 1.day)).to be false
+ end
+ end
+
+ describe "#second_monday_of_january" do
+ subject { academic_cycle.second_monday_of_january }
+
+ it { expect(subject.month).to eq 1 }
+ it { expect(subject.wday).to eq 1 }
+ it { expect(subject.day).to be_between(8, 14) }
+ it { expect(subject).to be_a(Date) }
+ end
+
+ describe "#last_day_of_february" do
+ subject { academic_cycle.last_day_of_february }
+
+ it { expect(subject.month).to eq 2 }
+ it { expect(subject.day).to be_between(28, 29) }
+ it { expect(subject).to be_a(Date) }
+ end
end
diff --git a/spec/services/determine_sign_off_period_spec.rb b/spec/services/determine_sign_off_period_spec.rb
index 7030a7d1b5..8133d663a2 100644
--- a/spec/services/determine_sign_off_period_spec.rb
+++ b/spec/services/determine_sign_off_period_spec.rb
@@ -4,7 +4,9 @@
describe DetermineSignOffPeriod do
describe ".call" do
- subject { described_class.call }
+ subject { described_class.call(previous_academic_cycle: academic_cycle) }
+
+ let(:academic_cycle) { create(:academic_cycle) }
before do
allow(Settings).to receive(:sign_off_period).and_return(nil)
@@ -18,13 +20,15 @@
all_dates = [*Date.new(current_year, 1, 1)..Date.new(current_year, 12, 31)]
outside_dates = all_dates - census_period_range - performance_period_range
- context "with a valid manual override" do
- before do
- allow(Settings).to receive(:sign_off_period).and_return(:census_period)
- end
+ %i[census_period performance_period outside_period].each do |sign_off_period|
+ context "with a valid manual override" do
+ before do
+ allow(Settings).to receive(:sign_off_period).and_return(sign_off_period)
+ end
- it "returns the manual override value" do
- expect(subject).to eq(:census_period)
+ it "returns the manual override value #{sign_off_period}" do
+ expect(subject).to eq(sign_off_period)
+ end
end
end
@@ -38,10 +42,15 @@
subject
expect(Sentry).to have_received(:capture_exception).with(instance_of(StandardError))
end
+ end
+
+ census_period_range.each do |census_date|
+ context "on #{census_date} the census sign off period" do
+ before do
+ allow(Time.zone).to receive(:today).and_return(census_date)
+ end
- census_period_range.each do |census_period|
- it "for #{census_period} it defaults back to the calculated behaviour" do
- allow(Time.zone).to receive(:today).and_return(census_period)
+ it "returns :census_period" do
expect(subject).to eq(:census_period)
end
end
@@ -51,6 +60,7 @@
context "on #{performance_date} the performance profiles sign off period" do
before do
allow(Time.zone).to receive(:today).and_return(performance_date)
+ allow(academic_cycle).to receive(:in_performance_profile_range?).with(performance_date).and_return(true)
end
it "returns :performance_period" do
@@ -66,7 +76,6 @@
end
it "returns :outside_period" do
- pp subject if subject != :outside_period
expect(subject).to eq(:outside_period)
end
end