Browse Source

Merge branch 'main' into CLDC-3241-Explanation-of-variables-on-CSVs

pull/2539/head
Manny Dinssa 2 years ago committed by GitHub
parent
commit
713464dd71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Gemfile.lock
  2. 50
      app/components/bulk_upload_error_row_component.html.erb
  3. 15
      app/controllers/organisations_controller.rb
  4. 11
      app/controllers/users_controller.rb
  5. 3
      app/frontend/controllers/index.js
  6. 46
      app/frontend/controllers/search_controller.js
  7. 49
      app/frontend/modules/search.js
  8. 14
      app/frontend/styles/application.scss
  9. 82
      app/helpers/filters_helper.rb
  10. 2
      app/models/form/lettings/pages/uprn_selection.rb
  11. 3
      app/models/form/question.rb
  12. 16
      app/models/lettings_log.rb
  13. 3
      app/models/log.rb
  14. 1
      app/models/organisation.rb
  15. 5
      app/models/user.rb
  16. 7
      app/models/validations/financial_validations.rb
  17. 52
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  18. 4
      app/services/bulk_upload/sales/year2024/row_parser.rb
  19. 11
      app/services/filter_manager.rb
  20. 2
      app/views/bulk_upload_lettings_results/summary.html.erb
  21. 2
      app/views/bulk_upload_sales_results/summary.html.erb
  22. 1
      app/views/filters/_radio_filter.html.erb
  23. 20
      app/views/filters/_text_select_filter.html.erb
  24. 3
      app/views/filters/assigned_to.html.erb
  25. 3
      app/views/filters/managed_by.html.erb
  26. 3
      app/views/filters/owned_by.html.erb
  27. 15
      app/views/logs/_log_filters.html.erb
  28. 2
      app/views/schemes/_scheme_filters.html.erb
  29. 12
      config/locales/en.yml
  30. 8
      config/routes.rb
  31. 2
      docs/Gemfile.lock
  32. 49
      spec/components/bulk_upload_error_row_component_spec.rb
  33. 4
      spec/features/lettings_log_spec.rb
  34. 6
      spec/features/organisation_spec.rb
  35. 7
      spec/features/user_spec.rb
  36. 284
      spec/helpers/filters_helper_spec.rb
  37. 4
      spec/models/form/lettings/questions/declaration_spec.rb
  38. 8
      spec/models/form/sales/questions/privacy_notice_spec.rb
  39. 6
      spec/models/user_spec.rb
  40. 8
      spec/models/validations/financial_validations_spec.rb
  41. 45
      spec/requests/organisations_controller_spec.rb
  42. 61
      spec/requests/users_controller_spec.rb
  43. 2
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  44. 58
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
  45. 11
      spec/services/bulk_upload/sales/year2024/row_parser_spec.rb
  46. 2
      spec/views/bulk_upload_lettings_results/show.html.erb_spec.rb
  47. 2
      spec/views/bulk_upload_lettings_results/summary.html.erb_spec.rb
  48. 3
      spec/views/bulk_upload_sales_results/show.html.erb_spec.rb
  49. 2
      spec/views/bulk_upload_sales_results/summary.html.erb_spec.rb

2
Gemfile.lock

@ -379,7 +379,7 @@ GEM
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.3.2) rexml (3.3.3)
strscan strscan
roo (2.10.1) roo (2.10.1)
nokogiri (~> 1) nokogiri (~> 1)

50
app/components/bulk_upload_error_row_component.html.erb

