Browse Source

Merge branch 'main' into CLDC-3857-Add-new-questions-to-organisation-setup

CLDC-3857-Add-new-questions-to-organisation-setup
Manny Dinssa 1 month ago committed by GitHub
parent
commit
d2a4a6a163
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      app/helpers/collection_time_helper.rb
  2. 2
      app/models/form/lettings/questions/armedforces.rb
  3. 2
      app/models/form/lettings/questions/hb.rb
  4. 1
      app/models/form/lettings/questions/hbrentshortfall.rb
  5. 3
      app/models/form/lettings/questions/housingneeds_other.rb
  6. 2
      app/models/form/lettings/questions/joint.rb
  7. 6
      app/models/form/lettings/questions/reason.rb
  8. 13
      app/models/form/sales/pages/service_charge.rb
  9. 3
      app/models/form/sales/questions/armed_forces.rb
  10. 3
      app/models/form/sales/questions/armed_forces_spouse.rb
  11. 1
      app/models/form/sales/questions/buyer1_previous_tenure.rb
  12. 3
      app/models/form/sales/questions/buyer2_living_in.rb
  13. 3
      app/models/form/sales/questions/buyer_still_serving.rb
  14. 3
      app/models/form/sales/questions/extra_borrowing.rb
  15. 4
      app/models/form/sales/questions/has_leasehold_charges.rb
  16. 27
      app/models/form/sales/questions/has_service_charge.rb
  17. 3
      app/models/form/sales/questions/household_disability.rb
  18. 3
      app/models/form/sales/questions/household_wheelchair.rb
  19. 2
      app/models/form/sales/questions/housing_benefits.rb
  20. 1
      app/models/form/sales/questions/la_nominations.rb
  21. 4
      app/models/form/sales/questions/leasehold_charges.rb
  22. 3
      app/models/form/sales/questions/mortgageused.rb
  23. 1
      app/models/form/sales/questions/number_joint_buyers.rb
  24. 1
      app/models/form/sales/questions/previous_tenure.rb
  25. 3
      app/models/form/sales/questions/previous_tenure_buyer2.rb
  26. 1
      app/models/form/sales/questions/prevown.rb
  27. 1
      app/models/form/sales/questions/prevshared.rb
  28. 3
      app/models/form/sales/questions/property_wheelchair_accessible.rb
  29. 15
      app/models/form/sales/questions/service_charge.rb
  30. 1
      app/models/form/sales/questions/staircase.rb
  31. 3
      app/models/form/sales/questions/staircase_sale.rb
  32. 2
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  33. 9
      app/models/lettings_log.rb
  34. 24
      app/models/sales_log.rb
  35. 2
      app/models/validations/local_authority_validations.rb
  36. 1
      app/models/validations/sales/financial_validations.rb
  37. 8
      app/services/bulk_upload/lettings/validator.rb
  38. 9
      app/services/bulk_upload/sales/validator.rb
  39. 141
      app/services/csv/sales_log_csv_service.rb
  40. 2
      app/services/feature_toggle.rb
  41. 2
      app/views/form/guidance/_finding_scheme.erb
  42. 4
      config/locales/forms/2023/lettings/tenancy_information.en.yml
  43. 4
      config/locales/forms/2024/lettings/tenancy_information.en.yml
  44. 1
      config/locales/forms/2025/lettings/guidance.en.yml
  45. 4
      config/locales/forms/2025/lettings/income_and_benefits.en.yml
  46. 6
      config/locales/forms/2025/lettings/tenancy_information.en.yml
  47. 21
      config/locales/forms/2025/sales/sale_information.en.yml
  48. 3
      config/locales/validations/sales/financial.en.yml
  49. 207
      lib/tasks/update_manual_address_entry_selected_prexisting_logs.rake
  50. 3
      spec/fixtures/files/sales_logs_csv_export_codes_25.csv
  51. 2
      spec/fixtures/files/sales_logs_csv_export_labels_23.csv
  52. 2
      spec/fixtures/files/sales_logs_csv_export_labels_24.csv
  53. 3
      spec/fixtures/files/sales_logs_csv_export_labels_25.csv
  54. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv
  55. 3
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv
  56. 13
      spec/helpers/merge_requests_helper_spec.rb
  57. 211
      spec/lib/tasks/update_manual_address_entry_selected_prexisting_logs_spec.rb
  58. 3
      spec/models/form/lettings/questions/housingneeds_other_spec.rb
  59. 2
      spec/models/form/lettings/questions/joint_spec.rb
  60. 4
      spec/models/form/lettings/questions/reason_spec.rb
  61. 29
      spec/models/form/sales/pages/service_charge_spec.rb
  62. 3
      spec/models/form/sales/questions/armed_forces_spec.rb
  63. 3
      spec/models/form/sales/questions/armed_forces_spouse_spec.rb
  64. 1
      spec/models/form/sales/questions/buyer1_previous_tenure_spec.rb
  65. 3
      spec/models/form/sales/questions/buyer2_living_in_spec.rb
  66. 3
      spec/models/form/sales/questions/buyer_still_serving_spec.rb
  67. 3
      spec/models/form/sales/questions/extra_borrowing_spec.rb
  68. 49
      spec/models/form/sales/questions/has_service_charge_spec.rb
  69. 3
      spec/models/form/sales/questions/household_disability_spec.rb
  70. 3
      spec/models/form/sales/questions/household_wheelchair_spec.rb
  71. 1
      spec/models/form/sales/questions/la_nominations_spec.rb
  72. 2
      spec/models/form/sales/questions/mortgageused_spec.rb
  73. 1
      spec/models/form/sales/questions/number_joint_buyers_spec.rb
  74. 3
      spec/models/form/sales/questions/previous_tenure_buyer2_spec.rb
  75. 1
      spec/models/form/sales/questions/previous_tenure_spec.rb
  76. 1
      spec/models/form/sales/questions/prevown_spec.rb
  77. 1
      spec/models/form/sales/questions/prevshared_spec.rb
  78. 3
      spec/models/form/sales/questions/property_wheelchair_accessible_spec.rb
  79. 37
      spec/models/form/sales/questions/service_charge_spec.rb
  80. 3
      spec/models/form/sales/questions/staircase_sale_spec.rb
  81. 1
      spec/models/form/sales/questions/staircase_spec.rb
  82. 2
      spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb
  83. 10
      spec/models/lettings_log_derived_fields_spec.rb
  84. 15
      spec/models/sales_log_spec.rb
  85. 7
      spec/models/validations/sales/financial_validations_spec.rb
  86. 2
      spec/requests/form_controller_spec.rb
  87. 2
      spec/requests/merge_requests_controller_spec.rb
  88. 64
      spec/services/csv/sales_log_csv_service_spec.rb

10
app/helpers/collection_time_helper.rb

@ -53,4 +53,14 @@ module CollectionTimeHelper
def archived_collection_start_year
current_collection_start_year - 2
end
def generate_different_date_within_collection_year(date, start_date_override: nil, end_date_override: nil)
start_date = [start_date_override, collection_start_date(date).to_date].compact.max.to_date
end_date = [end_date_override, collection_end_date(date).to_date].compact.min.to_date
return nil if start_date > end_date
available_dates = (start_date..end_date).to_a - [date.to_date]
available_dates.empty? ? nil : available_dates.sample
end
end

2
app/models/form/lettings/questions/armedforces.rb

@ -13,8 +13,8 @@ class Form::Lettings::Questions::Armedforces < ::Form::Question
"4" => { "value" => "Yes – the person is a current or former reserve" },
"5" => { "value" => "Yes – the person is a spouse or civil partner of a UK armed forces member and has been bereaved or separated from them within the last 2 years" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Person prefers not to say" },
"divider" => { "value" => true },
"6" => { "value" => "Don’t know" },
}.freeze

2
app/models/form/lettings/questions/hb.rb

@ -12,9 +12,9 @@ class Form::Lettings::Questions::Hb < ::Form::Question
"1" => { "value" => "Housing benefit" },
"6" => { "value" => "Universal Credit housing element" },
"9" => { "value" => "Neither" },
"10" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
"10" => { "value" => "Tenant prefers not to say" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 89, 2024 => 88 }.freeze

1
app/models/form/lettings/questions/hbrentshortfall.rb

@ -11,6 +11,7 @@ class Form::Lettings::Questions::Hbrentshortfall < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

3
app/models/form/lettings/questions/housingneeds_other.rb

