diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 1ccd2717c..603a8315c 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -1,5 +1,7 @@ name: Review app pipeline +concurrency: ${{ github.workflow }}-${{ github.event.pull_request.number }} + on: pull_request: types: @@ -120,6 +122,7 @@ jobs: cf set-env $APP_NAME S3_CONFIG $S3_CONFIG cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE $CSV_DOWNLOAD_PAAS_INSTANCE cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN + cf set-env $APP_NAME APP_HOST "https://dluhc-core-review-${{ github.event.pull_request.number }}.london.cloudapps.digital" - name: Bind postgres service env: @@ -142,6 +145,14 @@ jobs: run: | cf bind-service $APP_NAME $SERVICE_NAME --wait + - name: Bind S3 buckets services + env: + APP_NAME: dluhc-core-review-${{ github.event.pull_request.number }} + run: | + cf bind-service $APP_NAME dluhc-core-review-csv-bucket --wait + cf bind-service $APP_NAME dluhc-core-review-export-bucket --wait + cf bind-service $APP_NAME dluhc-core-review-import-bucket --wait + - name: Start review app env: APP_NAME: dluhc-core-review-${{ github.event.pull_request.number }} diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb new file mode 100644 index 000000000..108c25fef --- /dev/null +++ b/app/controllers/bulk_upload_lettings_logs_controller.rb @@ -0,0 +1,50 @@ +class BulkUploadLettingsLogsController < ApplicationController + before_action :authenticate_user! + + def start + if in_crossover_period? + redirect_to bulk_upload_lettings_log_path(id: "year") + else + redirect_to bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: current_year }) + end + end + + def show + render form.view_path + end + + def update + if form.valid? + redirect_to form.next_path + else + render form.view_path + end + end + +private + + def current_year + FormHandler.instance.forms["current_lettings"].start_date.year + end + + def in_crossover_period? + FormHandler.instance.forms.values.any?(&:in_crossover_period?) + end + + def form + @form ||= case params[:id] + when "year" + Forms::BulkUploadLettings::Year.new(form_params) + when "prepare-your-file" + Forms::BulkUploadLettings::PrepareYourFile.new(form_params) + when "upload-your-file" + Forms::BulkUploadLettings::UploadYourFile.new(form_params) + else + raise "Page not found for path #{params[:id]}" + end + end + + def form_params + params.fetch(:form, {}).permit(:year) + end +end diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb new file mode 100644 index 000000000..81d018d4c --- /dev/null +++ b/app/controllers/bulk_upload_sales_logs_controller.rb @@ -0,0 +1,50 @@ +class BulkUploadSalesLogsController < ApplicationController + before_action :authenticate_user! + + def start + if in_crossover_period? + redirect_to bulk_upload_sales_log_path(id: "year") + else + redirect_to bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: current_year }) + end + end + + def show + render form.view_path + end + + def update + if form.valid? + redirect_to form.next_path + else + render form.view_path + end + end + +private + + def current_year + FormHandler.instance.forms["current_sales"].start_date.year + end + + def in_crossover_period? + FormHandler.instance.forms.values.any?(&:in_crossover_period?) + end + + def form + @form ||= case params[:id] + when "year" + Forms::BulkUploadSales::Year.new(form_params) + when "prepare-your-file" + Forms::BulkUploadSales::PrepareYourFile.new(form_params) + when "upload-your-file" + Forms::BulkUploadSales::UploadYourFile.new(form_params) + else + raise "Page not found for path #{params[:id]}" + end + end + + def form_params + params.fetch(:form, {}).permit(:year) + end +end diff --git a/app/controllers/cookies_controller.rb b/app/controllers/cookies_controller.rb index 50d874e92..0e78fa1bc 100644 --- a/app/controllers/cookies_controller.rb +++ b/app/controllers/cookies_controller.rb @@ -18,6 +18,12 @@ class CookiesController < ApplicationController redirect_to cookies_path end + format.json do + render json: { + status: "ok", + message: %(You’ve #{analytics_consent == 'on' ? 'accepted' : 'rejected'} analytics cookies.), + } + end end end diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index 766501260..1da3bf5c4 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -38,8 +38,13 @@ class LocationsController < ApplicationController end def deactivate_confirm - @deactivation_date = params[:deactivation_date] - @deactivation_date_type = params[:deactivation_date_type] + @affected_logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) + if @affected_logs.count.zero? + deactivate + else + @deactivation_date = params[:deactivation_date] + @deactivation_date_type = params[:deactivation_date_type] + end end def deactivate @@ -50,12 +55,12 @@ class LocationsController < ApplicationController end def new_reactivation - @location_deactivation_period = LocationDeactivationPeriod.deactivations_without_reactivation.first + @location_deactivation_period = @location.location_deactivation_periods.deactivations_without_reactivation.first render "toggle_active", locals: { action: "reactivate" } end def reactivate - @location_deactivation_period = LocationDeactivationPeriod.deactivations_without_reactivation.first + @location_deactivation_period = @location.location_deactivation_periods.deactivations_without_reactivation.first @location_deactivation_period.reactivation_date = toggle_date("reactivation_date") @location_deactivation_period.reactivation_date_type = params[:location_deactivation_period][:reactivation_date_type] diff --git a/app/controllers/organisation_relationships_controller.rb b/app/controllers/organisation_relationships_controller.rb index f50349d20..e7a6a7856 100644 --- a/app/controllers/organisation_relationships_controller.rb +++ b/app/controllers/organisation_relationships_controller.rb @@ -39,7 +39,6 @@ class OrganisationRelationshipsController < ApplicationController def create_housing_provider child_organisation = @organisation - relationship_type = OrganisationRelationship::OWNING if params[:organisation][:related_organisation_id].empty? @organisation.errors.add :related_organisation_id, "You must choose a housing provider" @organisations = Organisation.where.not(id: child_organisation.id).pluck(:id, :name) @@ -47,21 +46,20 @@ class OrganisationRelationshipsController < ApplicationController return else parent_organisation = related_organisation - if OrganisationRelationship.exists?(child_organisation:, parent_organisation:, relationship_type:) + if OrganisationRelationship.exists?(child_organisation:, parent_organisation:) @organisation.errors.add :related_organisation_id, "You have already added this housing provider" @organisations = Organisation.where.not(id: child_organisation.id).pluck(:id, :name) render "organisation_relationships/add_housing_provider" return end end - create!(child_organisation:, parent_organisation:, relationship_type:) + create!(child_organisation:, parent_organisation:) flash[:notice] = "#{related_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} housing providers" redirect_to housing_providers_organisation_path end def create_managing_agent parent_organisation = @organisation - relationship_type = OrganisationRelationship::MANAGING if params[:organisation][:related_organisation_id].empty? @organisation.errors.add :related_organisation_id, "You must choose a managing agent" @organisations = Organisation.where.not(id: parent_organisation.id).pluck(:id, :name) @@ -69,14 +67,14 @@ class OrganisationRelationshipsController < ApplicationController return else child_organisation = related_organisation - if OrganisationRelationship.exists?(child_organisation:, parent_organisation:, relationship_type:) + if OrganisationRelationship.exists?(child_organisation:, parent_organisation:) @organisation.errors.add :related_organisation_id, "You have already added this managing agent" @organisations = Organisation.where.not(id: parent_organisation.id).pluck(:id, :name) render "organisation_relationships/add_managing_agent" return end end - create!(child_organisation:, parent_organisation:, relationship_type:) + create!(child_organisation:, parent_organisation:) flash[:notice] = "#{related_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents" redirect_to managing_agents_organisation_path end @@ -89,7 +87,6 @@ class OrganisationRelationshipsController < ApplicationController relationship = OrganisationRelationship.find_by!( child_organisation: @organisation, parent_organisation: target_organisation, - relationship_type: OrganisationRelationship::OWNING, ) relationship.destroy! flash[:notice] = "#{target_organisation.name} is no longer one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} housing providers" @@ -104,7 +101,6 @@ class OrganisationRelationshipsController < ApplicationController relationship = OrganisationRelationship.find_by!( parent_organisation: @organisation, child_organisation: target_organisation, - relationship_type: OrganisationRelationship::MANAGING, ) relationship.destroy! flash[:notice] = "#{target_organisation.name} is no longer one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents" @@ -113,8 +109,8 @@ class OrganisationRelationshipsController < ApplicationController private - def create!(child_organisation:, parent_organisation:, relationship_type:) - @resource = OrganisationRelationship.new(child_organisation:, parent_organisation:, relationship_type:) + def create!(child_organisation:, parent_organisation:) + @resource = OrganisationRelationship.new(child_organisation:, parent_organisation:) @resource.save! end diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 1a06d9d41..389446b7f 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -9,7 +9,7 @@ class SchemesController < ApplicationController def index redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support? - all_schemes = Scheme.all.order("service_name ASC") + all_schemes = Scheme.order(confirmed: :asc, service_name: :asc) @pagy, @schemes = pagy(filtered_collection(all_schemes, search_term)) @searched = search_term.presence @@ -27,10 +27,10 @@ class SchemesController < ApplicationController if params[:scheme_deactivation_period].blank? render "toggle_active", locals: { action: "deactivate" } else - @scheme_deactivation_period.deactivation_date = deactivation_date + @scheme_deactivation_period.deactivation_date = toggle_date("deactivation_date") @scheme_deactivation_period.deactivation_date_type = params[:scheme_deactivation_period][:deactivation_date_type] @scheme_deactivation_period.scheme = @scheme - if @scheme_deactivation_period.validate + if @scheme_deactivation_period.valid? redirect_to scheme_deactivate_confirm_path(@scheme, deactivation_date: @scheme_deactivation_period.deactivation_date, deactivation_date_type: @scheme_deactivation_period.deactivation_date_type) else render "toggle_active", locals: { action: "deactivate" }, status: :unprocessable_entity @@ -39,8 +39,13 @@ class SchemesController < ApplicationController end def deactivate_confirm - @deactivation_date = params[:deactivation_date] - @deactivation_date_type = params[:deactivation_date_type] + @affected_logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date]) + if @affected_logs.count.zero? + deactivate + else + @deactivation_date = params[:deactivation_date] + @deactivation_date_type = params[:deactivation_date_type] + end end def deactivate @@ -50,10 +55,25 @@ class SchemesController < ApplicationController redirect_to scheme_details_path(@scheme) end - def reactivate + def new_reactivation + @scheme_deactivation_period = @scheme.scheme_deactivation_periods.deactivations_without_reactivation.first render "toggle_active", locals: { action: "reactivate" } end + def reactivate + @scheme_deactivation_period = @scheme.scheme_deactivation_periods.deactivations_without_reactivation.first + + @scheme_deactivation_period.reactivation_date = toggle_date("reactivation_date") + @scheme_deactivation_period.reactivation_date_type = params[:scheme_deactivation_period][:reactivation_date_type] + + if @scheme_deactivation_period.update(reactivation_date: toggle_date("reactivation_date")) + flash[:notice] = reactivate_success_notice + redirect_to scheme_details_path(@scheme) + else + render "toggle_active", locals: { action: "reactivate" }, status: :unprocessable_entity + end + end + def new @scheme = Scheme.new end @@ -239,8 +259,7 @@ private :support_type, :arrangement_type, :intended_stay, - :confirmed, - :deactivation_date) + :confirmed) if arrangement_type_changed_to_different_org?(required_params) required_params[:managing_organisation_id] = nil @@ -303,18 +322,27 @@ private end end - def deactivation_date + def reactivate_success_notice + case @scheme.status + when :active + "#{@scheme.service_name} has been reactivated" + when :reactivating_soon + "#{@scheme.service_name} will reactivate on #{toggle_date('reactivation_date').to_time.to_formatted_s(:govuk_date)}" + end + end + + def toggle_date(key) if params[:scheme_deactivation_period].blank? return - elsif params[:scheme_deactivation_period][:deactivation_date_type] == "default" + elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default" return FormHandler.instance.current_collection_start_date - elsif params[:scheme_deactivation_period][:deactivation_date].present? - return params[:scheme_deactivation_period][:deactivation_date] + elsif params[:scheme_deactivation_period][key.to_sym].present? + return params[:scheme_deactivation_period][key.to_sym] end - day = params[:scheme_deactivation_period]["deactivation_date(3i)"] - month = params[:scheme_deactivation_period]["deactivation_date(2i)"] - year = params[:scheme_deactivation_period]["deactivation_date(1i)"] + day = params[:scheme_deactivation_period]["#{key}(3i)"] + month = params[:scheme_deactivation_period]["#{key}(2i)"] + year = params[:scheme_deactivation_period]["#{key}(1i)"] return nil if [day, month, year].any?(&:blank?) Time.zone.local(year.to_i, month.to_i, day.to_i) if Date.valid_date?(year.to_i, month.to_i, day.to_i) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9ab691a53..4ba32a442 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,6 +3,7 @@ class UsersController < ApplicationController include Devise::Controllers::SignInOut include Helpers::Email include Modules::SearchFilter + before_action :authenticate_user! before_action :find_resource, except: %i[new create] before_action :authenticate_scope!, except: %i[new] diff --git a/app/frontend/application.js b/app/frontend/application.js index ff4bae81a..36b730098 100644 --- a/app/frontend/application.js +++ b/app/frontend/application.js @@ -4,18 +4,19 @@ // files to reference that code so it'll be compiled. // Polyfills for IE +import '@stimulus/polyfills' import '@webcomponents/webcomponentsjs' import 'core-js/stable' -import 'regenerator-runtime/runtime' -import '@stimulus/polyfills' import 'custom-event-polyfill' import 'intersection-observer' +import 'regenerator-runtime/runtime' // import { initAll as GOVUKFrontend } from 'govuk-frontend' import { initAll as GOVUKPrototypeComponents } from 'govuk-prototype-components' -import './styles/application.scss' import './controllers' +import './cookie-banner' +import './styles/application.scss' require.context('govuk-frontend/govuk/assets') diff --git a/app/frontend/cookie-banner.js b/app/frontend/cookie-banner.js new file mode 100644 index 000000000..e39414f72 --- /dev/null +++ b/app/frontend/cookie-banner.js @@ -0,0 +1,57 @@ +const cookieBannerEl = document.querySelector('.js-cookie-banner') + +if (cookieBannerEl) { + const cookieFormEl = document.querySelector('.js-cookie-form') + + cookieFormEl.addEventListener('click', (e) => { + if (e.target.tagName !== 'BUTTON') { + return + } + + const body = new window.FormData(cookieFormEl) + body.append('cookies_form[accept_analytics_cookies]', e.target.value) + + fetch(cookieFormEl.action, { + method: 'PUT', + headers: { + Accept: 'application/json' + }, + body + }) + .then((res) => { + if (res.status >= 200 && res.status < 300) { + return res + } + + throw new Error(res) + }) + .then((res) => res.json()) + .then(({ message }) => { + const messageEl = cookieBannerEl.querySelector('.js-cookie-message') + messageEl.textContent = message + + cookieBannerEl + .querySelector('.js-cookie-banner__form') + .setAttribute('hidden', '') + cookieBannerEl + .querySelector('.js-cookie-banner__success') + .removeAttribute('hidden') + }) + + const gaSrc = window.analyticsScript + if (e.target.value === 'on' && gaSrc) { + const scriptEl = document.createElement('script') + scriptEl.src = gaSrc + document.body.appendChild(scriptEl) + } + + e.preventDefault() + }) + + const hideBannerEl = document.querySelector('.js-hide-cookie-banner') + hideBannerEl.addEventListener('click', (e) => { + e.preventDefault() + + cookieBannerEl.setAttribute('hidden', '') + }) +} diff --git a/app/helpers/locations_helper.rb b/app/helpers/locations_helper.rb index ca04826ba..be5d3d4c0 100644 --- a/app/helpers/locations_helper.rb +++ b/app/helpers/locations_helper.rb @@ -26,12 +26,12 @@ module LocationsHelper def display_location_attributes(location) base_attributes = [ { name: "Postcode", value: location.postcode }, - { name: "Local authority", value: location.location_admin_district }, { name: "Location name", value: location.name, edit: true }, + { name: "Local authority", value: location.location_admin_district }, { name: "Total number of units at this location", value: location.units }, { name: "Common type of unit", value: location.type_of_unit }, { name: "Mobility type", value: location.mobility_type }, - { name: "Code", value: location.location_code }, + { name: "Location code", value: location.location_code }, { name: "Availability", value: location_availability(location) }, ] @@ -42,22 +42,9 @@ module LocationsHelper base_attributes end - ActivePeriod = Struct.new(:from, :to) - def active_periods(location) - periods = [ActivePeriod.new(location.available_from, nil)] - - sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date)) - sorted_deactivation_periods.each do |deactivation| - periods.last.to = deactivation.deactivation_date - periods << ActivePeriod.new(deactivation.reactivation_date, nil) - end - - remove_overlapping_and_empty_periods(periods) - end - def location_availability(location) availability = "" - active_periods(location).each do |period| + location_active_periods(location).each do |period| if period.from.present? availability << "\nActive from #{period.from.to_formatted_s(:govuk_date)}" availability << " to #{(period.to - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{period.to.to_formatted_s(:govuk_date)}" if period.to.present? @@ -68,6 +55,19 @@ module LocationsHelper private + ActivePeriod = Struct.new(:from, :to) + def location_active_periods(location) + periods = [ActivePeriod.new(location.available_from, nil)] + + sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date)) + sorted_deactivation_periods.each do |deactivation| + periods.last.to = deactivation.deactivation_date + periods << ActivePeriod.new(deactivation.reactivation_date, nil) + end + + remove_overlapping_and_empty_periods(periods) + end + def remove_overlapping_and_empty_periods(periods) periods.select { |period| period.from.present? && (period.to.nil? || period.from < period.to) } end diff --git a/app/helpers/logs_helper.rb b/app/helpers/logs_helper.rb new file mode 100644 index 000000000..6567f0a13 --- /dev/null +++ b/app/helpers/logs_helper.rb @@ -0,0 +1,21 @@ +module LogsHelper + def log_type_for_controller(controller) + case controller.class.to_s + when "LettingsLogsController" + "lettings" + when "SalesLogsController" + "sales" + else + raise "Log type not found for #{controller.class}" + end + end + + def bulk_upload_path_for_controller(controller, id:) + case log_type_for_controller(controller) + when "lettings" + bulk_upload_lettings_log_path(id:) + when "sales" + bulk_upload_sales_log_path(id:) + end + end +end diff --git a/app/helpers/navigation_items_helper.rb b/app/helpers/navigation_items_helper.rb index 07c125a8d..d996894ea 100644 --- a/app/helpers/navigation_items_helper.rb +++ b/app/helpers/navigation_items_helper.rb @@ -65,11 +65,11 @@ module NavigationItemsHelper private def lettings_logs_current?(path) - path == "/lettings-logs" + path.starts_with?("/lettings-logs") end def sales_logs_current?(path) - path == "/sales-logs" + path.starts_with?("/sales-logs") end def users_current?(path) diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 6d12f6675..3d143f8a2 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -1,5 +1,5 @@ module SchemesHelper - def display_scheme_attributes(scheme) + def display_scheme_attributes(scheme, user) base_attributes = [ { name: "Scheme code", value: scheme.id_to_display }, { name: "Name", value: scheme.service_name, edit: true }, @@ -18,21 +18,57 @@ module SchemesHelper ] if FeatureToggle.scheme_toggle_enabled? - base_attributes.append({ name: "Status", value: scheme.status }) + base_attributes.append({ name: "Status", value: status_tag(scheme.status) }) + end + + if user.data_coordinator? + base_attributes.delete_if { |item| item[:name] == "Housing stock owned by" } end if scheme.arrangement_type_same? - base_attributes.delete({ name: "Organisation providing support", value: scheme.managing_organisation&.name }) + base_attributes.delete_if { |item| item[:name] == "Organisation providing support" } end base_attributes end def scheme_availability(scheme) - availability = "Active from #{scheme.available_from.to_formatted_s(:govuk_date)}" - scheme.scheme_deactivation_periods.each do |deactivation| - availability << " to #{(deactivation.deactivation_date - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{deactivation.deactivation_date.to_formatted_s(:govuk_date)}" - availability << "\nActive from #{deactivation.reactivation_date.to_formatted_s(:govuk_date)}" if deactivation.reactivation_date.present? + availability = "" + scheme_active_periods(scheme).each do |period| + if period.from.present? + availability << "\nActive from #{period.from.to_formatted_s(:govuk_date)}" + availability << " to #{(period.to - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{period.to.to_formatted_s(:govuk_date)}" if period.to.present? + end + end + availability.strip + end + +private + + ActivePeriod = Struct.new(:from, :to) + def scheme_active_periods(scheme) + periods = [ActivePeriod.new(scheme.available_from, nil)] + + sorted_deactivation_periods = remove_nested_periods(scheme.scheme_deactivation_periods.sort_by(&:deactivation_date)) + sorted_deactivation_periods.each do |deactivation| + periods.last.to = deactivation.deactivation_date + periods << ActivePeriod.new(deactivation.reactivation_date, nil) end - availability + + remove_overlapping_and_empty_periods(periods) + end + + def remove_overlapping_and_empty_periods(periods) + periods.select { |period| period.from.present? && (period.to.nil? || period.from < period.to) } + end + + def remove_nested_periods(periods) + periods.select { |inner_period| periods.none? { |outer_period| is_nested?(inner_period, outer_period) } } + end + + def is_nested?(inner, outer) + return false if inner == outer + return false if [inner.deactivation_date, inner.reactivation_date, outer.deactivation_date, outer.reactivation_date].any?(&:blank?) + + [inner.deactivation_date, inner.reactivation_date].all? { |date| date.between?(outer.deactivation_date, outer.reactivation_date) } end end diff --git a/app/helpers/tag_helper.rb b/app/helpers/tag_helper.rb index 6682f97fd..f8b19dd83 100644 --- a/app/helpers/tag_helper.rb +++ b/app/helpers/tag_helper.rb @@ -9,6 +9,7 @@ module TagHelper active: "Active", incomplete: "Incomplete", deactivating_soon: "Deactivating soon", + activating_soon: "Activating soon", reactivating_soon: "Reactivating soon", deactivated: "Deactivated", }.freeze @@ -21,6 +22,7 @@ module TagHelper active: "green", incomplete: "red", deactivating_soon: "yellow", + activating_soon: "blue", reactivating_soon: "blue", deactivated: "grey", }.freeze diff --git a/app/helpers/toggle_active_scheme_helper.rb b/app/helpers/toggle_active_scheme_helper.rb new file mode 100644 index 000000000..9f93939fd --- /dev/null +++ b/app/helpers/toggle_active_scheme_helper.rb @@ -0,0 +1,17 @@ +module ToggleActiveSchemeHelper + def toggle_scheme_form_path(action, scheme) + if action == "deactivate" + scheme_new_deactivation_path(scheme) + else + scheme_reactivate_path(scheme) + end + end + + def date_type_question(action) + action == "deactivate" ? :deactivation_date_type : :reactivation_date_type + end + + def date_question(action) + action == "deactivate" ? :deactivation_date : :reactivation_date + end +end diff --git a/app/models/form.rb b/app/models/form.rb index 22321f431..9d9acf2ea 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -223,4 +223,12 @@ class Form end end end + + def in_crossover_period?(now: Time.zone.now) + ((end_date - 3.months) < now) && (now < end_date) + end + + def inspect + "#<#{self.class} @type=#{type} @name=#{name}>" + end end diff --git a/app/models/form/sales/pages/buyer1_mortgage.rb b/app/models/form/sales/pages/buyer1_mortgage.rb new file mode 100644 index 000000000..e17e9124b --- /dev/null +++ b/app/models/form/sales/pages/buyer1_mortgage.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::Buyer1Mortgage < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_1_mortgage" + @header = "" + @description = "" + @subsection = subsection + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer1Mortgage.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/buyer2_income.rb b/app/models/form/sales/pages/buyer2_income.rb new file mode 100644 index 000000000..207b6ec46 --- /dev/null +++ b/app/models/form/sales/pages/buyer2_income.rb @@ -0,0 +1,19 @@ +class Form::Sales::Pages::Buyer2Income < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "buyer_2_income" + @header = "" + @description = "" + @subsection = subsection + @depends_on = [{ + "jointpur" => 1, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::Buyer2IncomeKnown.new(nil, nil, self), + Form::Sales::Questions::Buyer2Income.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/questions/buyer1_mortgage.rb b/app/models/form/sales/questions/buyer1_mortgage.rb new file mode 100644 index 000000000..c524c3495 --- /dev/null +++ b/app/models/form/sales/questions/buyer1_mortgage.rb @@ -0,0 +1,16 @@ +class Form::Sales::Questions::Buyer1Mortgage < ::Form::Question + def initialize(id, hsh, page) + super + @id = "inc1mort" + @check_answer_label = "Buyer 1's income used for mortgage application" + @header = "Was buyer 1's income used for a mortgage application" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @page = page + end + + ANSWER_OPTIONS = { + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }.freeze +end diff --git a/app/models/form/sales/questions/buyer2_income.rb b/app/models/form/sales/questions/buyer2_income.rb new file mode 100644 index 000000000..4abb306a0 --- /dev/null +++ b/app/models/form/sales/questions/buyer2_income.rb @@ -0,0 +1,14 @@ +class Form::Sales::Questions::Buyer2Income < ::Form::Question + def initialize(id, hsh, page) + super + @id = "income2" + @check_answer_label = "Buyer 2’s gross annual income" + @header = "Buyer 2’s gross annual income" + @type = "numeric" + @page = page + @min = 0 + @step = 1 + @width = 5 + @prefix = "£" + end +end diff --git a/app/models/form/sales/questions/buyer2_income_known.rb b/app/models/form/sales/questions/buyer2_income_known.rb new file mode 100644 index 000000000..f0897f6be --- /dev/null +++ b/app/models/form/sales/questions/buyer2_income_known.rb @@ -0,0 +1,21 @@ +class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question + def initialize(id, hsh, page) + super + @id = "income2nk" + @check_answer_label = "Buyer 2’s gross annual income" + @header = "Do you know buyer 2’s annual income?" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @page = page + @guidance_position = GuidancePosition::BOTTOM + @guidance_partial = "what_counts_as_income_sales" + @conditional_for = { + "income2" => [0], + } + end + + ANSWER_OPTIONS = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + }.freeze +end diff --git a/app/models/form/sales/sections/finances.rb b/app/models/form/sales/sections/finances.rb index c2c4082fd..eb5c1a7f5 100644 --- a/app/models/form/sales/sections/finances.rb +++ b/app/models/form/sales/sections/finances.rb @@ -6,7 +6,7 @@ class Form::Sales::Sections::Finances < ::Form::Section @description = "" @form = form @subsections = [ - Form::Sales::Subsections::IncomeBenefitsAndOutgoings.new(nil, nil, self), + Form::Sales::Subsections::IncomeBenefitsAndSavings.new(nil, nil, self), ] end end diff --git a/app/models/form/sales/subsections/income_benefits_and_outgoings.rb b/app/models/form/sales/subsections/income_benefits_and_outgoings.rb deleted file mode 100644 index 6ec5c6488..000000000 --- a/app/models/form/sales/subsections/income_benefits_and_outgoings.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Form::Sales::Subsections::IncomeBenefitsAndOutgoings < ::Form::Subsection - def initialize(id, hsh, section) - super - @id = "income_benefits_and_outgoings" - @label = "Income, benefits and outgoings" - @section = section - @depends_on = [{ "setup_completed?" => true }] - end - - def pages - @pages ||= [ - Form::Sales::Pages::Buyer1Income.new(nil, nil, self), - ] - end -end diff --git a/app/models/form/sales/subsections/income_benefits_and_savings.rb b/app/models/form/sales/subsections/income_benefits_and_savings.rb new file mode 100644 index 000000000..26ab3310b --- /dev/null +++ b/app/models/form/sales/subsections/income_benefits_and_savings.rb @@ -0,0 +1,17 @@ +class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection + def initialize(id, hsh, section) + super + @id = "income_benefits_and_savings" + @label = "Income, benefits and savings" + @section = section + @depends_on = [{ "setup_completed?" => true }] + end + + def pages + @pages ||= [ + Form::Sales::Pages::Buyer1Income.new(nil, nil, self), + Form::Sales::Pages::Buyer1Mortgage.new(nil, nil, self), + Form::Sales::Pages::Buyer2Income.new(nil, nil, self), + ] + end +end diff --git a/app/models/form_handler.rb b/app/models/form_handler.rb index 61b981436..03de5e290 100644 --- a/app/models/form_handler.rb +++ b/app/models/form_handler.rb @@ -49,6 +49,11 @@ class FormHandler today < window_end_date ? today.year - 1 : today.year end + def collection_start_date(date) + window_end_date = Time.zone.local(date.year, 4, 1) + date < window_end_date ? Time.zone.local(date.year - 1, 4, 1) : Time.zone.local(date.year, 4, 1) + end + def current_collection_start_date Time.zone.local(current_collection_start_year, 4, 1) end diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb new file mode 100644 index 000000000..684ba1437 --- /dev/null +++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb @@ -0,0 +1,41 @@ +module Forms + module BulkUploadLettings + class PrepareYourFile + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + def view_path + "bulk_upload_lettings_logs/forms/prepare_your_file" + end + + def back_path + if in_crossover_period? + Rails.application.routes.url_helpers.bulk_upload_lettings_log_path(id: "year", form: { year: }) + else + Rails.application.routes.url_helpers.lettings_logs_path + end + end + + def next_path + bulk_upload_lettings_log_path(id: "upload-your-file", form: { year: }) + end + + def template_path + "/files/bulk-upload-lettings-template-v1.xlsx" + end + + def year_combo + "#{year}/#{year + 1 - 2000}" + end + + private + + def in_crossover_period? + FormHandler.instance.forms.values.any?(&:in_crossover_period?) + end + end + end +end diff --git a/app/models/forms/bulk_upload_lettings/upload_your_file.rb b/app/models/forms/bulk_upload_lettings/upload_your_file.rb new file mode 100644 index 000000000..1415ffe19 --- /dev/null +++ b/app/models/forms/bulk_upload_lettings/upload_your_file.rb @@ -0,0 +1,19 @@ +module Forms + module BulkUploadLettings + class UploadYourFile + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + def view_path + "bulk_upload_lettings_logs/forms/upload_your_file" + end + + def back_path + bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) + end + end + end +end diff --git a/app/models/forms/bulk_upload_lettings/year.rb b/app/models/forms/bulk_upload_lettings/year.rb new file mode 100644 index 000000000..9fa17b19e --- /dev/null +++ b/app/models/forms/bulk_upload_lettings/year.rb @@ -0,0 +1,37 @@ +module Forms + module BulkUploadLettings + class Year + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + validates :year, presence: true + + def view_path + "bulk_upload_lettings_logs/forms/year" + end + + def options + possible_years.map do |year| + OpenStruct.new(id: year, name: "#{year}/#{year + 1}") + end + end + + def back_path + lettings_logs_path + end + + def next_path + bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) + end + + private + + def possible_years + FormHandler.instance.lettings_forms.values.map { |form| form.start_date.year }.sort.reverse + end + end + end +end diff --git a/app/models/forms/bulk_upload_sales/prepare_your_file.rb b/app/models/forms/bulk_upload_sales/prepare_your_file.rb new file mode 100644 index 000000000..da017dbbd --- /dev/null +++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb @@ -0,0 +1,41 @@ +module Forms + module BulkUploadSales + class PrepareYourFile + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + def view_path + "bulk_upload_sales_logs/forms/prepare_your_file" + end + + def back_path + if in_crossover_period? + Rails.application.routes.url_helpers.bulk_upload_sales_log_path(id: "year", form: { year: }) + else + Rails.application.routes.url_helpers.sales_logs_path + end + end + + def next_path + bulk_upload_sales_log_path(id: "upload-your-file", form: { year: }) + end + + def template_path + "/files/bulk-upload-sales-template-v1.xlsx" + end + + def year_combo + "#{year}/#{year + 1 - 2000}" + end + + private + + def in_crossover_period? + FormHandler.instance.forms.values.any?(&:in_crossover_period?) + end + end + end +end diff --git a/app/models/forms/bulk_upload_sales/upload_your_file.rb b/app/models/forms/bulk_upload_sales/upload_your_file.rb new file mode 100644 index 000000000..3d421e9f1 --- /dev/null +++ b/app/models/forms/bulk_upload_sales/upload_your_file.rb @@ -0,0 +1,19 @@ +module Forms + module BulkUploadSales + class UploadYourFile + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + def view_path + "bulk_upload_sales_logs/forms/upload_your_file" + end + + def back_path + bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) + end + end + end +end diff --git a/app/models/forms/bulk_upload_sales/year.rb b/app/models/forms/bulk_upload_sales/year.rb new file mode 100644 index 000000000..361061990 --- /dev/null +++ b/app/models/forms/bulk_upload_sales/year.rb @@ -0,0 +1,37 @@ +module Forms + module BulkUploadSales + class Year + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :year, :integer + + validates :year, presence: true + + def view_path + "bulk_upload_sales_logs/forms/year" + end + + def options + possible_years.map do |year| + OpenStruct.new(id: year, name: "#{year}/#{year + 1}") + end + end + + def back_path + sales_logs_path + end + + def next_path + bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) + end + + private + + def possible_years + FormHandler.instance.sales_forms.values.map { |form| form.start_date.year }.sort.reverse + end + end + end +end diff --git a/app/models/location.rb b/app/models/location.rb index c05a2351b..6eededcc0 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -370,19 +370,29 @@ class Location < ApplicationRecord end def available_from - startdate || [created_at, FormHandler.instance.current_collection_start_date].min + return startdate if startdate.present? + + FormHandler.instance.collection_start_date(created_at) + end + + def open_deactivation + location_deactivation_periods.deactivations_without_reactivation.first end - def status - open_deactivation = location_deactivation_periods.deactivations_without_reactivation.first - recent_deactivation = location_deactivation_periods.order("created_at").last + def recent_deactivation + location_deactivation_periods.order("created_at").last + end - return :deactivated if open_deactivation&.deactivation_date.present? && Time.zone.now >= open_deactivation.deactivation_date - return :deactivating_soon if open_deactivation&.deactivation_date.present? && Time.zone.now < open_deactivation.deactivation_date - return :reactivating_soon if recent_deactivation&.reactivation_date.present? && Time.zone.now < recent_deactivation.reactivation_date + def status(date = Time.zone.now) + return :incomplete unless confirmed + return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date + return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date + return :reactivating_soon if recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date + return :activating_soon if startdate.present? && date < startdate :active end + alias_method :status_at, :status def active? status == :active diff --git a/app/models/organisation.rb b/app/models/organisation.rb index cc0df4ef6..bb636624c 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -13,9 +13,10 @@ class Organisation < ApplicationRecord has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :child_organisations, through: :child_organisation_relationships - has_many :housing_provider_relationships, -> { where(relationship_type: OrganisationRelationship::OWNING) }, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship" + has_many :housing_provider_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship" has_many :housing_providers, through: :housing_provider_relationships, source: :parent_organisation - has_many :managing_agent_relationships, -> { where(relationship_type: OrganisationRelationship::MANAGING) }, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" + + has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } diff --git a/app/models/organisation_relationship.rb b/app/models/organisation_relationship.rb index d1f073737..034fc5d0e 100644 --- a/app/models/organisation_relationship.rb +++ b/app/models/organisation_relationship.rb @@ -1,16 +1,4 @@ class OrganisationRelationship < ApplicationRecord belongs_to :child_organisation, class_name: "Organisation" belongs_to :parent_organisation, class_name: "Organisation" - - scope :owning, -> { where(relationship_type: OWNING) } - scope :managing, -> { where(relationship_type: MANAGING) } - - OWNING = "owning".freeze - MANAGING = "managing".freeze - RELATIONSHIP_TYPE = { - OWNING => 0, - MANAGING => 1, - }.freeze - - enum relationship_type: RELATIONSHIP_TYPE end diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 8197a147c..bc243d449 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -210,18 +210,32 @@ class Scheme < ApplicationRecord end def available_from - created_at + FormHandler.instance.collection_start_date(created_at) end - def status - recent_deactivation = scheme_deactivation_periods.deactivations_without_reactivation.first - return :active if recent_deactivation.blank? - return :deactivating_soon if Time.zone.now < recent_deactivation.deactivation_date + def open_deactivation + scheme_deactivation_periods.deactivations_without_reactivation.first + end + + def recent_deactivation + scheme_deactivation_periods.order("created_at").last + end - :deactivated + def status(date = Time.zone.now) + return :incomplete unless confirmed + return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date + return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date + return :reactivating_soon if recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date + + :active end + alias_method :status_at, :status def active? status == :active end + + def reactivating_soon? + status == :reactivating_soon + end end diff --git a/app/models/scheme_deactivation_period.rb b/app/models/scheme_deactivation_period.rb index f83a68e62..f716cbc32 100644 --- a/app/models/scheme_deactivation_period.rb +++ b/app/models/scheme_deactivation_period.rb @@ -1,15 +1,40 @@ class SchemeDeactivationPeriodValidator < ActiveModel::Validator def validate(record) + scheme = record.scheme + recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first + if recent_deactivation.present? + validate_reactivation(record, recent_deactivation, scheme) + else + validate_deactivation(record, scheme) + end + end + + def validate_reactivation(record, recent_deactivation, scheme) + if record.reactivation_date.blank? + if record.reactivation_date_type.blank? + record.errors.add(:reactivation_date_type, message: I18n.t("validations.scheme.toggle_date.not_selected")) + elsif record.reactivation_date_type == "other" + record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.toggle_date.invalid")) + end + elsif !record.reactivation_date.between?(scheme.available_from, Time.zone.local(2200, 1, 1)) + record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: scheme.available_from.to_formatted_s(:govuk_date))) + elsif record.reactivation_date < recent_deactivation.deactivation_date + record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.reactivation.before_deactivation", date: recent_deactivation.deactivation_date.to_formatted_s(:govuk_date))) + end + end + + def validate_deactivation(record, scheme) if record.deactivation_date.blank? if record.deactivation_date_type.blank? - record.errors.add(:deactivation_date_type, message: I18n.t("validations.scheme.deactivation_date.not_selected")) + record.errors.add(:deactivation_date_type, message: I18n.t("validations.scheme.toggle_date.not_selected")) elsif record.deactivation_date_type == "other" - record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation_date.invalid")) + record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.invalid")) end + elsif scheme.scheme_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) } + record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation.during_deactivated_period")) else - collection_start_date = FormHandler.instance.current_collection_start_date - unless record.deactivation_date.between?(collection_start_date, Time.zone.local(2200, 1, 1)) - record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation_date.out_of_range", date: collection_start_date.to_formatted_s(:govuk_date))) + unless record.deactivation_date.between?(scheme.available_from, Time.zone.local(2200, 1, 1)) + record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: scheme.available_from.to_formatted_s(:govuk_date))) end end end diff --git a/app/models/validations/date_validations.rb b/app/models/validations/date_validations.rb index 0353abd26..ceec8ed9a 100644 --- a/app/models/validations/date_validations.rb +++ b/app/models/validations/date_validations.rb @@ -1,4 +1,6 @@ module Validations::DateValidations + include Validations::SharedValidations + def validate_property_major_repairs(record) date_valid?("mrcdate", record) if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"] @@ -59,6 +61,9 @@ module Validations::DateValidations if record["mrcdate"].present? && record.startdate < record["mrcdate"] record.errors.add :startdate, I18n.t("validations.setup.startdate.after_major_repair_date") end + + location_during_startdate_validation(record, :startdate) + scheme_during_startdate_validation(record, :startdate) end private diff --git a/app/models/validations/setup_validations.rb b/app/models/validations/setup_validations.rb index 44770c947..c1bdc3a71 100644 --- a/app/models/validations/setup_validations.rb +++ b/app/models/validations/setup_validations.rb @@ -1,10 +1,21 @@ module Validations::SetupValidations + include Validations::SharedValidations + def validate_irproduct_other(record) if intermediate_product_rent_type?(record) && record.irproduct_other.blank? record.errors.add :irproduct_other, I18n.t("validations.setup.intermediate_rent_product_name.blank") end end + def validate_location(record) + location_during_startdate_validation(record, :location_id) + end + + def validate_scheme(record) + location_during_startdate_validation(record, :scheme_id) + scheme_during_startdate_validation(record, :scheme_id) + end + private def intermediate_product_rent_type?(record) diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index 9b4f8a3ff..2694fd743 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -33,4 +33,39 @@ module Validations::SharedValidations end end end + + def location_during_startdate_validation(record, field) + location_inactive_status = inactive_status(record.startdate, record.location) + + if location_inactive_status.present? + date, scope, deactivation_date = location_inactive_status.values_at(:date, :scope, :deactivation_date) + record.errors.add field, I18n.t("validations.setup.startdate.location.#{scope}", postcode: record.location.postcode, date:, deactivation_date:) + end + end + + def scheme_during_startdate_validation(record, field) + scheme_inactive_status = inactive_status(record.startdate, record.scheme) + if scheme_inactive_status.present? + date, scope, deactivation_date = scheme_inactive_status.values_at(:date, :scope, :deactivation_date) + record.errors.add field, I18n.t("validations.setup.startdate.scheme.#{scope}", name: record.scheme.service_name, date:, deactivation_date:) + end + end + + def inactive_status(date, resource) + return if date.blank? || resource.blank? + + status = resource.status_at(date) + return unless %i[reactivating_soon activating_soon deactivated].include?(status) + + closest_reactivation = resource.recent_deactivation + open_deactivation = resource.open_deactivation + + date = case status + when :reactivating_soon then closest_reactivation.reactivation_date + when :activating_soon then resource&.available_from + when :deactivated then open_deactivation.deactivation_date + end + + { scope: status, date: date&.to_formatted_s(:govuk_date), deactivation_date: closest_reactivation&.deactivation_date&.to_formatted_s(:govuk_date) } + end end diff --git a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb new file mode 100644 index 000000000..d8cfedd08 --- /dev/null +++ b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb @@ -0,0 +1,33 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %> + <%= f.hidden_field :year %> + + Upload lettings logs in bulk (<%= @form.year_combo %>) +

Prepare your file

+ +

Create your file

+ + +

Check your data

+ + +

Save your file

+ + + <%= f.govuk_submit %> + <% end %> +
+
diff --git a/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb b/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb new file mode 100644 index 000000000..86dde8ae2 --- /dev/null +++ b/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb @@ -0,0 +1,17 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "upload-your-file"), method: :patch do |f| %> + <%= f.govuk_error_summary %> + +
+ Upload your file goes here +
+ +
+ year selected <%= @form.year %> +
+ + <%= f.govuk_submit %> +<% end %> diff --git a/app/views/bulk_upload_lettings_logs/forms/year.html.erb b/app/views/bulk_upload_lettings_logs/forms/year.html.erb new file mode 100644 index 000000000..8ba1c280f --- /dev/null +++ b/app/views/bulk_upload_lettings_logs/forms/year.html.erb @@ -0,0 +1,16 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "year"), method: :patch do |f| %> + <%= f.govuk_error_summary %> + + <%= f.govuk_collection_radio_buttons :year, + @form.options, + :id, + :name, + legend: { text: "Which year are you uploading data for?", size: "l" }, + caption: { text: "Upload lettings logs in bulk", size: "l" } %> + + <%= f.govuk_submit %> +<% end %> diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb new file mode 100644 index 000000000..0157b66eb --- /dev/null +++ b/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb @@ -0,0 +1,33 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ <%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %> + <%= f.hidden_field :year %> + + Upload sales logs in bulk (<%= @form.year_combo %>) +

