diff --git a/Gemfile b/Gemfile
index 358e71070..741f484bd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -49,6 +49,7 @@ gem "paper_trail"
# Store active record objects in version whodunnits
gem "paper_trail-globalid"
# Request rate limiting
+gem "rack", ">= 2.2.6.3"
gem "rack-attack"
gem "redis", "~> 4.8"
# Receive exceptions and configure alerts
diff --git a/Gemfile.lock b/Gemfile.lock
index 7da2ef437..b9b16ab1e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -279,7 +279,7 @@ GEM
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.2)
- rack (2.2.6.2)
+ rack (2.2.6.3)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-mini-profiler (2.3.4)
@@ -478,6 +478,7 @@ DEPENDENCIES
propshaft
pry-byebug
puma (~> 5.0)
+ rack (>= 2.2.6.3)
rack-attack
rack-mini-profiler (~> 2.0)
rails (~> 7.0.2)
diff --git a/app/components/search_component.rb b/app/components/search_component.rb
index d13882645..36d621240 100644
--- a/app/components/search_component.rb
+++ b/app/components/search_component.rb
@@ -23,6 +23,8 @@ class SearchComponent < ViewComponent::Base
user_path(current_user)
elsif request.path.include?("organisations")
organisations_path
+ elsif request.path.include?("sales-logs")
+ sales_logs_path
elsif request.path.include?("logs")
lettings_logs_path
end
diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb
index 391651cde..9b37cd2b5 100644
--- a/app/controllers/bulk_upload_lettings_logs_controller.rb
+++ b/app/controllers/bulk_upload_lettings_logs_controller.rb
@@ -21,6 +21,11 @@ class BulkUploadLettingsLogsController < ApplicationController
end
end
+ def guidance
+ @form = Forms::BulkUploadLettings::PrepareYourFile.new
+ render "bulk_upload_shared/guidance"
+ end
+
private
def current_year
diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb
index 5f9b8d9a6..2b3432399 100644
--- a/app/controllers/bulk_upload_sales_logs_controller.rb
+++ b/app/controllers/bulk_upload_sales_logs_controller.rb
@@ -21,6 +21,11 @@ class BulkUploadSalesLogsController < ApplicationController
end
end
+ def guidance
+ @form = Forms::BulkUploadSales::PrepareYourFile.new
+ render "bulk_upload_shared/guidance"
+ end
+
private
def current_year
diff --git a/app/helpers/logs_helper.rb b/app/helpers/logs_helper.rb
index 88ab2b314..7653b7e5c 100644
--- a/app/helpers/logs_helper.rb
+++ b/app/helpers/logs_helper.rb
@@ -23,4 +23,20 @@ module LogsHelper
array = bulk_upload ? [bulk_upload.id] : []
array.index_with { |_bulk_upload_id| "With logs from bulk upload" }
end
+
+ def search_label_for_controller(controller)
+ case log_type_for_controller(controller)
+ when "lettings"
+ "Search by log ID, tenant code, property reference or postcode"
+ when "sales"
+ "Search by log ID, purchaser code or postcode"
+ end
+ end
+
+ def csv_download_url_for_controller(controller)
+ case log_type_for_controller(controller)
+ when "lettings"
+ csv_download_lettings_logs_path(search: params["search"])
+ end
+ end
end
diff --git a/app/mailers/bulk_upload_mailer.rb b/app/mailers/bulk_upload_mailer.rb
index 94079828f..fe1e81517 100644
--- a/app/mailers/bulk_upload_mailer.rb
+++ b/app/mailers/bulk_upload_mailer.rb
@@ -66,17 +66,40 @@ class BulkUploadMailer < NotifyMailer
)
end
- def send_bulk_upload_failed_file_setup_error_mail(user, bulk_upload)
+ def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
+ bulk_upload_link = if bulk_upload.lettings?
+ start_bulk_upload_lettings_logs_url
+ else
+ start_bulk_upload_sales_logs_url
+ end
+
+ validator_class = if bulk_upload.lettings?
+ BulkUpload::Lettings::Validator
+ else
+ BulkUpload::Sales::Validator
+ end
+
+ errors = bulk_upload
+ .bulk_upload_errors
+ .where(category: "setup")
+ .group(:col, :field)
+ .count
+ .keys
+ .sort_by { |_col, field| field }
+ .map do |col, field|
+ "- Column #{col} (#{validator_class.question_for_field(field.to_sym)})"
+ end
+
send_email(
- user.email,
+ bulk_upload.user.email,
BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
{
- filename: "[#{bulk_upload} filename]",
- upload_timestamp: "[#{bulk_upload} upload_timestamp]",
- lettings_or_sales: "[#{bulk_upload} lettings_or_sales]",
- year_combo: "[#{bulk_upload} year_combo]",
- errors_list: "[#{bulk_upload} errors_list]",
- bulk_upload_link: "[#{bulk_upload} bulk_upload_link]",
+ filename: bulk_upload.filename,
+ upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
+ lettings_or_sales: bulk_upload.log_type,
+ year_combo: bulk_upload.year_combo,
+ errors_list: errors.join("\n"),
+ bulk_upload_link:,
},
)
end
diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb
index 37e659652..d42fb3903 100644
--- a/app/models/derived_variables/lettings_log_variables.rb
+++ b/app/models/derived_variables/lettings_log_variables.rb
@@ -1,5 +1,31 @@
module DerivedVariables::LettingsLogVariables
- RENT_TYPE_MAPPING = { 0 => 1, 1 => 2, 2 => 2, 3 => 3, 4 => 3, 5 => 3 }.freeze
+ # renttype and unitletas values are different for intermediate rent (3 for renttype and 4 for unitletas)
+ RENT_TYPE_MAPPING = {
+ 0 => 1, # "Social Rent" => "Social Rent"
+ 1 => 2, # "Affordable Rent" => "Affordable Rent"
+ 2 => 2, # "London Affordable Rent" => "Affordable Rent"
+ 3 => 3, # "Rent to Buy" => "Intermediate Rent"
+ 4 => 3, # "London Living Rent" => "Intermediate Rent"
+ 5 => 3, # "Other intermediate rent product" => "Intermediate Rent"
+ }.freeze
+
+ UNITLETAS_MAPPING = {
+ 0 => 1, # "Social Rent" => "Social Rent basis"
+ 1 => 2, # "Affordable Rent" => "Affordable Rent basis"
+ 2 => 2, # "London Affordable Rent" => "Affordable Rent basis"
+ 3 => 4, # "Rent to Buy" => "Intermediate Rent basis"
+ 4 => 4, # "London Living Rent" => "Intermediate Rent basis"
+ 5 => 4, # "Other intermediate rent product" => "Intermediate Rent basis"
+ }.freeze
+
+ UNITLETAS_MAPPING_23_24 = {
+ 0 => 1, # "Social Rent" => "Social Rent basis"
+ 1 => 2, # "Affordable Rent" => "Affordable Rent basis"
+ 2 => 5, # "London Affordable Rent" => "London Affordable Rent basis"
+ 3 => 6, # "Rent to Buy" => "Rent to Buy basis"
+ 4 => 7, # "London Living Rent" => "London Living Rent basis"
+ 5 => 8, # "Other intermediate rent product" => "Another Intermediate Rent basis"
+ }.freeze
def scheme_has_multiple_locations?
return false unless scheme
@@ -49,6 +75,7 @@ module DerivedVariables::LettingsLogVariables
self.voiddate = startdate
self.first_time_property_let_as_social_housing = 0
self.rsnvac = 14
+ self.unitletas = form.start_date.year >= 2023 ? UNITLETAS_MAPPING_23_24[rent_type] : UNITLETAS_MAPPING[rent_type]
if is_general_needs?
# fixed term
self.prevten = 32 if managing_organisation&.provider_type == "PRP"
diff --git a/app/models/form/lettings/pages/person_age.rb b/app/models/form/lettings/pages/person_age.rb
index 66f7d3d89..bbb5da01e 100644
--- a/app/models/form/lettings/pages/person_age.rb
+++ b/app/models/form/lettings/pages/person_age.rb
@@ -1,15 +1,21 @@
class Form::Lettings::Pages::PersonAge < ::Form::Page
- def initialize(id, hsh, subsection, person_index:)
+ def initialize(id, hsh, subsection, person_index:, person_type: "non_child")
super(id, hsh, subsection)
- @id = "person_#{person_index}_age"
- @depends_on = [{ "details_known_#{person_index}" => 0 }]
+ @id = "person_#{person_index}_age_#{person_type}"
@person_index = person_index
+ @person_type = person_type
+ @depends_on = [
+ {
+ "details_known_#{person_index}" => 0,
+ "person_#{person_index}_child_relation?" => (person_type == "child"),
+ },
+ ]
end
def questions
@questions ||= [
Form::Lettings::Questions::AgeKnown.new(nil, nil, self, person_index: @person_index),
- Form::Lettings::Questions::Age.new(nil, nil, self, person_index: @person_index),
+ Form::Lettings::Questions::Age.new(nil, nil, self, person_index: @person_index, person_type: @person_type),
]
end
end
diff --git a/app/models/form/lettings/questions/age.rb b/app/models/form/lettings/questions/age.rb
index c1054cead..6b76a6945 100644
--- a/app/models/form/lettings/questions/age.rb
+++ b/app/models/form/lettings/questions/age.rb
@@ -1,5 +1,5 @@
class Form::Lettings::Questions::Age < ::Form::Question
- def initialize(id, hsh, page, person_index:)
+ def initialize(id, hsh, page, person_index:, person_type:)
super(id, hsh, page)
@id = "age#{person_index}"
@check_answer_label = "Person #{person_index}’s age"
@@ -9,8 +9,9 @@ class Form::Lettings::Questions::Age < ::Form::Question
@inferred_check_answers_value = [{ "condition" => { "age#{person_index}_known" => 1 }, "value" => "Not known" }]
@check_answers_card_number = person_index
@max = 120
- @min = 0
+ @min = 1
@step = 1
- @question_number = 31 + 4 * person_index
+ @hint_text = "For a child under 1, enter 1" if person_type == "child"
+ @question_number = 31 + (4 * person_index)
end
end
diff --git a/app/models/form/lettings/questions/housingneeds_other.rb b/app/models/form/lettings/questions/housingneeds_other.rb
index 2ad1f6f36..fd1b463a0 100644
--- a/app/models/form/lettings/questions/housingneeds_other.rb
+++ b/app/models/form/lettings/questions/housingneeds_other.rb
@@ -3,7 +3,7 @@ class Form::Lettings::Questions::HousingneedsOther < ::Form::Question
super
@id = "housingneeds_other"
@check_answer_label = "Other disabled access needs"
- @header = "Do they have any other access needs?"
+ @header = "Do they have any other disabled access needs?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = ""
diff --git a/app/models/form/lettings/questions/net_income_known.rb b/app/models/form/lettings/questions/net_income_known.rb
index 0bf1195fb..226569b9e 100644
--- a/app/models/form/lettings/questions/net_income_known.rb
+++ b/app/models/form/lettings/questions/net_income_known.rb
@@ -2,7 +2,7 @@ class Form::Lettings::Questions::NetIncomeKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "net_income_known"
- @check_answer_label = "Do you know the household’s combined income?"
+ @check_answer_label = "Do you know the household’s combined total income after tax?"
@header = "Do you know the household’s combined income after tax?"
@type = "radio"
@check_answers_card_number = 0
diff --git a/app/models/form/lettings/questions/offered_social_let.rb b/app/models/form/lettings/questions/offered_social_let.rb
index 214dc6ed7..cf8e2f6cf 100644
--- a/app/models/form/lettings/questions/offered_social_let.rb
+++ b/app/models/form/lettings/questions/offered_social_let.rb
@@ -3,13 +3,13 @@ class Form::Lettings::Questions::OfferedSocialLet < ::Form::Question
super
@id = "offered"
@check_answer_label = "Times previously offered since becoming available"
- @header = "Since becoming available, how many times has the property been previously offered?"
+ @header = "How many times was the property offered between becoming vacant and this letting?"
@type = "numeric"
@width = 2
@check_answers_card_number = 0
@max = 150
@min = 0
- @hint_text = "If the property is being offered for let for the first time, enter 0."
+ @hint_text = "Do not include the offer that led to this letting.This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0."
@step = 1
@question_number = 18
end
diff --git a/app/models/form/lettings/questions/person_relationship.rb b/app/models/form/lettings/questions/person_relationship.rb
index c561d43de..3edb40cd6 100644
--- a/app/models/form/lettings/questions/person_relationship.rb
+++ b/app/models/form/lettings/questions/person_relationship.rb
@@ -15,7 +15,7 @@ class Form::Lettings::Questions::PersonRelationship < ::Form::Question
"P" => { "value" => "Partner" },
"C" => {
"value" => "Child",
- "hint" => "Must be eligible for child benefit, aged under 16 or under 20 if still in full-time education.",
+ "hint" => "Must be eligible for child benefit: under age 16 or under 20 if still in full-time education.",
},
"X" => { "value" => "Other" },
"divider" => { "value" => true },
diff --git a/app/models/form/lettings/questions/rsnvac.rb b/app/models/form/lettings/questions/rsnvac.rb
index 6b7ff2d58..22d039748 100644
--- a/app/models/form/lettings/questions/rsnvac.rb
+++ b/app/models/form/lettings/questions/rsnvac.rb
@@ -37,6 +37,9 @@ class Form::Lettings::Questions::Rsnvac < ::Form::Question
"18" => {
"value" => "Tenant moved to care home",
},
+ "20" => {
+ "value" => "Tenant moved to long-stay hospital or similar institution",
+ },
"6" => {
"value" => "Tenant abandoned property",
},
diff --git a/app/models/form/lettings/subsections/household_characteristics.rb b/app/models/form/lettings/subsections/household_characteristics.rb
index 8a5bdf8c1..9fc6948bd 100644
--- a/app/models/form/lettings/subsections/household_characteristics.rb
+++ b/app/models/form/lettings/subsections/household_characteristics.rb
@@ -30,6 +30,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new(nil, nil, self),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 2),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 2),
@@ -44,6 +45,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 2),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 3),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 3),
@@ -58,6 +60,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 3),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 4),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 4),
@@ -72,6 +75,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 4),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 5),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 5),
@@ -86,6 +90,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 5),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 6),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 6),
@@ -100,6 +105,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 6),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 7),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 7),
@@ -114,6 +120,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 7),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 8),
+ Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8, person_type: "child"),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8),
Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self,
person_index: 8),
diff --git a/app/models/form/sales/pages/about_staircase.rb b/app/models/form/sales/pages/about_staircase.rb
index 2d1a17db4..4d5882916 100644
--- a/app/models/form/sales/pages/about_staircase.rb
+++ b/app/models/form/sales/pages/about_staircase.rb
@@ -1,17 +1,18 @@
class Form::Sales::Pages::AboutStaircase < ::Form::Page
- def initialize(id, hsh, subsection)
- super
- @id = "about_staircasing"
+ def initialize(id, hsh, subsection, joint_purchase:)
+ super(id, hsh, subsection)
+ @joint_purchase = joint_purchase
@header = "About the staircasing transaction"
@depends_on = [{
"staircase" => 1,
+ "joint_purchase?" => joint_purchase,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseBought.new(nil, nil, self),
- Form::Sales::Questions::StaircaseOwned.new(nil, nil, self),
+ Form::Sales::Questions::StaircaseOwned.new(nil, nil, self, joint_purchase: @joint_purchase),
staircase_sale_question,
].compact
end
diff --git a/app/models/form/sales/pages/buyer_previous.rb b/app/models/form/sales/pages/buyer_previous.rb
index 361b3ba47..f37c6a847 100644
--- a/app/models/form/sales/pages/buyer_previous.rb
+++ b/app/models/form/sales/pages/buyer_previous.rb
@@ -1,12 +1,13 @@
class Form::Sales::Pages::BuyerPrevious < ::Form::Page
- def initialize(id, hsh, subsection)
- super
- @id = "buyer_previous"
+ def initialize(id, hsh, subsection, joint_purchase:)
+ super(id, hsh, subsection)
+ @joint_purchase = joint_purchase
+ @depends_on = [{ "joint_purchase?" => joint_purchase }]
end
def questions
@questions ||= [
- Form::Sales::Questions::BuyerPrevious.new(nil, nil, self),
+ Form::Sales::Questions::BuyerPrevious.new(nil, nil, self, joint_purchase: @joint_purchase),
]
end
end
diff --git a/app/models/form/sales/pages/previous_ownership.rb b/app/models/form/sales/pages/previous_ownership.rb
index 88b438e07..50711baf7 100644
--- a/app/models/form/sales/pages/previous_ownership.rb
+++ b/app/models/form/sales/pages/previous_ownership.rb
@@ -1,12 +1,13 @@
class Form::Sales::Pages::PreviousOwnership < ::Form::Page
- def initialize(id, hsh, subsection)
- super
- @id = "previous_ownership"
+ def initialize(id, hsh, subsection, joint_purchase:)
+ super(id, hsh, subsection)
+ @joint_purchase = joint_purchase
+ @depends_on = [{ "joint_purchase?" => @joint_purchase }]
end
def questions
@questions ||= [
- Form::Sales::Questions::Prevown.new(nil, nil, self),
+ Form::Sales::Questions::Prevown.new(nil, nil, self, joint_purchase: @joint_purchase),
]
end
end
diff --git a/app/models/form/sales/questions/buyer_previous.rb b/app/models/form/sales/questions/buyer_previous.rb
index b74d23ccd..4a851a487 100644
--- a/app/models/form/sales/questions/buyer_previous.rb
+++ b/app/models/form/sales/questions/buyer_previous.rb
@@ -1,9 +1,9 @@
class Form::Sales::Questions::BuyerPrevious < ::Form::Question
- def initialize(id, hsh, page)
- super
+ def initialize(id, hsh, page, joint_purchase:)
+ super(id, hsh, page)
@id = "soctenant"
- @check_answer_label = "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?"
- @header = "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?"
+ @check_answer_label = I18n.t("check_answer_labels.soctenant", count: joint_purchase ? 2 : 1)
+ @header = I18n.t("questions.soctenant", count: joint_purchase ? 2 : 1)
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = 84
diff --git a/app/models/form/sales/questions/prevown.rb b/app/models/form/sales/questions/prevown.rb
index 92b1977f3..1ae9fb90c 100644
--- a/app/models/form/sales/questions/prevown.rb
+++ b/app/models/form/sales/questions/prevown.rb
@@ -1,9 +1,9 @@
class Form::Sales::Questions::Prevown < ::Form::Question
- def initialize(id, hsh, page)
- super
+ def initialize(id, hsh, page, joint_purchase:)
+ super(id, hsh, page)
@id = "prevown"
- @check_answer_label = "Buyers previously owned a property"
- @header = "Has the buyer previously owned a property?"
+ @check_answer_label = I18n.t("check_answer_labels.prevown", count: joint_purchase ? 2 : 1)
+ @header = I18n.t("questions.prevown", count: joint_purchase ? 2 : 1)
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = 73
diff --git a/app/models/form/sales/questions/property_local_authority_known.rb b/app/models/form/sales/questions/property_local_authority_known.rb
index 7144f80a7..196ee8e6a 100644
--- a/app/models/form/sales/questions/property_local_authority_known.rb
+++ b/app/models/form/sales/questions/property_local_authority_known.rb
@@ -3,7 +3,7 @@ class Form::Sales::Questions::PropertyLocalAuthorityKnown < ::Form::Question
super
@id = "la_known"
@check_answer_label = "Local authority known"
- @header = "Do you know the local authority of the property?"
+ @header = "Do you know the property’s local authority?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = { "la" => [1] }
diff --git a/app/models/form/sales/questions/staircase_owned.rb b/app/models/form/sales/questions/staircase_owned.rb
index f5899b8f6..b8d5a65ec 100644
--- a/app/models/form/sales/questions/staircase_owned.rb
+++ b/app/models/form/sales/questions/staircase_owned.rb
@@ -1,9 +1,9 @@
class Form::Sales::Questions::StaircaseOwned < ::Form::Question
- def initialize(id, hsh, page)
- super
+ def initialize(id, hsh, page, joint_purchase:)
+ super(id, hsh, page)
@id = "stairowned"
- @check_answer_label = "Percentage the buyer now owns in total"
- @header = "What percentage of the property does the buyer now own in total?"
+ @check_answer_label = I18n.t("check_answer_labels.stairowned", count: joint_purchase ? 2 : 1)
+ @header = I18n.t("questions.stairowned", count: joint_purchase ? 2 : 1)
@type = "numeric"
@width = 5
@min = 0
diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb
index 571968c8f..0277acffb 100644
--- a/app/models/form/sales/subsections/household_characteristics.rb
+++ b/app/models/form/sales/subsections/household_characteristics.rb
@@ -3,7 +3,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
super
@id = "household_characteristics"
@label = "Household characteristics"
- @depends_on = [{ "setup_completed?" => true }]
+ @depends_on = [{ "setup_completed?" => true, "company_buyer?" => false }]
end
def pages
@@ -92,4 +92,8 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self)]
end
end
+
+ def displayed_in_tasklist?(log)
+ !log.company_buyer?
+ end
end
diff --git a/app/models/form/sales/subsections/income_benefits_and_savings.rb b/app/models/form/sales/subsections/income_benefits_and_savings.rb
index 9ff584d47..502b87fc9 100644
--- a/app/models/form/sales/subsections/income_benefits_and_savings.rb
+++ b/app/models/form/sales/subsections/income_benefits_and_savings.rb
@@ -23,7 +23,8 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
Form::Sales::Pages::Savings.new(nil, nil, self),
Form::Sales::Pages::SavingsValueCheck.new("savings_value_check", nil, self),
Form::Sales::Pages::DepositValueCheck.new("savings_deposit_value_check", nil, self),
- Form::Sales::Pages::PreviousOwnership.new(nil, nil, self),
+ Form::Sales::Pages::PreviousOwnership.new("previous_ownership_joint_purchase", nil, self, joint_purchase: true),
+ Form::Sales::Pages::PreviousOwnership.new("previous_ownership_not_joint_purchase", nil, self, joint_purchase: false),
previous_shared_page,
].compact
end
diff --git a/app/models/form/sales/subsections/shared_ownership_scheme.rb b/app/models/form/sales/subsections/shared_ownership_scheme.rb
index 9b981279d..afd13ec47 100644
--- a/app/models/form/sales/subsections/shared_ownership_scheme.rb
+++ b/app/models/form/sales/subsections/shared_ownership_scheme.rb
@@ -10,14 +10,16 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
@pages ||= [
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::Staircase.new(nil, nil, self),
- Form::Sales::Pages::AboutStaircase.new(nil, nil, self),
+ Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true),
+ Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),
Form::Sales::Pages::Resale.new(nil, nil, self),
Form::Sales::Pages::ExchangeDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDateCheck.new(nil, nil, self),
Form::Sales::Pages::LaNominations.new(nil, nil, self),
- Form::Sales::Pages::BuyerPrevious.new(nil, nil, self),
+ Form::Sales::Pages::BuyerPrevious.new("buyer_previous_joint_purchase", nil, self, joint_purchase: true),
+ Form::Sales::Pages::BuyerPrevious.new("buyer_previous_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),
Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self),
Form::Sales::Pages::PreviousTenure.new(nil, nil, self),
diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
index 5bfc0a2aa..bfaa622b2 100644
--- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
@@ -29,6 +29,10 @@ module Forms
"/files/bulk-upload-lettings-template-v1.xlsx"
end
+ def specification_path
+ "/files/bulk-upload-lettings-specification-2022-23.xlsx"
+ end
+
def year_combo
"#{year}/#{year + 1 - 2000}"
end
diff --git a/app/models/forms/bulk_upload_sales/prepare_your_file.rb b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
index 5c7e775ef..52d70a85c 100644
--- a/app/models/forms/bulk_upload_sales/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
@@ -27,6 +27,10 @@ module Forms
"/files/bulk-upload-sales-template-v1.xlsx"
end
+ def specification_path
+ "/files/bulk-upload-sales-specification-2022-23.xlsx"
+ end
+
def year_combo
"#{year}/#{year + 1 - 2000}"
end
diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb
index 6864738cf..6491c100f 100644
--- a/app/models/lettings_log.rb
+++ b/app/models/lettings_log.rb
@@ -40,7 +40,6 @@ class LettingsLog < Log
scope :filter_by_year, ->(year) { where(startdate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
scope :filter_by_tenant_code, ->(tenant_code) { where("tenancycode ILIKE ?", "%#{tenant_code}%") }
scope :filter_by_propcode, ->(propcode) { where("propcode ILIKE ?", "%#{propcode}%") }
- scope :filter_by_postcode, ->(postcode_full) { where("REPLACE(postcode_full, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") }
scope :filter_by_location_postcode, ->(postcode_full) { left_joins(:location).where("REPLACE(locations.postcode, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") }
scope :search_by, lambda { |param|
filter_by_location_postcode(param)
diff --git a/app/models/local_authority.rb b/app/models/local_authority.rb
new file mode 100644
index 000000000..4017ada52
--- /dev/null
+++ b/app/models/local_authority.rb
@@ -0,0 +1,2 @@
+class LocalAuthority < ApplicationRecord
+end
diff --git a/app/models/log.rb b/app/models/log.rb
index 505839c69..f5f5f4946 100644
--- a/app/models/log.rb
+++ b/app/models/log.rb
@@ -18,6 +18,7 @@ class Log < ApplicationRecord
years.each { |year| query = query.or(filter_by_year(year)) }
query.all
}
+ scope :filter_by_postcode, ->(postcode_full) { where("REPLACE(postcode_full, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") }
scope :filter_by_id, ->(id) { where(id:) }
scope :filter_by_user, lambda { |selected_user, user|
if !selected_user.include?("all") && user.present?
@@ -80,6 +81,12 @@ class Log < ApplicationRecord
end
end
+ (2..8).each do |person_num|
+ define_method("person_#{person_num}_child_relation?") do
+ send("relat#{person_num}") == "C"
+ end
+ end
+
private
def plural_gender_for_person(person_num)
diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb
index b2c6b08da..90f130c04 100644
--- a/app/models/sales_log.rb
+++ b/app/models/sales_log.rb
@@ -33,7 +33,12 @@ class SalesLog < Log
before_validation :set_derived_fields!
scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
- scope :search_by, ->(param) { filter_by_id(param) }
+ scope :filter_by_purchaser_code, ->(purchid) { where("purchid ILIKE ?", "%#{purchid}%") }
+ scope :search_by, lambda { |param|
+ filter_by_purchaser_code(param)
+ .or(filter_by_postcode(param))
+ .or(filter_by_id(param))
+ }
scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org) }
OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze
@@ -257,6 +262,10 @@ class SalesLog < Log
ownershipsch == 1
end
+ def company_buyer?
+ companybuy == 1
+ end
+
def buyers_age_for_old_persons_shared_ownership_invalid?
return unless old_persons_shared_ownership?
diff --git a/app/models/validations/sales/household_validations.rb b/app/models/validations/sales/household_validations.rb
index f9318ab0f..ec73b4abb 100644
--- a/app/models/validations/sales/household_validations.rb
+++ b/app/models/validations/sales/household_validations.rb
@@ -14,8 +14,8 @@ module Validations::Sales::HouseholdValidations
return unless record.postcode_full && record.ppostcode_full && record.discounted_ownership_sale?
unless record.postcode_full == record.ppostcode_full
- record.errors.add :postcode_full, I18n.t("validations.household.postcode.discounted_ownership")
- record.errors.add :ppostcode_full, I18n.t("validations.household.postcode.discounted_ownership")
+ record.errors.add :postcode_full, :postcodes_not_matching, message: I18n.t("validations.household.postcode.discounted_ownership")
+ record.errors.add :ppostcode_full, :postcodes_not_matching, message: I18n.t("validations.household.postcode.discounted_ownership")
end
end
diff --git a/app/services/bulk_upload/lettings/row_parser.rb b/app/services/bulk_upload/lettings/row_parser.rb
index 426f122a1..b772d0153 100644
--- a/app/services/bulk_upload/lettings/row_parser.rb
+++ b/app/services/bulk_upload/lettings/row_parser.rb
@@ -143,6 +143,20 @@ class BulkUpload::Lettings::RowParser
validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") },
inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }
validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } }
+
+ validates :field_12, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }
+ validates :field_13, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }
+ validates :field_14, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }
+ validates :field_15, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }
+ validates :field_16, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }
+ validates :field_17, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }
+ validates :field_18, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }
+ validates :field_19, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }
+
+ validates :field_96, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") }
+ validates :field_97, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") }
+ validates :field_98, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") }
+
validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }
validate :validate_data_types
@@ -181,7 +195,12 @@ class BulkUpload::Lettings::RowParser
log.errors.each do |error|
fields = field_mapping_for_errors[error.attribute] || []
- fields.each { |field| errors.add(field, error.type) }
+
+ fields.each do |field|
+ unless errors.include?(field)
+ errors.add(field, error.type)
+ end
+ end
end
errors.blank?
@@ -203,6 +222,10 @@ class BulkUpload::Lettings::RowParser
block_log_creation
end
+ def setup_section_incomplete?
+ log.form.setup_sections[0].subsections[0].is_incomplete?(log)
+ end
+
private
def validate_location_related
@@ -222,7 +245,7 @@ private
def validate_location_exists
if scheme && field_5.present? && location.nil?
- errors.add(:field_5, "Location could be found with provided scheme code")
+ errors.add(:field_5, "Location could be found with provided scheme code", category: :setup)
end
end
@@ -240,7 +263,7 @@ private
def validate_scheme_exists
if field_4.present? && scheme.nil?
- errors.add(:field_4, "The management group code is not correct")
+ errors.add(:field_4, "The management group code is not correct", category: :setup)
end
end
@@ -254,7 +277,7 @@ private
def validate_managing_org_exists
if managing_organisation.nil?
errors.delete(:field_113)
- errors.add(:field_113, "The managing organisation code is incorrect")
+ errors.add(:field_113, "The managing organisation code is incorrect", category: :setup)
end
end
@@ -269,7 +292,7 @@ private
def validate_owning_org_exists
if owning_organisation.nil?
errors.delete(:field_111)
- errors.add(:field_111, "The owning organisation code is incorrect")
+ errors.add(:field_111, "The owning organisation code is incorrect", category: :setup)
end
end
@@ -337,7 +360,7 @@ private
end
def validate_relevant_collection_window
- return unless start_date && bulk_upload.form
+ return if start_date.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(start_date)
errors.add(:field_96, I18n.t("validations.date.outside_collection_window"))
@@ -347,6 +370,8 @@ private
end
def start_date
+ return if field_98.blank? || field_97.blank? || field_96.blank?
+
Date.parse("20#{field_98.to_s.rjust(2, '0')}-#{field_97}-#{field_96}")
rescue StandardError
nil
@@ -387,10 +412,26 @@ private
next if log.optional_fields.include?(question.id)
next if question.completed?(log)
- fields.each { |field| errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase)) }
+ if setup_question?(question)
+ fields.each do |field|
+ if errors[field].present?
+ errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase), 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.check_answer_label&.downcase))
+ end
+ end
+ end
end
end
+ def setup_question?(question)
+ log.form.setup_sections[0].subsections[0].questions.include?(question)
+ end
+
def field_mapping_for_errors
{
lettype: [:field_1],
@@ -398,6 +439,8 @@ private
postcode_known: %i[field_107 field_108 field_109],
postcode_full: %i[field_107 field_108 field_109],
la: %i[field_107],
+ owning_organisation: [:field_111],
+ managing_organisation: [:field_113],
owning_organisation_id: [:field_111],
managing_organisation_id: [:field_113],
renewal: [:field_134],
@@ -628,28 +671,28 @@ private
attributes["declaration"] = field_132
attributes["age1_known"] = field_12 == "R" ? 1 : 0
- attributes["age1"] = field_12 if attributes["age1_known"].zero?
+ attributes["age1"] = field_12 if attributes["age1_known"].zero? && field_12&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age2_known"] = field_13 == "R" ? 1 : 0
- attributes["age2"] = field_13 if attributes["age2_known"].zero?
+ attributes["age2"] = field_13 if attributes["age2_known"].zero? && field_13&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age3_known"] = field_14 == "R" ? 1 : 0
- attributes["age3"] = field_14 if attributes["age3_known"].zero?
+ attributes["age3"] = field_14 if attributes["age3_known"].zero? && field_14&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age4_known"] = field_15 == "R" ? 1 : 0
- attributes["age4"] = field_15 if attributes["age4_known"].zero?
+ attributes["age4"] = field_15 if attributes["age4_known"].zero? && field_15&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age5_known"] = field_16 == "R" ? 1 : 0
- attributes["age5"] = field_16 if attributes["age5_known"].zero?
+ attributes["age5"] = field_16 if attributes["age5_known"].zero? && field_16&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age6_known"] = field_17 == "R" ? 1 : 0
- attributes["age6"] = field_17 if attributes["age6_known"].zero?
+ attributes["age6"] = field_17 if attributes["age6_known"].zero? && field_17&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age7_known"] = field_18 == "R" ? 1 : 0
- attributes["age7"] = field_18 if attributes["age7_known"].zero?
+ attributes["age7"] = field_18 if attributes["age7_known"].zero? && field_18&.match(/\A\d{1,3}\z|\AR\z/)
attributes["age8_known"] = field_19 == "R" ? 1 : 0
- attributes["age8"] = field_19 if attributes["age8_known"].zero?
+ attributes["age8"] = field_19 if attributes["age8_known"].zero? && field_19&.match(/\A\d{1,3}\z|\AR\z/)
attributes["sex1"] = field_20
attributes["sex2"] = field_21
@@ -868,6 +911,8 @@ private
0
when nil
rsnvac == 14 ? 1 : 0
+ else
+ field_134
end
end
diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb
index 992f06196..779f0315b 100644
--- a/app/services/bulk_upload/lettings/validator.rb
+++ b/app/services/bulk_upload/lettings/validator.rb
@@ -168,6 +168,7 @@ class BulkUpload::Lettings::Validator
row:,
cell: "#{cols[field_number_for_attribute(error.attribute) - col_offset + 1]}#{row}",
col: cols[field_number_for_attribute(error.attribute) - col_offset + 1],
+ category: error.options[:category],
)
end
end
@@ -185,12 +186,12 @@ class BulkUpload::Lettings::Validator
QUESTIONS[field]
end
-private
-
def any_setup_sections_incomplete?
- row_parsers.any? { |row_parser| row_parser.log.form.setup_sections[0].subsections[0].is_incomplete?(row_parser.log) }
+ row_parsers.any?(&:setup_section_incomplete?)
end
+private
+
def over_column_error_threshold?
fields = ("field_1".."field_134").to_a
percentage_threshold = (row_parsers.size * COLUMN_PERCENTAGE_ERROR_THRESHOLD).ceil
diff --git a/app/services/bulk_upload/processor.rb b/app/services/bulk_upload/processor.rb
index bbf7e6cd1..dcf68e594 100644
--- a/app/services/bulk_upload/processor.rb
+++ b/app/services/bulk_upload/processor.rb
@@ -12,11 +12,15 @@ class BulkUpload::Processor
validator.call
- create_logs if validator.create_logs?
- send_correct_and_upload_again_mail unless validator.create_logs?
-
- send_fix_errors_mail if created_logs_but_incompleted?
- send_success_mail if created_logs_and_all_completed?
+ if validator.any_setup_sections_incomplete?
+ send_setup_errors_mail
+ elsif validator.create_logs?
+ create_logs
+ send_fix_errors_mail if created_logs_but_incompleted?
+ send_success_mail if created_logs_and_all_completed?
+ else
+ send_correct_and_upload_again_mail
+ end
rescue StandardError => e
Sentry.capture_exception(e)
send_failure_mail
@@ -26,16 +30,28 @@ class BulkUpload::Processor
private
+ def send_setup_errors_mail
+ BulkUploadMailer
+ .send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
+ .deliver_later
+ end
+
def send_correct_and_upload_again_mail
- BulkUploadMailer.send_correct_and_upload_again_mail(bulk_upload:).deliver_later
+ BulkUploadMailer
+ .send_correct_and_upload_again_mail(bulk_upload:)
+ .deliver_later
end
def send_fix_errors_mail
- BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later
+ BulkUploadMailer
+ .send_bulk_upload_with_errors_mail(bulk_upload:)
+ .deliver_later
end
def send_success_mail
- BulkUploadMailer.send_bulk_upload_complete_mail(user:, bulk_upload:).deliver_later
+ BulkUploadMailer
+ .send_bulk_upload_complete_mail(user:, bulk_upload:)
+ .deliver_later
end
def created_logs_but_incompleted?
@@ -47,7 +63,9 @@ private
end
def send_failure_mail
- BulkUploadMailer.send_bulk_upload_failed_service_error_mail(bulk_upload:).deliver_later
+ BulkUploadMailer
+ .send_bulk_upload_failed_service_error_mail(bulk_upload:)
+ .deliver_later
end
def user
diff --git a/app/services/imports/local_authorities_service.rb b/app/services/imports/local_authorities_service.rb
new file mode 100644
index 000000000..23a4e2375
--- /dev/null
+++ b/app/services/imports/local_authorities_service.rb
@@ -0,0 +1,25 @@
+require "csv"
+
+module Imports
+ class LocalAuthoritiesService
+ attr_reader :path, :count
+
+ def initialize(path:)
+ @path = path
+ @count = 0
+ end
+
+ def call
+ CSV.foreach(path, headers: true) do |row|
+ LocalAuthority.upsert(
+ { code: row["code"],
+ name: row["name"],
+ start_date: Time.zone.local(row["start_year"], 4, 1),
+ end_date: (Time.zone.local(row["end_year"], 3, 31) if row["end_year"]) },
+ unique_by: %i[code],
+ )
+ @count += 1
+ end
+ end
+ end
+end
diff --git a/app/services/imports/sales_logs_import_service.rb b/app/services/imports/sales_logs_import_service.rb
index 3593a1227..ed127bff2 100644
--- a/app/services/imports/sales_logs_import_service.rb
+++ b/app/services/imports/sales_logs_import_service.rb
@@ -16,7 +16,9 @@ module Imports
private
def create_log(xml_doc)
+ # only import sales logs from 22/23 collection period onwards
return unless meta_field_value(xml_doc, "form-name").include?("Sales")
+ return unless compose_date(xml_doc, "DAY", "MONTH", "YEAR") >= Time.zone.local(2022, 4, 1)
attributes = {}
@@ -76,7 +78,7 @@ module Imports
attributes["inc2mort"] = unsafe_string_as_integer(xml_doc, "Q2Person2MortApplication")
attributes["hb"] = unsafe_string_as_integer(xml_doc, "Q2a")
attributes["frombeds"] = safe_string_as_integer(xml_doc, "Q20Bedrooms")
- attributes["staircase"] = unsafe_string_as_integer(xml_doc, "Q17aStaircase")
+ attributes["staircase"] = unsafe_string_as_integer(xml_doc, "Q17aStaircase") if attributes["ownershipsch"] == 1
attributes["stairbought"] = safe_string_as_integer(xml_doc, "PercentBought")
attributes["stairowned"] = safe_string_as_integer(xml_doc, "PercentOwns") if attributes["staircase"] == 1
attributes["mrent"] = safe_string_as_decimal(xml_doc, "Q28MonthlyRent")
@@ -102,7 +104,6 @@ module Imports
attributes["ppcodenk"] = previous_postcode_known(xml_doc, attributes["ppostcode_full"], attributes["prevloc"]) # Q7UNKNOWNPOSTCODE check mapping
attributes["ppostc1"] = string_or_nil(xml_doc, "PPOSTC1")
attributes["ppostc2"] = string_or_nil(xml_doc, "PPOSTC2")
- attributes["previous_la_known"] = nil
attributes["hhregres"] = unsafe_string_as_integer(xml_doc, "ArmedF")
attributes["hhregresstill"] = still_serving(xml_doc)
attributes["proplen"] = safe_string_as_integer(xml_doc, "Q16aProplen2") || safe_string_as_integer(xml_doc, "Q16aProplensec2")
@@ -111,7 +112,7 @@ module Imports
attributes["prevten"] = unsafe_string_as_integer(xml_doc, "Q6PrevTenure")
attributes["mortlen"] = mortgage_length(xml_doc, attributes)
attributes["extrabor"] = borrowing(xml_doc, attributes)
- attributes["mortgageused"] = unsafe_string_as_integer(xml_doc, "MORTGAGEUSED")
+ attributes["mortgageused"] = mortgage_used(xml_doc, attributes)
attributes["wchair"] = unsafe_string_as_integer(xml_doc, "Q15Wheelchair")
attributes["armedforcesspouse"] = unsafe_string_as_integer(xml_doc, "ARMEDFORCESSPOUSE")
attributes["hodate"] = compose_date(xml_doc, "HODAY", "HOMONTH", "HOYEAR")
@@ -130,13 +131,29 @@ module Imports
attributes["prevshared"] = nil # 23/24 variable
attributes["staircasesale"] = nil # 23/24 variable
- # Required for our form invalidated questions (not present in import)
- attributes["previous_la_known"] = 1 if attributes["prevloc"].present? && attributes["ppostcode_full"].blank?
- if attributes["la"].present? && attributes["postcode_full"].blank?
+ attributes["previous_la_known"] = 1 if attributes["prevloc"].present?
+ if attributes["la"].present?
attributes["la_known"] = 1
attributes["is_la_inferred"] = false
end
+ # Soft validations can become required answers, set them to yes by default
+ attributes["mortgage_value_check"] = 0
+ attributes["shared_ownership_deposit_value_check"] = 0
+ attributes["value_value_check"] = 0
+ attributes["savings_value_check"] = 0
+ attributes["income1_value_check"] = 0
+ attributes["deposit_value_check"] = 0
+ attributes["wheel_value_check"] = 0
+ attributes["retirement_value_check"] = 0
+ attributes["extrabor_value_check"] = 0
+ attributes["grant_value_check"] = 0
+ attributes["staircase_bought_value_check"] = 0
+ attributes["deposit_and_mortgage_value_check"] = 0
+ attributes["old_persons_shared_ownership_value_check"] = 0
+ attributes["income2_value_check"] = 0
+ attributes["monthly_charges_value_check"] = 0
+
# Sets the log creator
owner_id = meta_field_value(xml_doc, "owner-user-id").strip
if owner_id.present?
@@ -168,9 +185,31 @@ module Imports
end
end
- def rescue_validation_or_raise(sales_log, _attributes, _previous_status, exception)
- @logger.error("Log #{sales_log.old_id}: Failed to import")
- raise exception
+ def rescue_validation_or_raise(sales_log, attributes, previous_status, exception)
+ if %w[saved submitted-invalid].include?(previous_status)
+ sales_log.errors.each do |error|
+ @logger.warn("Log #{sales_log.old_id}: Removing field #{error.attribute} from log triggering validation: #{error.type}")
+ attributes.delete(error.attribute.to_s)
+ end
+ @logs_overridden << sales_log.old_id
+ if sales_log.errors.of_kind?(:postcode_full, :postcodes_not_matching)
+ @logger.warn("Log #{sales_log.old_id}: Removing postcode known and previous postcode known as the postcodes are invalid")
+ attributes.delete("pcodenk")
+ attributes.delete("ppcodenk")
+ end
+ save_sales_log(attributes, previous_status)
+ else
+ @logger.error("Log #{sales_log.old_id}: Failed to import")
+ sales_log.errors.each do |error|
+ @logger.error("Validation error: Field #{error.attribute}:")
+ @logger.error("\tOwning Organisation: #{sales_log.owning_organisation&.name}")
+ @logger.error("\tOld CORE ID: #{sales_log.old_id}")
+ @logger.error("\tOld CORE: #{attributes[error.attribute.to_s]&.inspect}")
+ @logger.error("\tNew CORE: #{sales_log.read_attribute(error.attribute)&.inspect}")
+ @logger.error("\tError message: #{error.type}")
+ end
+ raise exception
+ end
end
def compute_differences(sales_log, attributes)
@@ -189,6 +228,7 @@ module Imports
def fields_not_present_in_softwire_data
%w[created_by
income1_value_check
+ income2_value_check
mortgage_value_check
savings_value_check
deposit_value_check
@@ -388,7 +428,6 @@ module Imports
end
def monthly_charges(xml_doc, attributes)
- safe_string_as_decimal(xml_doc, "Q29MonthlyCharges")
case attributes["ownershipsch"]
when 1
safe_string_as_decimal(xml_doc, "Q29MonthlyCharges")
@@ -446,6 +485,17 @@ module Imports
UKPostcode.parse(postcode).to_s
end
+ def mortgage_used(xml_doc, attributes)
+ mortgageused = unsafe_string_as_integer(xml_doc, "MORTGAGEUSED")
+ return mortgageused unless mortgageused == 3
+
+ if attributes["mortgage"].present? || attributes["mortlen"].present? || attributes["extrabor"].present?
+ 1 # yes
+ else
+ 3 # don't know
+ end
+ end
+
def set_default_values(attributes)
attributes["armedforcesspouse"] ||= 7
attributes["hhregres"] ||= 8
diff --git a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb
index 199d87e5f..e48a190f9 100644
--- a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb
+++ b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb
@@ -13,7 +13,7 @@
Create your file
- Download the <%= govuk_link_to "bulk lettings template", @form.template_path %>
- - Export the data from your housing management system, matching the template
+ - Export the data from your housing management system, matching the template. <%= govuk_link_to "Find out more about exporting your data", guidance_bulk_upload_lettings_logs_path %>
- If you cannot export it in this format, you may have to input it manually
- You can not have a file with both general needs logs and supported housing logs. These must be in separate files
diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb
index 0157b66eb..d2a6bd70c 100644
--- a/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb
+++ b/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb
@@ -13,7 +13,7 @@
Create your file
- Download the <%= govuk_link_to "bulk sales template", @form.template_path %>
- - Export the data from your housing management system, matching the template
+ - Export the data from your housing management system, matching the template. <%= govuk_link_to "Find out more about exporting your data", guidance_bulk_upload_sales_logs_path %>
- If you cannot export it in this format, you may have to input it manually
diff --git a/app/views/bulk_upload_shared/guidance.html.erb b/app/views/bulk_upload_shared/guidance.html.erb
new file mode 100644
index 000000000..0c1f67bcc
--- /dev/null
+++ b/app/views/bulk_upload_shared/guidance.html.erb
@@ -0,0 +1,52 @@
+<% content_for :before_content do %>
+ <%= govuk_back_link href: :back %>
+<% end %>
+
+
+
+
+
How to upload logs in bulk
+
+
+
Uploading sales and lettings logs
+
You can upload one sales or lettings log at a time, or many at once (known as ‘bulk upload’) with a comma-separated values (CSV) spreadsheet file.
+
Bulk upload may be easier if your organisation deals with many logs, or if you can export CSV data from your Housing Management System (HMS). If your organisation only deals with a small amount of logs, or you cannot export CSV data, it’s probably easier to enter logs individually.
+ <%= govuk_warning_text text: "You cannot upload lettings and sales logs with the same template - you must export each data type separately, then upload them" %>
+
+
+
+
Creating your CSV files
+
To bulk upload successfully, all spreadsheets must be in the correct CSV format.
+
In most programs, you must resave files as CSV - it’s not usually the default setting. CSV files are also unformatted, so any formatting added before saving (for example colours) will automatically disappear.
+ <%= govuk_details(summary_text: "More about CSV") do %>
+
A CSV file is a basic spreadsheet with data values in plain text, and columns separated by commas. Each data row is a new text line.
+
CSV data is easier to process than more common advanced spreadsheet formats, for example Excel. It means CSV is well suited to upload large, or multiple data sets.
+ <% end %>
+
+
+
+
Exporting CSV data
+
Export CSV data directly from your current systems, or export then adjust it to CSV.
+
You can then upload it via a button at the top of the lettings and sales logs pages.
+ <%= govuk_details(summary_text: "My organisation has a CMS") do %>
+
Some HMS providers sell an add-on "eCORE" module, which exports CSV data for you.
+
It can take HMS providers a while to update these per new collection year, so you may have to wait for updates to export, or adjust your data manually post-export.
+ <% end %>
+ <%= govuk_details(summary_text: "My organisation does not have a CMS") do %>
+
Your organisation’s IT team may be able to export CSV data for you - <%= govuk_link_to "find out more about data specification", @form.specification_path %>. This document outlines:
+
+ - required fields
+ - each field's valid response
+ - if/when certain fields can be left blank
+
+
Fields can appear in any order, as long as you include the <%= govuk_link_to "template document", @form.template_path %> headers, to easily identify what each column represents. You can rearrange data columns to match your system exports, copy-pasting multiple columns at once. For data stored in multiple systems, you can copy-paste all columns for one system next to each other, repeating this for subsequent system exports.
+ <% end %>
+
+
+
+
Getting help
+
There is no step-by-step bulk upload guide like there is with single log upload. However, you can download <%= govuk_link_to "our template", @form.template_path %>, which you can copy-paste data into from your systems column-by-column. You can also view a post-upload report showing any data errors, and our <%= govuk_link_to "data specification", @form.specification_path %> can help fix these.
+
If you still need support mapping data in the way we need, DLUHC’s helpdesk can help. If your data is across multiple systems, or is hard to export as a single file in the correct format, you could try different exports, or copy-pasting data by hand.
+
+
+
diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb
index 31768c731..0e4cd76df 100644
--- a/app/views/logs/index.html.erb
+++ b/app/views/logs/index.html.erb
@@ -64,9 +64,9 @@
<%= render partial: "log_filters" %>
- <%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %>
+ <%= render SearchComponent.new(current_user:, search_label: search_label_for_controller(controller), value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
- <%= render partial: "log_list", locals: { logs: @logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, csv_download_url: csv_download_lettings_logs_path(search: @search_term) } %>
+ <%= render partial: "log_list", locals: { logs: @logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, csv_download_url: csv_download_url_for_controller(controller) } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
diff --git a/config/local_authorities_data/initial_local_authorities.csv b/config/local_authorities_data/initial_local_authorities.csv
new file mode 100644
index 000000000..f80fbb6f5
--- /dev/null
+++ b/config/local_authorities_data/initial_local_authorities.csv
@@ -0,0 +1,388 @@
+code,name,start_year,end_year
+S12000033,Aberdeen City,2021,
+S12000034,Aberdeenshire,2021,
+E07000223,Adur,2021,
+E07000032,Amber Valley,2021,
+S12000041,Angus,2021,
+N09000001,Antrim and Newtownabbey,2021,
+N09000011,Ards and North Down,2021,
+S12000035,Argyll and Bute,2021,
+N09000002,"Armagh City, Banbridge and Craigavon",2021,
+E07000224,Arun,2021,
+E07000170,Ashfield,2021,
+E07000105,Ashford,2021,
+E07000200,Babergh,2021,
+E09000002,Barking and Dagenham,2021,
+E09000003,Barnet,2021,
+E08000016,Barnsley,2021,
+E07000066,Basildon,2021,
+E07000084,Basingstoke and Deane,2021,
+E07000171,Bassetlaw,2021,
+E06000022,Bath and North East Somerset,2021,
+E06000055,Bedford,2021,
+N09000003,Belfast,2021,
+E09000004,Bexley,2021,
+E08000025,Birmingham,2021,
+E07000129,Blaby,2021,
+E06000008,Blackburn with Darwen,2021,
+E06000009,Blackpool,2021,
+W06000019,Blaenau Gwent,2021,
+E07000033,Bolsover,2021,
+E08000001,Bolton,2021,
+E07000136,Boston,2021,
+E06000058,"Bournemouth, Christchurch and Poole",2021,
+E06000036,Bracknell Forest,2021,
+E08000032,Bradford,2021,
+E07000067,Braintree,2021,
+E07000143,Breckland,2021,
+E09000005,Brent,2021,
+E07000068,Brentwood,2021,
+W06000013,Bridgend,2021,
+E06000043,Brighton and Hove,2021,
+E06000023,"Bristol, City of",2021,
+E07000144,Broadland,2021,
+E09000006,Bromley,2021,
+E07000234,Bromsgrove,2021,
+E07000095,Broxbourne,2021,
+E07000172,Broxtowe,2021,
+E06000060,Buckinghamshire,2021,
+E07000117,Burnley,2021,
+E08000002,Bury,2021,
+W06000018,Caerphilly,2021,
+E08000033,Calderdale,2021,
+E07000008,Cambridge,2021,
+E09000007,Camden,2021,
+E07000192,Cannock Chase,2021,
+E07000106,Canterbury,2021,
+W06000015,Cardiff,2021,
+W06000010,Carmarthenshire,2021,
+E07000069,Castle Point,2021,
+N09000004,Causeway Coast and Glens,2021,
+E06000056,Central Bedfordshire,2021,
+W06000008,Ceredigion,2021,
+E07000130,Charnwood,2021,
+E07000070,Chelmsford,2021,
+E07000078,Cheltenham,2021,
+E07000177,Cherwell,2021,
+E06000049,Cheshire East,2021,
+E06000050,Cheshire West and Chester,2021,
+E07000034,Chesterfield,2021,
+E07000225,Chichester,2021,
+E07000118,Chorley,2021,
+S12000036,City of Edinburgh,2021,
+E09000001,City of London,2021,
+S12000005,Clackmannanshire,2021,
+E07000071,Colchester,2021,
+W06000003,Conwy,2021,
+E07000150,Corby,2021,
+E06000052,Cornwall,2021,
+E07000079,Cotswold,2021,
+E06000047,County Durham,2021,
+E08000026,Coventry,2021,
+E07000226,Crawley,2021,
+E09000008,Croydon,2021,
+E06000063,Cumberland,2023,
+E07000096,Dacorum,2021,
+E06000005,Darlington,2021,
+E07000107,Dartford,2021,
+E07000151,Daventry,2021,
+W06000004,Denbighshire,2021,
+E06000015,Derby,2021,
+E07000035,Derbyshire Dales,2021,
+N09000005,Derry City and Strabane,2021,
+E08000017,Doncaster,2021,
+E06000059,Dorset,2021,
+E07000108,Dover,2021,
+E08000027,Dudley,2021,
+S12000006,Dumfries and Galloway,2021,
+S12000042,Dundee City,2021,
+E09000009,Ealing,2021,
+S12000008,East Ayrshire,2021,
+E07000009,East Cambridgeshire,2021,
+E07000040,East Devon,2021,
+S12000045,East Dunbartonshire,2021,
+E07000085,East Hampshire,2021,
+E07000242,East Hertfordshire,2021,
+E07000137,East Lindsey,2021,
+S12000010,East Lothian,2021,
+E07000152,East Northamptonshire,2021,
+S12000011,East Renfrewshire,2021,
+E06000011,East Riding of Yorkshire,2021,
+E07000193,East Staffordshire,2021,
+E07000244,East Suffolk,2021,
+E07000061,Eastbourne,2021,
+E07000086,Eastleigh,2021,
+E07000207,Elmbridge,2021,
+E09000010,Enfield,2021,
+E07000072,Epping Forest,2021,
+E07000208,Epsom and Ewell,2021,
+E07000036,Erewash,2021,
+E07000041,Exeter,2021,
+S12000014,Falkirk,2021,
+E07000087,Fareham,2021,
+E07000010,Fenland,2021,
+N09000006,Fermanagh and Omagh,2021,
+S12000047,Fife,2021,
+W06000005,Flintshire,2021,
+E07000112,Folkestone and Hythe,2021,
+E07000080,Forest of Dean,2021,
+E07000119,Fylde,2021,
+E08000037,Gateshead,2021,
+E07000173,Gedling,2021,
+S12000049,Glasgow City,2021,
+E07000081,Gloucester,2021,
+E07000088,Gosport,2021,
+E07000109,Gravesham,2021,
+E07000145,Great Yarmouth,2021,
+E09000011,Greenwich,2021,
+E07000209,Guildford,2021,
+W06000002,Gwynedd,2021,
+E09000012,Hackney,2021,
+E06000006,Halton,2021,
+E09000013,Hammersmith and Fulham,2021,
+E07000131,Harborough,2021,
+E09000014,Haringey,2021,
+E07000073,Harlow,2021,
+E09000015,Harrow,2021,
+E07000089,Hart,2021,
+E06000001,Hartlepool,2021,
+E07000062,Hastings,2021,
+E07000090,Havant,2021,
+E09000016,Havering,2021,
+E06000019,"Herefordshire, County of",2021,
+E07000098,Hertsmere,2021,
+E07000037,High Peak,2021,
+S12000017,Highland,2021,
+E09000017,Hillingdon,2021,
+E07000132,Hinckley and Bosworth,2021,
+E07000227,Horsham,2021,
+E09000018,Hounslow,2021,
+E07000011,Huntingdonshire,2021,
+E07000120,Hyndburn,2021,
+S12000018,Inverclyde,2021,
+E07000202,Ipswich,2021,
+W06000001,Isle of Anglesey,2021,
+E06000046,Isle of Wight,2021,
+E06000053,Isles of Scilly,2021,
+E09000019,Islington,2021,
+E09000020,Kensington and Chelsea,2021,
+E07000153,Kettering,2021,
+E07000146,King’s Lynn and West Norfolk,2021,
+E06000010,"Kingston upon Hull, City of",2021,
+E09000021,Kingston upon Thames,2021,
+E08000034,Kirklees,2021,
+E08000011,Knowsley,2021,
+E09000022,Lambeth,2021,
+E07000121,Lancaster,2021,
+E08000035,Leeds,2021,
+E06000016,Leicester,2021,
+E07000063,Lewes,2021,
+E09000023,Lewisham,2021,
+E07000194,Lichfield,2021,
+E07000138,Lincoln,2021,
+N09000007,Lisburn and Castlereagh,2021,
+E08000012,Liverpool,2021,
+E06000032,Luton,2021,
+E07000110,Maidstone,2021,
+E07000074,Maldon,2021,
+E07000235,Malvern Hills,2021,
+E08000003,Manchester,2021,
+E07000174,Mansfield,2021,
+E06000035,Medway,2021,
+E07000133,Melton,2021,
+W06000024,Merthyr Tydfil,2021,
+E09000024,Merton,2021,
+E07000042,Mid Devon,2021,
+E07000203,Mid Suffolk,2021,
+E07000228,Mid Sussex,2021,
+N09000009,Mid Ulster,2021,
+N09000008,Mid and East Antrim,2021,
+E06000002,Middlesbrough,2021,
+S12000019,Midlothian,2021,
+E06000042,Milton Keynes,2021,
+E07000210,Mole Valley,2021,
+W06000021,Monmouthshire,2021,
+S12000020,Moray,2021,
+S12000013,Na h-Eileanan Siar,2021,
+W06000012,Neath Port Talbot,2021,
+E07000091,New Forest,2021,
+E07000175,Newark and Sherwood,2021,
+E08000021,Newcastle upon Tyne,2021,
+E07000195,Newcastle-under-Lyme,2021,
+E09000025,Newham,2021,
+W06000022,Newport,2021,
+N09000010,"Newry, Mourne and Down",2021,
+S12000021,North Ayrshire,2021,
+E07000043,North Devon,2021,
+E07000038,North East Derbyshire,2021,
+E06000012,North East Lincolnshire,2021,
+E07000099,North Hertfordshire,2021,
+E07000139,North Kesteven,2021,
+S12000050,North Lanarkshire,2021,
+E06000013,North Lincolnshire,2021,
+E07000147,North Norfolk,2021,
+E06000024,North Somerset,2021,
+E08000022,North Tyneside,2021,
+E07000218,North Warwickshire,2021,
+E07000134,North West Leicestershire,2021,
+E07000154,Northampton,2021,
+E06000057,Northumberland,2021,
+E07000148,Norwich,2021,
+E06000018,Nottingham,2021,
+E07000219,Nuneaton and Bedworth,2021,
+E07000135,Oadby and Wigston,2021,
+E08000004,Oldham,2021,
+S12000023,Orkney Islands,2021,
+E07000178,Oxford,2021,
+W06000009,Pembrokeshire,2021,
+E07000122,Pendle,2021,
+S12000048,Perth and Kinross,2021,
+E06000031,Peterborough,2021,
+E06000026,Plymouth,2021,
+E06000044,Portsmouth,2021,
+W06000023,Powys,2021,
+E07000123,Preston,2021,
+E06000038,Reading,2021,
+E09000026,Redbridge,2021,
+E06000003,Redcar and Cleveland,2021,
+E07000236,Redditch,2021,
+E07000211,Reigate and Banstead,2021,
+S12000038,Renfrewshire,2021,
+W06000016,Rhondda Cynon Taf,2021,
+E07000124,Ribble Valley,2021,
+E09000027,Richmond upon Thames,2021,
+E08000005,Rochdale,2021,
+E07000075,Rochford,2021,
+E07000125,Rossendale,2021,
+E07000064,Rother,2021,
+E08000018,Rotherham,2021,
+E07000220,Rugby,2021,
+E07000212,Runnymede,2021,
+E07000176,Rushcliffe,2021,
+E07000092,Rushmoor,2021,
+E06000017,Rutland,2021,
+E08000006,Salford,2021,
+E08000028,Sandwell,2021,
+S12000026,Scottish Borders,2021,
+E08000014,Sefton,2021,
+E07000111,Sevenoaks,2021,
+E08000019,Sheffield,2021,
+S12000027,Shetland Islands,2021,
+E06000051,Shropshire,2021,
+E06000039,Slough,2021,
+E08000029,Solihull,2021,
+E06000066,Somerset,2023,
+S12000028,South Ayrshire,2021,
+E07000012,South Cambridgeshire,2021,
+E07000039,South Derbyshire,2021,
+E06000025,South Gloucestershire,2021,
+E07000044,South Hams,2021,
+E07000140,South Holland,2021,
+E07000141,South Kesteven,2021,
+S12000029,South Lanarkshire,2021,
+E07000149,South Norfolk,2021,
+E07000155,South Northamptonshire,2021,
+E07000179,South Oxfordshire,2021,
+E07000126,South Ribble,2021,
+E07000196,South Staffordshire,2021,
+E08000023,South Tyneside,2021,
+E06000045,Southampton,2021,
+E06000033,Southend-on-Sea,2021,
+E09000028,Southwark,2021,
+E07000213,Spelthorne,2021,
+E07000240,St Albans,2021,
+E08000013,St. Helens,2021,
+E07000197,Stafford,2021,
+E07000198,Staffordshire Moorlands,2021,
+E07000243,Stevenage,2021,
+S12000030,Stirling,2021,
+E08000007,Stockport,2021,
+E06000004,Stockton-on-Tees,2021,
+E06000021,Stoke-on-Trent,2021,
+E07000221,Stratford-on-Avon,2021,
+E07000082,Stroud,2021,
+E08000024,Sunderland,2021,
+E07000214,Surrey Heath,2021,
+E09000029,Sutton,2021,
+E07000113,Swale,2021,
+W06000011,Swansea,2021,
+E06000030,Swindon,2021,
+E08000008,Tameside,2021,
+E07000199,Tamworth,2021,
+E07000215,Tandridge,2021,
+E07000045,Teignbridge,2021,
+E06000020,Telford and Wrekin,2021,
+E07000076,Tendring,2021,
+E07000093,Test Valley,2021,
+E07000083,Tewkesbury,2021,
+E07000114,Thanet,2021,
+E07000102,Three Rivers,2021,
+E06000034,Thurrock,2021,
+E07000115,Tonbridge and Malling,2021,
+E06000027,Torbay,2021,
+W06000020,Torfaen,2021,
+E07000046,Torridge,2021,
+E09000030,Tower Hamlets,2021,
+E08000009,Trafford,2021,
+E07000116,Tunbridge Wells,2021,
+E07000077,Uttlesford,2021,
+W06000014,Vale of Glamorgan,2021,
+E07000180,Vale of White Horse,2021,
+E08000036,Wakefield,2021,
+E08000030,Walsall,2021,
+E09000031,Waltham Forest,2021,
+E09000032,Wandsworth,2021,
+E06000007,Warrington,2021,
+E07000222,Warwick,2021,
+E07000103,Watford,2021,
+E07000216,Waverley,2021,
+E07000065,Wealden,2021,
+E07000156,Wellingborough,2021,
+E07000241,Welwyn Hatfield,2021,
+E06000037,West Berkshire,2021,
+E07000047,West Devon,2021,
+S12000039,West Dunbartonshire,2021,
+E07000127,West Lancashire,2021,
+E07000142,West Lindsey,2021,
+S12000040,West Lothian,2021,
+E07000181,West Oxfordshire,2021,
+E07000245,West Suffolk,2021,
+E09000033,Westminster,2021,
+E06000064,Westmorland and Furness,2023,
+E08000010,Wigan,2021,
+E06000054,Wiltshire,2021,
+E07000094,Winchester,2021,
+E06000040,Windsor and Maidenhead,2021,
+E08000015,Wirral,2021,
+E07000217,Woking,2021,
+E06000041,Wokingham,2021,
+E08000031,Wolverhampton,2021,
+E07000237,Worcester,2021,
+E07000229,Worthing,2021,
+W06000006,Wrexham,2021,
+E07000238,Wychavon,2021,
+E07000128,Wyre,2021,
+E07000239,Wyre Forest,2021,
+E06000014,York,2021,
+E06000065,North Yorkshire,2023,
+N92000002,Northern Ireland,2021,
+S92000003,Scotland,2021,
+W92000004,Wales,2021,
+9300000XX,Outside UK,2021,
+E07000027,Barrow-in-Furness,2021,2023,
+E07000030,Eden,2021,2023,
+E07000031,South Lakeland,2021,2023,
+E07000026,Allerdale,2021,2023,
+E07000028,Carlisle,2021,2023,
+E07000029,Copeland,2021,2023,
+E07000163,Craven,2021,2023,
+E07000164,Hambleton,2021,2023,
+E07000165,Harrogate,2021,2023,
+E07000166,Richmondshire,2021,2023,
+E07000167,Ryedale,2021,2023,
+E07000168,Scarborough,2021,2023,
+E07000169,Selby,2021,2023,
+E07000187,Mendip,2021,2023,
+E07000188,Sedgemoor,2021,2023,
+E07000246,Somerset West and Taunton,2021,2023,
+E07000189,South Somerset,2021,2023,
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 8665c022a..c3762e259 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -538,6 +538,15 @@ en:
W: "Suitable for someone who uses a wheelchair and offers the full use of all rooms and facilities."
A: "Fitted with stairlifts, ramps, level access showers or grab rails."
N: "Not designed to wheelchair-user standards or fitted with any equipment or adaptations."
+ soctenant:
+ one: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?"
+ other: "Were any of the buyers private registered providers, housing association or local authority tenants immediately before this sale?"
+ prevown:
+ one: "Has the buyer previously owned a property?"
+ other: "Have any of the buyers previously owned a property?"
+ stairowned:
+ one: "What percentage of the property does the buyer now own in total?"
+ other: "What percentage of the property do the buyers now own in total?"
hints:
location:
@@ -551,6 +560,17 @@ en:
bulk_upload:
needstype: "General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing can include direct access hostels, group homes, residential care and nursing homes."
+ check_answer_labels:
+ soctenant:
+ one: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?"
+ other: "Any buyers were registered providers, housing association or local authority tenants immediately before this sale?"
+ prevown:
+ one: "Buyer previously owned a property"
+ other: "Buyers previously owned a property"
+ stairowned:
+ one: "Percentage the buyer now owns in total"
+ other: "Percentage the buyers now own in total"
+
warnings:
location:
deactivate:
diff --git a/config/routes.rb b/config/routes.rb
index 74b613989..02a9cb707 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -131,6 +131,7 @@ Rails.application.routes.draw do
resources :bulk_upload_lettings_logs, path: "bulk-upload-logs", only: %i[show update] do
collection do
get :start
+ get "guidance", to: "bulk_upload_lettings_logs#guidance"
end
end
@@ -165,6 +166,7 @@ Rails.application.routes.draw do
resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do
collection do
get :start
+ get "guidance", to: "bulk_upload_sales_logs#guidance"
end
end
diff --git a/db/migrate/20230301120116_add_category_to_bulk_upload_errors.rb b/db/migrate/20230301120116_add_category_to_bulk_upload_errors.rb
new file mode 100644
index 000000000..000cbbd6d
--- /dev/null
+++ b/db/migrate/20230301120116_add_category_to_bulk_upload_errors.rb
@@ -0,0 +1,5 @@
+class AddCategoryToBulkUploadErrors < ActiveRecord::Migration[7.0]
+ def change
+ add_column :bulk_upload_errors, :category, :text, null: true
+ end
+end
diff --git a/db/migrate/20230308101826_create_local_authorities.rb b/db/migrate/20230308101826_create_local_authorities.rb
new file mode 100644
index 000000000..f64964455
--- /dev/null
+++ b/db/migrate/20230308101826_create_local_authorities.rb
@@ -0,0 +1,13 @@
+class CreateLocalAuthorities < ActiveRecord::Migration[7.0]
+ def change
+ create_table :local_authorities do |t|
+ t.string :code, null: false
+ t.string :name, null: false
+ t.datetime :start_date, null: false
+ t.datetime :end_date
+ t.index %w[code], name: "index_local_authority_code", unique: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index afa0b6b74..eccfa98ca 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_03_01_144555) do
+ActiveRecord::Schema[7.0].define(version: 2023_03_08_101826) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -26,6 +26,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_01_144555) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "col"
+ t.text "category"
t.index ["bulk_upload_id"], name: "index_bulk_upload_errors_on_bulk_upload_id"
end
@@ -288,6 +289,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_01_144555) do
t.index ["updated_by_id"], name: "index_lettings_logs_on_updated_by_id"
end
+ create_table "local_authorities", force: :cascade do |t|
+ t.string "code", null: false
+ t.string "name", null: false
+ t.datetime "start_date", null: false
+ t.datetime "end_date"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["code"], name: "index_local_authority_code", unique: true
+ end
+
create_table "location_deactivation_periods", force: :cascade do |t|
t.datetime "deactivation_date"
t.datetime "reactivation_date"
diff --git a/db/seeds.rb b/db/seeds.rb
index 341677b27..e677ad50e 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -325,4 +325,10 @@ unless Rails.env.test?
end
end
end
+
+if LocalAuthority.count.zero?
+ path = "config/local_authorities_data/initial_local_authorities.csv"
+ service = Imports::LocalAuthoritiesService.new(path:)
+ service.call
+end
# rubocop:enable Rails/Output
diff --git a/lib/tasks/local_authorities.rake b/lib/tasks/local_authorities.rake
new file mode 100644
index 000000000..b812bbc94
--- /dev/null
+++ b/lib/tasks/local_authorities.rake
@@ -0,0 +1,13 @@
+namespace :data_import do
+ desc "Import local authorities data"
+ task :local_authorities, %i[path] => :environment do |_task, args|
+ path = args[:path]
+
+ raise "Usage: rake data_import:local_authorities['path/to/csv_file']" if path.blank?
+
+ service = Imports::LocalAuthoritiesService.new(path:)
+ service.call
+
+ pp "Created/updated #{service.count} local authority records" unless Rails.env.test?
+ end
+end
diff --git a/public/files/bulk-upload-lettings-specification-2022-23.xlsx b/public/files/bulk-upload-lettings-specification-2022-23.xlsx
new file mode 100644
index 000000000..15cec38b6
Binary files /dev/null and b/public/files/bulk-upload-lettings-specification-2022-23.xlsx differ
diff --git a/public/files/bulk-upload-sales-specification-2022-23.xlsx b/public/files/bulk-upload-sales-specification-2022-23.xlsx
new file mode 100644
index 000000000..b1e825fbb
Binary files /dev/null and b/public/files/bulk-upload-sales-specification-2022-23.xlsx differ
diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb
index f1da841a4..b2d6f37a5 100644
--- a/spec/factories/sales_log.rb
+++ b/spec/factories/sales_log.rb
@@ -14,7 +14,6 @@ FactoryBot.define do
ownershipsch { 2 }
type { 8 }
saledate { Time.utc(2023, 2, 2, 10, 36, 49) }
- companybuy { 1 }
jointpur { 1 }
beds { 2 }
jointmore { 1 }
diff --git a/spec/fixtures/files/local_authorities.csv b/spec/fixtures/files/local_authorities.csv
new file mode 100644
index 000000000..d1cebba6b
--- /dev/null
+++ b/spec/fixtures/files/local_authorities.csv
@@ -0,0 +1,6 @@
+code,name,start_year,end_year
+S12000033,Aberdeen City,2021,
+S12000034,Aberdeenshire,2021,
+E07000223,Adur,2021,
+E07000032,Amber Valley,2021,
+S12000041,Angus,2021,
\ No newline at end of file
diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
index b5dabde97..a2f124032 100644
--- a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
+++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
@@ -144,7 +144,7 @@
0
0
0
- 76000
+
1
47000
0
diff --git a/spec/lib/tasks/local_authorities_import_spec.rb b/spec/lib/tasks/local_authorities_import_spec.rb
new file mode 100644
index 000000000..67d952767
--- /dev/null
+++ b/spec/lib/tasks/local_authorities_import_spec.rb
@@ -0,0 +1,43 @@
+require "rails_helper"
+require "rake"
+
+RSpec.describe "data_import" do
+ describe ":local_authorities", type: :task do
+ subject(:task) { Rake::Task["data_import:local_authorities"] }
+
+ before do
+ LocalAuthority.destroy_all
+ Rake.application.rake_require("tasks/local_authorities")
+ Rake::Task.define_task(:environment)
+ task.reenable
+ end
+
+ context "when the rake task is run" do
+ let(:local_authorities_file_path) { "./spec/fixtures/files/local_authorities.csv" }
+ let(:wrong_file_path) { "/test/no_csv_here.csv" }
+
+ it "creates new local authorities records" do
+ expect { task.invoke(local_authorities_file_path) }.to change(LocalAuthority, :count).by(5)
+ expect(LocalAuthority.where(code: "S12000041").exists?).to be true
+ end
+
+ it "raises an error when no path is given" do
+ expect { task.invoke(nil) }.to raise_error(RuntimeError, "Usage: rake data_import:local_authorities['path/to/csv_file']")
+ end
+
+ it "raises an error when no file exists at the given path" do
+ expect { task.invoke(wrong_file_path) }.to raise_error(Errno::ENOENT)
+ end
+
+ context "when a record already exists with a matching code index" do
+ let!(:local_authority) { LocalAuthority.create(code: "S12000041", name: "Something else", start_date: Time.zone.local(2021, 4, 1)) }
+
+ it "updates local authority if the record is matched on code" do
+ task.invoke(local_authorities_file_path)
+ local_authority.reload
+ expect(local_authority.name).to eq("Angus")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mailers/bulk_upload_mailer_spec.rb b/spec/mailers/bulk_upload_mailer_spec.rb
index a0042cf69..0e38617a3 100644
--- a/spec/mailers/bulk_upload_mailer_spec.rb
+++ b/spec/mailers/bulk_upload_mailer_spec.rb
@@ -12,6 +12,38 @@ RSpec.describe BulkUploadMailer do
allow(notify_client).to receive(:send_email).and_return(true)
end
+ describe "#send_bulk_upload_failed_file_setup_error_mail" do
+ before do
+ create(:bulk_upload_error, bulk_upload:, col: "A", field: "field_1", category: "setup")
+ create(:bulk_upload_error, bulk_upload:, col: "E", field: "field_4", category: "setup")
+ create(:bulk_upload_error, bulk_upload:, col: "F", field: "field_5")
+ end
+
+ let(:expected_errors) do
+ [
+ "- Column A (What is the letting type?)",
+ "- Column E (Management group code)",
+ ]
+ end
+
+ it "sends correctly formed email" do
+ expect(notify_client).to receive(:send_email).with(
+ email_address: bulk_upload.user.email,
+ template_id: described_class::BULK_UPLOAD_FAILED_FILE_SETUP_ERROR_TEMPLATE_ID,
+ personalisation: {
+ filename: bulk_upload.filename,
+ upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
+ lettings_or_sales: bulk_upload.log_type,
+ year_combo: bulk_upload.year_combo,
+ errors_list: expected_errors.join("\n"),
+ bulk_upload_link: start_bulk_upload_lettings_logs_url,
+ },
+ )
+
+ mailer.send_bulk_upload_failed_file_setup_error_mail(bulk_upload:)
+ end
+ end
+
describe "#send_bulk_upload_complete_mail" do
it "sends correctly formed email" do
expect(notify_client).to receive(:send_email).with(
diff --git a/spec/models/form/lettings/pages/person_age_spec.rb b/spec/models/form/lettings/pages/person_age_spec.rb
index ca39ef2c9..3a5ee1655 100644
--- a/spec/models/form/lettings/pages/person_age_spec.rb
+++ b/spec/models/form/lettings/pages/person_age_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::PersonAge, type: :model do
- subject(:page) { described_class.new(nil, page_definition, subsection, person_index:) }
+ subject(:page) { described_class.new(nil, page_definition, subsection, person_index:, person_type:) }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:person_index) { 2 }
+ let(:person_type) { "non_child" }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@@ -24,14 +25,30 @@ RSpec.describe Form::Lettings::Pages::PersonAge, type: :model do
expect(page.questions.map(&:id)).to eq(%w[age2_known age2])
end
- it "has the correct id" do
- expect(page.id).to eq("person_2_age")
+ context "when child" do
+ let(:person_type) { "child" }
+
+ it "has the correct id" do
+ expect(page.id).to eq("person_2_age_child")
+ end
+
+ it "has correct depends_on" do
+ expect(page.depends_on).to eq(
+ [{ "details_known_2" => 0, "person_2_child_relation?" => true }],
+ )
+ end
end
- it "has correct depends_on" do
- expect(page.depends_on).to eq(
- [{ "details_known_2" => 0 }],
- )
+ context "when not child" do
+ it "has the correct id" do
+ expect(page.id).to eq("person_2_age_non_child")
+ end
+
+ it "has correct depends_on" do
+ expect(page.depends_on).to eq(
+ [{ "details_known_2" => 0, "person_2_child_relation?" => false }],
+ )
+ end
end
end
@@ -43,12 +60,12 @@ RSpec.describe Form::Lettings::Pages::PersonAge, type: :model do
end
it "has the correct id" do
- expect(page.id).to eq("person_3_age")
+ expect(page.id).to eq("person_3_age_non_child")
end
it "has correct depends_on" do
expect(page.depends_on).to eq(
- [{ "details_known_3" => 0 }],
+ [{ "details_known_3" => 0, "person_3_child_relation?" => false }],
)
end
end
diff --git a/spec/models/form/lettings/questions/age_spec.rb b/spec/models/form/lettings/questions/age_spec.rb
index 87c3dce22..40a6356fd 100644
--- a/spec/models/form/lettings/questions/age_spec.rb
+++ b/spec/models/form/lettings/questions/age_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::Age, type: :model do
- subject(:question) { described_class.new(nil, question_definition, page, person_index:) }
+ subject(:question) { described_class.new(nil, question_definition, page, person_index:, person_type:) }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:person_index) { 2 }
+ let(:person_type) { "non_child" }
it "has correct page" do
expect(question.page).to eq(page)
@@ -23,12 +24,22 @@ RSpec.describe Form::Lettings::Questions::Age, type: :model do
expect(question.derived?).to be false
end
- it "has the correct hint" do
- expect(question.hint_text).to be_nil
+ context "when child" do
+ let(:person_type) { "child" }
+
+ it "has the correct hint" do
+ expect(question.hint_text).to eq("For a child under 1, enter 1")
+ end
+ end
+
+ context "when not child" do
+ it "has no hint" do
+ expect(question.hint_text).to be nil
+ end
end
it "has the correct min" do
- expect(question.min).to eq(0)
+ expect(question.min).to eq(1)
end
it "has the correct max" do
diff --git a/spec/models/form/lettings/questions/net_income_known_spec.rb b/spec/models/form/lettings/questions/net_income_known_spec.rb
new file mode 100644
index 000000000..4a0925fa4
--- /dev/null
+++ b/spec/models/form/lettings/questions/net_income_known_spec.rb
@@ -0,0 +1,17 @@
+require "rails_helper"
+
+RSpec.describe Form::Lettings::Questions::NetIncomeKnown do
+ subject(:question) { described_class.new(nil, {}, nil) }
+
+ describe "#id" do
+ it "is net_income_known" do
+ expect(question.id).to eql("net_income_known")
+ end
+ end
+
+ describe "#type" do
+ it "is radio" do
+ expect(question.type).to eql("radio")
+ end
+ end
+end
diff --git a/spec/models/form/lettings/questions/offered_social_let_spec.rb b/spec/models/form/lettings/questions/offered_social_let_spec.rb
new file mode 100644
index 000000000..8a3622e02
--- /dev/null
+++ b/spec/models/form/lettings/questions/offered_social_let_spec.rb
@@ -0,0 +1,44 @@
+require "rails_helper"
+
+RSpec.describe Form::Lettings::Questions::OfferedSocialLet, type: :model do
+ subject(:question) { described_class.new(nil, nil, page) }
+
+ let(:page) { instance_double(Form::Page) }
+
+ it "has correct page" do
+ expect(question.page).to eq page
+ end
+
+ it "has the correct id" do
+ expect(question.id).to eq "offered"
+ end
+
+ it "has the correct header" do
+ expect(question.header).to eq "How many times was the property offered between becoming vacant and this letting?"
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq "Times previously offered since becoming available"
+ end
+
+ it "has the correct type" do
+ expect(question.type).to eq "numeric"
+ end
+
+ it "has the correct minimum and maximum values" do
+ expect(question.min).to eq 0
+ expect(question.max).to eq 150
+ end
+
+ it "has the correct step" do
+ expect(question.step).to eq 1
+ end
+
+ it "is not marked as derived" do
+ expect(question.derived?).to be false
+ end
+
+ it "has the correct hint_text" do
+ expect(question.hint_text).to eq "Do not include the offer that led to this letting.This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0."
+ end
+end
diff --git a/spec/models/form/lettings/questions/person_relationship_spec.rb b/spec/models/form/lettings/questions/person_relationship_spec.rb
index 04f071b9b..dca3d82a3 100644
--- a/spec/models/form/lettings/questions/person_relationship_spec.rb
+++ b/spec/models/form/lettings/questions/person_relationship_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Form::Lettings::Questions::PersonRelationship, type: :model do
end
it "has the correct answer_options" do
- expect(question.answer_options).to eq("C" => { "hint" => "Must be eligible for child benefit, aged under 16 or under 20 if still in full-time education.", "value" => "Child" },
+ expect(question.answer_options).to eq("C" => { "hint" => "Must be eligible for child benefit: under age 16 or under 20 if still in full-time education.", "value" => "Child" },
"P" => { "value" => "Partner" },
"R" => { "value" => "Person prefers not to say" },
"X" => { "value" => "Other" },
diff --git a/spec/models/form/lettings/questions/rsnvac_spec.rb b/spec/models/form/lettings/questions/rsnvac_spec.rb
new file mode 100644
index 000000000..302f566fe
--- /dev/null
+++ b/spec/models/form/lettings/questions/rsnvac_spec.rb
@@ -0,0 +1,79 @@
+require "rails_helper"
+
+RSpec.describe Form::Lettings::Questions::Rsnvac, type: :model do
+ subject(:question) { described_class.new(question_id, question_definition, page) }
+
+ let(:question_id) { nil }
+ let(:question_definition) { nil }
+ let(:page) { instance_double(Form::Page) }
+
+ it "has correct page" do
+ expect(question.page).to eq(page)
+ end
+
+ it "has the correct id" do
+ expect(question.id).to eq("rsnvac")
+ end
+
+ it "has the correct header" do
+ expect(question.header).to eq("What is the reason for the property being vacant?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Vacancy reason")
+ end
+
+ it "has the correct type" do
+ expect(question.type).to eq("radio")
+ end
+
+ it "is not marked as derived" do
+ expect(question.derived?).to be false
+ end
+
+ it "has the correct answer_options" do
+ expect(question.answer_options).to eq({
+ "13" => {
+ "value" => "Internal transfer",
+ "hint" => "Excluding renewals of a fixed-term tenancy",
+ },
+ "5" => {
+ "value" => "Previous tenant died with no succession",
+ },
+ "9" => {
+ "value" => "Re-let to tenant who occupied same property as temporary accommodation",
+ },
+ "14" => {
+ "value" => "Renewal of fixed-term tenancy",
+ },
+ "19" => {
+ "value" => "Tenant involved in a succession downsize",
+ },
+ "8" => {
+ "value" => "Tenant moved to private sector or other accommodation",
+ },
+ "12" => {
+ "value" => "Tenant moved to other social housing provider",
+ },
+ "18" => {
+ "value" => "Tenant moved to care home",
+ },
+ "20" => {
+ "value" => "Tenant moved to long-stay hospital or similar institution",
+ },
+ "6" => {
+ "value" => "Tenant abandoned property",
+ },
+ "10" => {
+ "value" => "Tenant was evicted due to rent arrears",
+ },
+ "11" => {
+ "value" => "Tenant was evicted due to anti-social behaviour",
+ },
+ })
+ end
+
+ it "has the correct check_answers_card_number" do
+ expect(question.check_answers_card_number).to eq(0)
+ end
+end
diff --git a/spec/models/form/lettings/subsections/household_characteristics_spec.rb b/spec/models/form/lettings/subsections/household_characteristics_spec.rb
index 96d722a2b..d0d55b8bf 100644
--- a/spec/models/form/lettings/subsections/household_characteristics_spec.rb
+++ b/spec/models/form/lettings/subsections/household_characteristics_spec.rb
@@ -36,7 +36,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
lead_tenant_over_retirement_value_check
person_2_known
person_2_relationship_to_lead
- person_2_age
+ person_2_age_child
+ person_2_age_non_child
no_females_pregnant_household_person_2_age_value_check
females_in_soft_age_range_in_pregnant_household_person_2_age_value_check
person_2_gender_identity
@@ -47,7 +48,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_2_over_retirement_value_check
person_3_known
person_3_relationship_to_lead
- person_3_age
+ person_3_age_child
+ person_3_age_non_child
no_females_pregnant_household_person_3_age_value_check
females_in_soft_age_range_in_pregnant_household_person_3_age_value_check
person_3_gender_identity
@@ -58,7 +60,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_3_over_retirement_value_check
person_4_known
person_4_relationship_to_lead
- person_4_age
+ person_4_age_child
+ person_4_age_non_child
no_females_pregnant_household_person_4_age_value_check
females_in_soft_age_range_in_pregnant_household_person_4_age_value_check
person_4_gender_identity
@@ -69,7 +72,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_4_over_retirement_value_check
person_5_known
person_5_relationship_to_lead
- person_5_age
+ person_5_age_child
+ person_5_age_non_child
no_females_pregnant_household_person_5_age_value_check
females_in_soft_age_range_in_pregnant_household_person_5_age_value_check
person_5_gender_identity
@@ -80,7 +84,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_5_over_retirement_value_check
person_6_known
person_6_relationship_to_lead
- person_6_age
+ person_6_age_child
+ person_6_age_non_child
no_females_pregnant_household_person_6_age_value_check
females_in_soft_age_range_in_pregnant_household_person_6_age_value_check
person_6_gender_identity
@@ -91,7 +96,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_6_over_retirement_value_check
person_7_known
person_7_relationship_to_lead
- person_7_age
+ person_7_age_child
+ person_7_age_non_child
no_females_pregnant_household_person_7_age_value_check
females_in_soft_age_range_in_pregnant_household_person_7_age_value_check
person_7_gender_identity
@@ -102,7 +108,8 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
person_7_over_retirement_value_check
person_8_known
person_8_relationship_to_lead
- person_8_age
+ person_8_age_child
+ person_8_age_non_child
no_females_pregnant_household_person_8_age_value_check
females_in_soft_age_range_in_pregnant_household_person_8_age_value_check
person_8_gender_identity
diff --git a/spec/models/form/sales/pages/about_staircase_spec.rb b/spec/models/form/sales/pages/about_staircase_spec.rb
index 3828f52e4..48aa82acf 100644
--- a/spec/models/form/sales/pages/about_staircase_spec.rb
+++ b/spec/models/form/sales/pages/about_staircase_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
- subject(:page) { described_class.new(page_id, page_definition, subsection) }
+ subject(:page) { described_class.new(page_id, page_definition, subsection, joint_purchase:) }
- let(:page_id) { nil }
+ let(:page_id) { "an_id" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
+ let(:joint_purchase) { false }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@@ -32,7 +33,7 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
end
it "has the correct id" do
- expect(page.id).to eq("about_staircasing")
+ expect(page.id).to eq("an_id")
end
it "has the correct header" do
@@ -43,9 +44,23 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
expect(page.description).to be_nil
end
- it "has correct depends_on" do
- expect(page.depends_on).to eq([{
- "staircase" => 1,
- }])
+ context "when not a joint purchase" do
+ it "has correct depends_on" do
+ expect(page.depends_on).to eq([{
+ "staircase" => 1,
+ "joint_purchase?" => false,
+ }])
+ end
+ end
+
+ context "when a joint purchase" do
+ let(:joint_purchase) { true }
+
+ it "has correct depends_on" do
+ expect(page.depends_on).to eq([{
+ "staircase" => 1,
+ "joint_purchase?" => true,
+ }])
+ end
end
end
diff --git a/spec/models/form/sales/pages/buyer_previous_spec.rb b/spec/models/form/sales/pages/buyer_previous_spec.rb
index a3355c137..e6dda073e 100644
--- a/spec/models/form/sales/pages/buyer_previous_spec.rb
+++ b/spec/models/form/sales/pages/buyer_previous_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::BuyerPrevious, type: :model do
- subject(:page) { described_class.new(page_id, page_definition, subsection) }
+ subject(:page) { described_class.new(page_id, page_definition, subsection, joint_purchase:) }
- let(:page_id) { nil }
+ let(:page_id) { "example" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
+ let(:joint_purchase) { false }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@@ -16,7 +17,7 @@ RSpec.describe Form::Sales::Pages::BuyerPrevious, type: :model do
end
it "has the correct id" do
- expect(page.id).to eq("buyer_previous")
+ expect(page.id).to eq("example")
end
it "has the correct header" do
@@ -26,4 +27,18 @@ RSpec.describe Form::Sales::Pages::BuyerPrevious, type: :model do
it "has the correct description" do
expect(page.description).to be_nil
end
+
+ context "when sales is a joint purchase" do
+ let(:joint_purchase) { true }
+
+ it "has the correct depends on" do
+ expect(page.depends_on).to eq([{ "joint_purchase?" => true }])
+ end
+ end
+
+ context "when sales is not a joint purchase" do
+ it "has the correct depends on" do
+ expect(page.depends_on).to eq([{ "joint_purchase?" => false }])
+ end
+ end
end
diff --git a/spec/models/form/sales/pages/previous_ownership_spec.rb b/spec/models/form/sales/pages/previous_ownership_spec.rb
index 7fe0d2795..69c93fefa 100644
--- a/spec/models/form/sales/pages/previous_ownership_spec.rb
+++ b/spec/models/form/sales/pages/previous_ownership_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::PreviousOwnership, type: :model do
- subject(:page) { described_class.new(page_id, page_definition, subsection) }
+ subject(:page) { described_class.new(page_id, page_definition, subsection, joint_purchase:) }
- let(:page_id) { nil }
+ let(:page_id) { "example_id" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
+ let(:joint_purchase) { true }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
@@ -16,7 +17,7 @@ RSpec.describe Form::Sales::Pages::PreviousOwnership, type: :model do
end
it "has the correct id" do
- expect(page.id).to eq("previous_ownership")
+ expect(page.id).to eq("example_id")
end
it "has the correct header" do
@@ -26,4 +27,16 @@ RSpec.describe Form::Sales::Pages::PreviousOwnership, type: :model do
it "has the correct description" do
expect(page.description).to be_nil
end
+
+ it "when sale is a joint purchase has the correct depends on" do
+ expect(page.depends_on).to eq([{ "joint_purchase?" => true }])
+ end
+
+ context "when sale is not a joint purchase" do
+ let(:joint_purchase) { false }
+
+ it "has the correct depends on" do
+ expect(page.depends_on).to eq([{ "joint_purchase?" => false }])
+ end
+ end
end
diff --git a/spec/models/form/sales/questions/buyer_previous_spec.rb b/spec/models/form/sales/questions/buyer_previous_spec.rb
index 07d3685f8..a1348f17b 100644
--- a/spec/models/form/sales/questions/buyer_previous_spec.rb
+++ b/spec/models/form/sales/questions/buyer_previous_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::BuyerPrevious, type: :model do
- subject(:question) { described_class.new(question_id, question_definition, page) }
+ subject(:question) { described_class.new(question_id, question_definition, page, joint_purchase:) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
+ let(:joint_purchase) { true }
it "has correct page" do
expect(question.page).to eq(page)
@@ -15,12 +16,26 @@ RSpec.describe Form::Sales::Questions::BuyerPrevious, type: :model do
expect(question.id).to eq("soctenant")
end
- it "has the correct header" do
- expect(question.header).to eq("Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?")
+ context "when a joint purchase" do
+ it "has the correct header" do
+ expect(question.header).to eq("Were any of the buyers private registered providers, housing association or local authority tenants immediately before this sale?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Any buyers were registered providers, housing association or local authority tenants immediately before this sale?")
+ end
end
- it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Buyer was a registered provider, housing association or local authority tenant immediately before this sale?")
+ context "when not a joint purchase" do
+ let(:joint_purchase) { false }
+
+ it "has the correct header" do
+ expect(question.header).to eq("Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Buyer was a registered provider, housing association or local authority tenant immediately before this sale?")
+ end
end
it "has the correct type" do
diff --git a/spec/models/form/sales/questions/prevown_spec.rb b/spec/models/form/sales/questions/prevown_spec.rb
index dda2b7c70..d7b101c99 100644
--- a/spec/models/form/sales/questions/prevown_spec.rb
+++ b/spec/models/form/sales/questions/prevown_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Prevown, type: :model do
- subject(:question) { described_class.new(question_id, question_definition, page) }
+ subject(:question) { described_class.new(question_id, question_definition, page, joint_purchase:) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
+ let(:joint_purchase) { false }
it "has correct page" do
expect(question.page).to eq(page)
@@ -15,12 +16,26 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do
expect(question.id).to eq("prevown")
end
- it "has the correct header" do
- expect(question.header).to eq("Has the buyer previously owned a property?")
+ context "when sale is not a joint purchase" do
+ it "has the correct header" do
+ expect(question.header).to eq("Has the buyer previously owned a property?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Buyer previously owned a property")
+ end
end
- it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Buyers previously owned a property")
+ context "when sale is a joint purchase" do
+ let(:joint_purchase) { true }
+
+ it "has the correct header" do
+ expect(question.header).to eq("Have any of the buyers previously owned a property?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Buyers previously owned a property")
+ end
end
it "has the correct type" do
diff --git a/spec/models/form/sales/questions/staircase_owned_spec.rb b/spec/models/form/sales/questions/staircase_owned_spec.rb
index cbd577784..8e78d540a 100644
--- a/spec/models/form/sales/questions/staircase_owned_spec.rb
+++ b/spec/models/form/sales/questions/staircase_owned_spec.rb
@@ -1,11 +1,12 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do
- subject(:question) { described_class.new(question_id, question_definition, page) }
+ subject(:question) { described_class.new(question_id, question_definition, page, joint_purchase:) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
+ let(:joint_purchase) { false }
it "has correct page" do
expect(question.page).to eq(page)
@@ -15,12 +16,26 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do
expect(question.id).to eq("stairowned")
end
- it "has the correct header" do
- expect(question.header).to eq("What percentage of the property does the buyer now own in total?")
+ context "when a joint purchase" do
+ let(:joint_purchase) { true }
+
+ it "has the correct header" do
+ expect(question.header).to eq("What percentage of the property do the buyers now own in total?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Percentage the buyers now own in total")
+ end
end
- it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Percentage the buyer now owns in total")
+ context "when not a joint purchase" do
+ it "has the correct header" do
+ expect(question.header).to eq("What percentage of the property does the buyer now own in total?")
+ end
+
+ it "has the correct check_answer_label" do
+ expect(question.check_answer_label).to eq("Percentage the buyer now owns in total")
+ end
end
it "has the correct type" do
diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb
index 4645b7231..5a025e8de 100644
--- a/spec/models/form/sales/subsections/household_characteristics_spec.rb
+++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb
@@ -195,6 +195,22 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
end
it "has correct depends on" do
- expect(household_characteristics.depends_on).to eq([{ "setup_completed?" => true }])
+ expect(household_characteristics.depends_on).to eq([{ "setup_completed?" => true, "company_buyer?" => false }])
+ end
+
+ context "when the sale is to a company buyer" do
+ let(:log) { FactoryBot.create(:sales_log, ownershipsch: 3, companybuy: 1) }
+
+ it "is not displayed in tasklist" do
+ expect(household_characteristics.displayed_in_tasklist?(log)).to eq(false)
+ end
+ end
+
+ context "when the sale is not to a company buyer" do
+ let(:log) { FactoryBot.create(:sales_log, ownershipsch: 3, companybuy: 2) }
+
+ it "is displayed in tasklist" do
+ expect(household_characteristics.displayed_in_tasklist?(log)).to eq(true)
+ end
end
end
diff --git a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb
index 6c0997fd2..aef451878 100644
--- a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb
+++ b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb
@@ -35,7 +35,8 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model
savings
savings_value_check
savings_deposit_value_check
- previous_ownership
+ previous_ownership_joint_purchase
+ previous_ownership_not_joint_purchase
],
)
end
@@ -62,7 +63,8 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model
savings
savings_value_check
savings_deposit_value_check
- previous_ownership
+ previous_ownership_joint_purchase
+ previous_ownership_not_joint_purchase
previous_shared
],
)
diff --git a/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb b/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
index ee9f624aa..3c189f93f 100644
--- a/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
+++ b/spec/models/form/sales/subsections/shared_ownership_scheme_spec.rb
@@ -16,14 +16,16 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipScheme, type: :model do
%w[
living_before_purchase_shared_ownership
staircasing
- about_staircasing
+ about_staircasing_joint_purchase
+ about_staircasing_not_joint_purchase
staircase_bought_value_check
resale
exchange_contracts
handover_date
handover_date_check
la_nominations
- buyer_previous
+ buyer_previous_joint_purchase
+ buyer_previous_not_joint_purchase
previous_bedrooms
previous_property_type
shared_ownership_previous_tenure
diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb
index b28f8bfea..c1bfafb57 100644
--- a/spec/models/lettings_log_spec.rb
+++ b/spec/models/lettings_log_spec.rb
@@ -1474,6 +1474,163 @@ RSpec.describe LettingsLog do
expect(record_from_db["rsnvac"]).to eq(14)
expect(lettings_log["rsnvac"]).to eq(14)
end
+
+ context "when deriving renttype and unitletas" do
+ before do
+ allow(FeatureToggle).to receive(:startdate_two_week_validation_enabled?).and_return(false)
+ lettings_log.update!(rent_type:, irproduct_other: "other")
+ end
+
+ context "when the rent_type is Social Rent (0)" do
+ let(:rent_type) { 0 }
+
+ it "derives and saves renttype and unitletas as Social rent(1)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(1)
+ expect(record_from_db["renttype"]).to eq(1)
+ end
+
+ it "derives and saves unitletas as Social rent(1)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(1)
+ expect(record_from_db["unitletas"]).to eq(1)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as Social rent(1)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(1)
+ expect(record_from_db["unitletas"]).to eq(1)
+ end
+ end
+ end
+
+ context "when the rent_type is Affordable Rent(1)" do
+ let(:rent_type) { 1 }
+
+ it "derives and saves renttype as Affordable Rent(2)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(2)
+ expect(record_from_db["renttype"]).to eq(2)
+ end
+
+ it "derives and saves unitletas as Affordable Rent(2)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(2)
+ expect(record_from_db["unitletas"]).to eq(2)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as Affordable Rent basis(2)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(2)
+ expect(record_from_db["unitletas"]).to eq(2)
+ end
+ end
+ end
+
+ context "when the rent_type is London Affordable Rent(2)" do
+ let(:rent_type) { 2 }
+
+ it "derives and saves renttype as London Affordable Rent(2)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(2)
+ expect(record_from_db["renttype"]).to eq(2)
+ end
+
+ it "derives and saves unitletas as London Affordable Rent(2)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(2)
+ expect(record_from_db["unitletas"]).to eq(2)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as London Affordable Rent basis(5)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(5)
+ expect(record_from_db["unitletas"]).to eq(5)
+ end
+ end
+ end
+
+ context "when the rent_type is Rent to Buy(3)" do
+ let(:rent_type) { 3 }
+
+ it "derives and saves renttype as Intermediate Rent(3)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(3)
+ expect(record_from_db["renttype"]).to eq(3)
+ end
+
+ it "derives and saves unitletas as Intermediate Rent(4)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(4)
+ expect(record_from_db["unitletas"]).to eq(4)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as Rent to Buy basis(6)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(6)
+ expect(record_from_db["unitletas"]).to eq(6)
+ end
+ end
+ end
+
+ context "when the rent_type is London Living Rent(4)" do
+ let(:rent_type) { 4 }
+
+ it "derives and saves renttype as Intermediate Rent(3)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(3)
+ expect(record_from_db["renttype"]).to eq(3)
+ end
+
+ it "derives and saves unitletas as Intermediate Rent(4)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(4)
+ expect(record_from_db["unitletas"]).to eq(4)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as London Living Rent basis(7)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(7)
+ expect(record_from_db["unitletas"]).to eq(7)
+ end
+ end
+ end
+
+ context "when the rent_type is Other intermediate rent product(5)" do
+ let(:rent_type) { 5 }
+
+ it "derives and saves renttype as Intermediate Rent(3)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select renttype from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.renttype).to eq(3)
+ expect(record_from_db["renttype"]).to eq(3)
+ end
+
+ it "derives and saves unitletas as Intermediate Rent(4)" do
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(4)
+ expect(record_from_db["unitletas"]).to eq(4)
+ end
+
+ context "and it is a 23/24 form" do
+ it "derives and saves unitletas as Other intermediate rent basis(8)" do
+ lettings_log.update!(startdate: Time.zone.local(2023, 5, 1))
+ record_from_db = ActiveRecord::Base.connection.execute("select unitletas from lettings_logs where id=#{lettings_log.id}").to_a[0]
+ expect(lettings_log.unitletas).to eq(8)
+ expect(record_from_db["unitletas"]).to eq(8)
+ end
+ end
+ end
+ end
end
context "when answering the household characteristics questions" do
@@ -1966,7 +2123,7 @@ RSpec.describe LettingsLog do
end
context "when a non select question associated with several pages is routed to" do
- let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2, needstype: 1) }
+ let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2, needstype: 1, renewal: 0) }
it "does not clear the answer value" do
lettings_log.update!({ unitletas: 1 })
diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
index fc27ed0d1..b915b571c 100644
--- a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
+++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb
@@ -31,4 +31,28 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
end
end
end
+
+ describe "GET /lettings-logs/bulk-upload-logs/guidance" do
+ context "when not in crossover period" do
+ let(:expected_year) { FormHandler.instance.forms["current_lettings"].start_date.year }
+
+ it "shows guidance page with correct title" do
+ Timecop.freeze(2022, 1, 1) do
+ get "/lettings-logs/bulk-upload-logs/guidance", params: {}
+
+ expect(response.body).to include("How to upload logs in bulk")
+ end
+ end
+ end
+
+ context "when in crossover period" do
+ it "shows guidance page with correct title" do
+ Timecop.freeze(2023, 6, 1) do
+ get "/lettings-logs/bulk-upload-logs/guidance", params: {}
+
+ expect(response.body).to include("How to upload logs in bulk")
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb
index 348179dd2..3e2aa5910 100644
--- a/spec/requests/bulk_upload_sales_logs_controller_spec.rb
+++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb
@@ -31,4 +31,28 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
end
end
end
+
+ describe "GET /sales-logs/bulk-upload-logs/guidance" do
+ context "when not in crossover period" do
+ let(:expected_year) { FormHandler.instance.forms["current_sales"].start_date.year }
+
+ it "shows guidance page with correct title" do
+ Timecop.freeze(2022, 1, 1) do
+ get "/sales-logs/bulk-upload-logs/guidance", params: {}
+
+ expect(response.body).to include("How to upload logs in bulk")
+ end
+ end
+ end
+
+ context "when in crossover period" do
+ it "shows guidance page with correct title" do
+ Timecop.freeze(2023, 6, 1) do
+ get "/sales-logs/bulk-upload-logs/guidance", params: {}
+
+ expect(response.body).to include("How to upload logs in bulk")
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/bulk_upload/lettings/row_parser_spec.rb b/spec/services/bulk_upload/lettings/row_parser_spec.rb
index cd16a88a1..73b35d70c 100644
--- a/spec/services/bulk_upload/lettings/row_parser_spec.rb
+++ b/spec/services/bulk_upload/lettings/row_parser_spec.rb
@@ -27,6 +27,118 @@ RSpec.describe BulkUpload::Lettings::RowParser do
}
end
+ let(:valid_attributes) do
+ {
+ bulk_upload:,
+ field_1: "1",
+ field_4: scheme.old_visible_id,
+ field_7: "123",
+ field_96: now.day.to_s,
+ field_97: now.month.to_s,
+ field_98: now.strftime("%g"),
+ field_108: "EC1N",
+ field_109: "2TD",
+ field_111: owning_org.old_visible_id,
+ field_113: managing_org.old_visible_id,
+ field_130: "1",
+ field_134: "2",
+ field_102: "2",
+ field_103: "1",
+ field_104: "1",
+ field_101: "1",
+ field_133: "2",
+ field_8: "1",
+ field_9: "2",
+ field_132: "1",
+
+ field_12: "42",
+ field_13: "41",
+ field_14: "20",
+ field_15: "18",
+ field_16: "16",
+ field_17: "14",
+ field_18: "12",
+ field_19: "20",
+
+ field_20: "F",
+ field_21: "M",
+ field_22: "F",
+ field_23: "M",
+ field_24: "F",
+ field_25: "M",
+ field_26: "F",
+ field_27: "M",
+
+ field_43: "17",
+ field_44: "18",
+
+ field_28: "P",
+ field_29: "C",
+ field_30: "X",
+ field_31: "R",
+ field_32: "C",
+ field_33: "C",
+ field_34: "X",
+
+ field_35: "1",
+ field_36: "2",
+ field_37: "6",
+ field_38: "7",
+ field_39: "8",
+ field_40: "9",
+ field_41: "0",
+ field_42: "10",
+
+ field_45: "1",
+ field_114: "4",
+ field_46: "1",
+
+ field_47: "1",
+
+ field_118: "2",
+
+ field_66: "5",
+ field_67: "2",
+ field_52: "31",
+ field_61: "3",
+ field_68: "12",
+
+ field_65: "1",
+ field_63: "EC1N",
+ field_64: "2TD",
+
+ field_69: "1",
+ field_70: "1",
+ field_71: "",
+ field_72: "1",
+ field_73: "",
+ field_74: "",
+
+ field_75: "1",
+ field_76: "2",
+ field_77: "2",
+
+ field_78: "2",
+
+ field_51: "1",
+ field_50: "2000",
+ field_116: "2",
+ field_48: "1",
+ field_49: "1",
+
+ field_79: "4",
+ field_80: "1234.56",
+ field_87: "1",
+ field_88: "234.56",
+
+ field_106: "15",
+ field_99: "0",
+ field_89: now.day.to_s,
+ field_90: now.month.to_s,
+ field_91: now.strftime("%g"),
+ }
+ end
+
before do
create(:organisation_relationship, parent_organisation: owning_org, child_organisation: managing_org)
end
@@ -83,117 +195,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end
context "when valid row" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "1",
- field_4: scheme.old_visible_id,
- field_7: "123",
- field_96: now.day.to_s,
- field_97: now.month.to_s,
- field_98: now.strftime("%g"),
- field_108: "EC1N",
- field_109: "2TD",
- field_111: owning_org.old_visible_id,
- field_113: managing_org.old_visible_id,
- field_130: "1",
- field_134: "2",
- field_102: "2",
- field_103: "1",
- field_104: "1",
- field_101: "1",
- field_133: "2",
- field_8: "1",
- field_9: "2",
- field_132: "1",
-
- field_12: "42",
- field_13: "41",
- field_14: "20",
- field_15: "18",
- field_16: "16",
- field_17: "14",
- field_18: "12",
- field_19: "20",
-
- field_20: "F",
- field_21: "M",
- field_22: "F",
- field_23: "M",
- field_24: "F",
- field_25: "M",
- field_26: "F",
- field_27: "M",
-
- field_43: "17",
- field_44: "18",
-
- field_28: "P",
- field_29: "C",
- field_30: "X",
- field_31: "R",
- field_32: "C",
- field_33: "C",
- field_34: "X",
-
- field_35: "1",
- field_36: "2",
- field_37: "6",
- field_38: "7",
- field_39: "8",
- field_40: "9",
- field_41: "0",
- field_42: "10",
-
- field_45: "1",
- field_114: "4",
- field_46: "1",
-
- field_47: "1",
-
- field_118: "2",
-
- field_66: "5",
- field_67: "2",
- field_52: "31",
- field_61: "3",
- field_68: "12",
-
- field_65: "1",
- field_63: "EC1N",
- field_64: "2TD",
-
- field_69: "1",
- field_70: "1",
- field_71: "",
- field_72: "1",
- field_73: "",
- field_74: "",
-
- field_75: "1",
- field_76: "2",
- field_77: "2",
-
- field_78: "2",
-
- field_51: "1",
- field_50: "2000",
- field_116: "2",
- field_48: "1",
- field_49: "1",
-
- field_79: "4",
- field_80: "1234.56",
- field_87: "1",
- field_88: "234.56",
-
- field_106: "15",
- field_99: "0",
- field_89: now.day.to_s,
- field_90: now.month.to_s,
- field_91: now.strftime("%g"),
- }
- end
+ let(:attributes) { valid_attributes }
it "returns true" do
expect(parser).to be_valid
@@ -210,6 +212,16 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end
end
+ context "when setup section not complete" do
+ let(:attributes) { { bulk_upload:, field_7: "123" } }
+
+ it "has errors on setup fields" do
+ errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
+
+ expect(errors).to eql(%i[field_1 field_98 field_97 field_96 field_111 field_113])
+ end
+ end
+
describe "#field_1" do
context "when null" do
let(:attributes) { { bulk_upload:, field_1: nil, field_4: "1" } }
@@ -410,6 +422,16 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end
end
+ describe "#field_12" do
+ context "when set to a non-sensical value" do
+ let(:attributes) { valid_attributes.merge(field_12: "A", field_35: "1") }
+
+ it "returns only one error" do
+ expect(parser.errors[:field_12].size).to be(1)
+ end
+ end
+ end
+
describe "#field_52" do # leaving reason
context "when field_134 is 1 meaning it is a renewal" do
context "when field_52 is 40" do
@@ -519,7 +541,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end
describe "fields 96, 97, 98 => startdate" do
- context "when any one of these fields is blank" do
+ context "when all of these fields are blank" do
let(:attributes) { { bulk_upload:, field_1: "1", field_96: nil, field_97: nil, field_98: nil } }
it "returns an error" do
@@ -531,6 +553,18 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end
end
+ context "when one of these fields is blank" do
+ let(:attributes) { { bulk_upload:, field_1: "1", field_96: "1", field_97: "1", field_98: nil } }
+
+ it "returns an error only on blank field" do
+ parser.valid?
+
+ expect(parser.errors[:field_96]).to be_blank
+ expect(parser.errors[:field_97]).to be_blank
+ expect(parser.errors[:field_98]).to be_present
+ end
+ end
+
context "when field 98 is 4 digits instead of 2" do
let(:attributes) { { bulk_upload:, field_98: "2022" } }
@@ -648,7 +682,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do
describe "#field_134" do
context "when an unpermitted value" do
- let(:attributes) { { bulk_upload:, field_134: 3 } }
+ let(:attributes) { { bulk_upload:, field_134: "3" } }
it "has errors on the field" do
expect(parser.errors[:field_134]).to be_present
@@ -714,6 +748,18 @@ RSpec.describe BulkUpload::Lettings::RowParser do
expect(parser.log.public_send(age)).to be(50)
end
end
+
+ context "when #{field} is a non-sensical value" do
+ let(:attributes) { { bulk_upload:, field.to_s => "A" } }
+
+ it "sets ##{known} to 0" do
+ expect(parser.log.public_send(known)).to be(0)
+ end
+
+ it "sets ##{age} to nil" do
+ expect(parser.log.public_send(age)).to be_nil
+ end
+ end
end
end
diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb
index 0aaeaac78..b9d351281 100644
--- a/spec/services/bulk_upload/lettings/validator_spec.rb
+++ b/spec/services/bulk_upload/lettings/validator_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "create validation error with correct values" do
validator.call
- error = BulkUploadError.order(:row, :field).first
+ error = BulkUploadError.find_by(field: "field_11")
expect(error.field).to eql("field_11")
expect(error.error).to eql("You must only answer the length of the tenancy if it's fixed-term")
@@ -51,6 +51,11 @@ RSpec.describe BulkUpload::Lettings::Validator do
expect(error.row).to eql("7")
expect(error.cell).to eql("L7")
expect(error.col).to eql("L")
+ expect(error.category).to be_nil
+
+ error = BulkUploadError.order(:row, :field).find_by(field: "field_111")
+
+ expect(error.category).to eql("setup")
end
end
@@ -255,11 +260,14 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
- file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
- file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
- file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
- file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
- file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
+ overrides = { age1: 50, age2: "R", age3: "R", age4: "4", age5: "R", age6: "R", age7: "R", age8: "R" }
+
+ file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
+ file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
+ file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
+ file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
+ file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
+
file.close
end
diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb
index d56b2ff4e..48b0c0258 100644
--- a/spec/services/bulk_upload/processor_spec.rb
+++ b/spec/services/bulk_upload/processor_spec.rb
@@ -91,6 +91,42 @@ RSpec.describe BulkUpload::Processor do
end
end
+ context "when a log has an incomplete setup section" do
+ let(:mock_downloader) do
+ instance_double(
+ BulkUpload::Downloader,
+ call: nil,
+ path: file_fixture("2022_23_lettings_bulk_upload.csv"),
+ delete_local_file!: nil,
+ )
+ end
+
+ let(:mock_validator) do
+ instance_double(
+ BulkUpload::Lettings::Validator,
+ invalid?: false,
+ call: nil,
+ any_setup_sections_incomplete?: true,
+ )
+ end
+
+ before do
+ allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
+ allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
+ end
+
+ it "sends setup failure email" do
+ mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
+
+ allow(BulkUploadMailer).to receive(:send_bulk_upload_failed_file_setup_error_mail).and_return(mail_double)
+
+ processor.call
+
+ expect(BulkUploadMailer).to have_received(:send_bulk_upload_failed_file_setup_error_mail)
+ expect(mail_double).to have_received(:deliver_later)
+ end
+ end
+
context "when processing a bulk upload with errors but below threshold (therefore creates logs)" do
let(:mock_downloader) do
instance_double(
@@ -106,6 +142,7 @@ RSpec.describe BulkUpload::Processor do
BulkUpload::Lettings::Validator,
invalid?: false,
call: nil,
+ any_setup_sections_incomplete?: false,
create_logs?: true,
)
end
@@ -156,6 +193,7 @@ RSpec.describe BulkUpload::Processor do
BulkUpload::Lettings::Validator,
invalid?: false,
call: nil,
+ any_setup_sections_incomplete?: false,
create_logs?: false,
)
end
@@ -216,6 +254,7 @@ RSpec.describe BulkUpload::Processor do
BulkUpload::Lettings::Validator,
call: nil,
create_logs?: true,
+ any_setup_sections_incomplete?: false,
invalid?: false,
)
end
diff --git a/spec/services/filter_service_spec.rb b/spec/services/filter_service_spec.rb
index 1e892fd56..2c89b1302 100644
--- a/spec/services/filter_service_spec.rb
+++ b/spec/services/filter_service_spec.rb
@@ -2,26 +2,80 @@ require "rails_helper"
describe FilterService do
describe "filter_by_search" do
- before do
- FactoryBot.create_list(:organisation, 5)
- FactoryBot.create(:organisation, name: "Acme LTD")
- end
+ context "when filtering organisations" do
+ before do
+ FactoryBot.create_list(:organisation, 5)
+ FactoryBot.create(:organisation, name: "Acme LTD")
+ end
+
+ let(:organisation_list) { Organisation.all }
+
+ context "when given a search term" do
+ let(:search_term) { "Acme" }
- let(:organisation_list) { Organisation.all }
+ it "filters the collection on search term" do
+ expect(described_class.filter_by_search(organisation_list, search_term).count).to eq(1)
+ end
+ end
- context "when given a search term" do
- let(:search_term) { "Acme" }
+ context "when not given a search term" do
+ let(:search_term) { nil }
- it "filters the collection on search term" do
- expect(described_class.filter_by_search(organisation_list, search_term).count).to eq(1)
+ it "does not filter the given collection" do
+ expect(described_class.filter_by_search(organisation_list, search_term).count).to eq(6)
+ end
end
end
- context "when not given a search term" do
- let(:search_term) { nil }
+ context "when filtering logs" do
+ context "when filtering lettings logs" do
+ before do
+ FactoryBot.create_list(:lettings_log, 5)
+ FactoryBot.create(:lettings_log, postcode_full: "SW1 1AA")
+ end
+
+ let(:lettings_log_list) { LettingsLog.all }
+
+ context "when given a postcode" do
+ let(:search_term) { "SW1 1AA" }
+
+ it "filters the collection on search term" do
+ expect(described_class.filter_by_search(lettings_log_list, search_term).count).to eq(1)
+ end
+ end
+
+ context "when not given a search term" do
+ let(:search_term) { nil }
+
+ it "does not filter the given collection" do
+ expect(described_class.filter_by_search(lettings_log_list, search_term).count).to eq(6)
+ end
+ end
+ end
+
+ context "when filtering sales logs" do
+ before do
+ FactoryBot.create_list(:sales_log, 5)
+ FactoryBot.create(:sales_log, purchid: "2")
+ end
+
+ let(:sales_log_list) { SalesLog.all }
+
+ context "when given a purchid" do
+ let(:search_term) { "2" }
+
+ it "filters the collection on search term" do
+ expect(described_class.filter_by_search(sales_log_list, search_term).count).to eq(1)
+ end
+ end
+
+ context "when not given a search term" do
+ let(:search_term) { nil }
- it "does not filter the given collection" do
- expect(described_class.filter_by_search(organisation_list, search_term).count).to eq(6)
+ it "does not filter the given collection" do
+ expect(described_class.filter_by_search(sales_log_list, search_term).count).to eq(6)
+ end
+ end
end
end
end
diff --git a/spec/services/imports/sales_logs_import_service_spec.rb b/spec/services/imports/sales_logs_import_service_spec.rb
index d2dc4ad94..2481cdce7 100644
--- a/spec/services/imports/sales_logs_import_service_spec.rb
+++ b/spec/services/imports/sales_logs_import_service_spec.rb
@@ -17,14 +17,6 @@ RSpec.describe Imports::SalesLogsImportService do
end
before do
- { "GL519EX" => "E07000078",
- "SW1A2AA" => "E09000033",
- "SW1A1AA" => "E09000033",
- "SW147QP" => "E09000027",
- "B955HZ" => "E07000221" }.each do |postcode, district_code|
- WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/#{postcode}/).to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"#{district_code}\",\"codes\":{\"admin_district\":\"#{district_code}\"}}}", headers: {})
- end
-
allow(Organisation).to receive(:find_by).and_return(nil)
allow(Organisation).to receive(:find_by).with(old_visible_id: organisation.old_visible_id).and_return(organisation)
allow(Organisation).to receive(:find_by).with(old_visible_id: managing_organisation.old_visible_id).and_return(managing_organisation)
@@ -119,6 +111,29 @@ RSpec.describe Imports::SalesLogsImportService do
end
end
+ context "and the log startdate is before 22/23 collection period" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:DAY").content = 10
+ sales_log_xml.at_xpath("//xmlns:MONTH").content = 10
+ sales_log_xml.at_xpath("//xmlns:YEAR").content = 2021
+ sales_log_xml.at_xpath("//xmlns:HODAY").content = 9
+ sales_log_xml.at_xpath("//xmlns:HOMONTH").content = 10
+ sales_log_xml.at_xpath("//xmlns:HOYEAR").content = 2021
+ sales_log_xml.at_xpath("//xmlns:EXDAY").content = 9
+ sales_log_xml.at_xpath("//xmlns:EXMONTH").content = 10
+ sales_log_xml.at_xpath("//xmlns:EXYEAR").content = 2021
+ end
+
+ it "does not create the log" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .to change(SalesLog, :count).by(0)
+ end
+ end
+
context "when the mortgage lender is set to an existing option" do
let(:sales_log_id) { "discounted_ownership_sales_log" }
@@ -188,6 +203,228 @@ RSpec.describe Imports::SalesLogsImportService do
end
end
+ context "and the mortgage soft validation is triggered (mortgage_value_check)" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q2Person1Income").content = "10"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the shared ownership deposit soft validation is triggered (shared_ownership_deposit_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:DerSaleType").content = "2"
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = "275000"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the purchase price soft validation is triggered (value_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ LaSaleRange.create!(la: "E09000033", bedrooms: 2, soft_min: 177_000, soft_max: 384_000, start_year: 2022)
+ sales_log_xml.at_xpath("//xmlns:Q22PurchasePrice").content = "2750"
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = "2750"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the purchase price soft validation is triggered (income1_value_check, income2_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q2Person1Income").content = "20"
+ sales_log_xml.at_xpath("//xmlns:Q2Person2Income").content = "10"
+ sales_log_xml.at_xpath("//xmlns:P2Eco").content = "1"
+ sales_log_xml.at_xpath("//xmlns:joint").content = "1 Yes"
+ sales_log_xml.at_xpath("//xmlns:JointMore").content = "2 No"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the savings soft validation is triggered (savings_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q3Savings").content = "200750"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the deposit soft validation is triggered (deposit_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q3Savings").content = "10"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the wheelchair soft validation is triggered (wheel_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q10Wheelchair").content = "1"
+ sales_log_xml.at_xpath("//xmlns:Disability").content = "2"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the retirement soft validation is triggered (retirement_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:P1Eco").content = "5"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the grant soft validation is triggered (grant_value_check)" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q32Reductions").content = "5000"
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = "270000"
+ sales_log_xml.at_xpath("//xmlns:Q33Discount").content = ""
+ sales_log_xml.at_xpath("//xmlns:DerSaleType").content = "22"
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and the stairbought soft validation is triggered (staircase_bought_value_check)" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:PercentBought").content = "51"
+ sales_log_xml.at_xpath("//xmlns:PercentOwns").content = "81"
+ sales_log_xml.at_xpath("//xmlns:Q17aStaircase").content = "1"
+ sales_log_xml.at_xpath("//xmlns:Q17Resale").content = ""
+ sales_log_xml.at_xpath("//xmlns:EXDAY").content = ""
+ sales_log_xml.at_xpath("//xmlns:EXMONTH").content = ""
+ sales_log_xml.at_xpath("//xmlns:EXYEAR").content = ""
+ sales_log_xml.at_xpath("//xmlns:HODAY").content = ""
+ sales_log_xml.at_xpath("//xmlns:HOMONTH").content = ""
+ sales_log_xml.at_xpath("//xmlns:HOYEAR").content = ""
+ end
+
+ it "completes the log" do
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.status).to eq("completed")
+ end
+ end
+
+ context "and it has an invalid record with invalid child, student and 16-19 age combination" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//meta:status").content = "submitted-invalid"
+ sales_log_xml.at_xpath("//xmlns:P2Age").content = 16
+ sales_log_xml.at_xpath("//xmlns:P2Eco").content = 7
+ sales_log_xml.at_xpath("//xmlns:P2Rel").content = "X"
+ end
+
+ it "intercepts the relevant validation error" do
+ expect(logger).to receive(:warn).with(/Removing field age2 from log triggering validation: Person cannot be aged 16-19 if they are a student but don't have relationship ‘child’/)
+ expect(logger).to receive(:warn).with(/Removing field relat2 from log triggering validation: Answer must be ‘child’ if the person is aged 16-19 and a student/)
+ expect(logger).to receive(:warn).with(/Removing field ecstat2 from log triggering validation: Person cannot be a student if they are aged 16-19 but don‘t have relationship ‘child’/)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .not_to raise_error
+ end
+
+ it "clears out the invalid answers" do
+ allow(logger).to receive(:warn)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+
+ expect(sales_log).not_to be_nil
+ expect(sales_log.age2).to be_nil
+ expect(sales_log.relat2).to be_nil
+ expect(sales_log.ecstat2).to be_nil
+ end
+ end
+
+ context "and it has an invalid record with invalid postcodes" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//meta:status").content = "submitted-invalid"
+ sales_log_xml.at_xpath("//xmlns:Q7Postcode").content = "A1 1AA"
+ sales_log_xml.at_xpath("//xmlns:Q14Postcode").content = "A1 2AA"
+ end
+
+ it "intercepts the relevant validation error" do
+ expect(logger).to receive(:warn).with(/Removing field postcode_full from log triggering validation: Buyer's last accommodation and discounted ownership postcodes must match/)
+ expect(logger).to receive(:warn).with(/Removing field ppostcode_full from log triggering validation: Buyer's last accommodation and discounted ownership postcodes must match/)
+ expect(logger).to receive(:warn).with(/Removing field postcode_full from log triggering validation: postcodes_not_matching/)
+ expect(logger).to receive(:warn).with(/Removing field ppostcode_full from log triggering validation: postcodes_not_matching/)
+ expect(logger).to receive(:warn).with(/Removing postcode known and previous postcode known as the postcodes are invalid/)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .not_to raise_error
+ end
+
+ it "clears out the invalid answers" do
+ allow(logger).to receive(:warn)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+
+ expect(sales_log).not_to be_nil
+ expect(sales_log.postcode_full).to be_nil
+ expect(sales_log.ppostcode_full).to be_nil
+ end
+ end
+
context "when inferring default answers for completed sales logs" do
context "when the armedforcesspouse is not answered" do
let(:sales_log_id) { "discounted_ownership_sales_log" }
@@ -799,6 +1036,18 @@ RSpec.describe Imports::SalesLogsImportService do
expect(sales_log&.ppostcode_full).to eq("GL51 9EX")
expect(sales_log&.status).to eq("completed")
end
+
+ it "correctly sets location fields for when location cannot be inferred from postcode" do
+ sales_log_xml.at_xpath("//xmlns:Q14ONSLACode").content = "E07000142"
+ sales_log_xml.at_xpath("//xmlns:Q14Postcode").content = "A11AA"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.pcodenk).to eq(0) # postcode known
+ expect(sales_log&.la_known).to eq(1) # la known
+ expect(sales_log&.la).to eq("E07000142")
+ expect(sales_log&.status).to eq("completed")
+ end
end
context "when setting default buyer 1 previous tenancy" do
@@ -824,6 +1073,59 @@ RSpec.describe Imports::SalesLogsImportService do
expect(sales_log&.prevten).to eq(2)
end
end
+
+ context "when mortgage used is don't know" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets mortgageused to don't know if mortlen, mortgage and extrabor are blank" do
+ sales_log_xml.at_xpath("//xmlns:MORTGAGEUSED").content = "3 Don't know"
+ sales_log_xml.at_xpath("//xmlns:Q35Borrowing").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q34b").content = ""
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q36CashDeposit").content = "134750"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgageused).to eq(3)
+ end
+
+ it "sets mortgageused to yes if mortgage is given" do
+ sales_log_xml.at_xpath("//xmlns:MORTGAGEUSED").content = "3 Don't know"
+ sales_log_xml.at_xpath("//xmlns:Q35Borrowing").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q34b").content = ""
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = "134750"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgageused).to eq(1)
+ end
+
+ it "sets mortgageused to yes if mortlen is given" do
+ sales_log_xml.at_xpath("//xmlns:MORTGAGEUSED").content = "3 Don't know"
+ sales_log_xml.at_xpath("//xmlns:Q35Borrowing").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q34b").content = "10"
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgageused).to eq(1)
+ end
+
+ it "sets mortgageused to yes if extrabor is given" do
+ sales_log_xml.at_xpath("//xmlns:MORTGAGEUSED").content = "3 Don't know"
+ sales_log_xml.at_xpath("//xmlns:Q35Borrowing").content = "3000"
+ sales_log_xml.at_xpath("//xmlns:Q34b").content = ""
+ sales_log_xml.at_xpath("//xmlns:CALCMORT").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgageused).to eq(1)
+ end
+ end
end
end
end
diff --git a/spec/support/bulk_upload/log_to_csv.rb b/spec/support/bulk_upload/log_to_csv.rb
index 628ee14b8..e15c9173b 100644
--- a/spec/support/bulk_upload/log_to_csv.rb
+++ b/spec/support/bulk_upload/log_to_csv.rb
@@ -22,14 +22,14 @@ class BulkUpload::LogToCsv
log.tenancy,
log.tenancyother, # 10
log.tenancylength,
- log.age1,
- log.age2,
- log.age3,
- log.age4,
- log.age5,
- log.age6,
- log.age7,
- log.age8,
+ log.age1 || overrides[:age1],
+ log.age2 || overrides[:age2],
+ log.age3 || overrides[:age3],
+ log.age4 || overrides[:age4],
+ log.age5 || overrides[:age5],
+ log.age6 || overrides[:age6],
+ log.age7 || overrides[:age7],
+ log.age8 || overrides[:age8],
log.sex1, # 20
log.sex2,