diff --git a/Gemfile.lock b/Gemfile.lock index c8d1edd1a..7fdef3710 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -379,7 +379,7 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.2) + rexml (3.3.3) strscan roo (2.10.1) nokogiri (~> 1) diff --git a/app/components/bulk_upload_error_row_component.html.erb b/app/components/bulk_upload_error_row_component.html.erb index 907c73298..4e9ff1390 100644 --- a/app/components/bulk_upload_error_row_component.html.erb +++ b/app/components/bulk_upload_error_row_component.html.erb @@ -1,13 +1,16 @@
<% if lettings? %> -

Row <%= row %> <%= tenant_code_html %> <%= property_ref_html %>

+

Row <%= row %> <%= tenant_code_html %> <%= property_ref_html %>

<% else %> -

Row <%= row %> <%= purchaser_code_html %>

+

Row <%= row %> <%= purchaser_code_html %>

<% end %>
+ <% potential_errors, critical_errors = bulk_upload_errors.partition { |error| error.category == "soft_validation" } %> +

Critical errors

+

These errors must be fixed to complete your logs.

<%= govuk_table do |table| %> <%= table.with_head do |head| %> <% head.with_row do |row| %> @@ -18,16 +21,49 @@ <% end %> <%= table.with_body do |body| %> - <% bulk_upload_errors.each do |error| %> + <% critical_errors.each do |error| %> <% body.with_row do |row| %> - <% row.with_cell(header: true, text: error.cell) %> - <% row.with_cell(text: question_for_field(error.field)) %> - <% row.with_cell(text: error.error) %> - <% row.with_cell(text: error.field.humanize) %> + <% row.with_cell(text: error.cell) %> + <% row.with_cell(text: question_for_field(error.field)) %> + <% row.with_cell(text: error.error, html_attributes: { class: "govuk-!-font-weight-bold" }) %> + <% row.with_cell(text: error.field.humanize) %> <% end %> <% end %> <% end %> <% end %> <% end %> + + <% if potential_errors.any? %> +

Potential errors

+

The following groups of cells might have conflicting data. Check the answers and fix any incorrect data.

If the answers are correct, fix the critical errors and reupload the file. You'll need to confirm that the following data is correct when the file only contains potential errors.