@ -13,7 +13,8 @@ class Form::Lettings::Questions::HousingneedsOther < ::Form::Question
{
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
"2" => { "value" => "Don't know" },
"divider" => { "value" => true },
"2" => { "value" => "Don’t know" },
}.freeze
else
{

2
app/models/form/lettings/questions/joint.rb

@ -11,7 +11,7 @@ class Form::Lettings::Questions::Joint < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => "true" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

6
app/models/form/lettings/questions/reason.rb

@ -45,8 +45,8 @@ class Form::Lettings::Questions::Reason < ::Form::Question
"30" => { "value" => "Under occupation (no incentive)" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"20" => { "value" => "Other" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
}.freeze
@ -83,9 +83,9 @@ class Form::Lettings::Questions::Reason < ::Form::Question
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"20" => { "value" => "Other" },
"28" => { "value" => "Don’t know" },
"divider" => { "value" => true },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
}.freeze
end

13
app/models/form/sales/pages/service_charge.rb

@ -0,0 +1,13 @@
class Form::Sales::Pages::ServiceCharge < ::Form::Page
def initialize(id, hsh, subsection)
super
@copy_key = "sales.sale_information.servicecharges"
end
def questions
@questions ||= [
Form::Sales::Questions::HasServiceCharge.new(nil, nil, self),
Form::Sales::Questions::ServiceCharge.new(nil, nil, self),
]
end
end

3
app/models/form/sales/questions/armed_forces.rb

@ -11,7 +11,8 @@ class Form::Sales::Questions::ArmedForces < ::Form::Question
"1" => { "value" => "Yes" },
"7" => { "value" => "No" },
"3" => { "value" => "Buyer prefers not to say" },
"8" => { "value" => "Don't know" },
"divider" => { "value" => true },
"8" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 62, 2024 => 64, 2025 => 61 }.freeze

3
app/models/form/sales/questions/armed_forces_spouse.rb

@ -11,7 +11,8 @@ class Form::Sales::Questions::ArmedForcesSpouse < ::Form::Question
"4" => { "value" => "Yes" },
"5" => { "value" => "No" },
"6" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Don't know" },
"divider" => { "value" => true },
"7" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 64, 2024 => 66, 2025 => 63 }.freeze

1
app/models/form/sales/questions/buyer1_previous_tenure.rb

@ -17,6 +17,7 @@ class Form::Sales::Questions::Buyer1PreviousTenure < ::Form::Question
"6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" },
"divider" => { "value" => true },
"0" => { "value" => "Don’t know" },
}
end

3
app/models/form/sales/questions/buyer2_living_in.rb

@ -10,7 +10,8 @@ class Form::Sales::Questions::Buyer2LivingIn < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 60, 2024 => 62, 2025 => 59 }.freeze

3
app/models/form/sales/questions/buyer_still_serving.rb

@ -11,7 +11,8 @@ class Form::Sales::Questions::BuyerStillServing < ::Form::Question
"4" => { "value" => "Yes" },
"5" => { "value" => "No" },
"6" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Don't know" },
"divider" => { "value" => true },
"7" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 63, 2024 => 65, 2025 => 62 }.freeze

3
app/models/form/sales/questions/extra_borrowing.rb

@ -12,7 +12,8 @@ class Form::Sales::Questions::ExtraBorrowing < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP = {

4
app/models/form/sales/questions/has_leasehold_charges.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::HasLeaseholdCharges < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "has_mscharge"
@copy_key = "sales.sale_information.leaseholdcharges.has_mscharge"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {
@ -16,6 +15,7 @@ class Form::Sales::Questions::HasLeaseholdCharges < ::Form::Question
],
}
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.leaseholdcharges.has_mscharge"
@question_number = QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.fetch(form.start_date.year, QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.max_by { |k, _v| k }.last)[ownershipsch]
end
@ -26,6 +26,6 @@ class Form::Sales::Questions::HasLeaseholdCharges < ::Form::Question
QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP = {
2024 => { 1 => 99, 2 => 110, 3 => 117 },
2025 => { 1 => 88, 2 => 111 },
2025 => { 2 => 111 },
}.freeze
end

27
app/models/form/sales/questions/has_service_charge.rb

@ -0,0 +1,27 @@
class Form::Sales::Questions::HasServiceCharge < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "has_mscharge"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {
"mscharge" => [1],
}
@hidden_in_check_answers = {
"depends_on" => [
{
"has_mscharge" => 1,
},
],
}
@copy_key = "sales.sale_information.servicecharges.has_servicecharge"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2025 => 88 }.freeze
end

3
app/models/form/sales/questions/household_disability.rb

@ -10,7 +10,8 @@ class Form::Sales::Questions::HouseholdDisability < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 65, 2024 => 67, 2025 => 64 }.freeze

3
app/models/form/sales/questions/household_wheelchair.rb

@ -10,7 +10,8 @@ class Form::Sales::Questions::HouseholdWheelchair < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 66, 2024 => 68, 2025 => 65 }.freeze

2
app/models/form/sales/questions/housing_benefits.rb

@ -11,8 +11,8 @@ class Form::Sales::Questions::HousingBenefits < ::Form::Question
ANSWER_OPTIONS = {
"2" => { "value" => "Housing benefit" },
"3" => { "value" => "Universal Credit housing element" },
"divider" => { "value" => true },
"1" => { "value" => "Neither housing benefit or Universal Credit housing element" },
"divider" => { "value" => true },
"4" => { "value" => "Don’t know " },
}.freeze

1
app/models/form/sales/questions/la_nominations.rb

@ -11,6 +11,7 @@ class Form::Sales::Questions::LaNominations < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

4
app/models/form/sales/questions/leasehold_charges.rb

@ -2,12 +2,12 @@ class Form::Sales::Questions::LeaseholdCharges < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mscharge"
@copy_key = "sales.sale_information.leaseholdcharges.mscharge"
@type = "numeric"
@min = 1
@step = 0.01
@width = 5
@prefix = "£"
@copy_key = "sales.sale_information.leaseholdcharges.mscharge"
@ownershipsch = ownershipsch
@question_number = QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.fetch(form.start_date.year, QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.max_by { |k, _v| k }.last)[ownershipsch]
end
@ -15,6 +15,6 @@ class Form::Sales::Questions::LeaseholdCharges < ::Form::Question
QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP = {
2023 => { 1 => 98, 2 => 109, 3 => 117 },
2024 => { 1 => 99, 2 => 110, 3 => 117 },
2025 => { 1 => 88, 2 => 111 },
2025 => { 2 => 111 },
}.freeze
end

3
app/models/form/sales/questions/mortgageused.rb

@ -22,11 +22,12 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
def answer_options_without_dont_know
ANSWER_OPTIONS.reject { |key, _v| key == "3" }
ANSWER_OPTIONS.reject { |key, _v| %w[3 divider].include?(key) }
end
def question_number_from_year_and_ownership

1
app/models/form/sales/questions/number_joint_buyers.rb

@ -10,6 +10,7 @@ class Form::Sales::Questions::NumberJointBuyers < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

1
app/models/form/sales/questions/previous_tenure.rb

@ -13,6 +13,7 @@ class Form::Sales::Questions::PreviousTenure < ::Form::Question
"2" => { "value" => "Affordable Rent" },
"3" => { "value" => "London Affordable Rent" },
"9" => { "value" => "Other" },
"divider" => { "value" => true },
"10" => { "value" => "Don’t know" },
}.freeze

3
app/models/form/sales/questions/previous_tenure_buyer2.rb

@ -16,7 +16,8 @@ class Form::Sales::Questions::PreviousTenureBuyer2 < ::Form::Question
"6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" },
"0" => { "value" => "Don't know" },
"divider" => { "value" => true },
"0" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 61, 2024 => 63, 2025 => 60 }.freeze

1
app/models/form/sales/questions/prevown.rb

@ -11,6 +11,7 @@ class Form::Sales::Questions::Prevown < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

1
app/models/form/sales/questions/prevshared.rb

@ -10,6 +10,7 @@ class Form::Sales::Questions::Prevshared < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze

3
app/models/form/sales/questions/property_wheelchair_accessible.rb

@ -11,7 +11,8 @@ class Form::Sales::Questions::PropertyWheelchairAccessible < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 17, 2024 => 21, 2025 => 19 }.freeze

15
app/models/form/sales/questions/service_charge.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::ServiceCharge < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "mscharge"
@type = "numeric"
@min = 1
@step = 0.01
@width = 5
@prefix = "£"
@copy_key = "sales.sale_information.servicecharges.servicecharge"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 88 }.freeze
end

1
app/models/form/sales/questions/staircase.rb

@ -17,6 +17,7 @@ class Form::Sales::Questions::Staircase < ::Form::Question
{
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
end

3
app/models/form/sales/questions/staircase_sale.rb

@ -11,7 +11,8 @@ class Form::Sales::Questions::StaircaseSale < ::Form::Question
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2023 => 79, 2024 => 81, 2025 => 92 }.freeze

2
app/models/form/sales/subsections/shared_ownership_initial_purchase.rb