@ -1,13 +1,16 @@
<div class="govuk-summary-card govuk-!-margin-bottom-6"> <div class="govuk-summary-card govuk-!-margin-bottom-6">
<div class="govuk-summary-card__title-wrapper"> <div class="govuk-summary-card__title-wrapper">
<% if lettings? %> <% if lettings? %>
<h3 class="govuk-summary-card__title"><strong>Row <%= row %></strong> <%= tenant_code_html %> <%= property_ref_html %></h3> <h3 class="govuk-summary-card__title govuk-!-font-weight-regular"><strong>Row <%= row %></strong> <%= tenant_code_html %> <%= property_ref_html %></h3>
<% else %> <% else %>
<h3 class="govuk-summary-card__title"><strong>Row <%= row %></strong> <%= purchaser_code_html %></h3> <h3 class="govuk-summary-card__title govuk-!-font-weight-regular"><strong>Row <%= row %></strong> <%= purchaser_code_html %></h3>
<% end %> <% end %>
</div> </div>
<div class="govuk-summary-card__content"> <div class="govuk-summary-card__content">
<% potential_errors, critical_errors = bulk_upload_errors.partition { |error| error.category == "soft_validation" } %>
<h2 class="govuk-heading-m">Critical errors</h2>
<p class="govuk-body">These errors must be fixed to complete your logs.</p>
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<% head.with_row do |row| %> <% head.with_row do |row| %>
@ -18,16 +21,49 @@
<% end %> <% end %>
<%= table.with_body do |body| %> <%= table.with_body do |body| %>
<% bulk_upload_errors.each do |error| %> <% critical_errors.each do |error| %>
<% body.with_row do |row| %> <% body.with_row do |row| %>
<% row.with_cell(header: true, text: error.cell) %> <% row.with_cell(text: error.cell) %>
<% row.with_cell(text: question_for_field(error.field)) %> <% row.with_cell(text: question_for_field(error.field)) %>
<% row.with_cell(text: error.error) %> <% row.with_cell(text: error.error, html_attributes: { class: "govuk-!-font-weight-bold" }) %>
<% row.with_cell(text: error.field.humanize) %> <% row.with_cell(text: error.field.humanize) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% if potential_errors.any? %>
<h2 class="govuk-heading-m">Potential errors</h2>
<p class="govuk-body">The following groups of cells might have conflicting data. Check the answers and fix any incorrect data.<br><br>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.</p>
<%= 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 %>
</div> </div>
</div> </div>

15
app/controllers/organisations_controller.rb

@ -4,8 +4,8 @@ class OrganisationsController < ApplicationController
include DuplicateLogsHelper include DuplicateLogsHelper
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_resource, except: %i[index new create] before_action :find_resource, except: %i[index new create search]
before_action :authenticate_scope!, except: [:index] 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, 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 :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] 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" render "schemes/changes"
end 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 private
def filter_type def filter_type

11
app/controllers/users_controller.rb

@ -32,6 +32,17 @@ class UsersController < ApplicationController
end end
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 def resend_invite
@user.send_confirmation_instructions @user.send_confirmation_instructions
flash[:notice] = "Invitation sent to #{@user.email}" flash[:notice] = "Invitation sent to #{@user.email}"

3
app/frontend/controllers/index.js

@ -13,6 +13,8 @@ import GovukfrontendController from './govukfrontend_controller.js'
import NumericQuestionController from './numeric_question_controller.js' import NumericQuestionController from './numeric_question_controller.js'
import SearchController from './search_controller.js'
import FilterLayoutController from './filter_layout_controller.js' import FilterLayoutController from './filter_layout_controller.js'
application.register('accessible-autocomplete', AccessibleAutocompleteController) application.register('accessible-autocomplete', AccessibleAutocompleteController)
application.register('conditional-filter', ConditionalFilterController) application.register('conditional-filter', ConditionalFilterController)
@ -20,3 +22,4 @@ application.register('conditional-question', ConditionalQuestionController)
application.register('govukfrontend', GovukfrontendController) application.register('govukfrontend', GovukfrontendController)
application.register('numeric-question', NumericQuestionController) application.register('numeric-question', NumericQuestionController)
application.register('filter-layout', FilterLayoutController) application.register('filter-layout', FilterLayoutController)
application.register('search', SearchController)

46
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)
})
}
}

