Browse Source

Merge branch 'CLDC-3116-create-blank-homepage' into CLDC-2252-homepage-task-section

# Conflicts:
#	app/views/home/index.html.erb
pull/2115/head
natdeanlewissoftwire 12 months ago
parent
commit
1dcb4aed59
  1. 2
      app/frontend/styles/_document-list.scss
  2. 20
      app/helpers/collection_time_helper.rb
  3. 8
      app/models/bulk_upload.rb
  4. 4
      app/models/form_handler.rb
  5. 18
      app/models/forms/bulk_upload_lettings/guidance.rb
  6. 18
      app/models/forms/bulk_upload_sales/guidance.rb
  7. 6
      app/models/log.rb
  8. 2
      app/models/validations/sales/property_validations.rb
  9. 10
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  10. 8
      app/services/bulk_upload/sales/year2023/row_parser.rb
  11. 13
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb
  12. 16
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb
  13. 97
      app/views/bulk_upload_shared/guidance.html.erb
  14. 35
      app/views/home/_upcoming_deadlines.html.erb
  15. 8
      app/views/home/index.html.erb
  16. 73
      app/views/layouts/_collection_resources.html.erb
  17. 2
      app/views/logs/index.html.erb
  18. 4
      app/views/organisations/show.html.erb
  19. 68
      app/views/start/guidance.html.erb
  20. 8
      app/views/start/index.html.erb
  21. 1
      config/routes.rb
  22. 30
      spec/helpers/collection_time_helper_spec.rb
  23. 8
      spec/models/validations/sales/property_validations_spec.rb
  24. 8
      spec/requests/lettings_logs_controller_spec.rb
  25. 36
      spec/requests/organisations_controller_spec.rb
  26. 8
      spec/requests/sales_logs_controller_spec.rb
  27. 85
      spec/requests/start_controller_spec.rb
  28. 4
      spec/requests/users_controller_spec.rb
  29. 35
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  30. 23
      spec/services/bulk_upload/sales/year2023/row_parser_spec.rb

2
app/frontend/styles/_document-list.scss

@ -12,7 +12,7 @@
} }
.app-document-list__item-title { .app-document-list__item-title {
@include govuk-font($size: 16, $weight: "bold"); @include govuk-font($size: 16);
margin: 0 0 govuk-spacing(1); margin: 0 0 govuk-spacing(1);
} }

20
app/helpers/collection_time_helper.rb

@ -45,4 +45,24 @@ module CollectionTimeHelper
def previous_collection_start_date def previous_collection_start_date
current_collection_start_date - 1.year current_collection_start_date - 1.year
end end
def quarter_for_date(date: Time.zone.now)
quarters = [
{ quarter: "Q3", cutoff_date: Time.zone.local(2024, 1, 12), start_date: Time.zone.local(2023, 10, 1), end_date: Time.zone.local(2023, 12, 31) },
{ quarter: "Q1", cutoff_date: Time.zone.local(2024, 7, 12), start_date: Time.zone.local(2024, 4, 1), end_date: Time.zone.local(2024, 6, 30) },
{ quarter: "Q2", cutoff_date: Time.zone.local(2024, 10, 11), start_date: Time.zone.local(2024, 7, 1), end_date: Time.zone.local(2024, 9, 30) },
{ quarter: "Q3", cutoff_date: Time.zone.local(2025, 1, 10), start_date: Time.zone.local(2024, 10, 1), end_date: Time.zone.local(2024, 12, 31) },
]
quarter = quarters.find { |q| date.between?(q[:start_date], q[:cutoff_date] + 1.day) }
return unless quarter
OpenStruct.new(
quarter: quarter[:quarter],
cutoff_date: quarter[:cutoff_date],
quarter_start_date: quarter[:start_date],
quarter_end_date: quarter[:end_date],
)
end
end end

8
app/models/bulk_upload.rb

@ -116,6 +116,14 @@ class BulkUpload < ApplicationRecord
bulk_upload_errors.distinct.count("row") bulk_upload_errors.distinct.count("row")
end end
def remaining_logs_with_errors_count
logs.filter_by_status("in_progress").count
end
def remaining_errors_count
logs.filter_by_status("in_progress").map(&:missing_answers_count).sum(0)
end
private private
def generate_identifier def generate_identifier

4
app/models/form_handler.rb

@ -28,6 +28,10 @@ class FormHandler
forms["current_lettings"] forms["current_lettings"]
end end
def previous_lettings_form
forms["previous_lettings"]
end
def current_sales_form def current_sales_form
forms["current_sales"] forms["current_sales"]
end end

18
app/models/forms/bulk_upload_lettings/guidance.rb

@ -15,17 +15,29 @@ module Forms
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
end end
def legacy_template_path def lettings_legacy_template_path
Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path
end end
def template_path def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path
end end
def specification_path def lettings_specification_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path
end end
def sales_legacy_template_path
Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path
end
def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path
end
def sales_specification_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).specification_path
end
end end
end end
end end

18
app/models/forms/bulk_upload_sales/guidance.rb

@ -15,15 +15,27 @@ module Forms
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
end end
def legacy_template_path def lettings_legacy_template_path
Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path
end
def lettings_template_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).template_path
end
def lettings_specification_path
Forms::BulkUploadLettings::PrepareYourFile.new(year:).specification_path
end
def sales_legacy_template_path
Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path
end end
def template_path def sales_template_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path Forms::BulkUploadSales::PrepareYourFile.new(year:).template_path
end end
def specification_path def sales_specification_path
Forms::BulkUploadSales::PrepareYourFile.new(year:).specification_path Forms::BulkUploadSales::PrepareYourFile.new(year:).specification_path
end end
end end

6
app/models/log.rb

@ -203,6 +203,12 @@ class Log < ApplicationRecord
}.compact }.compact
end end
def missing_answers_count
form.questions.count do |question|
!optional_fields.include?(question.id) && question.displayed_to_user?(self) && question.unanswered?(self) && !question.is_derived_or_has_inferred_check_answers_value?(self)
end
end
private private
# Handle logs that are older than previous collection start date # Handle logs that are older than previous collection start date

2
app/models/validations/sales/property_validations.rb

@ -5,6 +5,8 @@ module Validations::Sales::PropertyValidations
if record.discounted_ownership_sale? && record.ppostcode_full != record.postcode_full if record.discounted_ownership_sale? && record.ppostcode_full != record.postcode_full
record.errors.add :postcode_full, I18n.t("validations.property.postcode.must_match_previous") record.errors.add :postcode_full, I18n.t("validations.property.postcode.must_match_previous")
record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous") record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous")
record.errors.add :ownershipsch, I18n.t("validations.property.postcode.must_match_previous")
record.errors.add :uprn, I18n.t("validations.property.postcode.must_match_previous")
end end
end end

10
app/services/bulk_upload/lettings/year2023/row_parser.rb