@ -37,7 +37,7 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
Form::Sales::Pages::DepositDiscount.new("deposit_discount_optional", nil, self, optional: true),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::MonthlyRent.new(nil, nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::ServiceCharge.new("service_charge", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::EstateManagementFee.new("estate_management_fee", nil, self),
].compact

9
app/models/lettings_log.rb

@ -649,6 +649,7 @@ class LettingsLog < Log
super
self.postcode_known = nil if errors.attribute_names.include? :postcode_full
self.ppcodenk = nil if errors.attribute_names.include? :ppostcode_full
if errors.of_kind?(:earnings, :under_hard_min)
self.incfreq = nil
@ -714,9 +715,17 @@ class LettingsLog < Log
def process_postcode_changes!
self.postcode_full = upcase_and_remove_whitespace(postcode_full)
if is_renewal?
self.ppostcode_full = upcase_and_remove_whitespace(postcode_full)
end
return if postcode_full.blank?
self.postcode_known = 1
if is_renewal?
self.ppcodenk = 0
end
inferred_la = get_inferred_la(postcode_full)
self.is_la_inferred = inferred_la.present?
self.la = inferred_la if inferred_la.present?

24
app/models/sales_log.rb

@ -542,4 +542,28 @@ class SalesLog < Log
def log_type
"sales_log"
end
def has_servicecharge
has_mscharge if shared_ownership_scheme?
end
def has_servicecharge_label
form.get_question(:has_mscharge, self)&.label_from_value(has_mscharge) if shared_ownership_scheme?
end
def servicecharge
mscharge if shared_ownership_scheme?
end
def has_mscharge_value
has_mscharge if discounted_ownership_sale? || !form.start_year_2025_or_later?
end
def has_mscharge_label
form.get_question(:has_mscharge, self)&.label_from_value(has_mscharge) if discounted_ownership_sale? || !form.start_year_2025_or_later?
end
def mscharge_value
mscharge if discounted_ownership_sale? || !form.start_year_2025_or_later?
end
end

2
app/models/validations/local_authority_validations.rb

@ -1,6 +1,8 @@
module Validations::LocalAuthorityValidations
def validate_previous_accommodation_postcode(record)
postcode = record.ppostcode_full
return unless postcode
if record.previous_postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP))
error_message = I18n.t("validations.postcode")
record.errors.add :ppostcode_full, :wrong_format, message: error_message

1
app/models/validations/sales/financial_validations.rb

@ -134,6 +134,7 @@ module Validations::Sales::FinancialValidations
record.errors.add :stairowned, I18n.t("validations.sales.financial.stairowned.less_than_stairbought_plus_equity_plus_prev_staircasing", equity: formatted_equity, bought: formatted_stairbought, numprevstair: previous_staircasing_transactions, equity_sum:, stair_total: formatted_stairowned)
record.errors.add :stairbought, I18n.t("validations.sales.financial.stairbought.more_than_stairowned_minus_equity_minus_prev_staircasing", equity: formatted_equity, bought: formatted_stairbought, numprevstair: previous_staircasing_transactions, equity_sum:, stair_total: formatted_stairowned)
record.errors.add :numstair, I18n.t("validations.sales.financial.numstair.too_high_for_stairowned_minus_stairbought_minus_equity", equity: formatted_equity, bought: formatted_stairbought, numprevstair: previous_staircasing_transactions, equity_sum:, stair_total: formatted_stairowned)
record.errors.add :firststair, I18n.t("validations.sales.financial.firststair.invalid_for_stairowned_minus_stairbought_minus_equity", equity: formatted_equity, bought: formatted_stairbought, numprevstair: previous_staircasing_transactions, equity_sum:, stair_total: formatted_stairowned)
end
end
end

8
app/services/bulk_upload/lettings/validator.rb

@ -55,8 +55,10 @@ class BulkUpload::Lettings::Validator
row_parsers.each do |row_parser|
row_parser.log.blank_invalid_non_setup_fields!
end
if any_logs_invalid?
if invalid_row_numbers.any?
Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.")
Rails.logger.error("Lettings bulk upload #{bulk_upload.id} blocked due to invalid logs after blanking on rows #{invalid_row_numbers}")
"logs_invalid"
end
end
@ -101,8 +103,8 @@ private
end
end
def any_logs_invalid?
row_parsers.any? { |row_parser| row_parser.log.invalid? }
def invalid_row_numbers
row_parsers.each_with_index.map { |row_parser, index| index + row_offset + 1 if row_parser.log.invalid? }.compact
end
def csv_parser

9
app/services/bulk_upload/sales/validator.rb

@ -56,8 +56,9 @@ class BulkUpload::Sales::Validator
row_parser.log.blank_invalid_non_setup_fields!
end
if any_logs_invalid?
Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.")
if invalid_row_numbers.any?
Sentry.capture_message("Bulk upload #{bulk_upload.id} had log creation blocked due to invalid logs after blanking non setup fields. First invalid row number: #{invalid_row_numbers.first}.")
Rails.logger.error("Sales bulk upload #{bulk_upload.id} blocked due to invalid logs after blanking on rows #{invalid_row_numbers}")
"logs_invalid"
end
end
@ -98,8 +99,8 @@ private
end
end
def any_logs_invalid?
row_parsers.any? { |row_parser| row_parser.log.invalid? }
def invalid_row_numbers
row_parsers.each_with_index.map { |row_parser, index| index + row_offset + 1 if row_parser.log.invalid? }.compact
end
def csv_parser

141
app/services/csv/sales_log_csv_service.rb

@ -68,6 +68,46 @@ module Csv
labels: %i[monthly_charges_value_check],
codes: %i[monthly_charges_value_check],
},
stairlastday: {
labels: %i[lasttransaction day],
codes: %i[lasttransaction day],
},
stairlastmonth: {
labels: %i[lasttransaction month],
codes: %i[lasttransaction month],
},
stairlastyear: {
labels: %i[lasttransaction year],
codes: %i[lasttransaction year],
},
stairinitialday: {
labels: %i[initialpurchase day],
codes: %i[initialpurchase day],
},
stairinitialmonth: {
labels: %i[initialpurchase month],
codes: %i[initialpurchase month],
},
stairinitialyear: {
labels: %i[initialpurchase year],
codes: %i[initialpurchase year],
},
has_servicecharges: {
labels: %i[has_servicecharge_label],
codes: %i[has_servicecharge],
},
servicecharges: {
labels: %i[servicecharge],
codes: %i[servicecharge],
},
has_mscharge: {
labels: %i[has_mscharge_label],
codes: %i[has_mscharge_value],
},
mscharge: {
labels: %i[mscharge_value],
codes: %i[mscharge_value],
},
}.freeze
PERSON_DETAILS = {}.tap { |hash|
@ -97,7 +137,7 @@ module Csv
ORDERED_ADDRESS_FIELDS = %w[uprn address_line1 address_line2 town_or_city county postcode_full is_la_inferred la_label la uprn_selection address_search_value_check address_line1_input postcode_full_input address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered].freeze
SUPPORT_ONLY_ATTRIBUTES = %w[address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered created_by value_value_check mscharge_value_check].freeze
SUPPORT_ONLY_ATTRIBUTES = %w[address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered created_by created_by_id value_value_check mscharge_value_check].freeze
SUPPORT_ATTRIBUTE_NAME_MAPPINGS = {
"duplicate_set_id" => "DUPLICATESET",
@ -152,6 +192,19 @@ module Csv
"uprn_confirmed" => "UPRNCONFIRMED",
}.freeze
SUPPORT_ATTRIBUTE_NAME_MAPPINGS_2025 = {
"hholdcount" => "HHOLDCOUNT",
"created_by_id" => "CREATEDBYID",
"owning_organisation_id" => "OWNINGORGID",
"managing_organisation_id" => "MANINGORGID",
"assigned_to_id" => "USERNAMEID",
"updated_by" => "AMENDEDBY",
"updated_by_id" => "AMENDEDBYID",
"has_management_fee" => "HASESTATEFEE",
"management_fee" => "ESTATEFEE",
"has_servicecharges" => "HASSERVICECHARGES",
}.freeze
UPRN_CONFIRMED_LABELS = {
0 => "No",
1 => "Yes",
@ -161,20 +214,54 @@ module Csv
"uprn_confirmed" => UPRN_CONFIRMED_LABELS,
}.freeze
ATTRIBUTE_MAPPINGS = {
"saledate" => %w[day month year],
"exdate" => %w[exday exmonth exyear],
"hodate" => %w[hoday homonth hoyear],
"ppostcode_full" => %w[ppostc1 ppostc2],
"la" => %w[la la_label],
"prevloc" => %w[prevloc prevloc_label],
"assigned_to_id" => %w[created_by assigned_to],
"owning_organisation_id" => %w[owning_organisation_name],
"managing_organisation_id" => %w[managing_organisation_name],
"value" => %w[value value_value_check],
"mscharge" => %w[mscharge mscharge_value_check],
}.freeze
ATTRIBUTE_MAPPINGS_2024 = {
"uprn" => %w[uprn uprn_confirmed address_line1_input postcode_full_input uprn_selection],
}.freeze
ATTRIBUTE_MAPPINGS_2025 = {
"duplicate_set_id" => %w[duplicate_set_id owning_organisation_name owning_organisation_id managing_organisation_name managing_organisation_id],
"created_by_id" => %w[created_by created_by_id assigned_to assigned_to_id],
"updated_by_id" => %w[updated_by updated_by_id],
"bulk_upload_id" => %w[bulk_upload_id collection_start_year],
"prevten" => %w[hhtype prevten],
"mrent" => %w[mrent has_servicecharges servicecharges has_management_fee management_fee],
"lasttransaction" => %w[stairlastday stairlastmonth stairlastyear],
"initialpurchase" => %w[stairinitialday stairinitialmonth stairinitialyear],
"mrentprestaircasing" => %w[mrentprestaircasing grant discount extrabor has_mscharge mscharge mscharge_value_check],
}.freeze
def formatted_attribute_headers
return @attributes unless @user.support?
mappings = SUPPORT_ATTRIBUTE_NAME_MAPPINGS
mappings = mappings.merge(SUPPORT_ATTRIBUTE_NAME_MAPPINGS_2025) if @year == 2025
@attributes.map do |attribute|
SUPPORT_ATTRIBUTE_NAME_MAPPINGS[attribute] || attribute.upcase
mappings[attribute] || attribute.upcase
end
end
def sales_log_attributes
ordered_questions = FormHandler.instance.ordered_questions_for_year(@year, "sales")
ordered_questions.reject! { |q| q.id.match?(/((?<!la)_known)|(_check)|(_asked)|nationality_all_group|nationality_all_buyer2_group/) }
attributes = insert_derived_and_related_attributes(ordered_questions)
order_address_fields_for_support(attributes)
final_attributes = non_question_fields + attributes
ordered_questions.reject! { |q| q.id.match?(/organisation_id|created_by|assigned_to|soctenant|has_management_fee|management_fee|grant|discount|has_mscharge|mscharge|extrabor/) } if @year >= 2025
attributes = insert_checkbox_options(ordered_questions)
final_attributes = insert_derived_and_related_attributes(non_question_fields + attributes)
order_address_fields_for_support(final_attributes)
@user.support? ? final_attributes : final_attributes - SUPPORT_ONLY_ATTRIBUTES
end
@ -187,38 +274,40 @@ module Csv
}.compact
end
def insert_derived_and_related_attributes(ordered_questions)
def insert_checkbox_options(ordered_questions)
ordered_questions.flat_map do |question|
if question.type == "checkbox"
question.answer_options.keys
elsif attribute_mappings.key? question.id
attribute_mappings[question.id]
else
question.id
end
end
end
def insert_derived_and_related_attributes(attributes)
attributes.flat_map do |attribute|
if attribute_mappings.key? attribute
attribute_mappings[attribute]
else
attribute
end
end
end
def attribute_mappings
mappings = {
"saledate" => %w[day month year],
"exdate" => %w[exday exmonth exyear],
"hodate" => %w[hoday homonth hoyear],
"ppostcode_full" => %w[ppostc1 ppostc2],
"la" => %w[la la_label],
"prevloc" => %w[prevloc prevloc_label],
"assigned_to_id" => %w[created_by assigned_to],
"owning_organisation_id" => %w[owning_organisation_name],
"managing_organisation_id" => %w[managing_organisation_name],
"value" => %w[value value_value_check],
"mscharge" => %w[mscharge mscharge_value_check],
}
unless @user.support? && @year >= 2024
mappings["postcode_full"] = %w[pcode1 pcode2]
mappings = case @year
when 2024
ATTRIBUTE_MAPPINGS.merge(ATTRIBUTE_MAPPINGS_2024)
when 2025
ATTRIBUTE_MAPPINGS.merge(ATTRIBUTE_MAPPINGS_2024).merge(ATTRIBUTE_MAPPINGS_2025)
else
ATTRIBUTE_MAPPINGS
end
if @year >= 2024
mappings["uprn"] = %w[uprn uprn_confirmed address_line1_input postcode_full_input uprn_selection]
unless @user.support? && @year >= 2024
mappings = mappings.merge({ "postcode_full" => %w[pcode1 pcode2] })
end
mappings
end
@ -240,6 +329,8 @@ module Csv
%w[id status duplicate_set_id created_at updated_at old_form_id collection_start_year creation_method is_dpo]
when 2024
%w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo]
when 2025
%w[id status duplicate_set_id created_at created_by_id updated_at updated_by_id creation_method bulk_upload_id]
else
%w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo]
end