49
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 ? `<span class="autocomplete__option__append">${result.text}</span> <span>${result.append}</span>` : `<span>${result.text}</span>`
return result.hint ? `${html}<div class="autocomplete__option__hint">${result.hint}</div>` : html
} else {
return '<span>No results found</span>'
}
} catch (error) {
console.error('Error fetching user option:', error)
return value
}
}
export const enhanceOption = (option) => { export const enhanceOption = (option) => {
return { return {
text: option.text, 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) => { export const getSearchableName = (option) => {
return option.getAttribute('data-hint') ? option.text + ' ' + option.getAttribute('data-hint') : option.text 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
}

14
app/frontend/styles/application.scss

@ -81,3 +81,17 @@ $govuk-breakpoints: (
.govuk-footer { .govuk-footer {
border-top: govuk-spacing(2) solid $govuk-brand-colour; 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;
}

82
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?("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.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["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? && filter == "managing_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? return false if selected_filters[filter].blank?
@ -84,16 +84,54 @@ module FiltersHelper
JSON.parse(session[session_name_for(filter_type)])[filter] || "" JSON.parse(session[session_name_for(filter_type)])[filter] || ""
end 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 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) } [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) }
end 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 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) } [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 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 def collection_year_options
years = { years = {
current_collection_start_year.to_s => year_combo(current_collection_start_year), current_collection_start_year.to_s => year_combo(current_collection_start_year),
@ -125,11 +163,26 @@ module FiltersHelper
end end
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 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) } [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) }
end 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) def show_scheme_managing_org_filter?(user)
org = user.organisation org = user.organisation
@ -176,8 +229,8 @@ module FiltersHelper
{ id: "status", label: "Status", value: formatted_status_filter(session_filters) }, { 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, 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: "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: "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) }, { id: "managed_by", label: "Managed by", value: formatted_managed_by_filter(session_filters, filter_type) },
].compact ].compact
end end
@ -221,7 +274,7 @@ private
filters.each.sum do |category, category_filters| filters.each.sum do |category, category_filters|
if %w[years status needstypes bulk_upload_id].include?(category) if %w[years status needstypes bulk_upload_id].include?(category)
category_filters.count(&:present?) 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 1
else else
0 0
@ -256,26 +309,27 @@ private
return "All" if session_filters["assigned_to"].include?("all") return "All" if session_filters["assigned_to"].include?("all")
return "You" if session_filters["assigned_to"].include?("you") 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 return unless selected_user_option
"#{selected_user_option.name} (#{selected_user_option.hint})" "#{selected_user_option.name} (#{selected_user_option.email})"
end 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")) 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"] 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 return unless selected_owning_organisation_option
selected_owning_organisation_option&.name selected_owning_organisation_option&.name
end 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") 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 return unless selected_managing_organisation_option
selected_managing_organisation_option&.name selected_managing_organisation_option&.name

2
app/models/form/lettings/pages/uprn_selection.rb

@ -17,7 +17,7 @@ class Form::Lettings::Pages::UprnSelection < ::Form::Page
end end
def routed_to?(log, _current_user = nil) 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 end
def skip_text def skip_text

3
app/models/form/question.rb

@ -208,7 +208,8 @@ class Form::Question
end end
def unanswered_error_message 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 end
def suffix_label(log) def suffix_label(log)

16
app/models/lettings_log.rb

@ -132,6 +132,10 @@ class LettingsLog < Log
illness_type_10: false) 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 AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze
OPTIONAL_FIELDS = %w[tenancycode propcode chcharge].freeze OPTIONAL_FIELDS = %w[tenancycode propcode chcharge].freeze
RENT_TYPE_MAPPING_LABELS = { 1 => "Social Rent", 2 => "Affordable Rent", 3 => "Intermediate Rent" }.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? def non_location_setup_questions_completed?
form.setup_sections.all? do |section| form.setup_sections.all? do |section|
section.subsections.all? do |subsection| 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 = subsection.questions.reject { |q| optional_fields.include?(q.id) || %w[scheme_id location_id].include?(q.id) }
relevant_qs.all? do |question| relevant_applicable_qs = select_applicable_questions(self, relevant_qs)
relevant_applicable_qs.all? do |question|
question.completed?(self) question.completed?(self)
end end
end end
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! def resolve!
update(unresolved: false) update(unresolved: false)
end end