Prepare your file

+ +

Create your file

+ + +

Check your data

+ + +

Save your file

+ + + <%= f.govuk_submit %> + <% end %> +
+
diff --git a/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb b/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb new file mode 100644 index 000000000..a178339e8 --- /dev/null +++ b/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb @@ -0,0 +1,17 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "upload-your-file"), method: :patch do |f| %> + <%= f.govuk_error_summary %> + +
+ Upload your file goes here +
+ +
+ year selected <%= @form.year %> +
+ + <%= f.govuk_submit %> +<% end %> diff --git a/app/views/bulk_upload_sales_logs/forms/year.html.erb b/app/views/bulk_upload_sales_logs/forms/year.html.erb new file mode 100644 index 000000000..d8aa09172 --- /dev/null +++ b/app/views/bulk_upload_sales_logs/forms/year.html.erb @@ -0,0 +1,16 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "year"), method: :patch do |f| %> + <%= f.govuk_error_summary %> + + <%= f.govuk_collection_radio_buttons :year, + @form.options, + :id, + :name, + legend: { text: "Which year are you uploading data for?", size: "l" }, + caption: { text: "Upload sales logs in bulk", size: "l" } %> + + <%= f.govuk_submit %> +<% end %> diff --git a/app/views/cookies/_banner.html.erb b/app/views/cookies/_banner.html.erb new file mode 100644 index 000000000..7885bad47 --- /dev/null +++ b/app/views/cookies/_banner.html.erb @@ -0,0 +1,48 @@ + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 2e5f66da1..c52b587f3 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -25,15 +25,15 @@ <% gtm_container = get_gtm_container %> <% gtm_id = get_gtm_id %> - - - - <% if cookies[:accept_analytics_cookies] == "on" %> + + + + <% else %> - - <% if cookies[:accept_analytics_cookies] %> + <% if cookies[:accept_analytics_cookies] == "on" %> + <% end %> + <% unless cookies[:accept_analytics_cookies] || current_page?(cookies_path) %> + <%= render "cookies/banner" %> + <% end %> + <%= govuk_skip_link %> <%= govuk_header( diff --git a/app/views/locations/deactivate_confirm.html.erb b/app/views/locations/deactivate_confirm.html.erb index 47b7bbf60..6748af918 100644 --- a/app/views/locations/deactivate_confirm.html.erb +++ b/app/views/locations/deactivate_confirm.html.erb @@ -4,7 +4,7 @@ <% end %>

<%= @location.postcode %> - This change will affect <%= @location.lettings_logs.count %> logs + This change will affect <%= @affected_logs.count %> logs

<%= govuk_warning_text text: I18n.t("warnings.location.deactivate.review_logs") %> <%= f.hidden_field :confirm, value: true %> diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 5b2cbae47..812296be8 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -11,57 +11,104 @@ <%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %> -<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> +<% if FeatureToggle.location_toggle_enabled? %> +
+
+<% end %> + <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> -

Locations

+

Locations

-<%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %> + <%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %> -<%= govuk_section_break(visible: true, size: "m") %> + <%= govuk_section_break(visible: true, size: "m") %> +<% if FeatureToggle.location_toggle_enabled? %> +
+
+<% end %> -<%= govuk_table do |table| %> - <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> - <% end %> - <%= table.head do |head| %> - <%= head.row do |row| %> - <% row.cell(header: true, text: "Code", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Postcode", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Units", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Common unit type", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Mobility type", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Local authority", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Available from", html_attributes: { - scope: "col", - }) %> - <% end %> - <% end %> - <% @locations.each do |location| %> - <%= table.body do |body| %> - <%= body.row do |row| %> - <% row.cell(text: location.id) %> - <% row.cell(text: simple_format(location_cell_postcode(location, "/schemes/#{@scheme.id}/locations/#{location.id}"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> - <% row.cell(text: location.units) %> - <% row.cell(text: simple_format("#{location.type_of_unit}")) %> - <% row.cell(text: location.mobility_type) %> - <% row.cell(text: simple_format(location_cell_location_admin_district(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit-local-authority"), wrapper_tag: "div")) %> - <% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %> +<% if FeatureToggle.location_toggle_enabled? %> +
+
+ <%= govuk_table do |table| %> + <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> + <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> + <% end %> + <%= table.head do |head| %> + <%= head.row do |row| %> + <% row.cell(header: true, text: "Postcode", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Location code", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Status", html_attributes: { + scope: "col", + }) %> + <% end %> <% end %> + <% @locations.each do |location| %> + <%= table.body do |body| %> + <%= body.row do |row| %> + <% row.cell(text: simple_format(location_cell_postcode(location, scheme_location_path(@scheme, location)), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> + <% row.cell(text: location.id) %> + <% row.cell(text: status_tag(location.status)) %> + <% end %> + <% end %> + <% end %> + <% end %> + <%= govuk_button_link_to "Add a location", new_scheme_location_path(@scheme), secondary: true %> +
+
+ +<% else %> + <%= govuk_table do |table| %> + <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> + <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> + <% end %> + <%= table.head do |head| %> + <%= head.row do |row| %> + <% row.cell(header: true, text: "Code", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Postcode", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Units", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Common unit type", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Mobility type", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Local authority", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Available from", html_attributes: { + scope: "col", + }) %> + <% end %> + <% end %> + <% @locations.each do |location| %> + <%= table.body do |body| %> + <%= body.row do |row| %> + <% row.cell(text: location.id) %> + <% row.cell(text: simple_format(location_cell_postcode(location, scheme_location_path(@scheme, location)), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> + <% row.cell(text: location.units) %> + <% row.cell do %> + <%= simple_format(location.type_of_unit) %> + <% end %> + <% row.cell(text: location.mobility_type) %> + <% row.cell(text: simple_format(location_cell_location_admin_district(location, scheme_location_edit_local_authority_path(@scheme, location)), wrapper_tag: "div")) %> + <% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %> + <% end %> + <% end %> <% end %> <% end %> + <%= govuk_button_link_to "Add a location", new_scheme_location_path(@scheme), secondary: true %> + <% end %> -<%= govuk_button_link_to "Add a location", new_scheme_location_path(scheme_id: @scheme.id), secondary: true %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %> diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index 431ca0f45..a340973cf 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -10,14 +10,18 @@ <% end %>
-
+
<% if current_page?(controller: 'lettings_logs', action: 'index') %> - <%= govuk_button_to "Create a new lettings log", lettings_logs_path %> + <%= govuk_button_to "Create a new lettings log", lettings_logs_path, class: "govuk-!-margin-right-6" %> <% end %> + <% if FeatureToggle.sales_log_enabled? && current_page?(controller: 'sales_logs', action: 'index') %> - <%= govuk_button_to "Create a new sales log", sales_logs_path %> + <%= govuk_button_to "Create a new sales log", sales_logs_path, class: "govuk-!-margin-right-6" %> + <% end %> + + <% if FeatureToggle.bulk_upload_logs? %> + <%= govuk_button_link_to "Upload #{log_type_for_controller(controller)} logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %> <% end %> - <%#= govuk_link_to "Upload logs", bulk_upload_lettings_logs_path %>
<%= render partial: "log_filters" %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb index 976622ce0..5fa712d1e 100644 --- a/app/views/schemes/_scheme_list.html.erb +++ b/app/views/schemes/_scheme_list.html.erb @@ -5,31 +5,31 @@ <% end %> <%= table.head do |head| %> <%= head.row do |row| %> - <% row.cell(header: true, text: "Code", html_attributes: { - scope: "col", - }) %> <% row.cell(header: true, text: "Scheme", html_attributes: { scope: "col", }) %> - <% row.cell(header: true, text: "Locations", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Support provided by", html_attributes: { + <% row.cell(header: true, text: "Code", html_attributes: { scope: "col", }) %> - <% row.cell(header: true, text: "Created", html_attributes: { + <% row.cell(header: true, text: "Locations", html_attributes: { scope: "col", }) %> + <% if FeatureToggle.scheme_toggle_enabled? %> + <% row.cell(header: true, text: "Status", html_attributes: { + scope: "col", + }) %> + <% end %> <% end %> <% end %> <% @schemes.each do |scheme| %> <%= table.body do |body| %> <%= body.row do |row| %> - <% row.cell(text: scheme.id_to_display) %> <% row.cell(text: simple_format(scheme_cell(scheme), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> + <% row.cell(text: scheme.id_to_display) %> <% row.cell(text: scheme.locations&.count) %> - <% row.cell(text: scheme.managing_organisation&.name) %> - <% row.cell(text: scheme.confirmed? ? scheme.created_at.to_formatted_s(:govuk_date) : govuk_tag(colour: "grey", text: "Incomplete")) %> + <% if FeatureToggle.scheme_toggle_enabled? %> + <% row.cell(text: status_tag(scheme.status)) %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/schemes/deactivate_confirm.html.erb b/app/views/schemes/deactivate_confirm.html.erb index 0cfd454c8..b5dda160e 100644 --- a/app/views/schemes/deactivate_confirm.html.erb +++ b/app/views/schemes/deactivate_confirm.html.erb @@ -1,14 +1,12 @@ -<% title = "Deactivate #{@scheme.service_name}" %> -<% content_for :title, title %> <%= form_with model: @scheme_deactivation_period, url: scheme_deactivate_path(@scheme), method: "patch", local: true do |f| %> <% content_for :before_content do %> <%= govuk_back_link(href: :back) %> <% end %>

<%= @scheme.service_name %> - This change will affect <%= @scheme.lettings_logs.count %> logs + This change will affect <%= @affected_logs.count %> logs

- <%= govuk_warning_text text: I18n.t("warnings.scheme.deactivation.review_logs") %> + <%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs") %> <%= f.hidden_field :confirm, value: true %> <%= f.hidden_field :deactivation_date, value: @deactivation_date %> <%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %> diff --git a/app/views/schemes/show.html.erb b/app/views/schemes/show.html.erb index 2c01c06f1..5d93414d0 100644 --- a/app/views/schemes/show.html.erb +++ b/app/views/schemes/show.html.erb @@ -10,25 +10,33 @@ <%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %> -<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> +<% if FeatureToggle.location_toggle_enabled? %> +
+
+<% end %> + <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> -

Scheme

+

Scheme

-<%= govuk_summary_list do |summary_list| %> - <% display_scheme_attributes(@scheme).each do |attr| %> - <% next if current_user.data_coordinator? && attr[:name] == ("Housing stock owned by") %> - <%= summary_list.row do |row| %> - <% row.key { attr[:name].eql?("Registered under Care Standards Act 2000") ? "Registered under Care Standards Act 2000" : attr[:name].to_s.humanize } %> - <% row.value { attr[:name].eql?("Status") ? status_tag(attr[:value]) : details_html(attr) } %> - <% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %> - <% end %> - <% end %> + <%= govuk_summary_list do |summary_list| %> + <% display_scheme_attributes(@scheme, current_user).each do |attr| %> + <%= summary_list.row do |row| %> + <% row.key { attr[:name] } %> + <% row.value { details_html(attr) } %> + <% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %> + <% end %> + <% end %> + <% end %> + +<% if FeatureToggle.location_toggle_enabled? %> +
+
<% end %> <% if FeatureToggle.scheme_toggle_enabled? %> - <% if @scheme.active? %> + <% if @scheme.active? || @scheme.reactivating_soon? %> <%= govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(@scheme), warning: true %> <% else %> - <%= govuk_button_link_to "Reactivate this scheme", scheme_reactivate_path(@scheme) %> + <%= govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(@scheme) %> <% end %> <% end %> diff --git a/app/views/schemes/toggle_active.html.erb b/app/views/schemes/toggle_active.html.erb index fbb3ebc5f..1b7507375 100644 --- a/app/views/schemes/toggle_active.html.erb +++ b/app/views/schemes/toggle_active.html.erb @@ -1,29 +1,31 @@ <% title = "#{action.humanize} #{@scheme.service_name}" %> <% content_for :title, title %> + <% content_for :before_content do %> <%= govuk_back_link( text: "Back", href: scheme_details_path(@scheme), ) %> <% end %> -<%= form_with model: @scheme_deactivation_period, url: scheme_new_deactivation_path(@scheme), method: "patch", local: true do |f| %> + +<%= form_with model: @scheme_deactivation_period, url: toggle_scheme_form_path(action, @scheme), method: "patch", local: true do |f| %>
<% collection_start_date = FormHandler.instance.current_collection_start_date %> <%= f.govuk_error_summary %> - <%= f.govuk_radio_buttons_fieldset :deactivation_date_type, - legend: { text: I18n.t("questions.scheme.deactivation.apply_from") }, + <%= f.govuk_radio_buttons_fieldset date_type_question(action), + legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") }, caption: { text: title }, - hint: { text: I18n.t("hints.scheme.deactivation", date: collection_start_date.to_formatted_s(:govuk_date)) } do %> - <%= govuk_warning_text text: I18n.t("warnings.scheme.deactivation.existing_logs") %> - <%= f.govuk_radio_button :deactivation_date_type, + hint: { text: I18n.t("hints.scheme.toggle_active", date: collection_start_date.to_formatted_s(:govuk_date)) } do %> + <%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %> + <%= f.govuk_radio_button date_type_question(action), "default", label: { text: "From the start of the current collection period (#{collection_start_date.to_formatted_s(:govuk_date)})" } %> - <%= f.govuk_radio_button :deactivation_date_type, + <%= f.govuk_radio_button date_type_question(action), "other", label: { text: "For tenancies starting after a certain date" }, **basic_conditional_html_attributes({ "deactivation_date" => %w[other] }, "scheme") do %> - <%= f.govuk_date_field :deactivation_date, + <%= f.govuk_date_field date_question(action), legend: { text: "Date", size: "m" }, hint: { text: "For example, 27 3 2022" }, width: 20 %> diff --git a/config/environments/review_app.rb b/config/environments/review.rb similarity index 100% rename from config/environments/review_app.rb rename to config/environments/review.rb diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 2c6d0763f..fe1511153 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -5929,7 +5929,7 @@ "description": "", "questions": { "housingneeds_type": { - "header": "What type of access need do they have?", + "header": "What type of access needs do they have?", "hint_text": "", "type": "radio", "check_answer_label": "Disabled access needs", @@ -5947,7 +5947,7 @@ "value": true }, "3": { - "value": "None of the above" + "value": "None of the listed options" } } }, @@ -7470,7 +7470,7 @@ "household_charge": { "check_answer_label": "Does the household pay rent or charges?", "header": "Does the household pay rent or other charges for the accommodation?", - "hint_text": "", + "hint_text": "If rent is charged on the property then answer Yes to this question, even if the tenants do not pay it themselves.", "type": "radio", "answer_options": { "0": { diff --git a/config/initializers/feature_toggle.rb b/config/initializers/feature_toggle.rb index 7cd75ddd3..8fc2cd7d4 100644 --- a/config/initializers/feature_toggle.rb +++ b/config/initializers/feature_toggle.rb @@ -22,4 +22,8 @@ class FeatureToggle def self.managing_for_other_user_enabled? !Rails.env.production? end + + def self.bulk_upload_logs? + !Rails.env.production? + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index f839ccf7e..2463041f9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -38,6 +38,18 @@ en: create_password: "Create a password to finish setting up your account" reset_password: "Reset your password" + activemodel: + errors: + models: + forms/bulk_upload_lettings/year: + attributes: + year: + blank: You must select a collection period to upload for + forms/bulk_upload_sales/year: + attributes: + year: + blank: You must select a collection period to upload for + activerecord: errors: models: @@ -120,6 +132,14 @@ en: before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" after_void_date: "Enter a tenancy start date that is after the void date" after_major_repair_date: "Enter a tenancy start date that is after the major repair date" + location: + deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered." + reactivating_soon: "The location %{postcode} was deactivated on %{deactivation_date} and is not available on the date you entered. It reactivates on %{date}" + activating_soon: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}" + scheme: + deactivated: "%{name} was deactivated on %{date} and was not available on the day you entered" + reactivating_soon: "%{name} was deactivated on %{deactivation_date} and is not available on the date you entered. It reactivates on %{date}" + activating_soon: "%{name} is not available until %{date}. Enter a tenancy start date after %{date}" property: mrcdate: @@ -313,10 +333,16 @@ en: missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log." scheme: - deactivation_date: + toggle_date: not_selected: "Select one of the options" invalid: "Enter a valid day, month and year" out_of_range: "The date must be on or after the %{date}" + reactivation: + before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date" + deactivation: + during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date" + + location: toggle_date: @@ -324,8 +350,8 @@ en: invalid: "Enter a valid day, month and year" out_of_range: "The date must be on or after the %{date}" reactivation: - before_deactivation: "This location was deactivated on %{date}\nThe reactivation date must be on or after deactivation date" - deactivation: + before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date" + deactivation: during_deactivated_period: "The location is already deactivated during this date, please enter a different date" soft_validations: @@ -381,7 +407,7 @@ en: toggle_active: apply_from: "When should this change apply?" scheme: - deactivation: + toggle_active: apply_from: "When should this change apply?" descriptions: location: @@ -397,19 +423,21 @@ en: units: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff." toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." scheme: - deactivation: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." + toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed." warnings: location: deactivate: existing_logs: "It will not be possible to add logs with this location if their tenancy start date is on or after the date you enter. Any existing logs may be affected." review_logs: "Your data providers will need to review these logs and answer a few questions again. We’ll email each log creator with a list of logs that need updating." - reactivate: + reactivate: existing_logs: "You’ll be able to add logs with this location if their tenancy start date is on or after the date you enter." scheme: - deactivation: + deactivate: existing_logs: "It will not be possible to add logs with this scheme if their tenancy start date is on or after the date you enter. Any existing logs may be affected." review_logs: "Your data providers will need to review these logs and answer a few questions again. We’ll email each log creator with a list of logs that need updating." + reactivate: + existing_logs: "You’ll be able to add logs with this scheme if their tenancy start date is on or after the date you enter." test: one_argument: "This is based on the tenant’s work situation: %{ecstat1}" diff --git a/config/routes.rb b/config/routes.rb index 98d7151b1..aebc664ab 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -52,8 +52,10 @@ Rails.application.routes.draw do get "new-deactivation", to: "schemes#new_deactivation" get "deactivate-confirm", to: "schemes#deactivate_confirm" get "reactivate", to: "schemes#reactivate" + get "new-reactivation", to: "schemes#new_reactivation" patch "new-deactivation", to: "schemes#new_deactivation" patch "deactivate", to: "schemes#deactivate" + patch "reactivate", to: "schemes#reactivate" resources :locations do get "edit-name", to: "locations#edit_name" @@ -109,6 +111,12 @@ Rails.application.routes.draw do get "csv-download", to: "lettings_logs#download_csv" post "email-csv", to: "lettings_logs#email_csv" get "csv-confirmation", to: "lettings_logs#csv_confirmation" + + resources :bulk_upload_lettings_logs, path: "bulk-upload-logs" do + collection do + get :start + end + end end member do @@ -128,6 +136,14 @@ Rails.application.routes.draw do end resources :sales_logs, path: "/sales-logs" do + collection do + resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do + collection do + get :start + end + end + end + FormHandler.instance.sales_forms.each do |_key, form| form.pages.map do |page| get page.id.to_s.dasherize, to: "form#show_page" diff --git a/db/migrate/20221122122311_delete_relationship_type.rb b/db/migrate/20221122122311_delete_relationship_type.rb new file mode 100644 index 000000000..81982a25c --- /dev/null +++ b/db/migrate/20221122122311_delete_relationship_type.rb @@ -0,0 +1,5 @@ +class DeleteRelationshipType < ActiveRecord::Migration[7.0] + def change + remove_column :organisation_relationships, :relationship_type, :integer, null: false + end +end diff --git a/db/migrate/20221122130928_add_org_relation_indexes.rb b/db/migrate/20221122130928_add_org_relation_indexes.rb new file mode 100644 index 000000000..770e97fde --- /dev/null +++ b/db/migrate/20221122130928_add_org_relation_indexes.rb @@ -0,0 +1,10 @@ +class AddOrgRelationIndexes < ActiveRecord::Migration[7.0] + def change + add_index :organisation_relationships, :child_organisation_id + add_index :organisation_relationships, :parent_organisation_id + add_index :organisation_relationships, %i[parent_organisation_id child_organisation_id], unique: true, name: "index_org_rel_parent_child_uniq" + + add_foreign_key :organisation_relationships, :organisations, column: :parent_organisation_id + add_foreign_key :organisation_relationships, :organisations, column: :child_organisation_id + end +end diff --git a/db/migrate/20221124102329_add_mortgage1_to_sales.rb b/db/migrate/20221124102329_add_mortgage1_to_sales.rb new file mode 100644 index 000000000..472ac28b7 --- /dev/null +++ b/db/migrate/20221124102329_add_mortgage1_to_sales.rb @@ -0,0 +1,7 @@ +class AddMortgage1ToSales < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :inc1mort, :int + end + end +end diff --git a/db/migrate/20221125142847_add_buyer2_to_sales.rb b/db/migrate/20221125142847_add_buyer2_to_sales.rb new file mode 100644 index 000000000..883ad44dd --- /dev/null +++ b/db/migrate/20221125142847_add_buyer2_to_sales.rb @@ -0,0 +1,6 @@ +class AddBuyer2ToSales < ActiveRecord::Migration[7.0] + change_table :sales_logs, bulk: true do |t| + t.column :income2, :int + t.column :income2nk, :int + end +end diff --git a/db/schema.rb b/db/schema.rb index f2760b505..e5a4d27e4 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.0].define(version: 2022_11_17_103855) do +ActiveRecord::Schema[7.0].define(version: 2022_11_25_142847) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -286,7 +286,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do t.integer "parent_organisation_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "relationship_type", null: false + t.index ["child_organisation_id"], name: "index_organisation_relationships_on_child_organisation_id" + t.index ["parent_organisation_id", "child_organisation_id"], name: "index_org_rel_parent_child_uniq", unique: true + t.index ["parent_organisation_id"], name: "index_organisation_relationships_on_parent_organisation_id" end create_table "organisation_rent_periods", force: :cascade do |t| @@ -372,15 +374,18 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do t.integer "la_known" t.integer "income1" t.integer "income1nk" + t.integer "details_known_2" + t.integer "details_known_3" + t.integer "details_known_4" t.integer "age4" t.integer "age4_known" t.integer "age5" t.integer "age5_known" t.integer "age6" t.integer "age6_known" - t.integer "details_known_2" - t.integer "details_known_3" - t.integer "details_known_4" + t.integer "inc1mort" + t.integer "income2" + t.integer "income2nk" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" t.index ["managing_organisation_id"], name: "index_sales_logs_on_managing_organisation_id" t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" @@ -477,6 +482,8 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do add_foreign_key "lettings_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "lettings_logs", "schemes" add_foreign_key "locations", "schemes" + add_foreign_key "organisation_relationships", "organisations", column: "child_organisation_id" + add_foreign_key "organisation_relationships", "organisations", column: "parent_organisation_id" add_foreign_key "sales_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "schemes", "organisations", column: "managing_organisation_id" add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade diff --git a/db/seeds.rb b/db/seeds.rb index 7bcc90abc..bf40a59d1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -67,25 +67,21 @@ unless Rails.env.test? end end - OrganisationRelationship.create!( + OrganisationRelationship.find_or_create_by!( child_organisation: org, parent_organisation: housing_provider1, - relationship_type: OrganisationRelationship::OWNING, ) - OrganisationRelationship.create!( + OrganisationRelationship.find_or_create_by!( child_organisation: org, parent_organisation: housing_provider2, - relationship_type: OrganisationRelationship::OWNING, ) - OrganisationRelationship.create!( + OrganisationRelationship.find_or_create_by!( child_organisation: managing_agent1, parent_organisation: org, - relationship_type: OrganisationRelationship::MANAGING, ) - OrganisationRelationship.create!( + OrganisationRelationship.find_or_create_by!( child_organisation: managing_agent2, parent_organisation: org, - relationship_type: OrganisationRelationship::MANAGING, ) if (Rails.env.development? || Rails.env.review?) && User.count.zero? diff --git a/docs/infrastructure.md b/docs/infrastructure.md index 63bbf2e42..81dc3f165 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -122,6 +122,57 @@ After a sucessful deployment a comment will be added to the pull request with th Once a pull request has been closed the review app infrastructure will be tore down to save on any costs. Should you wish to re-open a closed pull request the review app will be spun up again. +### How to fix review app deployment failures + +One reason a review app deployment might fail is that it is attempting to run migrations which conflict with data in the database. For example you might have introduced a unique constraint, but the database associated with the review app has duplicate data in it that would violate this constraint, and so the migration cannot be run. There are two main ways to remedy this: + +**Method 1 - Edit database via console** +1. Log in to Cloud Foundry + ```bash + cf login -a api.london.cloud.service.gov.uk -u + ``` + * Your username should be the email address you signed up to GOVUK PaaS with. + * Choose the dev environment whilst logging in. +2. If you were already logged in then Cloud Foundry, then instead just target the dev environment + ```bash + cf target -o dluhc-core -s dev + ``` +3. Find the name of your app + ```bash + cf apps + ``` + * The app name will be in this format: `dluhc-core-review-`. +4. Open a console for your app + ```bash + cf ssh -t -c "/tmp/lifecycle/launcher /home/vcap/app 'rails console' ''" + ``` +5. Edit the database as appropriate, e.g. delete dodgy data and recreate correctly + +**Method 2 - Nuke and restart** + +1. Find the name of your app + ```bash + cf apps + ``` + * The app name will be in this format: `dluhc-core-review-`. +2. Delete the app + ```bash + cf delete + ``` +3. Find the name of the matching Postgres service + ```bash + cf services + ``` + * The service name will be in this format: `dluhc-core-review--postgres`. +4. Delete the service + ```bash + cf delete-service + ``` + * Use `cf services` or `cf service ` to check the operation status. + * There's no need to delete the Redis service. +5. Re-run the whole review app pipeline in GitHub + * If it fails it's likely that the deletion from the previous step hadn't completed yet. So just wait a few minutes and re-run the pipeline again. + ## Setting up Infrastructure for a new environment ### Staging diff --git a/public/files/bulk-upload-lettings-template-v1.xlsx b/public/files/bulk-upload-lettings-template-v1.xlsx new file mode 100644 index 000000000..ca30388ba Binary files /dev/null and b/public/files/bulk-upload-lettings-template-v1.xlsx differ diff --git a/public/files/bulk-upload-sales-template-v1.xlsx b/public/files/bulk-upload-sales-template-v1.xlsx new file mode 100644 index 000000000..73c9f829c Binary files /dev/null and b/public/files/bulk-upload-sales-template-v1.xlsx differ diff --git a/spec/components/check_answers_summary_list_card_component_spec.rb b/spec/components/check_answers_summary_list_card_component_spec.rb index 6273da69d..6e63c90cd 100644 --- a/spec/components/check_answers_summary_list_card_component_spec.rb +++ b/spec/components/check_answers_summary_list_card_component_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do context "when given a set of questions" do let(:user) { FactoryBot.build(:user) } - let(:log) { FactoryBot.build(:lettings_log, :completed, age2: 99) } + let(:log) { FactoryBot.build(:lettings_log, :completed, age2: 99, startdate: Time.zone.local(2021, 5, 1)) } let(:subsection_id) { "household_characteristics" } let(:subsection) { log.form.get_subsection(subsection_id) } let(:questions) { subsection.applicable_questions(log) } diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index d337bc592..217e8a605 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -60,7 +60,7 @@ FactoryBot.define do illness { 1 } preg_occ { 2 } startertenancy { 1 } - tenancylength { 5 } + tenancylength { nil } tenancy { 1 } ppostcode_full { Faker::Address.postcode } rsnvac { 6 } @@ -134,7 +134,7 @@ FactoryBot.define do property_relet { 0 } mrcdate { Time.zone.local(2020, 5, 5, 10, 36, 49) } incref { 0 } - startdate { Time.utc(2022, 2, 2, 10, 36, 49) } + startdate { Time.zone.today } armedforces { 1 } builtype { 1 } unitletas { 2 } diff --git a/spec/factories/location.rb b/spec/factories/location.rb index 3359f64cd..870140cd5 100644 --- a/spec/factories/location.rb +++ b/spec/factories/location.rb @@ -7,7 +7,7 @@ FactoryBot.define do mobility_type { %w[A M N W X].sample } location_code { "E09000033" } location_admin_district { "Westminster" } - startdate { Faker::Date.between(from: 6.months.ago, to: Time.zone.today) } + startdate { nil } confirmed { true } scheme trait :export do diff --git a/spec/factories/organisation_relationship.rb b/spec/factories/organisation_relationship.rb index 418599902..de2e2901b 100644 --- a/spec/factories/organisation_relationship.rb +++ b/spec/factories/organisation_relationship.rb @@ -2,13 +2,5 @@ FactoryBot.define do factory :organisation_relationship do child_organisation { FactoryBot.create(:organisation) } parent_organisation { FactoryBot.create(:organisation) } - - trait :owning do - relationship_type { OrganisationRelationship::OWNING } - end - - trait :managing do - relationship_type { OrganisationRelationship::MANAGING } - end end end diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index 8e37581c5..e55287d67 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -52,6 +52,9 @@ FactoryBot.define do age6 { 40 } income1nk { 0 } income1 { 10_000 } + inc1mort { 1 } + income2nk { 0 } + income2 { 10_000 } la_known { "1" } la { "E09000003" } end diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 4a85f2036..031d9b8c1 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -12,7 +12,7 @@ FactoryBot.define do owning_organisation { FactoryBot.create(:organisation) } managing_organisation { FactoryBot.create(:organisation) } confirmed { true } - created_at { Time.zone.now } + created_at { Time.zone.local(2021, 4, 1) } trait :export do sensitive { 1 } registered_under_care_act { 1 } diff --git a/spec/factories/scheme_deactivation_period.rb b/spec/factories/scheme_deactivation_period.rb index c073bc68a..95e33d9ce 100644 --- a/spec/factories/scheme_deactivation_period.rb +++ b/spec/factories/scheme_deactivation_period.rb @@ -1,5 +1,6 @@ FactoryBot.define do factory :scheme_deactivation_period do + deactivation_date { Time.zone.local(2022, 4, 1) } reactivation_date { nil } end end diff --git a/spec/features/bulk_upload_lettings_logs_spec.rb b/spec/features/bulk_upload_lettings_logs_spec.rb new file mode 100644 index 000000000..e9a05b07d --- /dev/null +++ b/spec/features/bulk_upload_lettings_logs_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +RSpec.describe "Bulk upload lettings log" do + let(:user) { create(:user) } + + before do + sign_in user + end + + context "when during crossover period" do + it "shows journey with year option" do + Timecop.freeze(2023, 6, 1) do + visit("/lettings-logs") + expect(page).to have_link("Upload lettings logs in bulk") + click_link("Upload lettings logs in bulk") + + expect(page).to have_content("Which year") + click_button("Continue") + + expect(page).to have_content("You must select a collection period to upload for") + choose("2022/2023") + click_button("Continue") + + click_link("Back") + + expect(page.find_field("form-year-2022-field")).to be_checked + click_button("Continue") + + expect(page).to have_content("Upload lettings logs in bulk (2022/23)") + click_button("Continue") + + expect(page).to have_content("Upload your file") + end + end + end + + context "when not it crossover period" do + it "shows journey with year option" do + Timecop.freeze(2023, 10, 1) do + visit("/lettings-logs") + expect(page).to have_link("Upload lettings logs in bulk") + click_link("Upload lettings logs in bulk") + + expect(page).to have_content("Upload lettings logs in bulk (2022/23)") + click_button("Continue") + + expect(page).to have_content("Upload your file") + end + end + end +end diff --git a/spec/features/bulk_upload_sales_logs_spec.rb b/spec/features/bulk_upload_sales_logs_spec.rb new file mode 100644 index 000000000..67187ff78 --- /dev/null +++ b/spec/features/bulk_upload_sales_logs_spec.rb @@ -0,0 +1,51 @@ +require "rails_helper" + +RSpec.describe "Bulk upload sales log" do + let(:user) { create(:user) } + + before do + sign_in user + end + + context "when during crossover period" do + it "shows journey with year option" do + Timecop.freeze(2023, 6, 1) do + visit("/sales-logs") + expect(page).to have_link("Upload sales logs in bulk") + click_link("Upload sales logs in bulk") + + expect(page).to have_content("Which year") + click_button("Continue") + + expect(page).to have_content("You must select a collection period to upload for") + choose("2022/2023") + click_button("Continue") + + click_link("Back") + + expect(page.find_field("form-year-2022-field")).to be_checked + click_button("Continue") + + expect(page).to have_content("Upload sales logs in bulk (2022/23)") + click_button("Continue") + + expect(page).to have_content("Upload your file") + end + end + end + + context "when not it crossover period" do + it "shows journey with year option" do + Timecop.freeze(2023, 10, 1) do + visit("/sales-logs") + expect(page).to have_link("Upload sales logs in bulk") + click_link("Upload sales logs in bulk") + + expect(page).to have_content("Upload sales logs in bulk (2022/23)") + click_button("Continue") + + expect(page).to have_content("Upload your file") + end + end + end +end diff --git a/spec/features/form/check_answers_page_spec.rb b/spec/features/form/check_answers_page_spec.rb index 03d237dd2..5f4665725 100644 --- a/spec/features/form/check_answers_page_spec.rb +++ b/spec/features/form/check_answers_page_spec.rb @@ -7,7 +7,7 @@ RSpec.describe "Form Check Answers Page" do let(:subsection) { "household-characteristics" } let(:conditional_subsection) { "conditional-question" } let(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } - let(:location) { FactoryBot.create(:location, scheme:, mobility_type: "N") } + let(:location) { FactoryBot.create(:location, scheme:, mobility_type: "N", startdate: Time.zone.local(2021, 4, 1)) } let(:lettings_log) do FactoryBot.create( @@ -36,6 +36,7 @@ RSpec.describe "Form Check Answers Page" do :completed, owning_organisation: user.organisation, managing_organisation: user.organisation, + startdate: Time.zone.local(2021, 5, 1), ) end let(:id) { lettings_log.id } diff --git a/spec/features/form/validations_spec.rb b/spec/features/form/validations_spec.rb index 54d4f1a48..0790096e2 100644 --- a/spec/features/form/validations_spec.rb +++ b/spec/features/form/validations_spec.rb @@ -28,6 +28,7 @@ RSpec.describe "validations" do managing_organisation: user.organisation, status: 1, declaration: nil, + startdate: Time.zone.local(2021, 5, 1), ) end let(:id) { lettings_log.id } diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 797b718af..bee63350a 100644 --- a/spec/features/schemes_spec.rb +++ b/spec/features/schemes_spec.rb @@ -195,11 +195,27 @@ RSpec.describe "Schemes scheme Features" do expect(page).to have_link("Locations") end - context "when I click locations link" do + context "when I click locations link and the new locations layout feature toggle is enabled" do before do click_link("Locations") end + it "shows details of those locations" do + locations.each do |location| + expect(page).to have_content(location.id) + expect(page).to have_content(location.postcode) + expect(page).to have_content(location.name) + expect(page).to have_content("Active") + end + end + end + + context "when I click locations link and the new locations layout feature toggle is disabled" do + before do + allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) + click_link("Locations") + end + it "shows details of those locations" do locations.each do |location| expect(page).to have_content(location.id) diff --git a/spec/fixtures/files/lettings_logs_download.csv b/spec/fixtures/files/lettings_logs_download.csv index f16b5f8ad..18c679926 100644 --- a/spec/fixtures/files/lettings_logs_download.csv +++ b/spec/fixtures/files/lettings_logs_download.csv @@ -1,2 +1,2 @@ id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,illness_type_0,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_managing_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,,9,1,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2022-06-05 01:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,,9,1,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} diff --git a/spec/fixtures/files/lettings_logs_download_non_support.csv b/spec/fixtures/files/lettings_logs_download_non_support.csv index ae0beeee0..0687c536e 100644 --- a/spec/fixtures/files/lettings_logs_download_non_support.csv +++ b/spec/fixtures/files/lettings_logs_download_non_support.csv @@ -1,2 +1,2 @@ id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,renewal,startdate,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,relat2,age2,sex2,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,prevloc_label,illness_type_1,illness_type_2,la_label,postcode_full,wchair,preg_occ,cbl,earnings,incfreq,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,tshortfall,chcharge,ppcodenk,ethnic_group,has_benefits,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,illness_type_0,sheltered,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_managing_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,,2 October 2021,,,,,,,,,,,,,,,,,,,,,Westminster,SE1 1TE,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,0,,,,,,,0,0,,,,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2022-06-05 01:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,,2 October 2021,,,,,,,,,,,,,,,,,,,,,Westminster,SE1 1TE,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,0,,,,,,,0,0,,,,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} diff --git a/spec/helpers/locations_helper_spec.rb b/spec/helpers/locations_helper_spec.rb index bccacba3c..96db265e3 100644 --- a/spec/helpers/locations_helper_spec.rb +++ b/spec/helpers/locations_helper_spec.rb @@ -59,8 +59,8 @@ RSpec.describe LocationsHelper do end it "returns one active period without to date" do - expect(active_periods(location).count).to eq(1) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil) + expect(location_active_periods(location).count).to eq(1) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil) end it "ignores reactivations that were deactivated on the same day" do @@ -68,8 +68,8 @@ RSpec.describe LocationsHelper do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location:) location.reload - expect(active_periods(location).count).to eq(1) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(location_active_periods(location).count).to eq(1) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) end it "returns sequential non reactivated active periods" do @@ -77,19 +77,19 @@ RSpec.describe LocationsHelper do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), location:) location.reload - expect(active_periods(location).count).to eq(2) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + expect(location_active_periods(location).count).to eq(2) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) end it "returns sequential reactivated active periods" do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), location:) FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), location:) location.reload - expect(active_periods(location).count).to eq(3) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) - expect(active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + expect(location_active_periods(location).count).to eq(3) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + expect(location_active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) end it "returns non sequential non reactivated active periods" do @@ -97,19 +97,19 @@ RSpec.describe LocationsHelper do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: nil, location:) location.reload - expect(active_periods(location).count).to eq(2) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + expect(location_active_periods(location).count).to eq(2) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) end it "returns non sequential reactivated active periods" do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), location:) FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), location:) location.reload - expect(active_periods(location).count).to eq(3) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) - expect(active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + expect(location_active_periods(location).count).to eq(3) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + expect(location_active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) end it "returns correct active periods when reactivation happends during a deactivated period" do @@ -117,9 +117,9 @@ RSpec.describe LocationsHelper do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), location:) location.reload - expect(active_periods(location).count).to eq(2) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil) + expect(location_active_periods(location).count).to eq(2) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil) end it "returns correct active periods when a full deactivation period happens during another deactivation period" do @@ -127,9 +127,9 @@ RSpec.describe LocationsHelper do FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), location:) location.reload - expect(active_periods(location).count).to eq(2) - expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) - expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil) + expect(location_active_periods(location).count).to eq(2) + expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) + expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil) end end @@ -139,12 +139,12 @@ RSpec.describe LocationsHelper do it "returns correct display attributes" do attributes = [ { name: "Postcode", value: location.postcode }, - { name: "Local authority", value: location.location_admin_district }, { name: "Location name", value: location.name, edit: true }, + { name: "Local authority", value: location.location_admin_district }, { name: "Total number of units at this location", value: location.units }, { name: "Common type of unit", value: location.type_of_unit }, { name: "Mobility type", value: location.mobility_type }, - { name: "Code", value: location.location_code }, + { name: "Location code", value: location.location_code }, { name: "Availability", value: "Active from 1 April 2022" }, { name: "Status", value: :active }, ] @@ -154,11 +154,11 @@ RSpec.describe LocationsHelper do context "when viewing availability" do context "with no deactivations" do - it "displays created_at as availability date if startdate is not present" do + it "displays previous collection start date as availability date if created_at is earlier than collection start date" do location.update!(startdate: nil) availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value] - expect(availability_attribute).to eq("Active from #{location.created_at.to_formatted_s(:govuk_date)}") + expect(availability_attribute).to eq("Active from 1 April 2021") end it "displays current collection start date as availability date if created_at is later than collection start date" do diff --git a/spec/helpers/schemes_helper_spec.rb b/spec/helpers/schemes_helper_spec.rb index 9db3639d9..af9f133ed 100644 --- a/spec/helpers/schemes_helper_spec.rb +++ b/spec/helpers/schemes_helper_spec.rb @@ -1,49 +1,276 @@ require "rails_helper" RSpec.describe SchemesHelper do + describe "Active periods" do + let(:scheme) { FactoryBot.create(:scheme, created_at: Time.zone.today) } + + before do + Timecop.freeze(2022, 10, 10) + end + + after do + Timecop.unfreeze + end + + it "returns one active period without to date" do + expect(scheme_active_periods(scheme).count).to eq(1) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil) + end + + it "ignores reactivations that were deactivated on the same day" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), scheme:) + scheme.reload + + expect(scheme_active_periods(scheme).count).to eq(1) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + end + + it "returns sequential non reactivated active periods" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), scheme:) + scheme.reload + + expect(scheme_active_periods(scheme).count).to eq(2) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + end + + it "returns sequential reactivated active periods" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:) + scheme.reload + expect(scheme_active_periods(scheme).count).to eq(3) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + expect(scheme_active_periods(scheme).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + end + + it "returns non sequential non reactivated active periods" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: nil, scheme:) + scheme.reload + + expect(scheme_active_periods(scheme).count).to eq(2) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + end + + it "returns non sequential reactivated active periods" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:) + scheme.reload + expect(scheme_active_periods(scheme).count).to eq(3) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6)) + expect(scheme_active_periods(scheme).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil) + end + + it "returns correct active periods when reactivation happends during a deactivated period" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 11, 11), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), scheme:) + scheme.reload + + expect(scheme_active_periods(scheme).count).to eq(2) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil) + end + + it "returns correct active periods when a full deactivation period happens during another deactivation period" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 11), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), scheme:) + scheme.reload + + expect(scheme_active_periods(scheme).count).to eq(2) + expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6)) + expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil) + end + end + + include TagHelper describe "display_scheme_attributes" do - let!(:scheme) { FactoryBot.create(:scheme, created_at: Time.zone.local(2022, 8, 8)) } + let(:owning_organisation) { FactoryBot.create(:organisation, name: "Acme LTD Owning") } + let(:managing_organisation) { FactoryBot.create(:organisation, name: "Acme LTD Managing") } + let!(:scheme) do + FactoryBot.create(:scheme, + service_name: "Test service_name", + sensitive: 0, + scheme_type: 7, + registered_under_care_act: 3, + owning_organisation:, + managing_organisation:, + arrangement_type: "V", + primary_client_group: "S", + has_other_client_group: 1, + secondary_client_group: "I", + support_type: 4, + intended_stay: "P", + created_at: Time.zone.local(2022, 4, 1)) + end + let!(:scheme_where_managing_organisation_is_owning_organisation) { FactoryBot.create(:scheme, arrangement_type: "D") } + let(:support_user) { FactoryBot.create(:user, :support) } + let(:coordinator_user) { FactoryBot.create(:user, :data_coordinator) } + + it "returns correct display attributes for a support user" do + attributes = [ + { name: "Scheme code", value: "S#{scheme.id}" }, + { name: "Name", value: "Test service_name", edit: true }, + { name: "Confidential information", value: "No", edit: true }, + { name: "Type of scheme", value: "Housing for older people" }, + { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, + { name: "Housing stock owned by", value: "Acme LTD Owning", edit: true }, + { name: "Support services provided by", value: "A registered charity or voluntary organisation" }, + { name: "Organisation providing support", value: "Acme LTD Managing" }, + { name: "Primary client group", value: "Rough sleepers" }, + { name: "Has another client group", value: "Yes" }, + { name: "Secondary client group", value: "Refugees (permanent)" }, + { name: "Level of support given", value: "High level" }, + { name: "Intended length of stay", value: "Permanent" }, + { name: "Availability", value: "Active from 1 April 2022" }, + { name: "Status", value: status_tag(:active) }, + ] + expect(display_scheme_attributes(scheme, support_user)).to eq(attributes) + end - it "returns correct display attributes" do + it "returns correct display attributes for a coordinator user" do attributes = [ - { name: "Scheme code", value: scheme.id_to_display }, - { name: "Name", value: scheme.service_name, edit: true }, - { name: "Confidential information", value: scheme.sensitive, edit: true }, - { name: "Type of scheme", value: scheme.scheme_type }, - { name: "Registered under Care Standards Act 2000", value: scheme.registered_under_care_act }, - { name: "Housing stock owned by", value: scheme.owning_organisation.name, edit: true }, - { name: "Support services provided by", value: scheme.arrangement_type }, - { name: "Primary client group", value: scheme.primary_client_group }, - { name: "Has another client group", value: scheme.has_other_client_group }, - { name: "Secondary client group", value: scheme.secondary_client_group }, - { name: "Level of support given", value: scheme.support_type }, - { name: "Intended length of stay", value: scheme.intended_stay }, - { name: "Availability", value: "Active from 8 August 2022" }, - { name: "Status", value: :active }, + { name: "Scheme code", value: "S#{scheme.id}" }, + { name: "Name", value: "Test service_name", edit: true }, + { name: "Confidential information", value: "No", edit: true }, + { name: "Type of scheme", value: "Housing for older people" }, + { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, + { name: "Support services provided by", value: "A registered charity or voluntary organisation" }, + { name: "Organisation providing support", value: "Acme LTD Managing" }, + { name: "Primary client group", value: "Rough sleepers" }, + { name: "Has another client group", value: "Yes" }, + { name: "Secondary client group", value: "Refugees (permanent)" }, + { name: "Level of support given", value: "High level" }, + { name: "Intended length of stay", value: "Permanent" }, + { name: "Availability", value: "Active from 1 April 2022" }, + { name: "Status", value: status_tag(:active) }, ] - expect(display_scheme_attributes(scheme)).to eq(attributes) + expect(display_scheme_attributes(scheme, coordinator_user)).to eq(attributes) + end + + context "when the scheme toggle is disabled" do + it "doesn't show the scheme status" do + allow(FeatureToggle).to receive(:scheme_toggle_enabled?).and_return(false) + attributes = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Status" } + expect(attributes).to be_nil + end + end + + context "when the managing organisation is the owning organisation" do + it "doesn't show the organisation providing support" do + attributes = display_scheme_attributes(scheme_where_managing_organisation_is_owning_organisation, support_user).find { |x| x[:name] == "Organisation providing support" } + expect(attributes).to be_nil + end end context "when viewing availability" do - context "with are no deactivations" do + context "with no deactivations" do it "displays created_at as availability date" do - availability_attribute = display_scheme_attributes(scheme).find { |x| x[:name] == "Availability" }[:value] + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] expect(availability_attribute).to eq("Active from #{scheme.created_at.to_formatted_s(:govuk_date)}") end + + it "displays current collection start date as availability date if created_at is later than collection start date" do + scheme.update!(created_at: Time.zone.local(2022, 4, 16)) + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022") + end end context "with previous deactivations" do + context "and all reactivated deactivations" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: Time.zone.local(2022, 9, 28), scheme:) + scheme.reload + end + + it "displays the timeline of availability" do + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022\nActive from 28 September 2022") + end + end + + context "and non reactivated deactivation" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: nil, scheme:) + scheme.reload + end + + it "displays the timeline of availability" do + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022") + end + end + end + + context "with out of order deactivations" do + context "and all reactivated deactivations" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: Time.zone.local(2022, 6, 18), scheme:) + scheme.reload + end + + it "displays the timeline of availability" do + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 18 June 2022 to 23 September 2022\nDeactivated on 24 September 2022\nActive from 28 September 2022") + end + end + + context "and one non reactivated deactivation" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: nil, scheme:) + scheme.reload + end + + it "displays the timeline of availability" do + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 28 September 2022") + end + end + end + + context "with multiple out of order deactivations" do + context "and one non reactivated deactivation" do + before do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 24), reactivation_date: Time.zone.local(2022, 10, 28), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: nil, scheme:) + scheme.reload + end + + it "displays the timeline of availability" do + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] + + expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 28 September 2022 to 23 October 2022\nDeactivated on 24 October 2022\nActive from 28 October 2022") + end + end + end + + context "with intersecting deactivations" do before do - FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:) - FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: nil, scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 10), reactivation_date: Time.zone.local(2022, 12, 1), scheme:) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 11, 11), reactivation_date: Time.zone.local(2022, 12, 11), scheme:) scheme.reload end it "displays the timeline of availability" do - availability_attribute = display_scheme_attributes(scheme).find { |x| x[:name] == "Availability" }[:value] + availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value] - expect(availability_attribute).to eq("Active from 8 August 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022") + expect(availability_attribute).to eq("Active from 1 April 2022 to 9 October 2022\nDeactivated on 10 October 2022\nActive from 11 December 2022") end end end diff --git a/spec/helpers/tag_helper_spec.rb b/spec/helpers/tag_helper_spec.rb index 3f32b8502..3373737de 100644 --- a/spec/helpers/tag_helper_spec.rb +++ b/spec/helpers/tag_helper_spec.rb @@ -11,6 +11,15 @@ RSpec.describe TagHelper do it "returns tag with correct status text and colour and custom class" do expect(status_tag("not_started", "app-tag--small")).to eq("Not started") + expect(status_tag("cannot_start_yet", "app-tag--small")).to eq("Cannot start yet") + expect(status_tag("in_progress", "app-tag--small")).to eq("In progress") + expect(status_tag("completed", "app-tag--small")).to eq("Completed") + expect(status_tag("active", "app-tag--small")).to eq("Active") + expect(status_tag("incomplete", "app-tag--small")).to eq("Incomplete") + expect(status_tag("deactivating_soon", "app-tag--small")).to eq("Deactivating soon") + expect(status_tag("activating_soon", "app-tag--small")).to eq("Activating soon") + expect(status_tag("reactivating_soon", "app-tag--small")).to eq("Reactivating soon") + expect(status_tag("deactivated", "app-tag--small")).to eq("Deactivated") end end end diff --git a/spec/jobs/email_csv_job_spec.rb b/spec/jobs/email_csv_job_spec.rb index aa27c421b..f4939b855 100644 --- a/spec/jobs/email_csv_job_spec.rb +++ b/spec/jobs/email_csv_job_spec.rb @@ -33,7 +33,8 @@ describe EmailCsvJob do :completed, owning_organisation: organisation, managing_organisation: organisation, - created_by: user) + created_by: user, + startdate: Time.zone.local(2021, 5, 1)) allow(Storage::S3Service).to receive(:new).and_return(storage_service) allow(storage_service).to receive(:write_file) diff --git a/spec/models/form/lettings/pages/housing_provider_spec.rb b/spec/models/form/lettings/pages/housing_provider_spec.rb index 014ffbd66..2b2be7bfc 100644 --- a/spec/models/form/lettings/pages/housing_provider_spec.rb +++ b/spec/models/form/lettings/pages/housing_provider_spec.rb @@ -79,7 +79,6 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do before do create( :organisation_relationship, - :owning, child_organisation: user.organisation, parent_organisation: housing_provider, ) @@ -101,13 +100,11 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do before do create( :organisation_relationship, - :owning, child_organisation: user.organisation, parent_organisation: housing_provider1, ) create( :organisation_relationship, - :owning, child_organisation: user.organisation, parent_organisation: housing_provider2, ) @@ -140,8 +137,8 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do context "with >0 housing_providers" do before do - create(:organisation_relationship, :owning, child_organisation: user.organisation) - create(:organisation_relationship, :owning, child_organisation: user.organisation) + create(:organisation_relationship, child_organisation: user.organisation) + create(:organisation_relationship, child_organisation: user.organisation) end it "is shown" do diff --git a/spec/models/form/lettings/pages/managing_organisation_spec.rb b/spec/models/form/lettings/pages/managing_organisation_spec.rb index 65c2589c6..01d6ba9b2 100644 --- a/spec/models/form/lettings/pages/managing_organisation_spec.rb +++ b/spec/models/form/lettings/pages/managing_organisation_spec.rb @@ -76,8 +76,8 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do context "with >1 managing_agents" do before do - create(:organisation_relationship, :managing, parent_organisation: log.owning_organisation) - create(:organisation_relationship, :managing, parent_organisation: log.owning_organisation) + create(:organisation_relationship, parent_organisation: log.owning_organisation) + create(:organisation_relationship, parent_organisation: log.owning_organisation) end it "is shown" do @@ -91,7 +91,6 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do before do create( :organisation_relationship, - :managing, child_organisation: managing_agent, parent_organisation: log.owning_organisation, ) @@ -128,8 +127,8 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do context "with >1 managing_agents" do before do - create(:organisation_relationship, :managing, parent_organisation: user.organisation) - create(:organisation_relationship, :managing, parent_organisation: user.organisation) + create(:organisation_relationship, parent_organisation: user.organisation) + create(:organisation_relationship, parent_organisation: user.organisation) end it "is shown" do @@ -143,7 +142,6 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do before do create( :organisation_relationship, - :managing, child_organisation: managing_agent, parent_organisation: user.organisation, ) diff --git a/spec/models/form/lettings/questions/housing_provider_spec.rb b/spec/models/form/lettings/questions/housing_provider_spec.rb index 5363a0227..187a75de4 100644 --- a/spec/models/form/lettings/questions/housing_provider_spec.rb +++ b/spec/models/form/lettings/questions/housing_provider_spec.rb @@ -107,7 +107,7 @@ RSpec.describe Form::Lettings::Questions::HousingProvider, type: :model do context "when housing providers != 0" do before do - create(:organisation_relationship, :owning, child_organisation: user.organisation) + create(:organisation_relationship, child_organisation: user.organisation) end it "is visible in check answers" do @@ -133,8 +133,8 @@ RSpec.describe Form::Lettings::Questions::HousingProvider, type: :model do context "when housing providers >= 2" do before do - create(:organisation_relationship, :owning, child_organisation: user.organisation) - create(:organisation_relationship, :owning, child_organisation: user.organisation) + create(:organisation_relationship, child_organisation: user.organisation) + create(:organisation_relationship, child_organisation: user.organisation) end it "is visible in check answers" do diff --git a/spec/models/form/lettings/questions/managing_organisation_spec.rb b/spec/models/form/lettings/questions/managing_organisation_spec.rb index e045601bf..b9b10702d 100644 --- a/spec/models/form/lettings/questions/managing_organisation_spec.rb +++ b/spec/models/form/lettings/questions/managing_organisation_spec.rb @@ -56,8 +56,29 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: true)) } let(:log) { create(:lettings_log) } - let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: user.organisation) } - let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: user.organisation) } + let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) } + let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) } + + let(:options) do + { + "" => "Select an option", + user.organisation.id => "#{user.organisation.name} (Your organisation)", + org_rel1.child_organisation.id => org_rel1.child_organisation.name, + org_rel2.child_organisation.id => org_rel2.child_organisation.name, + } + end + + it "shows managing agents with own org at the top" do + expect(question.displayed_answer_options(log, user)).to eq(options) + end + end + + context "when user not support and does not own stock" do + let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: false)) } + + let(:log) { create(:lettings_log) } + let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) } + let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) } let(:options) do { @@ -98,8 +119,8 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do let(:user) { create(:user, :support) } let(:log_owning_org) { create(:organisation, holds_own_stock: false) } let(:log) { create(:lettings_log, owning_organisation: log_owning_org) } - let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) } - let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) } + let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) } + let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:options) do { @@ -118,8 +139,8 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do let(:user) { create(:user, :support) } let(:log_owning_org) { create(:organisation, holds_own_stock: true) } let(:log) { create(:lettings_log, owning_organisation: log_owning_org) } - let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) } - let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) } + let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) } + let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) } let(:options) do { diff --git a/spec/models/form/sales/pages/buyer1_mortgage_spec.rb b/spec/models/form/sales/pages/buyer1_mortgage_spec.rb new file mode 100644 index 000000000..d3aa2e1bb --- /dev/null +++ b/spec/models/form/sales/pages/buyer1_mortgage_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer1Mortgage, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[inc1mort]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_1_mortgage") + end + + it "has the correct header" do + expect(page.header).to eq("") + end + + it "has the correct description" do + expect(page.description).to eq("") + end + + it "has correct depends_on" do + expect(page.depends_on).to be_nil + end +end diff --git a/spec/models/form/sales/pages/buyer2_income_spec.rb b/spec/models/form/sales/pages/buyer2_income_spec.rb new file mode 100644 index 000000000..0450ceed1 --- /dev/null +++ b/spec/models/form/sales/pages/buyer2_income_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::Buyer2Income, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[income2nk income2]) + end + + it "has the correct id" do + expect(page.id).to eq("buyer_2_income") + end + + it "has the correct header" do + expect(page.header).to eq("") + end + + it "has the correct description" do + expect(page.description).to eq("") + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([{ "jointpur" => 1 }]) + end +end diff --git a/spec/models/form/sales/questions/buyer1_mortgage_spec.rb b/spec/models/form/sales/questions/buyer1_mortgage_spec.rb new file mode 100644 index 000000000..0e5f49117 --- /dev/null +++ b/spec/models/form/sales/questions/buyer1_mortgage_spec.rb @@ -0,0 +1,40 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer1Mortgage, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("inc1mort") + end + + it "has the correct header" do + expect(question.header).to eq("Was buyer 1's income used for a mortgage application") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 1's income used for mortgage application") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }) + end +end diff --git a/spec/models/form/sales/questions/buyer2_income_known_spec.rb b/spec/models/form/sales/questions/buyer2_income_known_spec.rb new file mode 100644 index 000000000..06e7afc3e --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_income_known_spec.rb @@ -0,0 +1,55 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2IncomeKnown, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("income2nk") + end + + it "has the correct header" do + expect(question.header).to eq("Do you know buyer 2’s annual income?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s gross annual income") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + }) + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq({ + "income2" => [0], + }) + end + + it "has the correct guidance_partial" do + expect(question.guidance_partial).to eq("what_counts_as_income_sales") + end + + it "has the correct guidance position", :aggregate_failures do + expect(question.bottom_guidance?).to eq(true) + expect(question.top_guidance?).to eq(false) + end +end diff --git a/spec/models/form/sales/questions/buyer2_income_spec.rb b/spec/models/form/sales/questions/buyer2_income_spec.rb new file mode 100644 index 000000000..b7828918a --- /dev/null +++ b/spec/models/form/sales/questions/buyer2_income_spec.rb @@ -0,0 +1,53 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::Buyer2Income, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("income2") + end + + it "has the correct header" do + expect(question.header).to eq("Buyer 2’s gross annual income") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Buyer 2’s gross annual income") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end + + it "has the correct hint" do + expect(question.hint_text).to be_nil + end + + it "has correct width" do + expect(question.width).to eq(5) + end + + it "has correct step" do + expect(question.step).to eq(1) + end + + it "has correct prefix" do + expect(question.prefix).to eq("£") + end + + it "has correct min" do + expect(question.min).to eq(0) + end +end diff --git a/spec/models/form/sales/sections/finances_spec.rb b/spec/models/form/sales/sections/finances_spec.rb index 4797f6b4e..6c2f63ded 100644 --- a/spec/models/form/sales/sections/finances_spec.rb +++ b/spec/models/form/sales/sections/finances_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Form::Sales::Sections::Finances, type: :model do it "has correct subsections" do expect(section.subsections.map(&:id)).to eq( %w[ - income_benefits_and_outgoings + income_benefits_and_savings ], ) end diff --git a/spec/models/form/sales/subsections/income_benefits_and_outgoings_spec.rb b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb similarity index 71% rename from spec/models/form/sales/subsections/income_benefits_and_outgoings_spec.rb rename to spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb index 01028b0a8..bea0fe88d 100644 --- a/spec/models/form/sales/subsections/income_benefits_and_outgoings_spec.rb +++ b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndOutgoings, type: :model do +RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model do subject(:subsection) { described_class.new(subsection_id, subsection_definition, section) } let(:subsection_id) { nil } @@ -15,16 +15,18 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndOutgoings, type: :mode expect(subsection.pages.map(&:id)).to eq( %w[ buyer_1_income + buyer_1_mortgage + buyer_2_income ], ) end it "has the correct id" do - expect(subsection.id).to eq("income_benefits_and_outgoings") + expect(subsection.id).to eq("income_benefits_and_savings") end it "has the correct label" do - expect(subsection.label).to eq("Income, benefits and outgoings") + expect(subsection.label).to eq("Income, benefits and savings") end it "has correct depends on" do diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index e0ef853ae..6a335e232 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -61,14 +61,14 @@ RSpec.describe FormHandler do it "is able to load a current sales form" do form = form_handler.get_form("current_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(44) + expect(form.pages.count).to eq(46) expect(form.name).to eq("2022_2023_sales") end it "is able to load a previous sales form" do form = form_handler.get_form("previous_sales") expect(form).to be_a(Form) - expect(form.pages.count).to eq(44) + expect(form.pages.count).to eq(46) expect(form.name).to eq("2021_2022_sales") end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index a062a4aa5..f8bcddcca 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -235,4 +235,38 @@ RSpec.describe Form, type: :model do expect(form.sections[1].class).to eq(Form::Sales::Sections::PropertyInformation) end end + + describe "#in_crossover_period?" do + context "when now not specified" do + context "when after end period" do + subject(:form) { described_class.new(nil, 2022, [], "sales") } + + it "returns false" do + Timecop.freeze(2023, 8, 1) do + expect(form).not_to be_in_crossover_period + end + end + end + + context "when during crossover" do + subject(:form) { described_class.new(nil, 2022, [], "sales") } + + it "returns true" do + Timecop.freeze(2023, 6, 1) do + expect(form).to be_in_crossover_period + end + end + end + + context "when before crossover" do + subject(:form) { described_class.new(nil, 2022, [], "sales") } + + it "returns false" do + Timecop.freeze(2023, 1, 1) do + expect(form).not_to be_in_crossover_period + end + end + end + end + end end diff --git a/spec/models/forms/bulk_upload_lettings/year_spec.rb b/spec/models/forms/bulk_upload_lettings/year_spec.rb new file mode 100644 index 000000000..0b0babb30 --- /dev/null +++ b/spec/models/forms/bulk_upload_lettings/year_spec.rb @@ -0,0 +1,12 @@ +require "rails_helper" + +RSpec.describe Forms::BulkUploadLettings::Year do + subject(:form) { described_class.new } + + describe "#options" do + it "returns correct years" do + expect(form.options.map(&:id)).to eql([2022, 2021]) + expect(form.options.map(&:name)).to eql(%w[2022/2023 2021/2022]) + end + end +end diff --git a/spec/models/forms/bulk_upload_sales/year_spec.rb b/spec/models/forms/bulk_upload_sales/year_spec.rb new file mode 100644 index 000000000..2276b1e4d --- /dev/null +++ b/spec/models/forms/bulk_upload_sales/year_spec.rb @@ -0,0 +1,12 @@ +require "rails_helper" + +RSpec.describe Forms::BulkUploadSales::Year do + subject(:form) { described_class.new } + + describe "#options" do + it "returns correct years" do + expect(form.options.map(&:id)).to eql([2022, 2021]) + expect(form.options.map(&:name)).to eql(%w[2022/2023 2021/2022]) + end + end +end diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 45a24b964..15936b767 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -1672,7 +1672,7 @@ RSpec.describe LettingsLog do let(:scheme) { FactoryBot.create(:scheme) } let!(:location) { FactoryBot.create(:location, scheme:) } - before { lettings_log.update!(scheme:) } + before { lettings_log.update!(startdate: Time.zone.local(2022, 4, 2), scheme:) } it "derives the scheme location" do record_from_db = ActiveRecord::Base.connection.execute("select location_id from lettings_logs where id=#{lettings_log.id}").to_a[0] @@ -2375,7 +2375,7 @@ RSpec.describe LettingsLog do describe "csv download" do let(:scheme) { FactoryBot.create(:scheme) } - let(:location) { FactoryBot.create(:location, :export, scheme:, type_of_unit: 6, postcode: "SE11TE") } + let(:location) { FactoryBot.create(:location, :export, scheme:, type_of_unit: 6, postcode: "SE11TE", startdate: Time.zone.local(2021, 10, 1)) } let(:user) { FactoryBot.create(:user, organisation: location.scheme.owning_organisation) } let(:expected_content) { csv_export_file.read } diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 64a4855da..11c826d28 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -34,12 +34,18 @@ RSpec.describe Location, type: :model do expect { location.save! } .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Postcode #{I18n.t('validations.postcode')}") end + + it "does add an error when the postcode is missing" do + location.postcode = nil + expect { location.save! } + .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Postcode #{I18n.t('validations.postcode')}") + end end describe "#units" do let(:location) { FactoryBot.build(:location) } - it "does add an error when the postcode is invalid" do + it "does add an error when the number of units is invalid" do location.units = nil expect { location.save! } .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Units #{I18n.t('activerecord.errors.models.location.attributes.units.blank')}") @@ -49,13 +55,23 @@ RSpec.describe Location, type: :model do describe "#type_of_unit" do let(:location) { FactoryBot.build(:location) } - it "does add an error when the postcode is invalid" do + it "does add an error when the type of unit is invalid" do location.type_of_unit = nil expect { location.save! } .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Type of unit #{I18n.t('activerecord.errors.models.location.attributes.type_of_unit.blank')}") end end + describe "#mobility_type" do + let(:location) { FactoryBot.build(:location) } + + it "does add an error when the mobility type is invalid" do + location.mobility_type = nil + expect { location.save! } + .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Mobility type #{I18n.t('activerecord.errors.models.location.attributes.mobility_type.blank')}") + end + end + describe "paper trail" do let(:location) { FactoryBot.create(:location) } let!(:name) { location.name } @@ -123,6 +139,13 @@ RSpec.describe Location, type: :model do Timecop.unfreeze end + context "when location is not confirmed" do + it "returns incomplete " do + location.confirmed = false + expect(location.status).to eq(:incomplete) + end + end + context "when there have not been any previous deactivations" do it "returns active if the location has no deactivation records" do expect(location.status).to eq(:active) @@ -151,6 +174,12 @@ RSpec.describe Location, type: :model do location.save! expect(location.status).to eq(:reactivating_soon) end + + it "returns activating soon if the location has a future startdate" do + location.startdate = Time.zone.local(2022, 7, 7) + location.save! + expect(location.status).to eq(:activating_soon) + end end context "when there have been previous deactivations" do @@ -188,12 +217,62 @@ RSpec.describe Location, type: :model do expect(location.status).to eq(:reactivating_soon) end - it "returns if the location had a deactivation during another deactivation" do + it "returns reactivating soon if the location had a deactivation during another deactivation" do Timecop.freeze(2022, 6, 4) FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 2), location:) location.save! expect(location.status).to eq(:reactivating_soon) end + + it "returns activating soon if the location has a future startdate" do + location.startdate = Time.zone.local(2022, 7, 7) + location.save! + expect(location.status).to eq(:activating_soon) + end + end + end + + describe "available_from" do + context "when there is a startdate" do + let(:location) { FactoryBot.build(:location, startdate: Time.zone.local(2022, 4, 6)) } + + it "returns the startdate" do + expect(location.available_from).to eq(Time.zone.local(2022, 4, 6)) + end + end + + context "when there is no start date" do + context "and the location was created at the start of the 2022/23 collection window" do + let(:location) { FactoryBot.build(:location, created_at: Time.zone.local(2022, 4, 6), startdate: nil) } + + it "returns the beginning of 22/23 collection window" do + expect(location.available_from).to eq(Time.zone.local(2022, 4, 1)) + end + end + + context "and the location was created at the end of the 2022/23 collection window" do + let(:location) { FactoryBot.build(:location, created_at: Time.zone.local(2023, 2, 6), startdate: nil) } + + it "returns the beginning of 22/23 collection window" do + expect(location.available_from).to eq(Time.zone.local(2022, 4, 1)) + end + end + + context "and the location was created at the start of the 2021/22 collection window" do + let(:location) { FactoryBot.build(:location, created_at: Time.zone.local(2021, 4, 6), startdate: nil) } + + it "returns the beginning of 21/22 collection window" do + expect(location.available_from).to eq(Time.zone.local(2021, 4, 1)) + end + end + + context "and the location was created at the end of the 2021/22 collection window" do + let(:location) { FactoryBot.build(:location, created_at: Time.zone.local(2022, 2, 6), startdate: nil) } + + it "returns the beginning of 21/22 collection window" do + expect(location.available_from).to eq(Time.zone.local(2021, 4, 1)) + end + end end end end diff --git a/spec/models/organisation_relationship_spec.rb b/spec/models/organisation_relationship_spec.rb new file mode 100644 index 000000000..8857f29d6 --- /dev/null +++ b/spec/models/organisation_relationship_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +RSpec.describe OrganisationRelationship do + let(:parent_organisation) { create(:organisation) } + let(:child_organisation) { create(:organisation) } + + context "when a relationship exists" do + subject!(:relationship) do + described_class.create!(parent_organisation:, + child_organisation:) + end + + describe "parent#managing_agents" do + it "includes child" do + expect(parent_organisation.managing_agents).to include(child_organisation) + end + end + + describe "child#housing_providers" do + it "includes parent" do + expect(child_organisation.housing_providers).to include(parent_organisation) + end + end + end +end diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index d4d63b6a0..cf9afe170 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -34,14 +34,12 @@ RSpec.describe Organisation, type: :model do before do FactoryBot.create( :organisation_relationship, - :owning, child_organisation:, parent_organisation: organisation, ) FactoryBot.create( :organisation_relationship, - :owning, child_organisation: grandchild_organisation, parent_organisation: child_organisation, ) @@ -65,21 +63,12 @@ RSpec.describe Organisation, type: :model do before do FactoryBot.create( :organisation_relationship, - :managing, child_organisation:, parent_organisation: organisation, ) FactoryBot.create( :organisation_relationship, - :owning, - child_organisation:, - parent_organisation: organisation, - ) - - FactoryBot.create( - :organisation_relationship, - :owning, child_organisation: grandchild_organisation, parent_organisation: child_organisation, ) @@ -98,21 +87,12 @@ RSpec.describe Organisation, type: :model do before do FactoryBot.create( :organisation_relationship, - :managing, - child_organisation:, - parent_organisation: organisation, - ) - - FactoryBot.create( - :organisation_relationship, - :owning, child_organisation:, parent_organisation: organisation, ) FactoryBot.create( :organisation_relationship, - :managing, child_organisation: grandchild_organisation, parent_organisation: child_organisation, ) diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb index b19e8c992..d6bacb11a 100644 --- a/spec/models/scheme_spec.rb +++ b/spec/models/scheme_spec.rb @@ -125,6 +125,12 @@ RSpec.describe Scheme, type: :model do scheme.reload expect(scheme.status).to eq(:deactivated) end + + it "returns reactivating soon if the location has a future reactivation date" do + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7), reactivation_date: Time.zone.local(2022, 6, 8), scheme:) + scheme.save! + expect(scheme.status).to eq(:reactivating_soon) + end end context "when there have been previous deactivations" do @@ -153,6 +159,69 @@ RSpec.describe Scheme, type: :model do scheme.reload expect(scheme.status).to eq(:deactivated) end + + it "returns reactivating soon if the scheme has a future reactivation date" do + Timecop.freeze(2022, 6, 8) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7), reactivation_date: Time.zone.local(2022, 6, 9), scheme:) + scheme.save! + expect(scheme.status).to eq(:reactivating_soon) + end + + it "returns if the scheme had a deactivation during another deactivation" do + Timecop.freeze(2022, 6, 4) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 2), scheme:) + scheme.save! + expect(scheme.status).to eq(:reactivating_soon) + end + end + end + + describe "all schemes" do + before do + FactoryBot.create_list(:scheme, 4) + FactoryBot.create_list(:scheme, 3, confirmed: false) + end + + it "can sort the schemes by status" do + all_schemes = described_class.all.order(confirmed: :asc, service_name: :asc) + expect(all_schemes.count).to eq(7) + expect(all_schemes[0].status).to eq(:incomplete) + expect(all_schemes[1].status).to eq(:incomplete) + expect(all_schemes[2].status).to eq(:incomplete) + end + end + + describe "available_from" do + context "when the scheme was created at the start of the 2022/23 collection window" do + let(:scheme) { FactoryBot.build(:scheme, created_at: Time.zone.local(2022, 4, 6)) } + + it "returns the beginning of 22/23 collection window" do + expect(scheme.available_from).to eq(Time.zone.local(2022, 4, 1)) + end + end + + context "when the scheme was created at the end of the 2022/23 collection window" do + let(:scheme) { FactoryBot.build(:scheme, created_at: Time.zone.local(2023, 2, 6)) } + + it "returns the beginning of 22/23 collection window" do + expect(scheme.available_from).to eq(Time.zone.local(2022, 4, 1)) + end + end + + context "when the scheme was created at the start of the 2021/22 collection window" do + let(:scheme) { FactoryBot.build(:scheme, created_at: Time.zone.local(2021, 4, 6)) } + + it "returns the beginning of 21/22 collection window" do + expect(scheme.available_from).to eq(Time.zone.local(2021, 4, 1)) + end + end + + context "when the scheme was created at the end of the 2021/22 collection window" do + let(:scheme) { FactoryBot.build(:scheme, created_at: Time.zone.local(2022, 2, 6)) } + + it "returns the beginning of 21/22 collection window" do + expect(scheme.available_from).to eq(Time.zone.local(2021, 4, 1)) + end end end end diff --git a/spec/models/validations/date_validations_spec.rb b/spec/models/validations/date_validations_spec.rb index 0b20ab07a..87351b9dd 100644 --- a/spec/models/validations/date_validations_spec.rb +++ b/spec/models/validations/date_validations_spec.rb @@ -83,6 +83,153 @@ RSpec.describe Validations::DateValidations do date_validator.validate_startdate(record) expect(record.errors["startdate"]).to be_empty end + + context "with a deactivated location" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.location.deactivated", postcode: location.postcode, date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 6, 1) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + end + + context "with a location that is reactivating soon" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.location.reactivating_soon", postcode: location.postcode, date: "4 August 2022", deactivation_date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 9, 1) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + end + + context "with a location that has many reactivations soon" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), location:) + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 2), reactivation_date: Time.zone.local(2022, 8, 3), location:) + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 1), reactivation_date: Time.zone.local(2022, 9, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.location.reactivating_soon", postcode: location.postcode, date: "4 September 2022", deactivation_date: "1 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 10, 1) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + end + + context "with a location with no deactivation periods" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: Time.zone.local(2022, 9, 15)) } + + it "produces no error" do + record.startdate = Time.zone.local(2022, 10, 15) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + + it "produces an error when the date is before available_from date" do + record.startdate = Time.zone.local(2022, 8, 15) + record.location = location + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.location.activating_soon", postcode: location.postcode, date: "15 September 2022")) + end + end + + context "with a scheme that is reactivating soon" do + let(:scheme) { create(:scheme) } + + before do + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), scheme:) + scheme.reload + end + + it "produces error when tenancy start date is during deactivated scheme period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.scheme = scheme + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.scheme.reactivating_soon", name: scheme.service_name, date: "4 August 2022", deactivation_date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active scheme period" do + record.startdate = Time.zone.local(2022, 9, 1) + record.scheme = scheme + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + end + + context "with a scheme that has many reactivations soon" do + let(:scheme) { create(:scheme) } + + before do + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), scheme:) + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 2), reactivation_date: Time.zone.local(2022, 8, 3), scheme:) + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 1), reactivation_date: Time.zone.local(2022, 9, 4), scheme:) + scheme.reload + end + + it "produces error when tenancy start date is during deactivated scheme period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.scheme = scheme + date_validator.validate_startdate(record) + expect(record.errors["startdate"]) + .to include(match I18n.t("validations.setup.startdate.scheme.reactivating_soon", name: scheme.service_name, date: "4 September 2022", deactivation_date: "1 June 2022")) + end + + it "produces no error when tenancy start date is during an active scheme period" do + record.startdate = Time.zone.local(2022, 10, 1) + record.scheme = scheme + date_validator.validate_startdate(record) + expect(record.errors["startdate"]).to be_empty + end + end end describe "major repairs date" do diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb index 378aef904..fa95757c4 100644 --- a/spec/models/validations/setup_validations_spec.rb +++ b/spec/models/validations/setup_validations_spec.rb @@ -30,4 +30,198 @@ RSpec.describe Validations::SetupValidations do expect(record.errors["irproduct_other"]).to be_empty end end + + describe "#validate_scheme" do + context "with a deactivated location" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]) + .to include(match I18n.t("validations.setup.startdate.location.deactivated", postcode: location.postcode, date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 6, 1) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]).to be_empty + end + end + + context "with a location that is reactivating soon" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]) + .to include(match I18n.t("validations.setup.startdate.location.reactivating_soon", postcode: location.postcode, date: "4 August 2022", deactivation_date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 9, 1) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]).to be_empty + end + end + + context "with a location with no deactivation periods" do + let(:scheme) { create(:scheme, created_at: Time.zone.local(2022, 10, 3)) } + let(:location) { create(:location, scheme:, startdate: Time.zone.local(2022, 9, 15)) } + + it "produces no error" do + record.startdate = Time.zone.local(2022, 10, 15) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]).to be_empty + end + + it "produces an error when the date is before available_from date" do + record.startdate = Time.zone.local(2022, 8, 15) + record.location = location + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]) + .to include(match I18n.t("validations.setup.startdate.location.activating_soon", postcode: location.postcode, date: "15 September 2022")) + end + end + + context "with a scheme that is reactivating soon" do + let(:scheme) { create(:scheme, created_at: Time.zone.local(2022, 4, 1)) } + + before do + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), scheme:) + scheme.reload + end + + it "produces error when tenancy start date is during deactivated scheme period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.scheme = scheme + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]) + .to include(match I18n.t("validations.setup.startdate.scheme.reactivating_soon", name: scheme.service_name, date: "4 August 2022", deactivation_date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active scheme period" do + record.startdate = Time.zone.local(2022, 9, 1) + record.scheme = scheme + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]).to be_empty + end + end + + context "with a scheme that has many reactivations soon" do + let(:scheme) { create(:scheme, created_at: Time.zone.local(2022, 4, 1)) } + + before do + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), scheme:) + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 2), reactivation_date: Time.zone.local(2022, 8, 3), scheme:) + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 1), reactivation_date: Time.zone.local(2022, 9, 4), scheme:) + scheme.reload + end + + it "produces error when tenancy start date is during deactivated scheme period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.scheme = scheme + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]) + .to include(match I18n.t("validations.setup.startdate.scheme.reactivating_soon", name: scheme.service_name, date: "4 September 2022", deactivation_date: "1 June 2022")) + end + + it "produces no error when tenancy start date is during an active scheme period" do + record.startdate = Time.zone.local(2022, 10, 1) + record.scheme = scheme + setup_validator.validate_scheme(record) + expect(record.errors["scheme_id"]).to be_empty + end + end + end + + describe "#validate_location" do + context "with a deactivated location" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]) + .to include(match I18n.t("validations.setup.startdate.location.deactivated", postcode: location.postcode, date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 6, 1) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]).to be_empty + end + end + + context "with a location that is reactivating soon" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: nil) } + + before do + create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), reactivation_date: Time.zone.local(2022, 8, 4), location:) + location.reload + end + + it "produces error when tenancy start date is during deactivated location period" do + record.startdate = Time.zone.local(2022, 7, 5) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]) + .to include(match I18n.t("validations.setup.startdate.location.reactivating_soon", postcode: location.postcode, date: "4 August 2022", deactivation_date: "4 June 2022")) + end + + it "produces no error when tenancy start date is during an active location period" do + record.startdate = Time.zone.local(2022, 9, 1) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]).to be_empty + end + end + + context "with a location with no deactivation periods" do + let(:scheme) { create(:scheme) } + let(:location) { create(:location, scheme:, startdate: Time.zone.local(2022, 9, 15)) } + + it "produces no error" do + record.startdate = Time.zone.local(2022, 10, 15) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]).to be_empty + end + + it "produces an error when the date is before available_from date" do + record.startdate = Time.zone.local(2022, 8, 15) + record.location = location + setup_validator.validate_location(record) + expect(record.errors["location_id"]) + .to include(match I18n.t("validations.setup.startdate.location.activating_soon", postcode: location.postcode, date: "15 September 2022")) + end + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index cdeb71092..e25631df7 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -82,6 +82,7 @@ RSpec.configure do |config| config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::ControllerHelpers, type: :view config.include Devise::Test::IntegrationHelpers, type: :request + config.include Devise::Test::IntegrationHelpers, type: :feature config.include ViewComponent::TestHelpers, type: :component config.include Capybara::RSpecMatchers, type: :component config.include ActiveJob::TestHelper diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb new file mode 100644 index 000000000..788965e83 --- /dev/null +++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +RSpec.describe BulkUploadLettingsLogsController, type: :request do + let(:user) { FactoryBot.create(:user) } + let(:organisation) { user.organisation } + + before do + sign_in user + end + + describe "GET /lettings-logs/bulk-upload-logs/start" do + context "when not in crossover period" do + it "redirects to /prepare-your-file" do + Timecop.freeze(2022, 1, 1) do + get "/lettings-logs/bulk-upload-logs/start", params: {} + + expect(response).to redirect_to("/lettings-logs/bulk-upload-logs/prepare-your-file?form%5Byear%5D=2022") + end + end + end + + context "when in crossover period" do + it "redirects to /year" do + Timecop.freeze(2023, 6, 1) do + get "/lettings-logs/bulk-upload-logs/start", params: {} + + expect(response).to redirect_to("/lettings-logs/bulk-upload-logs/year") + end + end + end + end +end diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb new file mode 100644 index 000000000..f668b0da1 --- /dev/null +++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +RSpec.describe BulkUploadSalesLogsController, type: :request do + let(:user) { FactoryBot.create(:user) } + let(:organisation) { user.organisation } + + before do + sign_in user + end + + describe "GET /sales-logs/bulk-upload-logs/start" do + context "when not in crossover period" do + it "redirects to /prepare-your-file" do + Timecop.freeze(2022, 1, 1) do + get "/sales-logs/bulk-upload-logs/start", params: {} + + expect(response).to redirect_to("/sales-logs/bulk-upload-logs/prepare-your-file?form%5Byear%5D=2022") + end + end + end + + context "when in crossover period" do + it "redirects to /year" do + Timecop.freeze(2023, 6, 1) do + get "/sales-logs/bulk-upload-logs/start", params: {} + + expect(response).to redirect_to("/sales-logs/bulk-upload-logs/year") + end + end + end + end +end diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index 1bb73f399..a7f374f27 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -28,6 +28,7 @@ RSpec.describe FormController, type: :request do :completed, owning_organisation: organisation, managing_organisation: organisation, + startdate: Time.zone.local(2021, 5, 1), ) end let(:headers) { { "Accept" => "text/html" } } diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index 237149f7d..45463ceb1 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -847,7 +847,7 @@ RSpec.describe LocationsController, type: :request do context "when signed in as a data coordinator user" do let(:user) { FactoryBot.create(:user, :data_coordinator) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } - let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) } + let!(:locations) { FactoryBot.create_list(:location, 3, scheme:, startdate: Time.zone.local(2022, 4, 1)) } before do sign_in user @@ -858,7 +858,7 @@ RSpec.describe LocationsController, type: :request do let!(:another_scheme) { FactoryBot.create(:scheme) } before do - FactoryBot.create(:location, scheme:) + FactoryBot.create(:location, scheme:, startdate: Time.zone.local(2022, 4, 1)) end it "returns 404 not found" do @@ -867,7 +867,18 @@ RSpec.describe LocationsController, type: :request do end end - it "shows scheme" do + it "shows locations with correct data wben the new locations layout feature toggle is enabled" do + locations.each do |location| + expect(page).to have_content(location.id) + expect(page).to have_content(location.postcode) + expect(page).to have_content(location.name) + expect(page).to have_content(location.status) + end + end + + it "shows locations with correct data wben the new locations layout feature toggle is disabled" do + allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) + get "/schemes/#{scheme.id}/locations" locations.each do |location| expect(page).to have_content(location.id) expect(page).to have_content(location.postcode) @@ -964,7 +975,7 @@ RSpec.describe LocationsController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } let!(:scheme) { FactoryBot.create(:scheme) } - let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) } + let!(:locations) { FactoryBot.create_list(:location, 3, scheme:, startdate: Time.zone.local(2022, 4, 1)) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -972,11 +983,24 @@ RSpec.describe LocationsController, type: :request do get "/schemes/#{scheme.id}/locations" end - it "shows scheme" do + it "shows locations with correct data wben the new locations layout feature toggle is enabled" do + locations.each do |location| + expect(page).to have_content(location.id) + expect(page).to have_content(location.postcode) + expect(page).to have_content(location.name) + expect(page).to have_content(location.status) + end + end + + it "shows locations with correct data wben the new locations layout feature toggle is disabled" do + allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) + get "/schemes/#{scheme.id}/locations" locations.each do |location| expect(page).to have_content(location.id) expect(page).to have_content(location.postcode) expect(page).to have_content(location.type_of_unit) + expect(page).to have_content(location.mobility_type) + expect(page).to have_content(location.location_admin_district) expect(page).to have_content(location.startdate&.to_formatted_s(:govuk_date)) end end @@ -1243,11 +1267,13 @@ RSpec.describe LocationsController, type: :request do let!(:lettings_log) { FactoryBot.create(:lettings_log, :sh, location:, scheme:, startdate:, owning_organisation: user.organisation) } let(:startdate) { Time.utc(2022, 10, 11) } let(:add_deactivations) { nil } + let(:setup_locations) { nil } before do Timecop.freeze(Time.utc(2022, 10, 10)) sign_in user add_deactivations + setup_locations location.save! patch "/schemes/#{scheme.id}/locations/#{location.id}/new-deactivation", params: end @@ -1259,20 +1285,52 @@ RSpec.describe LocationsController, type: :request do context "with default date" do let(:params) { { location_deactivation_period: { deactivation_date_type: "default", deactivation_date: } } } - it "redirects to the confirmation page" do - follow_redirect! - expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{location.lettings_logs.count} logs") + context "and affected logs" do + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect 1 logs") + end + end + + context "and no affected logs" do + let(:setup_locations) { location.lettings_logs.update(location: nil) } + + it "redirects to the location page and updates the deactivation period" do + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + location.reload + expect(location.location_deactivation_periods.count).to eq(1) + expect(location.location_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2022, 4, 1)) + end end end context "with other date" do let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "10", "deactivation_date(2i)": "10", "deactivation_date(1i)": "2022" } } } - it "redirects to the confirmation page" do - follow_redirect! - expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{location.lettings_logs.count} logs") + context "and afected logs" do + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect #{location.lettings_logs.count} logs") + end + end + + context "and no affected logs" do + let(:setup_locations) { location.lettings_logs.update(location: nil) } + + it "redirects to the location page and updates the deactivation period" do + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + location.reload + expect(location.location_deactivation_periods.count).to eq(1) + expect(location.location_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2022, 10, 10)) + end end end diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index 83655156c..99c57a01a 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -21,8 +21,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") } before do - FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) - FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider) + FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider) get "/organisations/#{organisation.id}/housing-providers", headers:, params: {} end @@ -83,8 +83,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } before do - FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) - FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) + FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) + FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent) get "/organisations/#{organisation.id}/managing-agents", headers:, params: {} end @@ -153,7 +153,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "sets the organisation relationship attributes correctly" do request - expect(OrganisationRelationship).to exist(child_organisation_id: organisation.id, parent_organisation_id: housing_provider.id, relationship_type: OrganisationRelationship::OWNING) + expect(OrganisationRelationship).to exist(child_organisation_id: organisation.id, parent_organisation_id: housing_provider.id) end it "redirects to the organisation list" do @@ -181,7 +181,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "sets the organisation relationship attributes correctly" do request - expect(OrganisationRelationship).to exist(parent_organisation_id: organisation.id, child_organisation_id: managing_agent.id, relationship_type: OrganisationRelationship::MANAGING) + expect(OrganisationRelationship).to exist(parent_organisation_id: organisation.id, child_organisation_id: managing_agent.id) end it "redirects to the organisation list" do @@ -200,7 +200,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let(:request) { delete "/organisations/#{organisation.id}/housing-providers", headers:, params: } before do - FactoryBot.create(:organisation_relationship, :owning, child_organisation: organisation, parent_organisation: housing_provider) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider) end it "deletes the new organisation relationship" do @@ -225,7 +225,6 @@ RSpec.describe OrganisationRelationshipsController, type: :request do before do FactoryBot.create( :organisation_relationship, - :managing, parent_organisation: organisation, child_organisation: managing_agent, ) @@ -256,8 +255,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } before do - FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) - FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider) + FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider) get "/organisations/#{organisation.id}/housing-providers", headers:, params: {} end @@ -304,8 +303,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } before do - FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) - FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) + FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) + FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent) get "/organisations/#{organisation.id}/managing-agents", headers:, params: {} end @@ -383,7 +382,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "sets the organisation relationship attributes correctly" do request - expect(OrganisationRelationship).to exist(child_organisation_id: organisation.id, parent_organisation_id: housing_provider.id, relationship_type: OrganisationRelationship::OWNING) + expect(OrganisationRelationship).to exist(child_organisation_id: organisation.id, parent_organisation_id: housing_provider.id) end it "redirects to the organisation list" do @@ -411,7 +410,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "sets the organisation relationship attributes correctly" do request - expect(OrganisationRelationship).to exist(parent_organisation_id: organisation.id, child_organisation_id: managing_agent.id, relationship_type: OrganisationRelationship::MANAGING) + expect(OrganisationRelationship).to exist(parent_organisation_id: organisation.id, child_organisation_id: managing_agent.id) end it "redirects to the organisation list" do @@ -430,7 +429,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let(:request) { delete "/organisations/#{organisation.id}/housing-providers", headers:, params: } before do - FactoryBot.create(:organisation_relationship, :owning, child_organisation: organisation, parent_organisation: housing_provider) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider) end it "deletes the new organisation relationship" do @@ -455,7 +454,6 @@ RSpec.describe OrganisationRelationshipsController, type: :request do before do FactoryBot.create( :organisation_relationship, - :managing, parent_organisation: organisation, child_organisation: managing_agent, ) @@ -477,8 +475,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") } before do - FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) - FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider, relationship_type: OrganisationRelationship.relationship_types[:owning]) + FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: housing_provider) + FactoryBot.create(:organisation_relationship, child_organisation: other_organisation, parent_organisation: other_org_housing_provider) get "/organisations/#{organisation.id}/housing-providers", headers:, params: {} end @@ -531,8 +529,8 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") } before do - FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) - FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent, relationship_type: OrganisationRelationship.relationship_types[:managing]) + FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) + FactoryBot.create(:organisation_relationship, parent_organisation: other_organisation, child_organisation: other_org_managing_agent) get "/organisations/#{organisation.id}/managing-agents", headers:, params: {} end diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 0c3b47f1a..0769e15c6 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -274,7 +274,7 @@ RSpec.describe SchemesController, type: :request do it "renders reactivate this scheme" do expect(response).to have_http_status(:ok) - expect(page).to have_link("Reactivate this scheme", href: "/schemes/#{scheme.id}/reactivate") + expect(page).to have_link("Reactivate this scheme", href: "/schemes/#{scheme.id}/new-reactivation") end end @@ -283,7 +283,7 @@ RSpec.describe SchemesController, type: :request do it "renders reactivate this scheme" do expect(response).to have_http_status(:ok) - expect(page).to have_link("Reactivate this scheme", href: "/schemes/#{scheme.id}/reactivate") + expect(page).to have_link("Reactivate this scheme", href: "/schemes/#{scheme.id}/new-reactivation") end end end @@ -915,7 +915,6 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support" do let(:user) { FactoryBot.create(:user, :support) } let(:scheme_to_update) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } - # let!(:location) { FactoryBot.create(:location, scheme: scheme_to_update) } before do FactoryBot.create(:location, scheme: scheme_to_update) @@ -1768,15 +1767,17 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, created_at: Time.zone.today) } let!(:location) { FactoryBot.create(:location, scheme:) } let(:deactivation_date) { Time.utc(2022, 10, 10) } let!(:lettings_log) { FactoryBot.create(:lettings_log, :sh, location:, scheme:, startdate:, owning_organisation: user.organisation) } let(:startdate) { Time.utc(2022, 10, 11) } + let(:setup_schemes) { nil } before do Timecop.freeze(Time.utc(2022, 10, 10)) sign_in user + setup_schemes patch "/schemes/#{scheme.id}/new-deactivation", params: end @@ -1787,20 +1788,54 @@ RSpec.describe SchemesController, type: :request do context "with default date" do let(:params) { { scheme_deactivation_period: { deactivation_date_type: "default", deactivation_date: } } } - it "redirects to the confirmation page" do - follow_redirect! - expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + context "and affected logs" do + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + end + end + + context "and no affected logs" do + let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) } + + it "redirects to the location page and updates the deactivation period" do + follow_redirect! + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + scheme.reload + expect(scheme.scheme_deactivation_periods.count).to eq(1) + expect(scheme.scheme_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2022, 4, 1)) + end end end context "with other date" do let(:params) { { scheme_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "10", "deactivation_date(2i)": "10", "deactivation_date(1i)": "2022" } } } - it "redirects to the confirmation page" do - follow_redirect! - expect(response).to have_http_status(:ok) - expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + context "and affected logs" do + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect #{scheme.lettings_logs.count} logs") + end + end + + context "and no affected logs" do + let(:setup_schemes) { scheme.lettings_logs.update(scheme: nil) } + + it "redirects to the location page and updates the deactivation period" do + follow_redirect! + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + scheme.reload + expect(scheme.scheme_deactivation_periods.count).to eq(1) + expect(scheme.scheme_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2022, 10, 10)) + end end end @@ -1855,7 +1890,7 @@ RSpec.describe SchemesController, type: :request do it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.not_selected")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.not_selected")) end end @@ -1864,7 +1899,7 @@ RSpec.describe SchemesController, type: :request do it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.invalid")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.invalid")) end end @@ -1873,7 +1908,7 @@ RSpec.describe SchemesController, type: :request do it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.out_of_range", date: "1 April 2022")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.out_of_range", date: "1 April 2022")) end end @@ -1882,7 +1917,7 @@ RSpec.describe SchemesController, type: :request do it "displays page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.invalid")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.invalid")) end end @@ -1891,7 +1926,7 @@ RSpec.describe SchemesController, type: :request do it "displays page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.invalid")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.invalid")) end end @@ -1900,7 +1935,7 @@ RSpec.describe SchemesController, type: :request do it "displays page with an error message" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content(I18n.t("validations.scheme.deactivation_date.invalid")) + expect(page).to have_content(I18n.t("validations.scheme.toggle_date.invalid")) end end end diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 044fe6c93..71ad30400 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -59,7 +59,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737") } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.utc(2022, 2, 2, 10, 36, 49), tenancylength: 5) } it "generates a ZIP export file with the expected filename" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) @@ -237,7 +237,7 @@ RSpec.describe Exports::LettingsLogExportService do let(:csv_export_file) { File.open("spec/fixtures/exports/general_needs_log.csv", "r:UTF-8") } let(:expected_csv_filename) { "export_2022_05_01.csv" } - let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737") } + let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.utc(2022, 2, 2, 10, 36, 49), tenancylength: 5) } it "generates an CSV export file with the expected content" do expected_content = replace_entity_ids(lettings_log, csv_export_file.read) @@ -254,9 +254,9 @@ RSpec.describe Exports::LettingsLogExportService do let(:organisation) { FactoryBot.create(:organisation, provider_type: "LA") } let(:user) { FactoryBot.create(:user, organisation:) } let(:scheme) { FactoryBot.create(:scheme, :export, owning_organisation: organisation) } - let(:location) { FactoryBot.create(:location, :export, scheme:) } + let(:location) { FactoryBot.create(:location, :export, scheme:, startdate: Time.zone.local(2021, 4, 1)) } - let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, created_by: user, owning_organisation: organisation) } + let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, created_by: user, owning_organisation: organisation, startdate: Time.utc(2022, 2, 2, 10, 36, 49)) } it "generates an XML export file with the expected content" do expected_content = replace_entity_ids(lettings_log, export_file.read) diff --git a/spec/services/imports/lettings_logs_import_service_spec.rb b/spec/services/imports/lettings_logs_import_service_spec.rb index 453ee7e30..acb9d9dd4 100644 --- a/spec/services/imports/lettings_logs_import_service_spec.rb +++ b/spec/services/imports/lettings_logs_import_service_spec.rb @@ -30,9 +30,9 @@ RSpec.describe Imports::LettingsLogsImportService do FactoryBot.create(:user, old_user_id: "e29c492473446dca4d50224f2bb7cf965a261d6f", organisation:) # Location setup - FactoryBot.create(:location, old_visible_id: "10", postcode: "LS166FT", scheme_id: scheme1.id, mobility_type: "W") - FactoryBot.create(:location, scheme_id: scheme1.id) - FactoryBot.create(:location, old_visible_id: "10", postcode: "LS166FT", scheme_id: scheme2.id, mobility_type: "W") + FactoryBot.create(:location, old_visible_id: "10", postcode: "LS166FT", scheme_id: scheme1.id, mobility_type: "W", startdate: Time.zone.local(2021, 4, 1)) + FactoryBot.create(:location, scheme_id: scheme1.id, startdate: Time.zone.local(2021, 4, 1)) + FactoryBot.create(:location, old_visible_id: "10", postcode: "LS166FT", scheme_id: scheme2.id, mobility_type: "W", startdate: Time.zone.local(2021, 4, 1)) # Stub the form handler to use the real form allow(FormHandler.instance).to receive(:get_form).with("previous_lettings").and_return(real_2021_2022_form) diff --git a/spec/views/layouts/application_layout_spec.rb b/spec/views/layouts/application_layout_spec.rb new file mode 100644 index 000000000..2ee571e09 --- /dev/null +++ b/spec/views/layouts/application_layout_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +RSpec.describe "layouts/application" do + shared_examples "analytics cookie elements" do |banner:, scripts:| + define_negated_matcher :not_match, :match + + it "#{banner ? 'includes' : 'omits'} the cookie banner" do + banner_text = "We’d like to use analytics cookies so we can understand how you use the service and make improvements." + if banner + expect(rendered).to match(banner_text) + else + expect(rendered).not_to match(banner_text) + end + end + + it "#{scripts ? 'includes' : 'omits'} the analytics scripts" do + gtm_script_tag = /