2
app/services/feature_toggle.rb

@ -1,6 +1,6 @@
class FeatureToggle
def self.allow_future_form_use?
Rails.env.development? || Rails.env.review? || Rails.env.staging?
false
end
def self.bulk_upload_duplicate_log_check_enabled?

2
app/views/form/guidance/_finding_scheme.erb

@ -2,6 +2,8 @@
<%= govuk_details(summary_text: I18n.t("forms.#{@log.form.start_date.year}.lettings.guidance.finding_scheme.title")) do %>
<%= I18n.t("forms.#{@log.form.start_date.year}.lettings.guidance.finding_scheme.content").html_safe %>
<p><%= govuk_link_to(I18n.t("forms.#{@log.form.start_date.year}.lettings.guidance.finding_scheme.view_schemes_link_text"), clear_filters_url(filter_type: "schemes")) %></p>
<% if I18n.exists?("forms.#{@log.form.start_date.year}.lettings.guidance.finding_scheme.scheme_changes_link_text") %>
<p><%= govuk_link_to(I18n.t("forms.#{@log.form.start_date.year}.lettings.guidance.finding_scheme.scheme_changes_link_text"), scheme_changes_path) %></p>
<% end %>
<% end %>
</div>

4
config/locales/forms/2023/lettings/tenancy_information.en.yml

@ -54,13 +54,13 @@ en:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 2 years for social or affordable rent general needs logs and you do not need a log for shorter tenancies."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 2 years for social or affordable rent general needs logs and you do not need a log for shorter tenancies."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_intermediate_rent:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 1 year for intermediate rent general needs logs and you do not need a log for shorter tenancies."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 1 year for intermediate rent general needs logs and you do not need a log for shorter tenancies."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_periodic:
page_header: ""

4
config/locales/forms/2024/lettings/tenancy_information.en.yml

@ -54,13 +54,13 @@ en:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_intermediate_rent:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 1 year for intermediate rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 1 year."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 1 year for intermediate rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 1 year."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_periodic:
page_header: ""

1
config/locales/forms/2025/lettings/guidance.en.yml

@ -12,7 +12,6 @@ en:
title: "Can’t find your scheme?"
content: "<p>Schemes are attached to the organisation that owns the property. Check you have correctly answered question 1 \"Which organisation owns this property?\"</p>
<p>If your organisation’s schemes were migrated from old CORE, they may have new names and codes. Search by postcode to find your scheme.</p>"
scheme_changes_link_text: "Read more about how schemes have changed"
view_schemes_link_text: "View your organisation’s schemes"
privacy_notice_tenant:

4
config/locales/forms/2025/lettings/income_and_benefits.en.yml

@ -41,7 +41,7 @@ en:
page_header: ""
check_answer_label: "Does the household pay rent or charges"
check_answer_prompt: "Tell us if the household pay rent or charges"
hint_text: "If rent is charged on the property then answer Yes to this question, even if the tenants do not pay it themselves."
hint_text: "If rent is charged on the property then answer Yes to this question, even if the tenants do not pay it themselves."
question_text: "Does the household pay rent or other charges for the accommodation?"
period:
@ -124,5 +124,5 @@ en:
tshortfall:
check_answer_label: "Estimated outstanding amount"
check_answer_prompt: ""
hint_text: "Also known as the ‘outstanding amount’."
hint_text: ""
question_text: "Estimated outstanding amount"

6
config/locales/forms/2025/lettings/tenancy_information.en.yml

@ -14,7 +14,7 @@ en:
page_header: ""
check_answer_label: "Starter or introductory tenancy"
check_answer_prompt: "Tell us if it's a starter or introductory tenancy"
hint_text: "If the tenancy has an ‘introductory period’ answer ‘yes’.<br><br>You should submit a CORE log at the beginning of the starter tenancy or introductory period, with the best information you have at the time. You do not need to submit a log when a tenant later rolls onto the main tenancy."
hint_text: "If the tenancy has an ‘introductory period’, answer ‘yes’.<br><br>You should submit a CORE log at the beginning of the starter tenancy or introductory period, with the best information you have at the time. You do not need to submit a log when a tenant later rolls onto the main tenancy."
question_text: "Is this a starter tenancy?"
tenancy:
@ -54,13 +54,13 @@ en:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_intermediate_rent:
page_header: ""
check_answer_label: "Length of fixed-term tenancy"
check_answer_prompt: ""
hint_text: "Do not include the starter or introductory period.</br>The minimum period is 1 year for intermediate rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 1 year."
hint_text: "Do not include the starter or introductory period.</br></br>The minimum period is 1 year for intermediate rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 1 year."
question_text: "What is the length of the fixed-term tenancy to the nearest year?"
tenancy_length_periodic:
page_header: ""

21
config/locales/forms/2025/sales/sale_information.en.yml

@ -215,7 +215,7 @@ en:
page_header: "About the deposit"
check_answer_label: "Deposit amount"
check_answer_prompt: ""
hint_text: "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage. This excludes any grant or loan. As this is a fully staircased sale this question is optional. If you do not have the information available click save and continue"
hint_text: "Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage. This excludes any grant or loan."
question_text: "How much cash deposit was paid on the property?"
cashdis:
@ -248,16 +248,29 @@ en:
leaseholdcharges:
page_header: ""
has_mscharge:
check_answer_label: "Property service charges"
check_answer_prompt: "Enter service charges if any"
check_answer_label: "Property leasehold charges"
check_answer_prompt: "Enter leasehold charges if any"
hint_text: "For example, service and management charges"
question_text: "Does the property have any service charges?"
question_text: "Does the property have any monthly leasehold charges?"
mscharge:
check_answer_label: "Monthly leasehold charges"
check_answer_prompt: ""
hint_text: ""
question_text: "Enter the total monthly charge"
servicecharges:
page_header: ""
has_servicecharge:
check_answer_label: "Property service charges"
check_answer_prompt: "Enter service charges if any"
hint_text: "This includes any charges for day-to-day maintenance and repairs, building insurance, and any contributions to a sinking or reserved fund. It does not include estate management fees."
question_text: "Does the property have any service charges?"
servicecharge:
check_answer_label: "Monthly service charges"
check_answer_prompt: ""
hint_text: ""
question_text: "Enter the total monthly charge"
purchase_price:
discounted_ownership:
page_header: "About the price of the property"