3
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_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_owning_organisation, ->(owning_organisation, _user = nil) { where(owning_organisation:) }
scope :filter_by_managing_organisation, ->(managing_organisation, _user = nil) { where(managing_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 attr_accessor :skip_update_status, :skip_update_uprn_confirmed, :select_best_address_match, :skip_dpo_validation

1
app/models/organisation.rb

@ -18,6 +18,7 @@ class Organisation < ApplicationRecord
belongs_to :absorbing_organisation, class_name: "Organisation", optional: true belongs_to :absorbing_organisation, class_name: "Organisation", optional: true
has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id" has_many :absorbed_organisations, class_name: "Organisation", foreign_key: "absorbing_organisation_id"
scope :visible, -> { where(discarded_at: nil) } 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 def affiliated_stock_owners
ids = [] ids = []

5
app/models/user.rb

@ -77,6 +77,7 @@ class User < ApplicationRecord
scope :deactivated, -> { where(active: false) } scope :deactivated, -> { where(active: false) }
scope :active_status, -> { where(active: true).where.not(last_sign_in_at: nil) } scope :active_status, -> { where(active: true).where.not(last_sign_in_at: nil) }
scope :visible, -> { where(discarded_at: nil) } scope :visible, -> { where(discarded_at: nil) }
scope :own_and_managing_org_users, ->(organisation) { where(organisation: organisation.child_organisations + [organisation]) }
def lettings_logs def lettings_logs
if support? if support?
@ -209,9 +210,9 @@ class User < ApplicationRecord
def logs_filters(specific_org: false) def logs_filters(specific_org: false)
if (support? && !specific_org) || organisation.has_managing_agents? || organisation.has_stock_owners? 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 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
end end

7
app/models/validations/financial_validations.rb

@ -149,7 +149,12 @@ module Validations::FinancialValidations
unless record.managing_organisation.rent_periods.include? record.period unless record.managing_organisation.rent_periods.include? record.period
record.errors.add :period, :wrong_rent_period, message: I18n.t( 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, org_name: record.managing_organisation.name,
rent_period: record.form.get_question("period", record).label_from_value(record.period).downcase, rent_period: record.form.get_question("period", record).label_from_value(record.period).downcase,
) )

52
app/services/bulk_upload/lettings/year2024/row_parser.rb

@ -467,6 +467,7 @@ class BulkUpload::Lettings::Year2024::RowParser
fields.each do |field| fields.each do |field|
next if errors.include?(field) next if errors.include?(field)
next if error.type == :skip_bu_error
question = log.form.get_question(error.attribute, log) question = log.form.get_question(error.attribute, log)
@ -805,16 +806,14 @@ private
if setup_question?(question) if setup_question?(question)
fields.each do |field| fields.each do |field|
if errors.select { |e| fields.include?(e.attribute) }.none? if errors.select { |e| fields.include?(e.attribute) }.none? && field.present?
question_text = question.error_display_label.presence || "this question" errors.add(field, question.unanswered_error_message, category: :setup)
errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase), category: :setup) if field.present?
end end
end end
else else
fields.each do |field| fields.each do |field|
unless errors.any? { |e| fields.include?(e.attribute) } unless errors.any? { |e| fields.include?(e.attribute) }
question_text = question.error_display_label.presence || "this question" errors.add(field, question.unanswered_error_message)
errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase))
end end
end end
end end
@ -1131,10 +1130,6 @@ private
attributes["lettype"] = nil # should get this from rent_type attributes["lettype"] = nil # should get this from rent_type
attributes["tenancycode"] = field_13 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["owning_organisation"] = owning_organisation
attributes["managing_organisation"] = managing_organisation attributes["managing_organisation"] = managing_organisation
attributes["renewal"] = renewal attributes["renewal"] = renewal
@ -1305,22 +1300,29 @@ private
attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing
attributes["uprn_known"] = field_16.present? ? 1 : 0 if general_needs?
attributes["uprn_confirmed"] = 1 if field_16.present? attributes["uprn_known"] = field_16.present? ? 1 : 0
attributes["skip_update_uprn_confirmed"] = true attributes["uprn_confirmed"] = 1 if field_16.present?
attributes["uprn"] = field_16 attributes["skip_update_uprn_confirmed"] = true
attributes["address_line1"] = field_17 attributes["uprn"] = field_16
attributes["address_line1_as_entered"] = field_17 attributes["address_line1"] = field_17
attributes["address_line2"] = field_18 attributes["address_line1_as_entered"] = field_17
attributes["address_line2_as_entered"] = field_18 attributes["address_line2"] = field_18
attributes["town_or_city"] = field_19 attributes["address_line2_as_entered"] = field_18
attributes["town_or_city_as_entered"] = field_19 attributes["town_or_city"] = field_19
attributes["county"] = field_20 attributes["town_or_city_as_entered"] = field_19
attributes["county_as_entered"] = field_20 attributes["county"] = field_20
attributes["address_line1_input"] = address_line1_input attributes["county_as_entered"] = field_20
attributes["postcode_full_input"] = postcode_full attributes["postcode_full"] = postcode_full
attributes["postcode_full_as_entered"] = postcode_full attributes["postcode_full_as_entered"] = postcode_full
attributes["select_best_address_match"] = true if field_16.blank? && !supported_housing? 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 attributes
end end