@ -415,7 +415,13 @@ class BulkUpload::Lettings::Year2023::RowParser
fields = field_mapping_for_errors[error.attribute] || [] fields = field_mapping_for_errors[error.attribute] || []
fields.each do |field| fields.each do |field|
unless errors.include?(field) next if errors.include?(field)
question = log.form.get_question(error.attribute, log)
if question.present? && setup_question?(question)
errors.add(field, error.message, category: :setup)
else
errors.add(field, error.message) errors.add(field, error.message)
end end
end end
@ -1187,7 +1193,7 @@ private
attributes["tcharge"] = field_132 attributes["tcharge"] = field_132
attributes["chcharge"] = field_127 attributes["chcharge"] = field_127
attributes["is_carehome"] = field_127.present? ? 1 : 0 attributes["is_carehome"] = field_127.present? ? 1 : 0
attributes["household_charge"] = field_125 attributes["household_charge"] = supported_housing? ? field_125 : nil
attributes["hbrentshortfall"] = field_133 attributes["hbrentshortfall"] = field_133
attributes["tshortfall_known"] = tshortfall_known attributes["tshortfall_known"] = tshortfall_known
attributes["tshortfall"] = field_134 attributes["tshortfall"] = field_134

8
app/services/bulk_upload/sales/year2023/row_parser.rb

@ -509,7 +509,13 @@ class BulkUpload::Sales::Year2023::RowParser
fields = field_mapping_for_errors[error.attribute] || [] fields = field_mapping_for_errors[error.attribute] || []
fields.each do |field| fields.each do |field|
unless errors.include?(field) next if errors.include?(field)
question = log.form.get_question(error.attribute, log)
if question.present? && setup_question?(question)
errors.add(field, error.message, category: :setup)
else
errors.add(field, error.message) errors.add(field, error.message)
end end
end end

13
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb

@ -9,27 +9,30 @@
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span> <span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2> <h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p> <p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet"> <ul class="govuk-list govuk-list--bullet">
<li> <li>
<%= govuk_link_to "New template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form. <%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.
</li> </li>
<li> <li>
<%= govuk_link_to "Legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. <%= govuk_link_to "Download the legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end.
</li> </li>
</ul> </ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2> <h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet"> <ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with CORE data from your housing management system according to the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %>.</li> <li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li> <li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li><strong>Headers:</strong> If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers.</li> <li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
<li>If you have to manually enter large volumes of data into the bulk upload template, we recommend creating logs directly in the service instead. <%= govuk_link_to "Find out more about exporting your data", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %>.</li>
</ul> </ul>
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023/24 data.") %> <%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023/24 data.") %>

16
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb

@ -9,20 +9,24 @@
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span> <span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1> <h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }) %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2> <h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet"> <ul class="govuk-list govuk-list--bullet">
<li><%= govuk_link_to "New template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.</li> <li><%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form.</li>
<li><%= govuk_link_to "Legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end.</li> <li><%= govuk_link_to "Download the legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end.</li>
</ul> </ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2> <h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet"> <ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with CORE data from your housing management system according to the <%= govuk_link_to "Sales #{@form.year_combo} Bulk Upload Specification", @form.specification_path %>.</li> <li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Sales #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li> <li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li><strong>Headers:</strong> If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers.</li> <li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
<li>If you have to manually enter large volumes of data into the bulk upload template, we recommend creating logs directly in the service instead. <%= govuk_link_to "Find out more about exporting your data", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }) %>.</li>
<li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers.</li>
</ul> </ul>
<h2 class="govuk-heading-s">Save your file</h2> <h2 class="govuk-heading-s">Save your file</h2>

97
app/views/bulk_upload_shared/guidance.html.erb