3
config/locales/validations/sales/financial.en.yml

@ -83,6 +83,9 @@ en:
percentage_bought_equal_percentage_owned: "The percentage bought is %{stairbought}% and the percentage owned in total is %{stairowned}%. These figures cannot be the same."
more_than_stairowned_minus_equity_minus_prev_staircasing: "The initial equity stake is %{equity}%, the percentage bought is %{bought}%, and there have been %{numprevstair} previous staircasing transactions, totalling at least %{equity_sum}%, which is more than the total percentage owned by the buyers (%{stair_total}%). In a staircasing transaction, the total percentage owned must be at least the initial equity stake plus the percentage bought plus a minimum of 1% for each previous staircasing transaction."
firststair:
invalid_for_stairowned_minus_stairbought_minus_equity: "The initial equity stake is %{equity}%, the percentage bought is %{bought}%, and there have been %{numprevstair} previous staircasing transactions, totalling at least %{equity_sum}%, which is more than the total percentage owned by the buyers (%{stair_total}%). In a staircasing transaction, the total percentage owned must be at least the initial equity stake plus the percentage bought plus a minimum of 1% for each previous staircasing transaction."
numstair:
too_high_for_stairowned_minus_stairbought_minus_equity: "The initial equity stake is %{equity}%, the percentage bought is %{bought}%, and there have been %{numprevstair} previous staircasing transactions, totalling at least %{equity_sum}%, which is more than the total percentage owned by the buyers (%{stair_total}%). In a staircasing transaction, the total percentage owned must be at least the initial equity stake plus the percentage bought plus a minimum of 1% for each previous staircasing transaction."

207
lib/tasks/update_manual_address_entry_selected_prexisting_logs.rake

@ -1,207 +0,0 @@
namespace :bulk_update do
desc "Update logs with specific criteria and set manual_address_entry_selected to true"
task update_manual_address_entry_selected: :environment do
updated_lettings_logs_count = 0
lettings_postcode_fixed_count = 0
lettings_postcode_fixed_status_changed_count = 0
lettings_postcode_not_fixed_status_changed_count = 0
lettings_postcode_fixed_status_changed_ids = []
lettings_postcode_not_fixed_status_changed_ids = []
lettings_updated_without_issue = 0
updated_sales_logs_count = 0
sales_postcode_fixed_count = 0
sales_postcode_fixed_status_changed_count = 0
sales_postcode_not_fixed_status_changed_count = 0
sales_postcode_fixed_status_changed_ids = []
sales_postcode_not_fixed_status_changed_ids = []
sales_updated_without_issue = 0
lettings_logs = LettingsLog.filter_by_year(2024)
.where(status: %w[in_progress completed])
.where(needstype: 1, manual_address_entry_selected: false, uprn: nil)
.where("(address_line1 IS NOT NULL AND address_line1 != '') OR (address_line2 IS NOT NULL AND address_line2 != '') OR (town_or_city IS NOT NULL AND town_or_city != '') OR (county IS NOT NULL AND county != '') OR (postcode_full IS NOT NULL AND postcode_full != '')")
lettings_logs.find_each do |log|
status_pre_change = log.status
log.manual_address_entry_selected = true
if log.save
updated_lettings_logs_count += 1
Rails.logger.info "manual_address_entry_selected updated for lettings log #{log.id}"
else
Rails.logger.info "Could not save manual_address_entry_selected changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
postcode_fixed = false
if log.postcode_full.nil? && log.address_line1 == log.address_line1_input
log.postcode_full = log.postcode_full_input
if log.save
lettings_postcode_fixed_count += 1
Rails.logger.info "postcode_full updated by address_line1_input for lettings log #{log.id}"
postcode_fixed = true
else
Rails.logger.info "Could not save postcode_full changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
end
if log.postcode_full.nil? && log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered
log.postcode_full = log.postcode_full_as_entered
if log.save
lettings_postcode_fixed_count += 1
Rails.logger.info "postcode_full updated by address_line1_as_entered for lettings log #{log.id}"
postcode_fixed = true
else
Rails.logger.info "Could not save postcode_full changes to lettings log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
end
status_post_change = log.status
if status_pre_change != status_post_change
if postcode_fixed
lettings_postcode_fixed_status_changed_count += 1
lettings_postcode_fixed_status_changed_ids << log.id
else
lettings_postcode_not_fixed_status_changed_count += 1
lettings_postcode_not_fixed_status_changed_ids << log.id
end
else
lettings_updated_without_issue += 1
end
end
sales_logs = SalesLog.filter_by_year(2024)
.where(status: %w[in_progress completed])
.where(manual_address_entry_selected: false, uprn: nil)
.where("(address_line1 IS NOT NULL AND address_line1 != '') OR (address_line2 IS NOT NULL AND address_line2 != '') OR (town_or_city IS NOT NULL AND town_or_city != '') OR (county IS NOT NULL AND county != '') OR (postcode_full IS NOT NULL AND postcode_full != '')")
sales_logs.find_each do |log|
status_pre_change = log.status
log.manual_address_entry_selected = true
if log.save
updated_sales_logs_count += 1
Rails.logger.info "manual_address_entry_selected updated for sales log #{log.id}"
else
Rails.logger.info "Could not save manual_address_entry_selected changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
postcode_fixed = false
if log.postcode_full.nil? && log.address_line1 == log.address_line1_input
log.postcode_full = log.postcode_full_input
if log.save
sales_postcode_fixed_count += 1
Rails.logger.info "postcode_full updated by address_line1_input for sales log #{log.id}"
postcode_fixed = true
else
Rails.logger.info "Could not save postcode_full changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
end
if log.postcode_full.nil? && log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered
log.postcode_full = log.postcode_full_as_entered
if log.save
sales_postcode_fixed_count += 1
Rails.logger.info "postcode_full updated by address_line1_as_entered for sales log #{log.id}"
postcode_fixed = true
else
Rails.logger.info "Could not save postcode_full changes to sales log #{log.id} : #{log.errors.full_messages.join(', ')}"
end
end
status_post_change = log.status
if status_pre_change != status_post_change
if postcode_fixed
sales_postcode_fixed_status_changed_count += 1
sales_postcode_fixed_status_changed_ids << log.id
else
sales_postcode_not_fixed_status_changed_count += 1
sales_postcode_not_fixed_status_changed_ids << log.id
end
else
sales_updated_without_issue += 1
end
end
puts "#{updated_lettings_logs_count} lettings logs were updated."
puts "#{lettings_updated_without_issue} lettings logs were updated without issue."
puts "#{lettings_postcode_fixed_count} lettings logs where postcode fix was applied."
puts "#{lettings_postcode_fixed_status_changed_count} lettings logs with postcode fix and status changed."
puts "#{lettings_postcode_not_fixed_status_changed_count} lettings logs without postcode fix and status changed."
puts "IDs of lettings logs with postcode fix and status changed: [#{lettings_postcode_fixed_status_changed_ids.join(', ')}]"
puts "IDs of lettings logs without postcode fix and status changed: [#{lettings_postcode_not_fixed_status_changed_ids.join(', ')}]"
lettings_postcode_fixed_org_counts = LettingsLog.where(id: lettings_postcode_fixed_status_changed_ids).group(:owning_organisation_id).count
lettings_postcode_fixed_org_counts.each do |org_id, count|
puts "Org #{org_id}: #{count} logs with postcode fix and status changed."
end
lettings_postcode_not_fixed_org_counts = LettingsLog.where(id: lettings_postcode_not_fixed_status_changed_ids).group(:owning_organisation_id).count
lettings_postcode_not_fixed_org_counts.each do |org_id, count|
puts "Org #{org_id}: #{count} logs without postcode fix and status changed."
end
puts "#{updated_sales_logs_count} sales logs were updated."
puts "#{sales_updated_without_issue} sales logs were updated without issue."
puts "#{sales_postcode_fixed_count} sales logs where postcode fix was applied."
puts "#{sales_postcode_fixed_status_changed_count} sales logs with postcode fix and status changed."
puts "#{sales_postcode_not_fixed_status_changed_count} sales logs without postcode fix and status changed."
puts "IDs of sales logs with postcode fix and status changed: [#{sales_postcode_fixed_status_changed_ids.join(', ')}]"
puts "IDs of sales logs without postcode fix and status changed: [#{sales_postcode_not_fixed_status_changed_ids.join(', ')}]"
sales_postcode_fixed_org_counts = SalesLog.where(id: sales_postcode_fixed_status_changed_ids).group(:owning_organisation_id).count
sales_postcode_fixed_org_counts.each do |org_id, count|
puts "Org #{org_id}: #{count} logs with postcode fix and status changed."
end
sales_postcode_not_fixed_org_counts = SalesLog.where(id: sales_postcode_not_fixed_status_changed_ids).group(:owning_organisation_id).count
sales_postcode_not_fixed_org_counts.each do |org_id, count|
puts "Org #{org_id}: #{count} logs without postcode fix and status changed."
end
end
desc "Find logs to fix and update postcode_full if conditions are met"
task update_postcode_full_preexisting_manual_entry_logs: :environment do
updated_count = 0
fixed_count = 0
not_updated_count = 0
not_updated_ids = []
updated_but_not_fixed_ids = []
logs_to_fix = LettingsLog.filter_by_year(2024).where(manual_address_entry_selected: true, uprn: nil, status: "in_progress", postcode_full: nil, updated_at: Time.zone.parse("2025-03-19 16:00:00")..Time.zone.parse("2025-03-19 17:00:00"))
logs_to_fix.find_each do |log|
previous_version = log.versions[-2]
previous_status = previous_version&.reify&.status
if log.address_line1 == log.address_line1_input
log.postcode_full = log.postcode_full_input
elsif log.creation_method == "bulk upload" && log.address_line1 == log.address_line1_as_entered
log.postcode_full = log.postcode_full_as_entered
end
if log.postcode_full.present?
if log.save
Rails.logger.info "Updated postcode_full for lettings log #{log.id}"
updated_count += 1
if log.status == previous_status
fixed_count += 1
else
updated_but_not_fixed_ids << log.id
end
else
Rails.logger.info "Could not save changes to lettings log #{log.id}: #{log.errors.full_messages.join(', ')}"
not_updated_count += 1
not_updated_ids << log.id
end
else
not_updated_count += 1
not_updated_ids << log.id
end
end
puts "#{updated_count} logs updated."
puts "#{fixed_count} logs fixed."
puts "#{not_updated_count} logs not updated."
puts "IDs of logs not updated: [#{not_updated_ids.join(', ')}]"
puts "IDs of logs updated but not fixed: [#{updated_but_not_fixed_ids.join(', ')}]"
end
end