4
app/services/bulk_upload/sales/year2024/row_parser.rb

@ -1364,13 +1364,13 @@ private
if setup_question?(question) if setup_question?(question)
fields.each do |field| fields.each do |field|
unless errors.any? { |e| fields.include?(e.attribute) } 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
end end
else else
fields.each do |field| fields.each do |field|
unless errors.any? { |e| fields.include?(e.attribute) } 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 end
end end

11
app/services/filter_manager.rb

@ -24,6 +24,9 @@ class FilterManager
next if category == "owning_organisation" && all_orgs next if category == "owning_organisation" && all_orgs
next if category == "managing_organisation" && all_orgs next if category == "managing_organisation" && all_orgs
next if category == "assigned_to" 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) logs = logs.public_send("filter_by_#{category}", values, user)
end end
@ -94,11 +97,19 @@ class FilterManager
new_filters[filter] = params[filter] if params[filter].present? new_filters[filter] = params[filter] if params[filter].present?
end 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("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("managing_organisation") if params["managing_organisation_select"] == "all"
new_filters = new_filters.except("user") if params["assigned_to"] == "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["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 end
if (filter_type.include?("schemes") || filter_type.include?("users") || filter_type.include?("scheme_locations")) && params["status"].present? if (filter_type.include?("schemes") || filter_type.include?("users") || filter_type.include?("scheme_locations")) && params["status"].present?

2
app/views/bulk_upload_lettings_results/summary.html.erb

@ -4,7 +4,7 @@
<h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1> <h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1>
<p class="govuk-body-l"> <p class="govuk-body-l">
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.
</p> </p>
<p class="govuk-body-l"> <p class="govuk-body-l">

2
app/views/bulk_upload_sales_results/summary.html.erb

@ -4,7 +4,7 @@
<h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1> <h1 class="govuk-heading-l">Fix <%= pluralize(@bulk_upload.bulk_upload_errors.count, "error") %> and upload file again</h1>
<p class="govuk-body-l"> <p class="govuk-body-l">
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.
</p> </p>
<p class="govuk-body-l"> <p class="govuk-body-l">

1
app/views/filters/_radio_filter.html.erb

@ -10,6 +10,7 @@
collection: option[:conditional_filter][:options], collection: option[:conditional_filter][:options],
category: option[:conditional_filter][:category], category: option[:conditional_filter][:category],
label: option[:conditional_filter][:label], label: option[:conditional_filter][:label],
caption_text: option[:conditional_filter][:caption_text],
secondary: true, secondary: true,
hint_text: option[:conditional_filter][:hint_text], hint_text: option[:conditional_filter][:hint_text],
} %> } %>

20
app/views/filters/_text_select_filter.html.erb

@ -0,0 +1,20 @@
<span class="non-js-text-search-input-field">
<%= 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) %>
</span>
<%= 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| %>
<option value="<%= answer.id %>"
data-hint="<%= answer.hint %>"
<%= answer.id.to_s == selected_option(category, @filter_type).to_s ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name %></option>
<% end %>
<% end %>

3
app/views/filters/assigned_to.html.erb

@ -11,7 +11,8 @@
type: "select", type: "select",
label: "User", label: "User",
category: "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),
}, },
}, },
}, },

3
app/views/filters/managed_by.html.erb

@ -9,7 +9,8 @@
type: "select", type: "select",
label: "Managed by", label: "Managed by",
category: "managing_organisation", category: "managing_organisation",
options: managing_organisation_filter_options(current_user), options: managing_organisation_csv_filter_options(current_user),
caption_text: "Organisation name",
}, },
}, },
}, },

3
app/views/filters/owned_by.html.erb

@ -9,7 +9,8 @@
type: "select", type: "select",
label: "Owning Organisation", label: "Owning Organisation",
category: "owning_organisation", category: "owning_organisation",
options: owning_organisation_filter_options(current_user), options: all_owning_organisation_filter_options(current_user),
caption_text: "Organisation name",
}, },
}, },
}, },