@ -8,61 +8,78 @@
<h1 class="govuk-heading-l">How to upload logs in bulk</h1> <h1 class="govuk-heading-l">How to upload logs in bulk</h1>
<div class="govuk-!-padding-bottom-4"> <div class="govuk-!-padding-bottom-4">
<h2 class="govuk-heading-s">Uploading sales and lettings logs</h2>
<p class="govuk-body">You can upload one sales or lettings log at a time, or many at once (known as ‘bulk upload’) with a comma-separated values (CSV) spreadsheet file.</p> <p class="govuk-body">You can upload one sales or lettings log at a time, or many at once (known as ‘bulk upload’) with a comma-separated values (CSV) spreadsheet file.</p>
<p class="govuk-body">Bulk upload may be easier if your organisation deals with many logs, or if you can export CSV data from your Housing Management System (HMS). If your organisation only deals with a small amount of logs, or you cannot export CSV data, it’s probably easier to enter logs individually.</p> <p class="govuk-body">Bulk upload may be easier if your organisation deals with many logs, or if you can export CSV data from your Housing Management System (HMS).</p>
<%= govuk_warning_text text: "You cannot upload lettings and sales logs with the same template - you must export each data type separately, then upload them" %>
</div>
<div class="govuk-!-padding-bottom-4"> <p class="govuk-body">If your organisation only deals with a small amount of logs, or you cannot export CSV data, it’s probably easier to create logs individually using the online form.</p>
<h2 class="govuk-heading-s">Creating your CSV files</h2>
<p class="govuk-body">To bulk upload successfully, all spreadsheets must be in the correct CSV format.</p>
<p class="govuk-body">In most programs, you must resave files as CSV - it’s not usually the default setting. CSV files are also unformatted, so any formatting added before saving (for example colours) will automatically disappear.</p>
<%= govuk_details(summary_text: "More about CSV") do %>
<p class="govuk-body">A CSV file is a basic spreadsheet with data values in plain text, and columns separated by commas. Each data row is a new text line.</p>
<p class="govuk-body">CSV data is easier to process than more common advanced spreadsheet formats, for example Excel. It means CSV is well suited to upload large, or multiple data sets.</p>
<% end %>
</div> </div>
<div class="govuk-!-padding-bottom-4"> <%= govuk_accordion do |accordion| %>
<h2 class="govuk-heading-s">Exporting CSV data</h2> <%= accordion.with_section(heading_text: "Exporting your data", expanded: true) do %>
<p class="govuk-body">Export CSV data directly from your current systems.</p>
<p class="govuk-body">Export CSV data directly from your current systems, or export then adjust it to CSV.</p> <p class="govuk-body">You should export lettings and sales data separately, and data from different collection years separately.</p>
<p class="govuk-body">You can then upload it via a button at the top of the lettings and sales logs pages.</p> <p class="govuk-body">The exported CSV file should have one row per log so that the data can fit into the bulk upload template.</p>
<p class="govuk-body"><strong>If your organisation has an HMS</strong></p>
<%= govuk_details(summary_text: "My organisation has a HMS") do %> <p class="govuk-body">Some HMS providers sell an add-on "eCORE" module, which exports CSV data in the format we require.</p>
<p class="govuk-body">Some HMS providers sell an add-on "eCORE" module, which exports CSV data for you.</p> <p class="govuk-body"><strong>If your organisation does not have an HMS</strong></p>
<p class="govuk-!-font-weight-bold">It can take HMS providers a while to update these per new collection year, so you may have to wait for updates to export, or adjust your data manually post-export.</p> <p class="govuk-body">Your organisation’s IT team may be able to export CSV data for you, depending on how data is stored in your organisation.</p>
<p class="govuk-body">Review the bulk upload template and specification carefully to understand how we expect data to be mapped and formatted. The next 2 sections explain more about these documents.</p>
<% end %> <% end %>
<%= govuk_details(summary_text: "My organisation does not have a HMS") do %> <%= accordion.with_section(heading_text: "Using the bulk upload template") do %>
<p class="govuk-body">Your organisation’s IT team may be able to export CSV data for you - <%= govuk_link_to "find out more about data specification", @form.specification_path, target: "_blank" %>. This document outlines:</p> <p class="govuk-body">For each collection year, we publish a bulk upload template and specification.</p>
<p class="govuk-body">The bulk upload templates contain 7 or 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<ul class="govuk-list govuk-list--bullet"> <ul class="govuk-list govuk-list--bullet">
<li>required fields</li> <li>the CORE form questions and their field numbers</li>
<li>each field's valid response</li> <li>each field’s valid responses</li>
<li>if/when certain fields can be left blank</li> <li>if/when certain fields can be left blank</li>
<li>which fields are used to check for duplicate logs</li>
</ul> </ul>
<% if @form.year == 2022 %> <p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p>
<p class="govuk-body">Fields must be in the same order as <%= govuk_link_to "the template", @form.template_path %>. Copy each column of your exported data one at a time and paste into the correct template column.</p> <p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p>
<% else %> <p class="govuk-body">For 2023/24 uploads, there are 2 templates to choose from, a new template and a legacy template. They outline suggested ways of ordering your data.</p>
<p class="govuk-body">Fields can appear in any order, as long as you include the <%= govuk_link_to "template document", @form.template_path %> headers, to easily identify what each column represents. You can rearrange data columns to match your system exports, copy-pasting multiple columns at once. For data stored in multiple systems, you can copy-paste all columns for one system next to each other, repeating this for subsequent system exports.</p> <p class="govuk-body">You must include the headers in your file when you upload, unless your data matches the exact order of the legacy template. If you choose to remove the headers, you must also remove the blank column A.</p>
<p class="govuk-body"><strong>New template</strong>: In this template, the questions are in the same order as the 2023/24 paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.</p>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2023 to 2024) – New question ordering", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – New question ordering", @form.sales_template_path %></p>
<p class="govuk-body"><strong>Legacy template</strong>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.</p>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2023 to 2024) - Legacy version", @form.lettings_legacy_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – Legacy version", @form.sales_legacy_template_path %></p>
<% end %> <% end %>
<%= accordion.with_section(heading_text: "Using the bulk upload specification") do %>
<p class="govuk-body">The bulk upload specification contains the same information as the template headers, as well as more details about the accepted responses for each question. For multiple-choice questions, we use number or letter codes to represent each option, and the specification shows what answer each code represents.</p>
<p class="govuk-body">There is a separate specification for lettings and sales:</p>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload specification (2023 to 2024)", @form.lettings_specification_path, target: "_blank" %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload specification (2023 to 2024)", @form.sales_specification_path, target: "_blank" %></p>
<p class="govuk-body">If your upload fails because there are errors in the data, you can use the specification to help correct the errors. Having your file, the error report, and the specification open at the same time will make it easy to cross-reference field numbers and check accepted responses.</p>
<% end %> <% end %>
</div>
<div class="govuk-!-padding-bottom-4"> <%= accordion.with_section(heading_text: "Saving your file as a CSV") do %>
<h2 class="govuk-heading-s">Getting help</h2> <p class="govuk-body">To bulk upload successfully, all spreadsheets must be in the correct CSV format. </p>
<p class="govuk-body">There is no step-by-step bulk upload guide like there is with single log upload. However, you can download <%= @form.year == 2022 ? govuk_link_to("our template", @form.template_path) : "one of our templates" %>, which you can copy-paste data into from your systems column-by-column. You can also view a post-upload report showing any data errors, and our <%= govuk_link_to "data specification", @form.specification_path, target: "_blank" %> can help fix these.</p> <p class="govuk-body">If you’ve pasted your data into the template, you will need to resave the file as a CSV.</p>
<% if @form.year == 2023 %> <p class="govuk-body">CSV files are unformatted, so any formatting added before saving (for example colours) will automatically disappear.</p>
<%= govuk_details(summary_text: "How to choose the right template") do %> <p class="govuk-body"><strong>What is a CSV?</strong></p>
<p class="govuk-body">Use one of these templates to upload logs for 2023/24:</p> <p class="govuk-body">A CSV file is a basic spreadsheet with data values in plain text, and columns separated by commas. Each data row is a new text line.</p>
<p class="govuk-body"><%= govuk_link_to "New template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.</p> <p class="govuk-body">CSV data is easier to process than more common advanced spreadsheet formats, for example Excel. It means CSV is well suited to upload large data sets.</p>
<p class="govuk-body"><%= govuk_link_to "Legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.</p> <p class="govuk-body"></p>
<% end %>
<%= accordion.with_section(heading_text: "Next steps") do %>
<p class="govuk-body">Once you've saved your CSV file, you can upload it via a button at the top of the lettings and sales logs pages.</p>
<p class="govuk-body">When your file is done processing, you will receive an email explaining your next steps. If all your data is valid, your logs will be created. If some data is invalid, you’ll receive an email with instructions about how to resolve the errors.</p>
<p class="govuk-body">If your file has errors on fields 1 through 17, you must fix these in the CSV. This is because we need to know these answers to validate the rest of the data. Any errors in these fields will be featured in the error report’s summary tab.</p>
<p class="govuk-body">If none of your errors are in fields 1 through 17, you can choose how to fix the errors. You can either fix them in the CSV and reupload, or create partially complete logs and answer the remaining questions on the CORE site. Any errors that affect a significant number of logs will be featured in the error report’s summary tab to help you decide.</p>
<p class="govuk-body"></p>
<% end %>
<%= accordion.with_section(heading_text: "Getting help") do %>
<p class="govuk-body">The bulk upload template and specification should contain all the information you need to format your data correctly and fix any errors found during processing. If you still need support with your bulk upload, <%= govuk_link_to "contact the helpdesk", GlobalConstants::HELPDESK_URL %>.</p>
<% end %> <% end %>
<% end %> <% end %>
<p class="govuk-body">If you still need support mapping data in the way we need, DLUHC’s helpdesk can help. If your data is across multiple systems, or is hard to export as a single file in the correct format, you could try different exports, or copy-pasting data by hand.</p>
</div>
</div> </div>
</div> </div>