3
spec/fixtures/files/sales_logs_csv_export_codes_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

13
spec/helpers/merge_requests_helper_spec.rb

@ -1,6 +1,8 @@
require "rails_helper"
RSpec.describe MergeRequestsHelper do
include CollectionTimeHelper
describe "#merging_organisations_without_users_text" do
context "with 1 organisation" do
let(:organisation) { build(:organisation, name: "Org 1") }
@ -163,7 +165,8 @@ RSpec.describe MergeRequestsHelper do
let(:organisation) { create(:organisation, name: "Org 1") }
let(:merging_organisation) { create(:organisation, name: "Org 2") }
let(:merging_organisation_2) { create(:organisation, name: "Org 3") }
let(:merge_request) { create(:merge_request, absorbing_organisation: organisation, merge_date: Time.zone.today) }
let(:merge_date) { Time.zone.local(2025, 1, 1) }
let(:merge_request) { create(:merge_request, absorbing_organisation: organisation, merge_date:) }
before do
create(:merge_request_organisation, merge_request:, merging_organisation:)
@ -198,10 +201,10 @@ RSpec.describe MergeRequestsHelper do
context "when merging organisations have logs" do
before do
create(:lettings_log, owning_organisation: organisation)
create(:lettings_log, owning_organisation: merging_organisation, startdate: Time.zone.tomorrow)
create(:lettings_log, owning_organisation: merging_organisation, startdate: generate_different_date_within_collection_year(merge_date, start_date_override: merge_date, end_date_override: Time.zone.now + 14.days))
create(:lettings_log, owning_organisation: merging_organisation, startdate: Time.zone.yesterday)
create(:sales_log, owning_organisation: organisation)
create(:sales_log, owning_organisation: merging_organisation, saledate: Time.zone.tomorrow)
create(:sales_log, owning_organisation: merging_organisation, saledate: generate_different_date_within_collection_year(merge_date, start_date_override: merge_date, end_date_override: Time.zone.now + 14.days))
create(:sales_log, owning_organisation: merging_organisation, saledate: Time.zone.yesterday)
end
@ -235,8 +238,8 @@ RSpec.describe MergeRequestsHelper do
before do
create(:organisation_relationship, parent_organisation: merging_organisation_2, child_organisation: merging_organisation)
create(:merge_request_organisation, merge_request:, merging_organisation: merging_organisation_2)
create(:lettings_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, startdate: Time.zone.yesterday)
create(:sales_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, saledate: Time.zone.yesterday)
create(:lettings_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, startdate: generate_different_date_within_collection_year(merge_date, start_date_override: merge_date, end_date_override: Time.zone.now + 14.days))
create(:sales_log, assigned_to: merging_organisation_2.users.first, owning_organisation: merging_organisation_2, managing_organisation: merging_organisation, saledate: generate_different_date_within_collection_year(merge_date, start_date_override: merge_date, end_date_override: Time.zone.now + 14.days))
end
it "returns the correct merging_organisations_lettings_logs_outcomes_text text" do

211
spec/lib/tasks/update_manual_address_entry_selected_prexisting_logs_spec.rb

