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