35
app/views/home/_upcoming_deadlines.html.erb

@ -0,0 +1,35 @@
<h1 class="govuk-heading-l">Upcoming deadlines</h1>
<% current_lettings_form = FormHandler.instance.in_crossover_period? ? FormHandler.instance.previous_lettings_form : FormHandler.instance.current_lettings_form %>
<% formatted_deadline = "#{current_lettings_form.submission_deadline.strftime('%A')} #{current_lettings_form.submission_deadline.to_formatted_s(:govuk_date)}" %>
<% if FormHandler.instance.in_crossover_period? %>
<p class="govuk-body govuk-body-m"><strong>End of year deadline - <%= formatted_deadline %>:</strong> Deadline to submit logs for tenancies starting between <%= collection_start_date(Time.zone.now).to_formatted_s(:govuk_date) %> to <%= collection_end_date(Time.zone.now).to_formatted_s(:govuk_date) %></p>
<% end %>
<% current_quarter = quarter_for_date(date: Time.zone.now) %>
<% if current_quarter.present? %>
<p class="govuk-body govuk-body-m"><strong><%= "#{current_quarter.quarter} - #{current_quarter.cutoff_date.strftime('%A')} #{current_quarter.cutoff_date.to_formatted_s(:govuk_date)}" %>:</strong> Quarterly cut off date for tenancies and sales starting between <%= current_quarter.quarter_start_date.to_formatted_s(:govuk_date) %> and <%= current_quarter.quarter_end_date.to_formatted_s(:govuk_date) %>.</p>
<% end %>
<% if !FormHandler.instance.in_crossover_period? %>
<p class="govuk-body govuk-body-m">Try to complete your logs for each quarter by the cut-off date.</p>
<p class="govuk-body govuk-body-m">You can still create logs for a previous quarter after its cut-off date, as long as you complete them by the <strong>end-of-year deadline: <%= formatted_deadline %>.</strong></p>
<% end %>
<% if FormHandler.instance.in_crossover_period? %>
<% previous_lettings_form = FormHandler.instance.previous_lettings_form %>
<p class="govuk-body govuk-body-m">Prioritise completing logs for the closing collection year. You must complete all <%= previous_lettings_form.start_date.year %> to <%= previous_lettings_form.submission_deadline.year %> logs must by the end-of-year deadline. You can still create <%= current_lettings_form.start_date.year %> to <%= current_lettings_form.submission_deadline.year %> logs for this quarter after the quarterly cut-off date.</p>
<% end %>
<%= govuk_details(summary_text: "Quarterly cut-off dates for 2023 to 2024") do %>
<p class="govuk-body govuk-body-m">The 2023 to 2024 quarterly cut-off dates are:</p>
<ul class="govuk-list govuk-list--bullet">
<li class="govuk-!-padding-bottom-4"><strong>Q1 - Friday 14 July 2023:</strong> Quarterly cut-off date for tenancies and sales starting between 1 April 2023 and 30 June 2023.</li>
<li class="govuk-!-padding-bottom-4"><strong>Q2 - Friday 13 October 2023:</strong> Quarterly cut-off date for tenancies and sales starting between 1 July 2023 and 30 September 2023.</li>
<li class="govuk-!-padding-bottom-4"><strong>Q3 - Friday 12 January 2024:</strong> Quarterly cut-off date for tenancies and sales starting between 1 October 2023 and 31 December 2023.</li>
<li class="govuk-!-padding-bottom-4"><strong>End of year deadline - Friday 7 June 2024:</strong> Deadline for tenancies and sales starting between 1 January 2024 and 31 March 2024, plus any late submissions for the 2023 to 2024 collection year.</li>
</ul>
<p class="govuk-body govuk-body-m">It is important that you meet these cut-off dates because we submit data to the Office for National Statistics quarterly, helping them create essential inflation statistics.</p>
<p class="govuk-body govuk-body-m">Meeting these cut-off dates also gives you more accurate data for your own analysis, and reduces the burden at the end of the year.</p>
<p class="govuk-body govuk-body-m">If you are not able to meet these quarterly dates, submit your logs as soon as you can so that they can be included in the annual data.</p>
<% end %>

8
app/views/home/index.html.erb

@ -49,3 +49,11 @@
</div> </div>
</div> </div>
</div> </div>
<p class="govuk-body-l"><%= "Welcome back, #{@current_user.name}" %></p>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= render partial: "layouts/collection_resources" %>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<%= render partial: "home/upcoming_deadlines" %>
</div>
</div>

73
app/views/layouts/_collection_resources.html.erb