+ <%= govuk_table do |table| %> + <%= table.with_head do |head| %> + <% head.with_row do |row| %> + <% row.with_cell(header: true, text: "Cell") %> + <% row.with_cell(header: true, text: "Question") %> + <% row.with_cell(header: true, text: "Potential error") %> + <% row.with_cell(header: true, text: "Specification") %> + <% end %> + <% end %> + + <%= table.with_body do |body| %> + <% potential_errors.group_by(&:error).each do |error_message, errors| %> + <% errors.each_with_index do |error, index| %> + <% row_class = "grouped-rows" %> + <% row_class += " first-row" if index.zero? %> + <% row_class += " last-row" if index == errors.size - 1 %> + <% body.with_row(html_attributes: { class: row_class }) do |row| %> + <% row.with_cell(text: error.cell) %> + <% row.with_cell(text: question_for_field(error.field)) %> + <% if index == 0 %> + <% row.with_cell(text: error_message, rowspan: errors.size, html_attributes: { class: "govuk-!-font-weight-bold grouped-multirow-cell" }) %> + <% end %> + <% row.with_cell(text: error.field.humanize) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %>
diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index dfe50fa22..9d3e63b33 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -4,8 +4,8 @@ class OrganisationsController < ApplicationController include DuplicateLogsHelper before_action :authenticate_user! - before_action :find_resource, except: %i[index new create] - before_action :authenticate_scope!, except: [:index] + before_action :find_resource, except: %i[index new create search] + before_action :authenticate_scope!, except: %i[index search] before_action :session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv] before_action :session_filters, only: %i[users schemes email_schemes_csv download_schemes_csv] before_action -> { filter_manager.serialize_filters_to_session }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv] @@ -280,6 +280,17 @@ class OrganisationsController < ApplicationController render "schemes/changes" end + def search + org_options = current_user.support? ? Organisation.all : Organisation.affiliated_organisations(current_user.organisation) + organisations = org_options.search_by(params["query"]).limit(20) + + org_data = organisations.each_with_object({}) do |org, hash| + hash[org.id] = { value: org.name } + end + + render json: org_data.to_json + end + private def filter_type diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3fe3b1813..2f7bb2bd6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -32,6 +32,17 @@ class UsersController < ApplicationController end end + def search + user_options = current_user.support? ? User.all : User.own_and_managing_org_users(current_user.organisation) + users = user_options.search_by(params["query"]).limit(20) + + user_data = users.each_with_object({}) do |user, hash| + hash[user.id] = { value: user.name, hint: user.email } + end + + render json: user_data.to_json + end + def resend_invite @user.send_confirmation_instructions flash[:notice] = "Invitation sent to #{@user.email}" diff --git a/app/frontend/controllers/index.js b/app/frontend/controllers/index.js index ece539d15..ef29b99ca 100644 --- a/app/frontend/controllers/index.js +++ b/app/frontend/controllers/index.js @@ -13,6 +13,8 @@ import GovukfrontendController from './govukfrontend_controller.js' import NumericQuestionController from './numeric_question_controller.js' +import SearchController from './search_controller.js' + import FilterLayoutController from './filter_layout_controller.js' application.register('accessible-autocomplete', AccessibleAutocompleteController) application.register('conditional-filter', ConditionalFilterController) @@ -20,3 +22,4 @@ application.register('conditional-question', ConditionalQuestionController) application.register('govukfrontend', GovukfrontendController) application.register('numeric-question', NumericQuestionController) application.register('filter-layout', FilterLayoutController) +application.register('search', SearchController) diff --git a/app/frontend/controllers/search_controller.js b/app/frontend/controllers/search_controller.js new file mode 100644 index 000000000..565b9d0d1 --- /dev/null +++ b/app/frontend/controllers/search_controller.js @@ -0,0 +1,46 @@ +import { Controller } from '@hotwired/stimulus' +import accessibleAutocomplete from 'accessible-autocomplete' +import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' +import { searchSuggestion, fetchAndPopulateSearchResults, confirmSelectedOption, searchableName } from '../modules/search' + +const options = [] +const populateOptions = (results, selectEl) => { + selectEl.innerHTML = '' + + Object.keys(results).forEach((key) => { + const option = document.createElement('option') + option.value = key + option.innerHTML = results[key].value + if (results[key].hint) { option.setAttribute('data-hint', results[key].hint) } + option.setAttribute('text', searchableName(results[key])) + selectEl.appendChild(option) + options.push(option) + }) +} + +export default class extends Controller { + connect () { + const selectEl = this.element + const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name) + const rawFieldName = matches ? `${matches[1]}[${matches[2]}_raw]` : '' + const searchUrl = JSON.parse(this.element.dataset.info).search_url + + document.querySelectorAll('.non-js-text-search-input-field').forEach((el) => { + el.style.display = 'none' + }) + + accessibleAutocomplete.enhanceSelectElement({ + defaultValue: '', + selectElement: selectEl, + minLength: 1, + source: (query, populateResults) => { + fetchAndPopulateSearchResults(query, populateResults, searchUrl, populateOptions, selectEl) + }, + autoselect: true, + placeholder: 'Start typing to search', + templates: { suggestion: (value) => searchSuggestion(value, options) }, + name: rawFieldName, + onConfirm: (val) => confirmSelectedOption(selectEl, val) + }) + } +} diff --git a/app/frontend/modules/search.js b/app/frontend/modules/search.js index 71944746e..efdf7b9d0 100644 --- a/app/frontend/modules/search.js +++ b/app/frontend/modules/search.js @@ -117,6 +117,22 @@ export const suggestion = (value, options) => { } } +export const searchSuggestion = (value, options) => { + try { + const option = options.find((o) => o.getAttribute('text') === value) + if (option) { + const result = enhanceOption(option) + const html = result.append ? `${result.text} ${result.append}` : `${result.text}` + return result.hint ? `${html}
${result.hint}
` : html + } else { + return 'No results found' + } + } catch (error) { + console.error('Error fetching user option:', error) + return value + } +} + export const enhanceOption = (option) => { return { text: option.text, @@ -128,6 +144,39 @@ export const enhanceOption = (option) => { } } +export const fetchAndPopulateSearchResults = async (query, populateResults, relativeUrlRoute, populateOptions, selectEl) => { + if (/\S/.test(query)) { + const results = await fetchUserOptions(query, relativeUrlRoute) + populateOptions(results, selectEl) + populateResults(Object.values(results).map((o) => searchableName(o))) + } +} + +export const fetchUserOptions = async (query, searchUrl) => { + try { + const response = await fetch(`${searchUrl}?query=${encodeURIComponent(query)}`) + const results = await response.json() + return results + } catch (error) { + console.error('Error fetching user options:', error) + return [] + } +} + export const getSearchableName = (option) => { return option.getAttribute('data-hint') ? option.text + ' ' + option.getAttribute('data-hint') : option.text } + +export const searchableName = (option) => { + return option.hint ? option.value + ' ' + option.hint : option.value +} + +export const confirmSelectedOption = (selectEl, val) => { + const arrayOfOptions = Array.from(selectEl.options).filter(function (option, index, arr) { return option.value !== '' }) + + const selectedOption = [].filter.call( + arrayOfOptions, + (option) => option.getAttribute('text') === val + )[0] + if (selectedOption) selectedOption.selected = true +} diff --git a/app/frontend/styles/application.scss b/app/frontend/styles/application.scss index a81214894..62d4e932e 100644 --- a/app/frontend/styles/application.scss +++ b/app/frontend/styles/application.scss @@ -81,3 +81,17 @@ $govuk-breakpoints: ( .govuk-footer { border-top: govuk-spacing(2) solid $govuk-brand-colour; } + +.grouped-rows td { + border-top: none; + border-bottom: none; +} + +.grouped-rows.first-row td { + border-top: 1px solid #b1b4b6; +} + +.grouped-rows.last-row td, +.grouped-rows .grouped-multirow-cell { + border-bottom: 1px solid #b1b4b6; +} diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index 38c15b82b..5f8488bc9 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -11,8 +11,8 @@ module FiltersHelper return true if !selected_filters.key?("owning_organisation") && filter == "owning_organisation_select" && value == :all return true if !selected_filters.key?("managing_organisation") && filter == "managing_organisation_select" && value == :all - return true if selected_filters["owning_organisation"].present? && filter == "owning_organisation_select" && value == :specific_org - return true if selected_filters["managing_organisation"].present? && filter == "managing_organisation_select" && value == :specific_org + return true if (selected_filters["owning_organisation"].present? || selected_filters["owning_organisation_text_search"].present?) && filter == "owning_organisation_select" && value == :specific_org + return true if (selected_filters["managing_organisation"].present? || selected_filters["managing_organisation_text_search"].present?) && filter == "managing_organisation_select" && value == :specific_org return false if selected_filters[filter].blank? @@ -84,16 +84,54 @@ module FiltersHelper JSON.parse(session[session_name_for(filter_type)])[filter] || "" end - def owning_organisation_filter_options(user) + def all_owning_organisation_filter_options(user) organisation_options = user.support? ? Organisation.all : ([user.organisation] + user.organisation.stock_owners + user.organisation.absorbed_organisations).uniq [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) } end - def assigned_to_filter_options(user) + def owning_organisation_filter_options(user, filter_type) + if applied_filters(filter_type)["owning_organisation"].present? + organisation_id = applied_filters(filter_type)["owning_organisation"] + + org = if user.support? + Organisation.where(id: organisation_id)&.first + else + Organisation.affiliated_organisations(user.organisation).where(id: organisation_id)&.first + end + return [OpenStruct.new(id: org.id, name: org.name)] if org.present? + end + + [OpenStruct.new(id: "", name: "Select an option")] + end + + def assigned_to_csv_filter_options(user) user_options = user.support? ? User.all : (user.organisation.users + user.organisation.managing_agents.flat_map(&:users) + user.organisation.stock_owners.flat_map(&:users)).uniq [OpenStruct.new(id: "", name: "Select an option", hint: "")] + user_options.map { |user_option| OpenStruct.new(id: user_option.id, name: user_option.name, hint: user_option.email) } end + def assigned_to_filter_options(filter_type) + if applied_filters(filter_type)["assigned_to"] == "specific_user" && applied_filters(filter_type)["user"].present? + user_id = applied_filters(filter_type)["user"] + selected_user = if current_user.support? + User.where(id: user_id)&.first + else + User.own_and_managing_org_users(current_user.organisation).where(id: user_id)&.first + end + + return [OpenStruct.new(id: selected_user.id, name: selected_user.name, hint: selected_user.email)] if selected_user.present? + end + [OpenStruct.new(id: "", name: "Select an option", hint: "")] + end + + def filter_search_url(category) + case category + when :user + search_users_path + when :owning_organisation, :managing_organisation + search_organisations_path + end + end + def collection_year_options years = { current_collection_start_year.to_s => year_combo(current_collection_start_year), @@ -125,11 +163,26 @@ module FiltersHelper end end - def managing_organisation_filter_options(user) + def managing_organisation_csv_filter_options(user) organisation_options = user.support? ? Organisation.all : ([user.organisation] + user.organisation.managing_agents + user.organisation.absorbed_organisations).uniq [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) } end + def managing_organisation_filter_options(user, filter_type) + if applied_filters(filter_type)["managing_organisation"].present? + organisation_id = applied_filters(filter_type)["managing_organisation"] + + org = if user.support? + Organisation.where(id: organisation_id)&.first + else + Organisation.affiliated_organisations(user.organisation).where(id: organisation_id)&.first + end + return [OpenStruct.new(id: org.id, name: org.name)] if org.present? + end + + [OpenStruct.new(id: "", name: "Select an option")] + end + def show_scheme_managing_org_filter?(user) org = user.organisation @@ -176,8 +229,8 @@ module FiltersHelper { id: "status", label: "Status", value: formatted_status_filter(session_filters) }, filter_type == "lettings_logs" ? { id: "needstype", label: "Needs type", value: formatted_needstype_filter(session_filters) } : nil, { id: "assigned_to", label: "Assigned to", value: formatted_assigned_to_filter(session_filters) }, - { id: "owned_by", label: "Owned by", value: formatted_owned_by_filter(session_filters) }, - { id: "managed_by", label: "Managed by", value: formatted_managed_by_filter(session_filters) }, + { id: "owned_by", label: "Owned by", value: formatted_owned_by_filter(session_filters, filter_type) }, + { id: "managed_by", label: "Managed by", value: formatted_managed_by_filter(session_filters, filter_type) }, ].compact end @@ -221,7 +274,7 @@ private filters.each.sum do |category, category_filters| if %w[years status needstypes bulk_upload_id].include?(category) category_filters.count(&:present?) - elsif %w[user owning_organisation managing_organisation].include?(category) + elsif %w[user owning_organisation managing_organisation user_text_search owning_organisation_text_search managing_organisation_text_search].include?(category) 1 else 0 @@ -256,26 +309,27 @@ private return "All" if session_filters["assigned_to"].include?("all") return "You" if session_filters["assigned_to"].include?("you") - selected_user_option = assigned_to_filter_options(current_user).find { |x| x.id == session_filters["user"].to_i } + User.own_and_managing_org_users(current_user.organisation).find(session_filters["user"].to_i).name + selected_user_option = User.own_and_managing_org_users(current_user.organisation).find(session_filters["user"].to_i) return unless selected_user_option - "#{selected_user_option.name} (#{selected_user_option.hint})" + "#{selected_user_option.name} (#{selected_user_option.email})" end - def formatted_owned_by_filter(session_filters) + def formatted_owned_by_filter(session_filters, filter_type) return "All" if params["id"].blank? && (session_filters["owning_organisation"].blank? || session_filters["owning_organisation"]&.include?("all")) session_org_id = session_filters["owning_organisation"] || params["id"] - selected_owning_organisation_option = owning_organisation_filter_options(current_user).find { |org| org.id == session_org_id.to_i } + selected_owning_organisation_option = owning_organisation_filter_options(current_user, filter_type).find { |org| org.id == session_org_id.to_i } return unless selected_owning_organisation_option selected_owning_organisation_option&.name end - def formatted_managed_by_filter(session_filters) + def formatted_managed_by_filter(session_filters, filter_type) return "All" if session_filters["managing_organisation"].blank? || session_filters["managing_organisation"].include?("all") - selected_managing_organisation_option = managing_organisation_filter_options(current_user).find { |org| org.id == session_filters["managing_organisation"].to_i } + selected_managing_organisation_option = managing_organisation_filter_options(current_user, filter_type).find { |org| org.id == session_filters["managing_organisation"].to_i } return unless selected_managing_organisation_option selected_managing_organisation_option&.name diff --git a/app/models/form/lettings/pages/uprn_selection.rb b/app/models/form/lettings/pages/uprn_selection.rb index 162c608f3..4c7ca4ae1 100644 --- a/app/models/form/lettings/pages/uprn_selection.rb +++ b/app/models/form/lettings/pages/uprn_selection.rb @@ -17,7 +17,7 @@ class Form::Lettings::Pages::UprnSelection < ::Form::Page end def routed_to?(log, _current_user = nil) - (log.uprn_known.nil? || log.uprn_known.zero?) && log.address_line1_input.present? && log.postcode_full_input.present? && (1..10).cover?(log.address_options&.count) + !log.is_supported_housing? && (log.uprn_known.nil? || log.uprn_known.zero?) && log.address_line1_input.present? && log.postcode_full_input.present? && (1..10).cover?(log.address_options&.count) end def skip_text diff --git a/app/models/form/question.rb b/app/models/form/question.rb index 960af8909..1fbced841 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -208,7 +208,8 @@ class Form::Question end def unanswered_error_message - I18n.t("validations.not_answered", question: error_display_label.downcase) + question_text = error_display_label.presence || "this question" + I18n.t("validations.not_answered", question: question_text.downcase) end def suffix_label(log) diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index eab05c997..10ab612cd 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -132,6 +132,10 @@ class LettingsLog < Log illness_type_10: false) } + scope :filter_by_user_text_search, ->(param, user) { where(assigned_to: user.support? ? User.search_by(param) : User.own_and_managing_org_users(user.organisation).search_by(param)) } + scope :filter_by_owning_organisation_text_search, ->(param, _user) { where(owning_organisation: Organisation.search_by(param)) } + scope :filter_by_managing_organisation_text_search, ->(param, _user) { where(managing_organisation: Organisation.search_by(param)) } + AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze OPTIONAL_FIELDS = %w[tenancycode propcode chcharge].freeze RENT_TYPE_MAPPING_LABELS = { 1 => "Social Rent", 2 => "Affordable Rent", 3 => "Intermediate Rent" }.freeze @@ -581,14 +585,22 @@ class LettingsLog < Log def non_location_setup_questions_completed? form.setup_sections.all? do |section| section.subsections.all? do |subsection| - relevant_qs = subsection.applicable_questions(self).reject { |q| optional_fields.include?(q.id) || %w[scheme_id location_id].include?(q.id) } - relevant_qs.all? do |question| + relevant_qs = subsection.questions.reject { |q| optional_fields.include?(q.id) || %w[scheme_id location_id].include?(q.id) } + relevant_applicable_qs = select_applicable_questions(self, relevant_qs) + relevant_applicable_qs.all? do |question| question.completed?(self) end end end end + # this is the same as the subsection method, but only for given questions + def select_applicable_questions(log, questions) + questions.select do |q| + (q.displayed_to_user?(log) && !q.derived?(log)) || q.is_derived_or_has_inferred_check_answers_value?(log) + end + end + def resolve! update(unresolved: false) end diff --git a/app/models/log.rb b/app/models/log.rb index c095a8276..3a6c1e982 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -53,6 +53,9 @@ class Log < ApplicationRecord scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) } scope :filter_by_owning_organisation, ->(owning_organisation, _user = nil) { where(owning_organisation:) } scope :filter_by_managing_organisation, ->(managing_organisation, _user = nil) { where(managing_organisation:) } + scope :filter_by_user_text_search, ->(param, user) { where(assigned_to: user.support? ? User.search_by(param) : User.own_and_managing_org_users(user.organisation).search_by(param)) } + scope :filter_by_owning_organisation_text_search, ->(param, _user) { where(owning_organisation: Organisation.search_by(param)) } + scope :filter_by_managing_organisation_text_search, ->(param, _user) { where(managing_organisation: Organisation.search_by(param)) } attr_accessor :skip_update_status, :skip_update_uprn_confirmed, :select_best_address_match, :skip_dpo_validation diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 8f77df166..65b35c24e 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -18,6 +18,7 @@ class Organisation < ApplicationRecord belongs_to :absorbing_organisation, class_name: "Organisation", optional: true has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id" scope :visible, -> { where(discarded_at: nil) } + scope :affiliated_organisations, ->(organisation) { where(id: (organisation.child_organisations + [organisation] + organisation.parent_organisations + organisation.absorbed_organisations).map(&:id)) } def affiliated_stock_owners ids = [] diff --git a/app/models/user.rb b/app/models/user.rb index d25faaa53..c79ceb0d9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -77,6 +77,7 @@ class User < ApplicationRecord scope :deactivated, -> { where(active: false) } scope :active_status, -> { where(active: true).where.not(last_sign_in_at: nil) } scope :visible, -> { where(discarded_at: nil) } + scope :own_and_managing_org_users, ->(organisation) { where(organisation: organisation.child_organisations + [organisation]) } def lettings_logs if support? @@ -209,9 +210,9 @@ class User < ApplicationRecord def logs_filters(specific_org: false) if (support? && !specific_org) || organisation.has_managing_agents? || organisation.has_stock_owners? - %w[years status needstypes assigned_to user managing_organisation owning_organisation bulk_upload_id] + %w[years status needstypes assigned_to user managing_organisation owning_organisation bulk_upload_id user_text_search owning_organisation_text_search managing_organisation_text_search] else - %w[years status needstypes assigned_to user bulk_upload_id] + %w[years status needstypes assigned_to user bulk_upload_id user_text_search] end end diff --git a/app/models/validations/financial_validations.rb b/app/models/validations/financial_validations.rb index 5e7d92d2c..53e50a92f 100644 --- a/app/models/validations/financial_validations.rb +++ b/app/models/validations/financial_validations.rb @@ -149,7 +149,12 @@ module Validations::FinancialValidations unless record.managing_organisation.rent_periods.include? record.period record.errors.add :period, :wrong_rent_period, message: I18n.t( - "validations.financial.rent_period.invalid_for_org", + "validations.financial.rent_period.invalid_for_org.period", + org_name: record.managing_organisation.name, + rent_period: record.form.get_question("period", record).label_from_value(record.period).downcase, + ) + record.errors.add :managing_organisation_id, :skip_bu_error, message: I18n.t( + "validations.financial.rent_period.invalid_for_org.managing_org", org_name: record.managing_organisation.name, rent_period: record.form.get_question("period", record).label_from_value(record.period).downcase, ) diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index 8b336f7b9..913e5d9e5 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -467,6 +467,7 @@ class BulkUpload::Lettings::Year2024::RowParser fields.each do |field| next if errors.include?(field) + next if error.type == :skip_bu_error question = log.form.get_question(error.attribute, log) @@ -805,16 +806,14 @@ private if setup_question?(question) fields.each do |field| - if errors.select { |e| fields.include?(e.attribute) }.none? - question_text = question.error_display_label.presence || "this question" - errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase), category: :setup) if field.present? + if errors.select { |e| fields.include?(e.attribute) }.none? && field.present? + errors.add(field, question.unanswered_error_message, category: :setup) end end else fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - question_text = question.error_display_label.presence || "this question" - errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase)) + errors.add(field, question.unanswered_error_message) end end end @@ -1131,10 +1130,6 @@ private attributes["lettype"] = nil # should get this from rent_type attributes["tenancycode"] = field_13 - attributes["la"] = field_23 - attributes["la_as_entered"] = field_23 - attributes["postcode_known"] = postcode_known - attributes["postcode_full"] = postcode_full attributes["owning_organisation"] = owning_organisation attributes["managing_organisation"] = managing_organisation attributes["renewal"] = renewal @@ -1305,22 +1300,29 @@ private attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing - attributes["uprn_known"] = field_16.present? ? 1 : 0 - attributes["uprn_confirmed"] = 1 if field_16.present? - attributes["skip_update_uprn_confirmed"] = true - attributes["uprn"] = field_16 - attributes["address_line1"] = field_17 - attributes["address_line1_as_entered"] = field_17 - attributes["address_line2"] = field_18 - attributes["address_line2_as_entered"] = field_18 - attributes["town_or_city"] = field_19 - attributes["town_or_city_as_entered"] = field_19 - attributes["county"] = field_20 - attributes["county_as_entered"] = field_20 - attributes["address_line1_input"] = address_line1_input - attributes["postcode_full_input"] = postcode_full - attributes["postcode_full_as_entered"] = postcode_full - attributes["select_best_address_match"] = true if field_16.blank? && !supported_housing? + if general_needs? + attributes["uprn_known"] = field_16.present? ? 1 : 0 + attributes["uprn_confirmed"] = 1 if field_16.present? + attributes["skip_update_uprn_confirmed"] = true + attributes["uprn"] = field_16 + attributes["address_line1"] = field_17 + attributes["address_line1_as_entered"] = field_17 + attributes["address_line2"] = field_18 + attributes["address_line2_as_entered"] = field_18 + attributes["town_or_city"] = field_19 + attributes["town_or_city_as_entered"] = field_19 + attributes["county"] = field_20 + attributes["county_as_entered"] = field_20 + attributes["postcode_full"] = postcode_full + attributes["postcode_full_as_entered"] = postcode_full + attributes["postcode_known"] = postcode_known + attributes["la"] = field_23 + attributes["la_as_entered"] = field_23 + + attributes["address_line1_input"] = address_line1_input + attributes["postcode_full_input"] = postcode_full + attributes["select_best_address_match"] = true if field_16.blank? + end attributes end diff --git a/app/services/bulk_upload/sales/year2024/row_parser.rb b/app/services/bulk_upload/sales/year2024/row_parser.rb index 68f638e17..d39715f30 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -1364,13 +1364,13 @@ private if setup_question?(question) fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup) + errors.add(field, question.unanswered_error_message, category: :setup) end end else fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase)) + errors.add(field, question.unanswered_error_message) end end end diff --git a/app/services/filter_manager.rb b/app/services/filter_manager.rb index 8d661b3fa..9f68a097c 100644 --- a/app/services/filter_manager.rb +++ b/app/services/filter_manager.rb @@ -24,6 +24,9 @@ class FilterManager next if category == "owning_organisation" && all_orgs next if category == "managing_organisation" && all_orgs next if category == "assigned_to" + next if category == "user_text_search" && filters["assigned_to"] != "specific_user" + next if category == "owning_organisation_text_search" && all_orgs + next if category == "managing_organisation_text_search" && all_orgs logs = logs.public_send("filter_by_#{category}", values, user) end @@ -94,11 +97,19 @@ class FilterManager new_filters[filter] = params[filter] if params[filter].present? end + if params["action"] == "download_csv" + new_filters["assigned_to"] = "all" if new_filters["assigned_to"] == "specific_user" && new_filters["user_text_search"].present? + new_filters["owning_organisation_select"] = "all" if new_filters["owning_organisation_select"] == "specific_organisation" && new_filters["owning_organisation_text_search"].present? + new_filters["managing_organisation_select"] = "all" if new_filters["managing_organisation_select"] == "specific_organisation" && new_filters["managing_organisation_text_search"].present? + end new_filters = new_filters.except("owning_organisation") if params["owning_organisation_select"] == "all" new_filters = new_filters.except("managing_organisation") if params["managing_organisation_select"] == "all" new_filters = new_filters.except("user") if params["assigned_to"] == "all" new_filters["user"] = current_user.id.to_s if params["assigned_to"] == "you" + new_filters = new_filters.except("user_text_search") if params["assigned_to"] == "all" || params["assigned_to"] == "you" + new_filters = new_filters.except("owning_organisation_text_search") if params["owning_organisation_select"] == "all" + new_filters = new_filters.except("managing_organisation_text_search") if params["managing_organisation_select"] == "all" end if (filter_type.include?("schemes") || filter_type.include?("users") || filter_type.include?("scheme_locations")) && params["status"].present? diff --git a/app/views/bulk_upload_lettings_results/summary.html.erb b/app/views/bulk_upload_lettings_results/summary.html.erb index d6a1127af..8c36af632 100644 --- a/app/views/bulk_upload_lettings_results/summary.html.erb +++ b/app/views/bulk_upload_lettings_results/summary.html.erb @@ -4,7 +4,7 @@

Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again

- We could not create logs from your bulk upload. Below is a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadLettings::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. + We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadLettings::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file.

diff --git a/app/views/bulk_upload_sales_results/summary.html.erb b/app/views/bulk_upload_sales_results/summary.html.erb index 8fd6c22ed..af504acbd 100644 --- a/app/views/bulk_upload_sales_results/summary.html.erb +++ b/app/views/bulk_upload_sales_results/summary.html.erb @@ -4,7 +4,7 @@

Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again

- We could not create logs from your bulk upload. Below is a list of everything that you need to fix your spreadsheet. You can download the <%= govuk_link_to "specification", Forms::BulkUploadSales::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file. + We could not create logs from your bulk upload because of the following errors. Download the <%= govuk_link_to "specification", Forms::BulkUploadSales::PrepareYourFile.new(year: @bulk_upload.year).specification_path, target: "_blank" %> to help you fix the cells in your CSV file.

diff --git a/app/views/filters/_radio_filter.html.erb b/app/views/filters/_radio_filter.html.erb index 6eb902dba..e4d23573c 100644 --- a/app/views/filters/_radio_filter.html.erb +++ b/app/views/filters/_radio_filter.html.erb @@ -10,6 +10,7 @@ collection: option[:conditional_filter][:options], category: option[:conditional_filter][:category], label: option[:conditional_filter][:label], + caption_text: option[:conditional_filter][:caption_text], secondary: true, hint_text: option[:conditional_filter][:hint_text], } %> diff --git a/app/views/filters/_text_select_filter.html.erb b/app/views/filters/_text_select_filter.html.erb new file mode 100644 index 000000000..ecc997bba --- /dev/null +++ b/app/views/filters/_text_select_filter.html.erb @@ -0,0 +1,20 @@ + +<%= f.govuk_text_field "#{category}_text_search".to_sym, + label: { text: label, hidden: secondary }, + "data-controller": "search conditional-filter", + caption: { text: caption_text }, + "data-info": { search_url: filter_search_url(category.to_sym) }.to_json, + value: selected_option("#{category}_text_search", @filter_type) %> + +<%= f.govuk_select(category.to_sym, + label: { text: label, hidden: secondary }, + "data-controller": "search conditional-filter", + "hidden": true, + "data-info": { search_url: filter_search_url(category.to_sym) }.to_json) do %> + <% collection.each do |answer| %> + + <% end %> + <% end %> diff --git a/app/views/filters/assigned_to.html.erb b/app/views/filters/assigned_to.html.erb index 9f0582fbb..778d63c8a 100644 --- a/app/views/filters/assigned_to.html.erb +++ b/app/views/filters/assigned_to.html.erb @@ -11,7 +11,8 @@ type: "select", label: "User", category: "user", - options: assigned_to_filter_options(current_user), + caption_text: "User's name or email", + options: assigned_to_csv_filter_options(current_user), }, }, }, diff --git a/app/views/filters/managed_by.html.erb b/app/views/filters/managed_by.html.erb index e3d849c9b..5d4b684f3 100644 --- a/app/views/filters/managed_by.html.erb +++ b/app/views/filters/managed_by.html.erb @@ -9,7 +9,8 @@ type: "select", label: "Managed by", category: "managing_organisation", - options: managing_organisation_filter_options(current_user), + options: managing_organisation_csv_filter_options(current_user), + caption_text: "Organisation name", }, }, }, diff --git a/app/views/filters/owned_by.html.erb b/app/views/filters/owned_by.html.erb index 7acfd459c..271b68de9 100644 --- a/app/views/filters/owned_by.html.erb +++ b/app/views/filters/owned_by.html.erb @@ -9,7 +9,8 @@ type: "select", label: "Owning Organisation", category: "owning_organisation", - options: owning_organisation_filter_options(current_user), + options: all_owning_organisation_filter_options(current_user), + caption_text: "Organisation name", }, }, }, diff --git a/app/views/logs/_log_filters.html.erb b/app/views/logs/_log_filters.html.erb index aaef70377..3beab4b6b 100644 --- a/app/views/logs/_log_filters.html.erb +++ b/app/views/logs/_log_filters.html.erb @@ -66,10 +66,11 @@ "specific_user": { label: "Specific user", conditional_filter: { - type: "select", + type: "text_select", label: "User", category: "user", - options: assigned_to_filter_options(current_user), + options: assigned_to_filter_options(@filter_type), + caption_text: "User's name or email", }, }, }, @@ -86,10 +87,11 @@ "specific_org": { label: "Specific owning organisation", conditional_filter: { - type: "select", + type: "text_select", label: "Owning Organisation", category: "owning_organisation", - options: owning_organisation_filter_options(current_user), + options: owning_organisation_filter_options(current_user, @filter_type), + caption_text: "Organisation name", }, }, }, @@ -107,10 +109,11 @@ "specific_org": { label: "Specific managing organisation", conditional_filter: { - type: "select", + type: "text_select", label: user_or_org_lettings_path? ? "Managed by" : "Reported by", category: "managing_organisation", - options: managing_organisation_filter_options(current_user), + options: managing_organisation_filter_options(current_user, @filter_type), + caption_text: "Organisation name", }, }, }, diff --git a/app/views/schemes/_scheme_filters.html.erb b/app/views/schemes/_scheme_filters.html.erb index ca0538463..51687a096 100644 --- a/app/views/schemes/_scheme_filters.html.erb +++ b/app/views/schemes/_scheme_filters.html.erb @@ -35,7 +35,7 @@ type: "select", label: "Owning Organisation", category: "owning_organisation", - options: owning_organisation_filter_options(current_user), + options: all_owning_organisation_filter_options(current_user), }, }, }, diff --git a/config/locales/en.yml b/config/locales/en.yml index a70e7082b..a53b375fe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -436,7 +436,9 @@ en: under_10: "Enter a total charge that is at least £10.00 per week" less_than_shortfall: "The total charge must be more than the outstanding amount" rent_period: - invalid_for_org: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation" + invalid_for_org: + period: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation" + managing_org: "%{org_name} does not use %{rent_period} as a rent period. Set another rent period on this log, or a data coordinator can add rent periods to this organisation" carehome: out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}" not_provided: "Enter how much rent and other charges the household pays %{period}" @@ -587,13 +589,13 @@ en: declaration: missing: - pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log." - post_2024: "You must show or give access to the MHCLG privacy notice to the tenant before you can submit this log." + pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log" + post_2024: "You must show or give the tenant access to the MHCLG privacy notice before you can submit this log" privacynotice: missing: - pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log." - post_2024: "You must show or give access to the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log." + pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log" + post_2024: "You must show or give the %{buyer_or_buyers} access to the MHCLG privacy notice before you can submit this log" scheme: toggle_date: diff --git a/config/routes.rb b/config/routes.rb index e0d9631e9..faea457fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,6 +125,10 @@ Rails.application.routes.draw do get "edit-dpo", to: "users#dpo" get "edit-key-contact", to: "users#key_contact" + collection do + get :search + end + member do get "deactivate", to: "users#deactivate" get "reactivate", to: "users#reactivate" @@ -191,6 +195,10 @@ Rails.application.routes.draw do get "delete-confirmation", to: "organisations#delete_confirmation" delete "delete", to: "organisations#delete" end + + collection do + get :search + end end resources :merge_requests, path: "/merge-request" do diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index ee982b784..849732bc5 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -226,7 +226,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.3.2) + rexml (3.3.3) strscan rouge (3.26.0) ruby2_keywords (0.0.5) diff --git a/spec/components/bulk_upload_error_row_component_spec.rb b/spec/components/bulk_upload_error_row_component_spec.rb index 01af041cb..b593a0048 100644 --- a/spec/components/bulk_upload_error_row_component_spec.rb +++ b/spec/components/bulk_upload_error_row_component_spec.rb @@ -108,4 +108,53 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do expect(result).to have_content(expected) end end + + context "when there are potential errors" do + let(:row) { rand(9_999) } + let(:tenant_code) { SecureRandom.hex(4) } + let(:property_ref) { SecureRandom.hex(4) } + let(:purchaser_code) { nil } + let(:category) { "soft_validation" } + let(:field_46) { 40 } + let(:field_50) { 5 } + let(:error) { "You told us this person is aged 40 years and retired." } + let(:bulk_upload) { create(:bulk_upload, :lettings) } + let(:bulk_upload_errors) do + [ + FactoryBot.build( + :bulk_upload_error, + bulk_upload:, + row:, + tenant_code:, + property_ref:, + purchaser_code:, + field: :field_46, + error:, + category:, + ), + FactoryBot.build( + :bulk_upload_error, + bulk_upload:, + row:, + tenant_code:, + property_ref:, + purchaser_code:, + field: :field_50, + error:, + category:, + ), + ] + end + + it "renders the potential errors section" do + result = render_inline(described_class.new(bulk_upload_errors:)) + expect(result).to have_content("Potential errors") + end + + it "renders the potential error message" do + expected = error + result = render_inline(described_class.new(bulk_upload_errors:)) + expect(result).to have_content(expected, count: 1) + end + end end diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index 2b977fdd7..ac9a1e4a8 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -89,9 +89,9 @@ RSpec.describe "Lettings Log Features" do check("In progress") choose("You") choose("Specific owning organisation") - select(stock_owner_1.name, from: "owning_organisation") + fill_in("owning-organisation-text-search-field", with: "stock") choose("Specific managing organisation") - select(managing_agent_1.name, from: "managing_organisation") + fill_in("managing-organisation-text-search-field", with: "managing") click_button("Apply filters") end diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb index 65f787c2a..1867285eb 100644 --- a/spec/features/organisation_spec.rb +++ b/spec/features/organisation_spec.rb @@ -199,14 +199,14 @@ RSpec.describe "User Features" do it "can filter lettings logs by year" do check("years-2022-field") click_button("Apply filters") - expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&years[]=2022&status[]=&needstypes[]=&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=&managing_organisation_select=all&managing_organisation=") + expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&years[]=2022&status[]=&needstypes[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=") expect(page).not_to have_link first_log.id.to_s, href: "/lettings-logs/#{first_log.id}" end it "can filter lettings logs by needstype" do check("needstypes-1-field") click_button("Apply filters") - expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&status[]=&needstypes[]=&needstypes[]=1&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=&managing_organisation_select=all&managing_organisation=") + expect(page).to have_current_path("/organisations/#{org_id}/lettings-logs?years[]=&status[]=&needstypes[]=&needstypes[]=1&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=") other_general_needs_logs.each do |general_needs_log| expect(page).to have_link general_needs_log.id.to_s, href: "/lettings-logs/#{general_needs_log.id}" end @@ -245,7 +245,7 @@ RSpec.describe "User Features" do end check("years-2022-field") click_button("Apply filters") - expect(page).to have_current_path("/organisations/#{org_id}/sales-logs?years[]=&years[]=2022&status[]=&assigned_to=all&user=&owning_organisation_select=all&owning_organisation=&managing_organisation_select=all&managing_organisation=") + expect(page).to have_current_path("/organisations/#{org_id}/sales-logs?years[]=&years[]=2022&status[]=&assigned_to=all&user_text_search=&user=&owning_organisation_select=all&owning_organisation_text_search=&owning_organisation=&managing_organisation_select=all&managing_organisation_text_search=&managing_organisation=") expect(page).not_to have_link first_log.id.to_s, href: "/sales-logs/#{first_log.id}" end end diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 169465cb1..c30abe1e9 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -796,12 +796,13 @@ RSpec.describe "User Features" do visit("/lettings-logs") choose("owning-organisation-select-specific-org-field", allow_label_click: true) expect(page).to have_field("owning-organisation-field", with: "") - find("#owning-organisation-field").click.native.send_keys("F", "i", "l", "t", :down, :enter) + find("#owning-organisation-field").click.native.send_keys("F", "i", "l", "t") + select(parent_organisation.name, from: "owning-organisation-field-select", visible: false) click_button("Apply filters") - expect(page).to have_current_path("/lettings-logs?%5Byears%5D%5B%5D=&%5Bstatus%5D%5B%5D=&%5Bneedstypes%5D%5B%5D=&assigned_to=all&owning_organisation_select=specific_org&owning_organisation=#{parent_organisation.id}&managing_organisation_select=all") + expect(page).to have_current_path("/lettings-logs?%5Byears%5D%5B%5D=&%5Bstatus%5D%5B%5D=&%5Bneedstypes%5D%5B%5D=&assigned_to=all&user_text_search=&owning_organisation_select=specific_org&owning_organisation_text_search=&owning_organisation=#{parent_organisation.id}&managing_organisation_select=all&managing_organisation_text_search=") choose("owning-organisation-select-all-field", allow_label_click: true) click_button("Apply filters") - expect(page).to have_current_path("/lettings-logs?%5Byears%5D%5B%5D=&%5Bstatus%5D%5B%5D=&%5Bneedstypes%5D%5B%5D=&assigned_to=all&owning_organisation_select=all&managing_organisation_select=all") + expect(page).to have_current_path("/lettings-logs?%5Byears%5D%5B%5D=&%5Bstatus%5D%5B%5D=&%5Bneedstypes%5D%5B%5D=&assigned_to=all&user_text_search=&owning_organisation_select=all&owning_organisation_text_search=&managing_organisation_select=all&managing_organisation_text_search=") end end end diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb index c141ae70b..7eab2f7b2 100644 --- a/spec/helpers/filters_helper_spec.rb +++ b/spec/helpers/filters_helper_spec.rb @@ -183,19 +183,147 @@ RSpec.describe FiltersHelper do OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), OpenStruct.new(id: 9_999_999, name: "Other organisation"), ]) + + context "when no organisation is selected in the filters" do + it "returns an empty list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a specific child organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": child_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: child_organisation.id, name: "Child organisation"), + ]) + end + end + + context "when a specific parent organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": parent_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), + ]) + end + end + + context "when a specific absorbed organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": absorbed_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), + ]) + end + end + + context "when a specific non related organisation is selected in the filters" do + let(:unrelated_organisation) { create(:organisation, name: "Unrelated organisation") } + + before do + session[:lettings_logs_filters] = { "owning_organisation": unrelated_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: unrelated_organisation.id, name: "Unrelated organisation"), + ]) + end + end + + context "when a non existing organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": 143_542_542 }.to_json + end + + it "returns an empty list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end end end context "with a data coordinator user" do let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: child_organisation) } - it "returns a list of parent orgs and your own organisation" do - expect(owning_organisation_filter_options(user.reload)).to eq([ - OpenStruct.new(id: "", name: "Select an option"), - OpenStruct.new(id: child_organisation.id, name: "Child organisation"), - OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), - OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), - ]) + context "when no organisation is selected in the filters" do + it "returns an empty list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a specific child organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": child_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: child_organisation.id, name: "Child organisation"), + ]) + end + end + + context "when a specific parent organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": parent_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), + ]) + end + end + + context "when a specific absorbed organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": absorbed_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), + ]) + end + end + + context "when a specific non related organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": create(:organisation).id }.to_json + end + + it "returns an empty list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a non existing organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "owning_organisation": 143_542_542 }.to_json + end + + it "returns an empty list" do + expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end end end end @@ -222,19 +350,147 @@ RSpec.describe FiltersHelper do OpenStruct.new(id: child_organisation.id, name: "Child organisation"), OpenStruct.new(id: 9_999_999, name: "Other organisation"), ]) + + context "when no organisation is selected in the filters" do + it "returns an empty list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a specific child organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": child_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: child_organisation.id, name: "Child organisation"), + ]) + end + end + + context "when a specific parent organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": parent_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), + ]) + end + end + + context "when a specific absorbed organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": absorbed_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), + ]) + end + end + + context "when a specific non related organisation is selected in the filters" do + let(:unrelated_organisation) { create(:organisation, name: "Unrelated organisation") } + + before do + session[:lettings_logs_filters] = { "managing_organisation": unrelated_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: unrelated_organisation.id, name: "Unrelated organisation"), + ]) + end + end + + context "when a non existing organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": 143_542_542 }.to_json + end + + it "returns an empty list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end end end context "with a data coordinator user" do let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: parent_organisation) } - it "returns a list of child orgs and your own organisation" do - expect(managing_organisation_filter_options(user.reload)).to eq([ - OpenStruct.new(id: "", name: "Select an option"), - OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), - OpenStruct.new(id: child_organisation.id, name: "Child organisation"), - OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), - ]) + context "when no organisation is selected in the filters" do + it "returns an empty list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a specific child organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": child_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: child_organisation.id, name: "Child organisation"), + ]) + end + end + + context "when a specific parent organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": parent_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), + ]) + end + end + + context "when a specific absorbed organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": absorbed_organisation.id }.to_json + end + + it "returns the selected organisation in the list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), + ]) + end + end + + context "when a specific non related organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": create(:organisation).id }.to_json + end + + it "returns an empty list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end + end + + context "when a non existing organisation is selected in the filters" do + before do + session[:lettings_logs_filters] = { "managing_organisation": 143_542_542 }.to_json + end + + it "returns an empty list" do + expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([ + OpenStruct.new(id: "", name: "Select an option"), + ]) + end end end end diff --git a/spec/models/form/lettings/questions/declaration_spec.rb b/spec/models/form/lettings/questions/declaration_spec.rb index ab9bcebef..c64e7f3c8 100644 --- a/spec/models/form/lettings/questions/declaration_spec.rb +++ b/spec/models/form/lettings/questions/declaration_spec.rb @@ -63,7 +63,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log") end end @@ -87,7 +87,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give access to the MHCLG privacy notice to the tenant before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show or give the tenant access to the MHCLG privacy notice before you can submit this log") end end end diff --git a/spec/models/form/sales/questions/privacy_notice_spec.rb b/spec/models/form/sales/questions/privacy_notice_spec.rb index 4be918c5c..9fb81057a 100644 --- a/spec/models/form/sales/questions/privacy_notice_spec.rb +++ b/spec/models/form/sales/questions/privacy_notice_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log") end end @@ -78,7 +78,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log") end end end @@ -100,7 +100,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give access to the MHCLG privacy notice to the buyer before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show or give the buyer access to the MHCLG privacy notice before you can submit this log") end end @@ -118,7 +118,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give access to the MHCLG privacy notice to the buyers before you can submit this log.") + expect(question.unanswered_error_message).to eq("You must show or give the buyers access to the MHCLG privacy notice before you can submit this log") end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index edb998ac3..6a04e9a0b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -164,7 +164,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year and status" do - expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user bulk_upload_id]) + expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user bulk_upload_id user_text_search]) end end @@ -174,7 +174,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year, status, managing_organisation and owning_organisation" do - expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user managing_organisation owning_organisation bulk_upload_id]) + expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user managing_organisation owning_organisation bulk_upload_id managing_organisation_text_search owning_organisation_text_search user_text_search]) end end end @@ -215,7 +215,7 @@ RSpec.describe User, type: :model do end it "can filter lettings logs by user, year, status, managing_organisation and owning_organisation" do - expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user owning_organisation managing_organisation bulk_upload_id]) + expect(user.logs_filters).to match_array(%w[years status needstypes assigned_to user owning_organisation managing_organisation bulk_upload_id managing_organisation_text_search owning_organisation_text_search user_text_search]) end end diff --git a/spec/models/validations/financial_validations_spec.rb b/spec/models/validations/financial_validations_spec.rb index c743381f7..a6eefa708 100644 --- a/spec/models/validations/financial_validations_spec.rb +++ b/spec/models/validations/financial_validations_spec.rb @@ -165,7 +165,13 @@ RSpec.describe Validations::FinancialValidations do financial_validator.validate_rent_period(record) expect(record.errors["period"]) .to include(match I18n.t( - "validations.financial.rent_period.invalid_for_org", + "validations.financial.rent_period.invalid_for_org.period", + org_name: user.organisation.name, + rent_period: "every 4 weeks", + )) + expect(record.errors["managing_organisation_id"]) + .to include(match I18n.t( + "validations.financial.rent_period.invalid_for_org.managing_org", org_name: user.organisation.name, rent_period: "every 4 weeks", )) diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 13879a38c..d3e8a8155 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -75,6 +75,13 @@ RSpec.describe OrganisationsController, type: :request do end end end + + describe "#search" do + it "redirects to the sign in page" do + get "/organisations/search" + expect(response).to redirect_to("/account/sign-in") + end + end end context "when user is signed in" do @@ -807,6 +814,25 @@ RSpec.describe OrganisationsController, type: :request do end end end + + describe "#search" do + let(:parent_organisation) { create(:organisation, name: "parent test organisation") } + let(:child_organisation) { create(:organisation, name: "child test organisation") } + + before do + user.organisation.update!(name: "test organisation") + create(:organisation_relationship, parent_organisation: user.organisation, child_organisation:) + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation:) + create(:organisation, name: "other organisation test organisation") + end + + it "only searches within the current user's organisation, managing agents and stock owners" do + get "/organisations/search", headers:, params: { query: "test organisation" } + result = JSON.parse(response.body) + expect(result.count).to eq(3) + expect(result.keys).to match_array([user.organisation.id.to_s, parent_organisation.id.to_s, child_organisation.id.to_s]) + end + end end context "with a data provider user" do @@ -2077,6 +2103,25 @@ RSpec.describe OrganisationsController, type: :request do end end end + + describe "#search" do + let(:parent_organisation) { create(:organisation, name: "parent test organisation") } + let(:child_organisation) { create(:organisation, name: "child test organisation") } + let!(:other_organisation) { create(:organisation, name: "other organisation test organisation") } + + before do + user.organisation.update!(name: "test organisation") + create(:organisation_relationship, parent_organisation: user.organisation, child_organisation:) + create(:organisation_relationship, child_organisation: user.organisation, parent_organisation:) + end + + it "searches within all the organisations" do + get "/organisations/search", headers:, params: { query: "test organisation" } + result = JSON.parse(response.body) + expect(result.count).to eq(4) + expect(result.keys).to match_array([user.organisation.id.to_s, parent_organisation.id.to_s, child_organisation.id.to_s, other_organisation.id.to_s]) + end + end end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index bb0a1cca3..8e87f7f28 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -117,6 +117,13 @@ RSpec.describe UsersController, type: :request do expect(response).to redirect_to("/account/sign-in") end end + + describe "#search" do + it "redirects to the sign in page" do + get "/users/search" + expect(response).to redirect_to("/account/sign-in") + end + end end context "when user is signed in as a data provider" do @@ -404,6 +411,25 @@ RSpec.describe UsersController, type: :request do expect(response).to have_http_status(:unauthorized) end end + + describe "#search" do + let(:parent_relationship) { create(:organisation_relationship, parent_organisation: user.organisation) } + let(:child_relationship) { create(:organisation_relationship, child_organisation: user.organisation) } + let!(:org_user) { create(:user, organisation: user.organisation, name: "test_name") } + let!(:managing_user) { create(:user, organisation: parent_relationship.child_organisation, name: "managing_agent_test_name") } + + before do + create(:user, organisation: child_relationship.parent_organisation, name: "stock_owner_test_name") + create(:user, name: "other_organisation_test_name") + end + + it "only searches within the current user's organisation and managing agents" do + get "/users/search", headers:, params: { query: "test_name" } + result = JSON.parse(response.body) + expect(result.count).to eq(2) + expect(result.keys).to match_array([org_user.id.to_s, managing_user.id.to_s]) + end + end end context "when user is signed in as a data coordinator" do @@ -1174,6 +1200,25 @@ RSpec.describe UsersController, type: :request do expect(response).to have_http_status(:unauthorized) end end + + describe "#search" do + let(:parent_relationship) { create(:organisation_relationship, parent_organisation: user.organisation) } + let(:child_relationship) { create(:organisation_relationship, child_organisation: user.organisation) } + let!(:org_user) { create(:user, organisation: user.organisation, email: "test_name@example.com") } + let!(:managing_user) { create(:user, organisation: parent_relationship.child_organisation, email: "managing_agent_test_name@example.com") } + + before do + create(:user, email: "other_organisation_test_name@example.com") + create(:user, organisation: child_relationship.parent_organisation, email: "stock_owner_test_name@example.com") + end + + it "only searches within the current user's organisation and managing agents" do + get "/users/search", headers:, params: { query: "test_name" } + result = JSON.parse(response.body) + expect(result.count).to eq(2) + expect(result.keys).to match_array([org_user.id.to_s, managing_user.id.to_s]) + end + end end context "when user is signed in as a support user" do @@ -2111,6 +2156,22 @@ RSpec.describe UsersController, type: :request do expect(page).not_to have_link("User to be deleted") end end + + describe "#search" do + let(:parent_relationship) { create(:organisation_relationship, parent_organisation: user.organisation) } + let(:child_relationship) { create(:organisation_relationship, child_organisation: user.organisation) } + let!(:org_user) { create(:user, organisation: user.organisation, name: "test_name") } + let!(:managing_user) { create(:user, organisation: child_relationship.parent_organisation, name: "stock_owner_test_name") } + let!(:owner_user) { create(:user, organisation: parent_relationship.child_organisation, name: "managing_agent_test_name") } + let!(:other_user) { create(:user, name: "other_organisation_test_name") } + + it "searches all users" do + get "/users/search", headers:, params: { query: "test_name" } + result = JSON.parse(response.body) + expect(result.count).to eq(4) + expect(result.keys).to match_array([org_user.id.to_s, managing_user.id.to_s, owner_user.id.to_s, other_user.id.to_s]) + end + end end describe "title link" do diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index a8cfd0a79..e38396328 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -686,7 +686,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "cannot be nulled" do parser.valid? - expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log."]) + expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log"]) end end end diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb index fbff000fd..42a05e33c 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -747,7 +747,19 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "cannot be nulled" do parser.valid? - expect(parser.errors[:field_15]).to eq(["You must answer tenant has seen the privacy notice"]) + expect(parser.errors[:field_15]).to eq(["You must show or give the tenant access to the MHCLG privacy notice before you can submit this log"]) + end + end + + context "when there is a :skip_bu_error error" do + let(:managing_org) { create(:organisation, :with_old_visible_id, rent_periods: [4, 1]) } + let(:attributes) { valid_attributes.merge({ field_123: 3, field_128: 80 }) } + + it "does not add that error" do + parser.valid? + + expect(parser.log.errors.map(&:attribute).sort).to eql(%i[managing_organisation_id period]) + expect(parser.errors.map(&:attribute)).to eql(%i[field_123]) end end end @@ -1855,7 +1867,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#uprn" do - let(:attributes) { { bulk_upload:, field_16: "12" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_16: "12" } } it "sets to given value" do expect(parser.log.uprn).to eql("12") @@ -1864,7 +1876,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do describe "#uprn_known" do context "when uprn specified" do - let(:attributes) { { bulk_upload:, field_16: "12" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_16: "12" } } it "sets to 1" do expect(parser.log.uprn_known).to be(1) @@ -1873,7 +1885,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end context "when uprn blank" do - let(:attributes) { { bulk_upload:, field_16: "", field_4: 1 } } + let(:attributes) { { bulk_upload:, field_4: 1, field_16: "" } } it "sets to 0" do expect(parser.log.uprn_known).to be(0) @@ -1882,7 +1894,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#address_line1" do - let(:attributes) { { bulk_upload:, field_17: "123 Sesame Street" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_17: "123 Sesame Street" } } it "sets to given value" do expect(parser.log.address_line1).to eql("123 Sesame Street") @@ -1890,7 +1902,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#address_line2" do - let(:attributes) { { bulk_upload:, field_18: "Cookie Town" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_18: "Cookie Town" } } it "sets to given value" do expect(parser.log.address_line2).to eql("Cookie Town") @@ -1898,7 +1910,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#town_or_city" do - let(:attributes) { { bulk_upload:, field_19: "London" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_19: "London" } } it "sets to given value" do expect(parser.log.town_or_city).to eql("London") @@ -1906,13 +1918,39 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#county" do - let(:attributes) { { bulk_upload:, field_20: "Greater London" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_20: "Greater London" } } it "sets to given value" do expect(parser.log.county).to eql("Greater London") end end + describe "address related fields for supported housing logs" do + context "when address data is provided for a supported housing log" do + let(:attributes) { { bulk_upload:, field_4: 2, field_16: nil, field_17: "Flat 1", field_18: "Example Place", field_19: "London", field_20: "Greater London", field_21: "SW1A", field_22: "1AA" } } + + it "is not set on the log" do + expect(parser.log.uprn).to be_nil + expect(parser.log.uprn_known).to be_nil + expect(parser.log.address_line1).to be_nil + expect(parser.log.address_line1_as_entered).to be_nil + expect(parser.log.address_line2).to be_nil + expect(parser.log.address_line2_as_entered).to be_nil + expect(parser.log.town_or_city).to be_nil + expect(parser.log.town_or_city_as_entered).to be_nil + expect(parser.log.county).to be_nil + expect(parser.log.county_as_entered).to be_nil + expect(parser.log.postcode_full).to be_nil + expect(parser.log.postcode_full_as_entered).to be_nil + expect(parser.log.la).to be_nil + expect(parser.log.la_as_entered).to be_nil + expect(parser.log.address_line1_input).to be_nil + expect(parser.log.postcode_full_input).to be_nil + expect(parser.log.select_best_address_match).to be_nil + end + end + end + [ %w[age1_known details_known_1 age1 field_42 field_47 field_49], %w[age2_known details_known_2 age2 field_48 field_47 field_49], @@ -2599,7 +2637,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#postcode_full" do - let(:attributes) { { bulk_upload:, field_21: " EC1N ", field_22: " 2TD " } } + let(:attributes) { { bulk_upload:, field_4: 1, field_21: " EC1N ", field_22: " 2TD " } } it "strips whitespace" do expect(parser.log.postcode_full).to eql("EC1N 2TD") @@ -2607,7 +2645,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end describe "#la" do - let(:attributes) { { bulk_upload:, field_23: "E07000223" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_23: "E07000223" } } it "sets to given value" do expect(parser.log.la).to eql("E07000223") diff --git a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb index 115995f3e..656ff7a2f 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -1042,12 +1042,21 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do describe "#field_18" do # data protection let(:attributes) { setup_section_params.merge({ field_18: nil }) } + before do + parser.valid? + end + context "when not answered" do it "returns a setup error" do - parser.valid? expect(parser.errors.where(:field_18, category: :setup)).to be_present end end + + context "when the privacy notice is not accepted" do + it "cannot be nulled" do + expect(parser.errors[:field_18]).to eq(["You must show or give the buyer access to the MHCLG privacy notice before you can submit this log"]) + end + end end [ diff --git a/spec/views/bulk_upload_lettings_results/show.html.erb_spec.rb b/spec/views/bulk_upload_lettings_results/show.html.erb_spec.rb index 3dea3ce92..637dfa3a9 100644 --- a/spec/views/bulk_upload_lettings_results/show.html.erb_spec.rb +++ b/spec/views/bulk_upload_lettings_results/show.html.erb_spec.rb @@ -33,7 +33,7 @@ RSpec.describe "bulk_upload_lettings_results/show.html.erb" do fragment = Capybara::Node::Simple.new(rendered) - expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100]) + expect(fragment.find_css("table tbody td").map(&:inner_text).values_at(0, 4)).to eql(%w[Z100 AA100]) end end end diff --git a/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb b/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb index 05e2dce85..ac0c1f82d 100644 --- a/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb +++ b/spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb @@ -33,7 +33,7 @@ RSpec.describe "bulk_upload_lettings_results/summary.html.erb" do fragment = Capybara::Node::Simple.new(rendered) - expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100]) + expect(fragment.find_css("table tbody td").map(&:inner_text).values_at(0, 4)).to eql(%w[Z100 AA100]) end end end diff --git a/spec/views/bulk_upload_sales_results/show.html.erb_spec.rb b/spec/views/bulk_upload_sales_results/show.html.erb_spec.rb index 785d830b3..dc6751dc8 100644 --- a/spec/views/bulk_upload_sales_results/show.html.erb_spec.rb +++ b/spec/views/bulk_upload_sales_results/show.html.erb_spec.rb @@ -32,8 +32,7 @@ RSpec.describe "bulk_upload_sales_results/show.html.erb" do render fragment = Capybara::Node::Simple.new(rendered) - - expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100]) + expect(fragment.find_css("table tbody td").map(&:inner_text).values_at(0, 4)).to eql(%w[Z100 AA100]) end end end diff --git a/spec/views/bulk_upload_sales_results/summary.html.erb_spec.rb b/spec/views/bulk_upload_sales_results/summary.html.erb_spec.rb index 6c00e5adf..b3d9aa006 100644 --- a/spec/views/bulk_upload_sales_results/summary.html.erb_spec.rb +++ b/spec/views/bulk_upload_sales_results/summary.html.erb_spec.rb @@ -33,7 +33,7 @@ RSpec.describe "bulk_upload_sales_results/summary.html.erb" do fragment = Capybara::Node::Simple.new(rendered) - expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100]) + expect(fragment.find_css("table tbody td").map(&:inner_text).values_at(0, 4)).to eql(%w[Z100 AA100]) end end end