15
app/views/logs/_log_filters.html.erb

@ -66,10 +66,11 @@
"specific_user": { "specific_user": {
label: "Specific user", label: "Specific user",
conditional_filter: { conditional_filter: {
type: "select", type: "text_select",
label: "User", label: "User",
category: "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": { "specific_org": {
label: "Specific owning organisation", label: "Specific owning organisation",
conditional_filter: { conditional_filter: {
type: "select", type: "text_select",
label: "Owning Organisation", label: "Owning Organisation",
category: "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": { "specific_org": {
label: "Specific managing organisation", label: "Specific managing organisation",
conditional_filter: { conditional_filter: {
type: "select", type: "text_select",
label: user_or_org_lettings_path? ? "Managed by" : "Reported by", label: user_or_org_lettings_path? ? "Managed by" : "Reported by",
category: "managing_organisation", category: "managing_organisation",
options: managing_organisation_filter_options(current_user), options: managing_organisation_filter_options(current_user, @filter_type),
caption_text: "Organisation name",
}, },
}, },
}, },

2
app/views/schemes/_scheme_filters.html.erb

@ -35,7 +35,7 @@
type: "select", type: "select",
label: "Owning Organisation", label: "Owning Organisation",
category: "owning_organisation", category: "owning_organisation",
options: owning_organisation_filter_options(current_user), options: all_owning_organisation_filter_options(current_user),
}, },
}, },
}, },

12
config/locales/en.yml

@ -436,7 +436,9 @@ en:
under_10: "Enter a total charge that is at least £10.00 per week" 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" less_than_shortfall: "The total charge must be more than the outstanding amount"
rent_period: 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: carehome:
out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}" 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}" not_provided: "Enter how much rent and other charges the household pays %{period}"
@ -587,13 +589,13 @@ en:
declaration: declaration:
missing: missing:
pre_2024: "You must show 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 access to 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: privacynotice:
missing: missing:
pre_2024: "You must show 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 access to 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: scheme:
toggle_date: toggle_date:

8
config/routes.rb

@ -125,6 +125,10 @@ Rails.application.routes.draw do
get "edit-dpo", to: "users#dpo" get "edit-dpo", to: "users#dpo"
get "edit-key-contact", to: "users#key_contact" get "edit-key-contact", to: "users#key_contact"
collection do
get :search
end
member do member do
get "deactivate", to: "users#deactivate" get "deactivate", to: "users#deactivate"
get "reactivate", to: "users#reactivate" get "reactivate", to: "users#reactivate"
@ -191,6 +195,10 @@ Rails.application.routes.draw do
get "delete-confirmation", to: "organisations#delete_confirmation" get "delete-confirmation", to: "organisations#delete_confirmation"
delete "delete", to: "organisations#delete" delete "delete", to: "organisations#delete"
end end
collection do
get :search
end
end end
resources :merge_requests, path: "/merge-request" do resources :merge_requests, path: "/merge-request" do

2
docs/Gemfile.lock

@ -226,7 +226,7 @@ GEM
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rexml (3.3.2) rexml (3.3.3)
strscan strscan
rouge (3.26.0) rouge (3.26.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)

49
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) expect(result).to have_content(expected)
end end
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 end

4
spec/features/lettings_log_spec.rb

@ -89,9 +89,9 @@ RSpec.describe "Lettings Log Features" do
check("In progress") check("In progress")
choose("You") choose("You")
choose("Specific owning organisation") 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") 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") click_button("Apply filters")
end end

6
spec/features/organisation_spec.rb

@ -199,14 +199,14 @@ RSpec.describe "User Features" do
it "can filter lettings logs by year" do it "can filter lettings logs by year" do
check("years-2022-field") check("years-2022-field")
click_button("Apply filters") 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}" expect(page).not_to have_link first_log.id.to_s, href: "/lettings-logs/#{first_log.id}"
end end
it "can filter lettings logs by needstype" do it "can filter lettings logs by needstype" do
check("needstypes-1-field") check("needstypes-1-field")
click_button("Apply filters") 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| 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}" expect(page).to have_link general_needs_log.id.to_s, href: "/lettings-logs/#{general_needs_log.id}"
end end
@ -245,7 +245,7 @@ RSpec.describe "User Features" do
end end
check("years-2022-field") check("years-2022-field")
click_button("Apply filters") 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}" expect(page).not_to have_link first_log.id.to_s, href: "/sales-logs/#{first_log.id}"
end end
end end