@ -1,211 +0,0 @@
require "rails_helper"
require "rake"
RSpec.describe "update_manual_address_entry_selected_preexisting_logs_spec", type: :task do
before do
Rake.application.rake_require("tasks/update_manual_address_entry_selected_prexisting_logs")
Rake::Task.define_task(:environment)
task.reenable
end
describe "bulk_update:update_manual_address_entry_selected" do
let(:task) { Rake::Task["bulk_update:update_manual_address_entry_selected"] }
let(:lettings_log_uprn_entered) do
build(:lettings_log, :completed, startdate: Time.zone.local(2024, 6, 1), needstype: 1, manual_address_entry_selected: false)
end
let(:lettings_log_uprn_found) do
build(:lettings_log, :completed, startdate: Time.zone.local(2024, 9, 1), needstype: 1, manual_address_entry_selected: false, address_line1_input: "1 Test Street", postcode_full_input: "SW1 1AA")
end
let(:lettings_log_address_fields_not_entered) do
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 9, 1), needstype: 1)
end
let(:lettings_log_address_manually_entered) do
build(:lettings_log, :completed_without_uprn, startdate: Time.zone.local(2024, 12, 1), needstype: 1)
end
let(:sales_log_uprn_entered) do
build(:sales_log, :completed, saledate: Time.zone.local(2024, 12, 1), manual_address_entry_selected: false)
end
let(:sales_log_uprn_found) do
build(:sales_log, :completed, saledate: Time.zone.local(2024, 7, 1), manual_address_entry_selected: false, address_line1_input: "1 Test Street", postcode_full_input: "SW1 1AA")
end
let(:sales_log_address_fields_not_entered) do
build(:sales_log, :inprogress_without_address_fields, saledate: Time.zone.local(2024, 12, 30))
end
let(:sales_log_address_manually_entered) do
build(:sales_log, :completed_without_uprn, saledate: Time.zone.local(2024, 12, 30))
end
context "when running the task" do
context "when logs do not meet the criteria" do
before do
lettings_log_uprn_found.save!(validate: false)
lettings_log_uprn_entered.save!(validate: false)
lettings_log_address_fields_not_entered.save!(validate: false)
sales_log_uprn_found.save!(validate: false)
sales_log_uprn_entered.save!(validate: false)
sales_log_address_fields_not_entered.save!(validate: false)
end
it "does not update logs with a UPRN entered" do
task.invoke
lettings_log_uprn_entered.reload
sales_log_uprn_entered.reload
expect(lettings_log_uprn_entered.manual_address_entry_selected).to be false
expect(lettings_log_uprn_entered.uprn).to eq("10033558653")
expect(sales_log_uprn_entered.manual_address_entry_selected).to be false
expect(sales_log_uprn_entered.uprn).to eq("10033558653")
end
it "does not update logs with a UPRN found" do
task.invoke
lettings_log_uprn_found.reload
sales_log_uprn_found.reload
expect(lettings_log_uprn_found.manual_address_entry_selected).to be false
expect(lettings_log_uprn_found.uprn).to eq("10033558653")
expect(sales_log_uprn_found.manual_address_entry_selected).to be false
expect(sales_log_uprn_found.uprn).to eq("10033558653")
end
it "does not update logs with no UPRN or address fields entered" do
task.invoke
lettings_log_address_fields_not_entered.reload
sales_log_address_fields_not_entered.reload
expect(lettings_log_address_fields_not_entered.manual_address_entry_selected).to be false
expect(sales_log_address_fields_not_entered.manual_address_entry_selected).to be false
end
end
context "when logs do meet the criteria" do
before do
lettings_log_address_manually_entered.manual_address_entry_selected = false
lettings_log_address_manually_entered.save!(validate: false)
sales_log_address_manually_entered.manual_address_entry_selected = false
sales_log_address_manually_entered.save!(validate: false)
end
it "updates logs with an address manually entered" do
expect(lettings_log_address_manually_entered.manual_address_entry_selected).to be false
expect(lettings_log_address_manually_entered.address_line1).to eq("1 Test Street")
expect(lettings_log_address_manually_entered.address_line2).to eq("Testville")
expect(lettings_log_address_manually_entered.town_or_city).to eq("Testford")
expect(lettings_log_address_manually_entered.postcode_full).to eq("SW1 1AA")
expect(sales_log_address_manually_entered.manual_address_entry_selected).to be false
expect(sales_log_address_manually_entered.address_line1).to eq("1 Test Street")
expect(sales_log_address_manually_entered.address_line2).to eq("Testville")
expect(sales_log_address_manually_entered.town_or_city).to eq("Testford")
expect(sales_log_address_manually_entered.postcode_full).to eq("SW1 1AA")
task.invoke
lettings_log_address_manually_entered.reload
sales_log_address_manually_entered.reload
expect(lettings_log_address_manually_entered.manual_address_entry_selected).to be true
expect(lettings_log_address_manually_entered.address_line1).to eq("1 Test Street")
expect(lettings_log_address_manually_entered.address_line2).to eq("Testville")
expect(lettings_log_address_manually_entered.town_or_city).to eq("Testford")
expect(lettings_log_address_manually_entered.postcode_full).to eq("SW1 1AA")
expect(sales_log_address_manually_entered.manual_address_entry_selected).to be true
expect(sales_log_address_manually_entered.address_line1).to eq("1 Test Street")
expect(sales_log_address_manually_entered.address_line2).to eq("Testville")
expect(sales_log_address_manually_entered.town_or_city).to eq("Testford")
expect(sales_log_address_manually_entered.postcode_full).to eq("SW1 1AA")
end
end
end
end
describe "bulk_update:update_postcode_full_preexisting_manual_entry_logs" do
let(:task) { Rake::Task["bulk_update:update_postcode_full_preexisting_manual_entry_logs"] }
let(:lettings_log_to_fix) do
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), updated_at: Time.zone.parse("2025-03-19 16:30:00"))
end
let(:bu_lettings_log_to_fix) do
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), creation_method: "bulk upload", updated_at: Time.zone.parse("2025-03-19 16:30:00"))
end
let(:lettings_log_not_to_fix) do
build(:lettings_log, :inprogress_without_address_fields, startdate: Time.zone.local(2024, 6, 1), updated_at: Time.zone.parse("2025-03-19 15:30:00"))
end
before do
lettings_log_to_fix.manual_address_entry_selected = true
lettings_log_to_fix.address_line1 = "1 Test Street"
lettings_log_to_fix.address_line2 = "Testville"
lettings_log_to_fix.town_or_city = "Testford"
lettings_log_to_fix.postcode_full = nil
lettings_log_to_fix.address_line1_input = "1 Test Street"
lettings_log_to_fix.postcode_full_input = "SW1 2BB"
lettings_log_to_fix.save!(validate: false)
bu_lettings_log_to_fix.manual_address_entry_selected = true
bu_lettings_log_to_fix.address_line1 = "1 Test Street"
bu_lettings_log_to_fix.address_line2 = "Testville"
bu_lettings_log_to_fix.town_or_city = "Testford"
bu_lettings_log_to_fix.postcode_full = nil
bu_lettings_log_to_fix.address_line1_as_entered = "1 Test Street"
bu_lettings_log_to_fix.postcode_full_as_entered = "SW1 2BB"
bu_lettings_log_to_fix.save!(validate: false)
lettings_log_not_to_fix.postcode_full = nil
lettings_log_not_to_fix.save!(validate: false)
end
context "when running the task" do
it "updates logs that meet the criteria" do
expect(lettings_log_to_fix.postcode_full).to be_nil
expect(lettings_log_to_fix.address_line1).to eq("1 Test Street")
expect(lettings_log_to_fix.address_line2).to eq("Testville")
expect(lettings_log_to_fix.town_or_city).to eq("Testford")
expect(lettings_log_to_fix.address_line1_input).to eq("1 Test Street")
expect(lettings_log_to_fix.postcode_full_input).to eq("SW1 2BB")
expect(bu_lettings_log_to_fix.postcode_full).to be_nil
expect(bu_lettings_log_to_fix.address_line1_input).to be_nil
expect(bu_lettings_log_to_fix.address_line1).to eq("1 Test Street")
expect(bu_lettings_log_to_fix.address_line2).to eq("Testville")
expect(bu_lettings_log_to_fix.town_or_city).to eq("Testford")
expect(bu_lettings_log_to_fix.address_line1_as_entered).to eq("1 Test Street")
expect(bu_lettings_log_to_fix.postcode_full_as_entered).to eq("SW1 2BB")
task.invoke
lettings_log_to_fix.reload
bu_lettings_log_to_fix.reload
expect(lettings_log_to_fix.postcode_full).to eq(lettings_log_to_fix.postcode_full_input)
expect(lettings_log_to_fix.postcode_full).to eq("SW1 2BB")
expect(bu_lettings_log_to_fix.postcode_full).to eq(bu_lettings_log_to_fix.postcode_full_as_entered)
expect(bu_lettings_log_to_fix.postcode_full).to eq("SW1 2BB")
end
it "does not update logs that do not meet the criteria" do
task.invoke
lettings_log_not_to_fix.reload
expect(lettings_log_not_to_fix.postcode_full).to be_nil
end
end
end
end

3
spec/models/form/lettings/questions/housingneeds_other_spec.rb

@ -49,7 +49,8 @@ RSpec.describe Form::Lettings::Questions::HousingneedsOther, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
"2" => { "value" => "Don't know" },
"divider" => { "value" => true },
"2" => { "value" => "Don’t know" },
})
end
end

2
spec/models/form/lettings/questions/joint_spec.rb

@ -31,7 +31,7 @@ RSpec.describe Form::Lettings::Questions::Joint, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => "true" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

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

@ -162,10 +162,10 @@ RSpec.describe Form::Lettings::Questions::Reason, type: :model do
"30" => { "value" => "Under occupation (no incentive)" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"47" => { "value" => "Tenant prefers not to say" },
"20" => { "value" => "Other" },
"28" => { "value" => "Don’t know" },
"divider" => { "value" => true },
"47" => { "value" => "Tenant prefers not to say" },
"28" => { "value" => "Don’t know" },
})
end
end

29
spec/models/form/sales/pages/service_charge_spec.rb

@ -0,0 +1,29 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::ServiceCharge, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[has_mscharge mscharge])
end
it "has the correct id" do
expect(page.id).to eq(nil)
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to be_nil
end
end

3
spec/models/form/sales/questions/armed_forces_spec.rb

@ -28,7 +28,8 @@ RSpec.describe Form::Sales::Questions::ArmedForces, type: :model do
"1" => { "value" => "Yes" },
"7" => { "value" => "No" },
"3" => { "value" => "Buyer prefers not to say" },
"8" => { "value" => "Don't know" },
"divider" => { "value" => true },
"8" => { "value" => "Don’t know" },
})
end
end

3
spec/models/form/sales/questions/armed_forces_spouse_spec.rb

@ -28,7 +28,8 @@ RSpec.describe Form::Sales::Questions::ArmedForcesSpouse, type: :model do
"4" => { "value" => "Yes" },
"5" => { "value" => "No" },
"6" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Don't know" },
"divider" => { "value" => true },
"7" => { "value" => "Don’t know" },
})
end
end

1
spec/models/form/sales/questions/buyer1_previous_tenure_spec.rb

@ -32,6 +32,7 @@ RSpec.describe Form::Sales::Questions::Buyer1PreviousTenure, type: :model do
"6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" },
"divider" => { "value" => true },
"0" => { "value" => "Don’t know" },
})
end

3
spec/models/form/sales/questions/buyer2_living_in_spec.rb

@ -25,7 +25,8 @@ RSpec.describe Form::Sales::Questions::Buyer2LivingIn, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end
end

3
spec/models/form/sales/questions/buyer_still_serving_spec.rb

@ -28,7 +28,8 @@ RSpec.describe Form::Sales::Questions::BuyerStillServing, type: :model do
"4" => { "value" => "Yes" },
"5" => { "value" => "No" },
"6" => { "value" => "Buyer prefers not to say" },
"7" => { "value" => "Don't know" },
"divider" => { "value" => true },
"7" => { "value" => "Don’t know" },
})
end
end

3
spec/models/form/sales/questions/extra_borrowing_spec.rb

@ -27,7 +27,8 @@ RSpec.describe Form::Sales::Questions::ExtraBorrowing, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

49
spec/models/form/sales/questions/has_service_charge_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::HasServiceCharge, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 4)) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, id: "shared_ownership", form:)) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("has_mscharge")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "No" },
"1" => { "value" => "Yes" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq({
"mscharge" => [1],
})
end
it "has correct hidden_in_check_answers for" do
expect(question.hidden_in_check_answers).to eq({
"depends_on" => [
{
"has_mscharge" => 1,
},
],
})
end
end

3
spec/models/form/sales/questions/household_disability_spec.rb

@ -27,7 +27,8 @@ RSpec.describe Form::Sales::Questions::HouseholdDisability, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

3
spec/models/form/sales/questions/household_wheelchair_spec.rb

@ -27,7 +27,8 @@ RSpec.describe Form::Sales::Questions::HouseholdWheelchair, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

1
spec/models/form/sales/questions/la_nominations_spec.rb

@ -27,6 +27,7 @@ RSpec.describe Form::Sales::Questions::LaNominations, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

2
spec/models/form/sales/questions/mortgageused_spec.rb

@ -25,6 +25,7 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end
@ -156,6 +157,7 @@ private
expect(question.displayed_answer_options(log)).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

1
spec/models/form/sales/questions/number_joint_buyers_spec.rb

@ -33,6 +33,7 @@ RSpec.describe Form::Sales::Questions::NumberJointBuyers, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

3
spec/models/form/sales/questions/previous_tenure_buyer2_spec.rb

@ -31,7 +31,8 @@ RSpec.describe Form::Sales::Questions::PreviousTenureBuyer2, type: :model do
"6" => { "value" => "Living with family or friends" },
"7" => { "value" => "Temporary accommodation" },
"9" => { "value" => "Other" },
"0" => { "value" => "Don't know" },
"divider" => { "value" => true },
"0" => { "value" => "Don’t know" },
})
end
end