@ -1,94 +1,59 @@
<div class="app-card"> <% if current_user %>
<h1 class="govuk-heading-l">Collection resources</h1>
<p class="govuk-body"><strong><%= govuk_link_to "Guidance for submitting social housing lettings and sales data (CORE)", guidance_path %></strong></p>
<% else %>
<h2 class="govuk-heading-m">Collection resources</h2> <h2 class="govuk-heading-m">Collection resources</h2>
<p class="govuk-body-s">For lettings starting during 1 April 2023 to 31 March 2024 and sales completing during the same period, use the 2023/24 forms.</p> <% end %>
<p class="govuk-body">Use the 2023 to 2024 forms for lettings that start and sales that complete between 1 April 2023 and 31 March 2024.</p>
<%= govuk_tabs(title: "Collection resources") do |c| %>
<% if FormHandler.instance.lettings_form_for_start_year(2023) && FormHandler.instance.lettings_form_for_start_year(2023).edit_end_date > Time.zone.today %> <% if FormHandler.instance.lettings_form_for_start_year(2023) && FormHandler.instance.lettings_form_for_start_year(2023).edit_end_date > Time.zone.today %>
<h3 class="govuk-heading-s">Lettings 2023/24</h3> <% c.with_tab(label: "Lettings 2023/24") do %>
<%= render DocumentListComponent.new(items: [ <%= render DocumentListComponent.new(items: [
{ {
name: "Lettings log for tenants (2023/24)", name: "Download the lettings log for tenants (2023 to 2024)",
href: download_23_24_lettings_form_path, href: download_23_24_lettings_form_path,
metadata: file_type_size_and_pages("2023_24_lettings_paper_form.pdf", number_of_pages: 8), metadata: file_type_size_and_pages("2023_24_lettings_paper_form.pdf", number_of_pages: 8),
}, },
{ {
name: "Lettings bulk upload template (2023/24) – New question ordering", name: "Download the lettings bulk upload template (2023 to 2024) – New question ordering",
href: download_23_24_lettings_bulk_upload_template_path, href: download_23_24_lettings_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-lettings-template-2023-24.xlsx"),
}, },
{ {
name: "Lettings bulk upload template (2023/24)", name: "Download the lettings bulk upload template (2023 to 2024) – Legacy version",
href: download_23_24_lettings_bulk_upload_legacy_template_path, href: download_23_24_lettings_bulk_upload_legacy_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-legacy-template-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-lettings-legacy-template-2023-24.xlsx"),
}, },
{ {
name: "Lettings bulk upload specification (2023/24)", name: "Download the lettings bulk upload specification (2023 to 2024)",
href: download_23_24_lettings_bulk_upload_specification_path, href: download_23_24_lettings_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-specification-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-lettings-specification-2023-24.xlsx"),
}, },
]) %> ]) %>
<% end %>
<h3 class="govuk-heading-s">Sales 2023/24</h3> <% c.with_tab(label: "Sales 2023/24") do %>
<%= render DocumentListComponent.new(items: [ <%= render DocumentListComponent.new(items: [
{ {
name: "Sales log for buyers (2023/24)", name: "Download the sales log for buyers (2023 to 2024)",
href: download_23_24_sales_form_path, href: download_23_24_sales_form_path,
metadata: file_type_size_and_pages("2023_24_sales_paper_form.pdf", number_of_pages: 8), metadata: file_type_size_and_pages("2023_24_sales_paper_form.pdf", number_of_pages: 8),
}, },
{ {
name: "Sales bulk upload template (2023/24) – New question ordering", name: "Download the sales bulk upload template (2023 to 2024) – New question ordering",
href: download_23_24_sales_bulk_upload_template_path, href: download_23_24_sales_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-template-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-sales-template-2023-24.xlsx"),
}, },
{ {
name: "Sales bulk upload template (2023/24)", name: "Download the sales bulk upload template (2023 to 2024) – Legacy version",
href: download_23_24_sales_bulk_upload_legacy_template_path, href: download_23_24_sales_bulk_upload_legacy_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-legacy-template-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-sales-legacy-template-2023-24.xlsx"),
}, },
{ {
name: "Sales bulk upload specification (2023/24)", name: "Download the sales bulk upload specification (2023 to 2024)",
href: download_23_24_sales_bulk_upload_specification_path, href: download_23_24_sales_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-sales-specification-2023-24.xlsx"), metadata: file_type_size_and_pages("bulk-upload-sales-specification-2023-24.xlsx"),
}, },
]) %> ]) %>
<% end %> <% end %>
<% if FormHandler.instance.lettings_form_for_start_year(2022) && FormHandler.instance.lettings_form_for_start_year(2022).edit_end_date > Time.zone.today %>
<h3 class="govuk-heading-s">Lettings 2022/23</h3>
<%= render DocumentListComponent.new(items: [
{
name: "Lettings log for tenants (2022/23)",
href: download_22_23_lettings_form_path,
metadata: file_type_size_and_pages("2022_23_lettings_paper_form.pdf", number_of_pages: 4),
},
{
name: "Lettings bulk upload template (2022/23)",
href: download_22_23_lettings_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-template-2022-23.xlsx"),
},
{
name: "Lettings bulk upload specification (2022/23)",
href: download_22_23_lettings_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-lettings-specification-2022-23.xlsx"),
},
]) %>
<h3 class="govuk-heading-s">Sales 2022/23</h3>
<%= render DocumentListComponent.new(items: [
{
name: "Sales log for buyers (2022/23)",
href: download_22_23_sales_form_path,
metadata: file_type_size_and_pages("2022_23_sales_paper_form.pdf", number_of_pages: 5),
},
{
name: "Sales bulk upload template (2022/23)",
href: download_22_23_sales_bulk_upload_template_path,
metadata: file_type_size_and_pages("bulk-upload-sales-template-2022-23.xlsx"),
},
{
name: "Sales bulk upload specification (2022/23)",
href: download_22_23_sales_bulk_upload_specification_path,
metadata: file_type_size_and_pages("bulk-upload-sales-template-2022-23.xlsx"),
},
]) %>
<% end %> <% end %>
</div> <% end %>

2
app/views/logs/index.html.erb

@ -48,7 +48,7 @@
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<p class="govuk-body-l"> <p class="govuk-body-l">
You have uploaded <%= pluralize(@bulk_upload.logs.count, "log") %>. There are errors in <%= pluralize(@bulk_upload.logs_with_errors_count, "log") %>, and <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> in total. Select the log to fix the errors. You have uploaded <%= pluralize(@bulk_upload.logs.count, "log") %>. There are errors in <%= pluralize(@bulk_upload.remaining_logs_with_errors_count, "log") %>, and <%= pluralize(@bulk_upload.remaining_errors_count, "error") %> in total. Select the log to fix the errors.
</p> </p>
<p class="govuk-body"> <p class="govuk-body">

4
app/views/organisations/show.html.erb

@ -41,8 +41,4 @@
<% end %> <% end %>
<%= render partial: "organisations/merged_organisation_details" %> <%= render partial: "organisations/merged_organisation_details" %>
</div> </div>
<div class="govuk-grid-column-one-third-from-desktop">
<%= render partial: "layouts/collection_resources" %>
</div>
</div> </div>

68
app/views/start/guidance.html.erb