7
spec/features/user_spec.rb

@ -796,12 +796,13 @@ RSpec.describe "User Features" do
visit("/lettings-logs") visit("/lettings-logs")
choose("owning-organisation-select-specific-org-field", allow_label_click: true) choose("owning-organisation-select-specific-org-field", allow_label_click: true)
expect(page).to have_field("owning-organisation-field", with: "") 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") 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) choose("owning-organisation-select-all-field", allow_label_click: true)
click_button("Apply filters") 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 end
end end

284
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: parent_organisation.id, name: "Parent organisation"),
OpenStruct.new(id: 9_999_999, name: "Other 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
end end
context "with a data coordinator user" do context "with a data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: child_organisation) } let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: child_organisation) }
it "returns a list of parent orgs and your own organisation" do context "when no organisation is selected in the filters" do
expect(owning_organisation_filter_options(user.reload)).to eq([ it "returns an empty list" do
OpenStruct.new(id: "", name: "Select an option"), expect(owning_organisation_filter_options(user.reload, "lettings_logs")).to eq([
OpenStruct.new(id: child_organisation.id, name: "Child organisation"), OpenStruct.new(id: "", name: "Select an option"),
OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), ])
OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), 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 end
end end
@ -222,19 +350,147 @@ RSpec.describe FiltersHelper do
OpenStruct.new(id: child_organisation.id, name: "Child organisation"), OpenStruct.new(id: child_organisation.id, name: "Child organisation"),
OpenStruct.new(id: 9_999_999, name: "Other 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
end end
context "with a data coordinator user" do context "with a data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: parent_organisation) } let(:user) { FactoryBot.create(:user, :data_coordinator, organisation: parent_organisation) }
it "returns a list of child orgs and your own organisation" do context "when no organisation is selected in the filters" do
expect(managing_organisation_filter_options(user.reload)).to eq([ it "returns an empty list" do
OpenStruct.new(id: "", name: "Select an option"), expect(managing_organisation_filter_options(user.reload, "lettings_logs")).to eq([
OpenStruct.new(id: parent_organisation.id, name: "Parent organisation"), OpenStruct.new(id: "", name: "Select an option"),
OpenStruct.new(id: child_organisation.id, name: "Child organisation"), ])
OpenStruct.new(id: absorbed_organisation.id, name: "Absorbed organisation"), 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 end
end end

4
spec/models/form/lettings/questions/declaration_spec.rb

@ -63,7 +63,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
end end
it "returns correct unanswered_error_message" do 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
end end
@ -87,7 +87,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
end end
it "returns correct unanswered_error_message" do 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 end
end end

8
spec/models/form/sales/questions/privacy_notice_spec.rb

@ -60,7 +60,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end end
it "returns correct unanswered_error_message" do 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
end end
@ -78,7 +78,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end end
it "returns correct unanswered_error_message" do 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 end
end end
@ -100,7 +100,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end end
it "returns correct unanswered_error_message" do 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
end end
@ -118,7 +118,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end end
it "returns correct unanswered_error_message" do 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 end
end end

6
spec/models/user_spec.rb

@ -164,7 +164,7 @@ RSpec.describe User, type: :model do
end end
it "can filter lettings logs by user, year and status" do 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
end end
@ -174,7 +174,7 @@ RSpec.describe User, type: :model do
end end
it "can filter lettings logs by user, year, status, managing_organisation and owning_organisation" do 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 end
end end
@ -215,7 +215,7 @@ RSpec.describe User, type: :model do
end end
it "can filter lettings logs by user, year, status, managing_organisation and owning_organisation" do 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
end end

8
spec/models/validations/financial_validations_spec.rb

@ -165,7 +165,13 @@ RSpec.describe Validations::FinancialValidations do
financial_validator.validate_rent_period(record) financial_validator.validate_rent_period(record)
expect(record.errors["period"]) expect(record.errors["period"])
.to include(match I18n.t( .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, org_name: user.organisation.name,
rent_period: "every 4 weeks", rent_period: "every 4 weeks",
)) ))

45
spec/requests/organisations_controller_spec.rb

@ -75,6 +75,13 @@ RSpec.describe OrganisationsController, type: :request do
end end
end 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 end
context "when user is signed in" do context "when user is signed in" do
@ -807,6 +814,25 @@ RSpec.describe OrganisationsController, type: :request do
end end
end 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 end
context "with a data provider user" do context "with a data provider user" do
@ -2077,6 +2103,25 @@ RSpec.describe OrganisationsController, type: :request do
end end
end 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
end end

61
spec/requests/users_controller_spec.rb

@ -117,6 +117,13 @@ RSpec.describe UsersController, type: :request do
expect(response).to redirect_to("/account/sign-in") expect(response).to redirect_to("/account/sign-in")
end end
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 end
context "when user is signed in as a data provider" do 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) expect(response).to have_http_status(:unauthorized)
end end
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 end
context "when user is signed in as a data coordinator" do 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) expect(response).to have_http_status(:unauthorized)
end end
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 end
context "when user is signed in as a support user" do 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") expect(page).not_to have_link("User to be deleted")
end end
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 end
describe "title link" do describe "title link" do