1
spec/models/form/sales/questions/previous_tenure_spec.rb

@ -29,6 +29,7 @@ RSpec.describe Form::Sales::Questions::PreviousTenure, type: :model do
"2" => { "value" => "Affordable Rent" },
"3" => { "value" => "London Affordable Rent" },
"9" => { "value" => "Other" },
"divider" => { "value" => true },
"10" => { "value" => "Don’t know" },
})
end

1
spec/models/form/sales/questions/prevown_spec.rb

@ -28,6 +28,7 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

1
spec/models/form/sales/questions/prevshared_spec.rb

@ -27,6 +27,7 @@ RSpec.describe Form::Sales::Questions::Prevshared, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

3
spec/models/form/sales/questions/property_wheelchair_accessible_spec.rb

@ -33,7 +33,8 @@ RSpec.describe Form::Sales::Questions::PropertyWheelchairAccessible, type: :mode
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end
end

37
spec/models/form/sales/questions/service_charge_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::ServiceCharge, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("mscharge")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct width" do
expect(question.width).to be 5
end
it "has the correct min" do
expect(question.min).to be 1
end
it "has the correct prefix" do
expect(question.prefix).to eq("£")
end
end

3
spec/models/form/sales/questions/staircase_sale_spec.rb

@ -31,7 +31,8 @@ RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

1
spec/models/form/sales/questions/staircase_spec.rb

@ -33,6 +33,7 @@ RSpec.describe Form::Sales::Questions::Staircase, type: :model do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"divider" => { "value" => true },
"3" => { "value" => "Don’t know" },
})
end

2
spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb

@ -46,7 +46,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :
deposit_discount_optional
shared_ownership_deposit_value_check
monthly_rent
leasehold_charges_shared_ownership
service_charge
monthly_charges_shared_ownership_value_check
estate_management_fee
],

10
spec/models/lettings_log_derived_fields_spec.rb

@ -1016,11 +1016,13 @@ RSpec.describe LettingsLog, type: :model do
postcode = "SW1A 1AA"
log.assign_attributes(postcode_known: 1, postcode_full: postcode, renewal: 1)
expect { log.send :process_postcode_changes! }.to change(log, :la).to(expected_la)
expect { log.set_derived_fields! }
.to change(log, :ppostcode_full).to(postcode)
expect { log.send :process_postcode_changes! }
.to change(log, :la).to(expected_la)
.and change(log, :ppostcode_full).to(postcode)
.and change(log, :ppcodenk).to(0)
.and change(log, :prevloc).to(expected_la)
expect { log.set_derived_fields! }
.to change(log, :prevloc).to(expected_la)
end
it "clears values for previous location and related fields when log is a renewal and current values are cleared" do

15
spec/models/sales_log_spec.rb

@ -3,6 +3,8 @@ require "shared/shared_log_examples"
# rubocop:disable RSpec/MessageChain
RSpec.describe SalesLog, type: :model do
include CollectionTimeHelper
let(:owning_organisation) { create(:organisation) }
let(:assigned_to_user) { create(:user) }
@ -102,6 +104,7 @@ RSpec.describe SalesLog, type: :model do
after do
Timecop.return
Singleton.__init__(FormHandler)
end
it "has returns the correct form based on the start date" do
@ -250,7 +253,7 @@ RSpec.describe SalesLog, type: :model do
end
context "when there is a log with a different sale date" do
let!(:different_sale_date_log) { create(:sales_log, :duplicate, saledate: Time.zone.tomorrow, owning_organisation: organisation) }
let!(:different_sale_date_log) { create(:sales_log, :duplicate, saledate: generate_different_date_within_collection_year(Time.zone.now, end_date_override: Time.zone.now + 14.days), owning_organisation: organisation) }
it "does not return a log with a different sale date as a duplicate" do
expect(described_class.duplicate_logs(log)).not_to include(different_sale_date_log)
@ -376,7 +379,7 @@ RSpec.describe SalesLog, type: :model do
context "when there is a log with a different sale date" do
before do
create(:sales_log, :duplicate, saledate: Time.zone.tomorrow)
create(:sales_log, :duplicate, saledate: generate_different_date_within_collection_year(Time.zone.now, end_date_override: Time.zone.now + 14.days))
end
it "does not return a log with a different sale date as a duplicate" do
@ -1085,16 +1088,18 @@ RSpec.describe SalesLog, type: :model do
context "when form year changes and LA is no longer active" do
let!(:sales_log) { create(:sales_log) }
let(:end_date) { Time.zone.local(2025, 3, 30) }
let(:date_after_end_date) { Time.zone.local(2025, 3, 31) }
before do
LocalAuthority.find_by(code: "E08000003").update!(end_date: Time.zone.today)
LocalAuthority.find_by(code: "E08000003").update!(end_date:)
end
it "removes the LA" do
sales_log.update!(saledate: Time.zone.yesterday, la: "E08000003")
sales_log.update!(saledate: end_date, la: "E08000003")
expect(sales_log.reload.la).to eq("E08000003")
sales_log.update!(saledate: Time.zone.tomorrow)
sales_log.update!(saledate: date_after_end_date)
expect(sales_log.reload.la).to eq(nil)
expect(sales_log.reload.is_la_inferred).to eq(false)
end

7
spec/models/validations/sales/financial_validations_spec.rb

@ -514,9 +514,10 @@ RSpec.describe Validations::Sales::FinancialValidations do
record.numstair = 3
financial_validator.validate_staircase_difference(record)
expect(record.errors["equity"]).to include(I18n.t("validations.sales.financial.equity.more_than_stairowned_minus_stairbought_minus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["stairowned"]).to include(I18n.t("validations.sales.financial.equity.more_than_stairowned_minus_stairbought_minus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["stairbought"]).to include(I18n.t("validations.sales.financial.equity.more_than_stairowned_minus_stairbought_minus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["numstair"]).to include(I18n.t("validations.sales.financial.equity.more_than_stairowned_minus_stairbought_minus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["stairowned"]).to include(I18n.t("validations.sales.financial.stairowned.less_than_stairbought_plus_equity_plus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["stairbought"]).to include(I18n.t("validations.sales.financial.stairbought.more_than_stairowned_minus_equity_minus_prev_staircasing", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["numstair"]).to include(I18n.t("validations.sales.financial.numstair.too_high_for_stairowned_minus_stairbought_minus_equity", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
expect(record.errors["firststair"]).to include(I18n.t("validations.sales.financial.firststair.invalid_for_stairowned_minus_stairbought_minus_equity", equity: 9, bought: 10, numprevstair: 2, equity_sum: 21, stair_total: 20))
end
it "does not add errors if stairnum is present and stairowned is enough more than stairbought + equity" do

2
spec/requests/form_controller_spec.rb

@ -1675,10 +1675,12 @@ RSpec.describe FormController, type: :request do
context "and changing an answer" do
before do
Timecop.freeze(2024, 5, 6)
Singleton.__init__(FormHandler)
end
after do
Timecop.return
Singleton.__init__(FormHandler)
end
it "navigates to follow-up questions when required" do

2
spec/requests/merge_requests_controller_spec.rb

@ -416,7 +416,7 @@ RSpec.describe MergeRequestsController, type: :request do
context "when merge date set to a date more than 1 year in the future" do
let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation) }
let(:params) do
{ merge_request: { page: "merge_date", "merge_date": [(Time.zone.now.day + 1).to_s, Time.zone.now.month.to_s, (Time.zone.now.year + 1).to_s].join("/") } }
{ merge_request: { page: "merge_date", "merge_date": (Time.zone.now + 1.year + 1.day).strftime("%d/%m/%Y") } }
end
let(:request) do

64
spec/services/csv/sales_log_csv_service_spec.rb

@ -213,6 +213,26 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
let(:year) { 2025 }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
it "exports the CSV with the 2025 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_labels_25.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
@ -281,6 +301,26 @@ RSpec.describe Csv::SalesLogCsvService do
expect(la_label_value).to eq "Westminster"
end
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
let(:year) { 2025 }
before do
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_25.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
context "when the requested form is 2024" do
let(:now) { Time.zone.local(2024, 5, 1) }
let(:fixed_time) { Time.zone.local(2024, 5, 1) }
@ -359,5 +399,29 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
end
context "and the requested form is 2025" do
let(:year) { 2025 }
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
context "and exporting with labels" do
let(:export_type) { "labels" }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv")
values_to_delete = %w[id owning_organisation_id managing_organisation_id assigned_to_id updated_by_id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
end
end
end

Loading…
Cancel
Save