@ -0,0 +1,68 @@
<h1 class="govuk-heading-l govuk-!-width-two-thirds">
Guidance for submitting social housing lettings and sales data
</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-body">This page includes details of when a CORE log is and is not required, what to do if a tenant or buyer is reluctant to answer questions in a log, and other information about submitting logs using CORE.</p>
<%= govuk_accordion do |accordion| %>
<%= accordion.with_section(heading_text: "How to create logs", expanded: true) do %>
<p class="govuk-body">There are 2 ways to create logs on CORE.</p>
<p class="govuk-body">You can create logs one at a time by answering questions using the online form. Click the “Create a new log” button on the logs page to create logs this way.</p>
<p class="govuk-body">You can also create many logs at once by uploading a CSV file. This might be faster than creating logs individually if your organisation has its own database and a way to export the data. Click the “Upload logs in bulk” button on the logs page to create logs this way. For more information, <%= govuk_link_to "read the full guidance on bulk upload", bulk_upload_lettings_log_path(id: "guidance", form: { year: current_collection_start_year }) %>.</p>
<p class="govuk-body">Once you have created and completed a log, there is nothing more you need to do to submit the data.</p>
<% end %>
<%= accordion.with_section(heading_text: "What scenarios require a new log?") do %>
<p class="govuk-body">For general needs, you should complete a log for each new tenancy intended to last 2 years or more if it is social rent or affordable rent, or of any length if it is intermediate rent.</p>
<p class="govuk-body">For supported housing, you should complete a log for each new letting of any length.</p>
<p class="govuk-body">If a new tenancy agreement is signed, create a new log.</p>
<% end %>
<%= accordion.with_section(heading_text: "Types of lettings you should create logs for") do %>
<p class="govuk-body">You’ll need to create a log for:</p>
<ul class="govuk-list govuk-list--bullet">
<li>Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.</li>
<li>Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.</li>
<li>Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.</li>
<li>Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.</li>
<li>Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.</li>
<li>Tenants under the Rough Sleepers Initiative or Rough Sleeping Accommodation Programme, where accommodation is permanent.</li>
<li>Households previously provided with temporary accommodation to meet a duty under the homelessness legislation who are allocated a tenancy as a settled home ending the duty (this may be the same property).</li>
<li>Refugees and asylum seekers who have been granted indefinite leave to remain, humanitarian protection or exceptional leave to remain.</li>
<li>Affordable Rent lettings – where up to 80% of market rent can be charged and a new supply agreement is signed.</li>
<li>London Affordable Rent lettings – a type of Affordable Rent available in London through the Greater London Authority (GLA).</li>
<li>Intermediate Rent lettings – where the rent must not exceed 80% of the current market rate (including any service charges).</li>
<li>Rent to Buy lettings – where a discount of up to 20% market rent is charged for a single rental period for a minimum of 5 years. After that period, the tenant is offered the chance to purchase the property (either shared ownership or outright) at full market value.</li>
<li>London Living Rent lettings – a type of Intermediate Rent available in London through the Greater London Authority (GLA).</li>
</ul>
<% end %>
<%= accordion.with_section(heading_text: "Types of lettings you should not create logs for") do %>
<p class="govuk-body">You don’t need to create a log for:</p>
<ul class="govuk-list govuk-list--bullet">
<li>Temporary general needs housing with a fixed period of less than 2 years if they are social or affordable rent. (Temporary lettings for intermediate rent and supported housing should be recorded).</li>
<li>Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.</li>
<li>Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.</li>
<li>Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.</li>
<li>Lettings where no new tenancy agreement is signed.</li>
<li>Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.</li>
<li>Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.</li>
<li>Successions and assignments.</li>
<li>Demotion of a secure or assured tenancy, and any subsequent conversion of the demoted tenancy to a secure or assured tenancy.</li>
<li>Lettings made to asylum seekers who are awaiting a decision on their applications for asylum under the Immigration and Asylum Act 1999.</li>
<li>Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.</li>
</ul>
<% end %>
<%= accordion.with_section(heading_text: "What if someone is reluctant to answer any questions?") do %>
<p class="govuk-body">If a tenant or buyer is reluctant to answer questions as part of a log, you should explain that:</p>
<ul class="govuk-list govuk-list--bullet">
<li>all information they provide is anonymous and will not affect their housing, benefits or other services they receive.</li>
<li>the data they provide is vital in helping to build a complete picture of social housing in England and is used to inform social housing policy.</li>
</ul>
<p class="govuk-body">If a tenant or buyer is still unwilling or unable to answer questions, select the ‘Don’t know’ or ‘Tenant/person prefers not to say’ options.</p>
<% end %>
<% end %>
</div>
</div>

8
app/views/start/index.html.erb

@ -16,13 +16,19 @@
href: start_path, href: start_path,
) %> ) %>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<h2 class="govuk-heading-m">Before you start</h2> <h2 class="govuk-heading-m">Before you start</h2>
<p class="govuk-body">Use your account details to sign in.</p> <p class="govuk-body">Use your account details to sign in.</p>
<p class="govuk-body">If you need to set up a new account, speak to your organisation’s CORE data coordinator. If you don’t know who that is, <%= govuk_link_to("contact the helpdesk", GlobalConstants::HELPDESK_URL) %>.</p> <p class="govuk-body">If you need to set up a new account, speak to your organisation’s CORE data coordinator. If you don’t know who that is, <%= govuk_link_to("contact the helpdesk", GlobalConstants::HELPDESK_URL) %>.</p>
<p class="govuk-body">You can <%= govuk_mail_to("dluhc.digital-services@levellingup.gov.uk", "request an account", subject: "CORE: Request a new account") %> if your organisation doesn’t have one.</p> <p class="govuk-body">You can <%= govuk_mail_to("dluhc.digital-services@levellingup.gov.uk", "request an account", subject: "CORE: Request a new account") %> if your organisation doesn’t have one.</p>
<p class="govuk-body"><strong><%= govuk_link_to guidance_path do %>Guidance for submitting social housing lettings and sales data (CORE)<% end %></strong><p>
</div>
</div> </div>
<div class="govuk-grid-column-one-third-from-desktop"> <hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= render partial: "layouts/collection_resources" %> <%= render partial: "layouts/collection_resources" %>
</div> </div>
</div> </div>

1
config/routes.rb

@ -30,6 +30,7 @@ Rails.application.routes.draw do
resource :cookies, only: %i[show update] resource :cookies, only: %i[show update]
root to: "start#index" root to: "start#index"
get "/guidance", to: "start#guidance"
get "/logs", to: redirect("lettings-logs") get "/logs", to: redirect("lettings-logs")
get "/accessibility-statement", to: "content#accessibility_statement" get "/accessibility-statement", to: "content#accessibility_statement"

30
spec/helpers/collection_time_helper_spec.rb

@ -109,4 +109,34 @@ RSpec.describe CollectionTimeHelper do
end end
end end
end end
describe "#quarter_for_date" do
it "returns correct cutoff date for curent quarter" do
quarter = quarter_for_date(date: Time.zone.local(2023, 10, 1))
expect(quarter.cutoff_date).to eq(Time.zone.local(2024, 1, 12))
expect(quarter.quarter_start_date).to eq(Time.zone.local(2023, 10, 1))
expect(quarter.quarter_end_date).to eq(Time.zone.local(2023, 12, 31))
end
it "returns correct cutoff date for the first quarter of 2024/25" do
quarter = quarter_for_date(date: Time.zone.local(2024, 4, 1))
expect(quarter.cutoff_date).to eq(Time.zone.local(2024, 7, 12))
expect(quarter.quarter_start_date).to eq(Time.zone.local(2024, 4, 1))
expect(quarter.quarter_end_date).to eq(Time.zone.local(2024, 6, 30))
end
it "returns correct cutoff date for the second quarter of 2024/25" do
quarter = quarter_for_date(date: Time.zone.local(2024, 9, 30))
expect(quarter.cutoff_date).to eq(Time.zone.local(2024, 10, 11))
expect(quarter.quarter_start_date).to eq(Time.zone.local(2024, 7, 1))
expect(quarter.quarter_end_date).to eq(Time.zone.local(2024, 9, 30))
end
it "returns correct cutoff date for the third quarter of 2024/25" do
quarter = quarter_for_date(date: Time.zone.local(2024, 10, 25))
expect(quarter.cutoff_date).to eq(Time.zone.local(2025, 1, 10))
expect(quarter.quarter_start_date).to eq(Time.zone.local(2024, 10, 1))
expect(quarter.quarter_end_date).to eq(Time.zone.local(2024, 12, 31))
end
end
end end

8
spec/models/validations/sales/property_validations_spec.rb