2
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 it "cannot be nulled" do
parser.valid? 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 end
end end

58
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 it "cannot be nulled" do
parser.valid? 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 end
end end
@ -1855,7 +1867,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#uprn" do 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 it "sets to given value" do
expect(parser.log.uprn).to eql("12") expect(parser.log.uprn).to eql("12")
@ -1864,7 +1876,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
describe "#uprn_known" do describe "#uprn_known" do
context "when uprn specified" 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 it "sets to 1" do
expect(parser.log.uprn_known).to be(1) expect(parser.log.uprn_known).to be(1)
@ -1873,7 +1885,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
context "when uprn blank" do 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 it "sets to 0" do
expect(parser.log.uprn_known).to be(0) expect(parser.log.uprn_known).to be(0)
@ -1882,7 +1894,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#address_line1" do 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 it "sets to given value" do
expect(parser.log.address_line1).to eql("123 Sesame Street") expect(parser.log.address_line1).to eql("123 Sesame Street")
@ -1890,7 +1902,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#address_line2" do 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 it "sets to given value" do
expect(parser.log.address_line2).to eql("Cookie Town") expect(parser.log.address_line2).to eql("Cookie Town")
@ -1898,7 +1910,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#town_or_city" do 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 it "sets to given value" do
expect(parser.log.town_or_city).to eql("London") expect(parser.log.town_or_city).to eql("London")
@ -1906,13 +1918,39 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#county" do 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 it "sets to given value" do
expect(parser.log.county).to eql("Greater London") expect(parser.log.county).to eql("Greater London")
end end
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[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], %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 end
describe "#postcode_full" do 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 it "strips whitespace" do
expect(parser.log.postcode_full).to eql("EC1N 2TD") expect(parser.log.postcode_full).to eql("EC1N 2TD")
@ -2607,7 +2645,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end end
describe "#la" do 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 it "sets to given value" do
expect(parser.log.la).to eql("E07000223") expect(parser.log.la).to eql("E07000223")

11
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 describe "#field_18" do # data protection
let(:attributes) { setup_section_params.merge({ field_18: nil }) } let(:attributes) { setup_section_params.merge({ field_18: nil }) }
before do
parser.valid?
end
context "when not answered" do context "when not answered" do
it "returns a setup error" do it "returns a setup error" do
parser.valid?
expect(parser.errors.where(:field_18, category: :setup)).to be_present expect(parser.errors.where(:field_18, category: :setup)).to be_present
end end
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 end
[ [

2
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) 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 end
end end

2
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) 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 end
end end

3
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 render
fragment = Capybara::Node::Simple.new(rendered) fragment = Capybara::Node::Simple.new(rendered)
expect(fragment.find_css("table tbody td").map(&:inner_text).values_at(0, 4)).to eql(%w[Z100 AA100])
expect(fragment.find_css("table tbody th").map(&:inner_text)).to eql(%w[Z100 AA100])
end end
end end
end end

2
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) 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 end
end end

Loading…
Cancel
Save