@ -25,12 +25,16 @@ RSpec.describe Validations::Sales::PropertyValidations do
record.postcode_full = "SW1A 1AA" record.postcode_full = "SW1A 1AA"
property_validator.validate_postcodes_match_if_discounted_ownership(record) property_validator.validate_postcodes_match_if_discounted_ownership(record)
expect(record.errors["postcode_full"]).to be_empty expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["ppostcode_full"]).to be_empty
expect(record.errors["ownershipsch"]).to be_empty
end end
it "when postcode_full is not present no error is added" do it "when postcode_full is not present no error is added" do
record.ppostcode_full = "SW1A 1AA" record.ppostcode_full = "SW1A 1AA"
property_validator.validate_postcodes_match_if_discounted_ownership(record) property_validator.validate_postcodes_match_if_discounted_ownership(record)
expect(record.errors["postcode_full"]).to be_empty expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["ppostcode_full"]).to be_empty
expect(record.errors["ownershipsch"]).to be_empty
end end
it "when postcodes match no error is added" do it "when postcodes match no error is added" do
@ -38,6 +42,8 @@ RSpec.describe Validations::Sales::PropertyValidations do
record.ppostcode_full = "SW1A 1AA" record.ppostcode_full = "SW1A 1AA"
property_validator.validate_postcodes_match_if_discounted_ownership(record) property_validator.validate_postcodes_match_if_discounted_ownership(record)
expect(record.errors["postcode_full"]).to be_empty expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["ppostcode_full"]).to be_empty
expect(record.errors["ownershipsch"]).to be_empty
end end
it "when postcodes do not match an error is added" do it "when postcodes do not match an error is added" do
@ -45,6 +51,8 @@ RSpec.describe Validations::Sales::PropertyValidations do
record.ppostcode_full = "SW1A 0AA" record.ppostcode_full = "SW1A 0AA"
property_validator.validate_postcodes_match_if_discounted_ownership(record) property_validator.validate_postcodes_match_if_discounted_ownership(record)
expect(record.errors["postcode_full"]).to include(match I18n.t("validations.property.postcode.must_match_previous")) expect(record.errors["postcode_full"]).to include(match I18n.t("validations.property.postcode.must_match_previous"))
expect(record.errors["ppostcode_full"]).to include(match I18n.t("validations.property.postcode.must_match_previous"))
expect(record.errors["ownershipsch"]).to include(match I18n.t("validations.property.postcode.must_match_previous"))
end end
end end
end end

8
spec/requests/lettings_logs_controller_spec.rb

@ -552,7 +552,7 @@ RSpec.describe LettingsLogsController, type: :request do
let(:user) { create(:user, organisation:) } let(:user) { create(:user, organisation:) }
let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
let!(:included_log) { create(:lettings_log, :in_progress, bulk_upload:, owning_organisation: organisation) } let!(:included_log) { create(:lettings_log, :completed, age1: nil, bulk_upload:, owning_organisation: organisation) }
let!(:excluded_log) { create(:lettings_log, :in_progress, owning_organisation: organisation, tenancycode: "fake_code") } let!(:excluded_log) { create(:lettings_log, :in_progress, owning_organisation: organisation, tenancycode: "fake_code") }
before do before do
@ -602,6 +602,12 @@ RSpec.describe LettingsLogsController, type: :request do
expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 1 error in total. Select the log to fix the errors.") expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 1 error in total. Select the log to fix the errors.")
end end
it "displays dynamic error number" do
included_log.update!(age2: nil)
get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}"
expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 2 errors in total. Select the log to fix the errors.")
end
it "displays meta info about the bulk upload" do it "displays meta info about the bulk upload" do
get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}" get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}"
expect(page).to have_content(bulk_upload.filename) expect(page).to have_content(bulk_upload.filename)

36
spec/requests/organisations_controller_spec.rb

@ -310,42 +310,6 @@ RSpec.describe OrganisationsController, type: :request do
it "redirects to details" do it "redirects to details" do
expect(response).to have_http_status(:redirect) expect(response).to have_http_status(:redirect)
end end
context "and 2022 collection window is open" do
let(:set_time) { allow(Time).to receive(:now).and_return(Time.zone.local(2023, 1, 1)) }
it "displays correct resources for 2022/23 and 2023/24 collection years" do
follow_redirect!
expect(page).to have_content("Lettings 2023/24")
expect(page).to have_content("Sales 2023/24")
expect(page).to have_content("Lettings 2022/23")
expect(page).to have_content("Sales 2022/23")
end
end
context "and 2022 collection window is closed for editing" do
let(:set_time) { allow(Time).to receive(:now).and_return(Time.zone.local(2024, 1, 1)) }
it "displays correct resources for 2022/23 and 2023/24 collection years" do
follow_redirect!
expect(page).to have_content("Lettings 2023/24")
expect(page).to have_content("Sales 2023/24")
expect(page).not_to have_content("Lettings 2022/23")
expect(page).not_to have_content("Sales 2022/23")
end
end
context "and 2023 collection window is closed for editing" do
let(:set_time) { allow(Time).to receive(:now).and_return(Time.zone.local(2025, 1, 1)) }
it "displays correct resources for 2022/23 and 2023/24 collection years" do
follow_redirect!
expect(page).not_to have_content("Lettings 2023/24")
expect(page).not_to have_content("Sales 2023/24")
expect(page).not_to have_content("Lettings 2022/23")
expect(page).not_to have_content("Sales 2022/23")
end
end
end end
context "with an organisation that are not in scope for the user, i.e. that they do not belong to" do context "with an organisation that are not in scope for the user, i.e. that they do not belong to" do

8
spec/requests/sales_logs_controller_spec.rb

@ -449,7 +449,7 @@ RSpec.describe SalesLogsController, type: :request do
let(:user) { create(:user, organisation:) } let(:user) { create(:user, organisation:) }
let(:bulk_upload) { create(:bulk_upload, :sales, user:) } let(:bulk_upload) { create(:bulk_upload, :sales, user:) }
let!(:included_log) { create(:sales_log, :in_progress, purchid: "included_id", bulk_upload:, owning_organisation: organisation) } let!(:included_log) { create(:sales_log, :completed, age1: nil, purchid: "included_id", bulk_upload:, owning_organisation: organisation) }
let!(:excluded_log) { create(:sales_log, :in_progress, purchid: "excluded_id", owning_organisation: organisation) } let!(:excluded_log) { create(:sales_log, :in_progress, purchid: "excluded_id", owning_organisation: organisation) }
before do before do
@ -499,6 +499,12 @@ RSpec.describe SalesLogsController, type: :request do
expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 1 error in total. Select the log to fix the errors.") expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 1 error in total. Select the log to fix the errors.")
end end
it "displays dynamic error number" do
included_log.update!(age2: nil)
get "/lettings-logs?bulk_upload_id[]=#{bulk_upload.id}"
expect(page).to have_content("You have uploaded 1 log. There are errors in 1 log, and 2 errors in total. Select the log to fix the errors.")
end
it "displays meta info about the bulk upload" do it "displays meta info about the bulk upload" do
get "/sales-logs?bulk_upload_id[]=#{bulk_upload.id}" get "/sales-logs?bulk_upload_id[]=#{bulk_upload.id}"
expect(page).to have_content(bulk_upload.filename) expect(page).to have_content(bulk_upload.filename)

85
spec/requests/start_controller_spec.rb

@ -0,0 +1,85 @@
require "rails_helper"
RSpec.describe StartController, type: :request do
let(:user) { create(:user) }
let(:headers) { { "Accept" => "text/html" } }
let(:page) { Capybara::Node::Simple.new(response.body) }
let(:notify_client) { instance_double(Notifications::Client) }
let(:devise_notify_mailer) { DeviseNotifyMailer.new }
before do
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer)
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true)
end
describe "GET" do
context "when the user is not signed in" do
it "routes user to the start page" do
get "/", headers: headers, params: {}
expect(path).to eq("/")
expect(page).to have_content("Start now")
end
end
context "when the user is signed in" do
before do
sign_in user
end
it "routes user to the home page" do
get "/", headers:, params: {}
expect(page).to have_content("Welcome back")
end
context "and 2023 collection window is open for editing" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 1, 1))
end
it "displays correct resources for 2022/23 and 2023/24 collection years" do
get "/", headers: headers, params: {}
expect(page).to have_content("Lettings 2023/24")
expect(page).to have_content("Sales 2023/24")
end
end
context "and 2023 collection window is closed for editing" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2025, 1, 1))
end
it "displays correct resources for 2022/23 and 2023/24 collection years" do
get "/", headers: headers, params: {}
expect(page).not_to have_content("Lettings 2023/24")
expect(page).not_to have_content("Sales 2023/24")
end
end
it "shows guidance link" do
get "/", headers: headers, params: {}
expect(page).to have_content("Guidance for submitting social housing lettings and sales data (CORE)")
end
end
end
describe "guidance page" do
context "when the user is not signed in" do
it "routes user to the guidance page" do
get "/guidance", headers:, params: {}
expect(page).to have_content("Guidance for submitting social housing lettings and sales data")
end
end
context "when the user is signed in" do
before do
sign_in user
end
it "routes user to the guidance page" do
get "/guidance", headers:, params: {}
expect(page).to have_content("Guidance for submitting social housing lettings and sales data")
end
end
end
end

4
spec/requests/users_controller_spec.rb

@ -76,7 +76,7 @@ RSpec.describe UsersController, type: :request do
it "routes user to the home page" do it "routes user to the home page" do
sign_in user sign_in user
get "/", headers:, params: {} get "/", headers:, params: {}
expect(path).to include("/") expect(path).to eq("/")
expect(page).to have_content("Welcome back") expect(page).to have_content("Welcome back")
expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">" expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">"
expect(CGI.unescape_html(response.body)).to include(expected_link) expect(CGI.unescape_html(response.body)).to include(expected_link)
@ -2027,7 +2027,7 @@ RSpec.describe UsersController, type: :request do
it "routes user to the home page" do it "routes user to the home page" do
get "/", headers:, params: {} get "/", headers:, params: {}
expect(path).to include("/") expect(path).to eq("/")
expect(page).to have_content("Welcome back") expect(page).to have_content("Welcome back")
expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">" expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">"
expect(CGI.unescape_html(response.body)).to include(expected_link) expect(CGI.unescape_html(response.body)).to include(expected_link)

35
spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb

@ -1465,6 +1465,29 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
expect(parser.errors.where(:field_3)).not_to be_present expect(parser.errors.where(:field_3)).not_to be_present
end end
end end
context "when user's org has absorbed owning organisation before the startdate" do
let(:merged_org) { create(:organisation, :with_old_visible_id, holds_own_stock: true) }
let(:attributes) { setup_section_params.merge({ field_1: merged_org.old_visible_id, field_2: merged_org.old_visible_id, field_3: user.email }) }
before do
merged_org.update!(absorbing_organisation: user.organisation, merge_date: Time.zone.today - 5.years)
merged_org.reload
user.organisation.reload
end
it "is not permitted" do
parser = described_class.new(attributes)
parser.valid?
expect(parser.errors[:field_1]).to include(/The owning organisation must be active on the tenancy start date/)
expect(parser.errors[:field_2]).to include(/The managing organisation must be active on the tenancy start date/)
expect(parser.errors[:field_7]).to include(/Enter a date when the owning and managing organisation was active/)
expect(parser.errors[:field_8]).to include(/Enter a date when the owning and managing organisation was active/)
expect(parser.errors[:field_9]).to include(/Enter a date when the owning and managing organisation was active/)
end
end
end end
describe "#field_2" do # managing org describe "#field_2" do # managing org
@ -2235,12 +2258,22 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
end end
describe "#household_charge" do describe "#household_charge" do
let(:attributes) { { bulk_upload:, field_125: "1" } } context "when log is general needs" do
let(:attributes) { { bulk_upload:, field_4: 1, field_125: "1" } }
it "sets correct value from mapping" do
expect(parser.log.household_charge).to eq(nil)
end
end
context "when log is supported housing" do
let(:attributes) { { bulk_upload:, field_4: 2, field_125: "1" } }
it "sets correct value from mapping" do it "sets correct value from mapping" do
expect(parser.log.household_charge).to eq(1) expect(parser.log.household_charge).to eq(1)
end end
end end
end
describe "#chcharge" do describe "#chcharge" do
let(:attributes) { { bulk_upload:, field_127: "123.45" } } let(:attributes) { { bulk_upload:, field_127: "123.45" } }

23
spec/services/bulk_upload/sales/year2023/row_parser_spec.rb

@ -420,6 +420,29 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
expect(parser.errors.where(:field_2)).not_to be_present expect(parser.errors.where(:field_2)).not_to be_present
end end
end end
context "when user's org has absorbed owning organisation before the startdate" do
let(:merged_org) { create(:organisation, :with_old_visible_id, holds_own_stock: true) }
let(:attributes) { setup_section_params.merge({ field_1: merged_org.old_visible_id, field_2: user.email }) }
before do
merged_org.update!(absorbing_organisation: user.organisation, merge_date: Time.zone.today - 3.years)
merged_org.reload
user.organisation.reload
user.reload
end
it "is not permitted" do
parser = described_class.new(attributes)
parser.valid?
expect(parser.errors[:field_1]).to include(/The owning organisation must be active on the sale completion date/)
expect(parser.errors[:field_3]).to include(/Enter a date when the owning organisation was active/)
expect(parser.errors[:field_4]).to include(/Enter a date when the owning organisation was active/)
expect(parser.errors[:field_5]).to include(/Enter a date when the owning organisation was active/)
end
end
end end
describe "#field_2" do # username for created_by describe "#field_2" do # username for created_by

Loading…
Cancel
Save