Browse Source

separate out shared ownership leasehold charge into separate service charge question

pull/3022/head
Carolyn 3 weeks ago
parent
commit
13a930dcb6
  1. 2
      app/helpers/bulk_upload/sales_log_to_csv.rb
  2. 1
      app/models/derived_variables/sales_log_variables.rb
  3. 14
      app/models/form/sales/pages/leasehold_charges.rb
  4. 23
      app/models/form/sales/pages/monthly_charges_value_check.rb
  5. 16
      app/models/form/sales/questions/has_leasehold_charges.rb
  6. 16
      app/models/form/sales/questions/leasehold_charges.rb
  7. 2
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  8. 2
      app/models/form/sales/subsections/outright_sale.rb
  9. 3
      app/models/form/sales/subsections/property_information.rb
  10. 4
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  11. 2
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  12. 2
      app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb
  13. 4
      app/models/sales_log.rb
  14. 4
      app/models/validations/sales/financial_validations.rb
  15. 18
      app/models/validations/sales/soft_validations.rb
  16. 17
      app/services/bulk_upload/sales/year2025/row_parser.rb
  17. 2
      app/services/csv/sales_log_csv_service.rb
  18. 4
      app/services/exports/sales_log_export_service.rb
  19. 25
      config/locales/forms/2025/sales/sale_information.en.yml
  20. 4
      config/locales/validations/sales/financial.en.yml
  21. 8
      db/schema.rb
  22. 2
      spec/factories/sales_log.rb
  23. 68
      spec/models/validations/sales/soft_validations_spec.rb

2
app/helpers/bulk_upload/sales_log_to_csv.rb

@ -499,7 +499,7 @@ class BulkUpload::SalesLogToCsv
log.deposit,
log.cashdis,
log.mrent,
log.mscharge,
log.servicecharge,
log.management_fee,
log.stairbought,

1
app/models/derived_variables/sales_log_variables.rb

@ -7,6 +7,7 @@ module DerivedVariables::SalesLogVariables
self.pregblank = 1 if no_buyer_organisation?
self.ethnic = 17 if ethnic_refused?
self.mscharge = nil if no_monthly_leasehold_charges?
self.servicecharge = nil if no_monthly_service_charges?
if exdate.present?
self.exday = exdate.day
self.exmonth = exdate.month

14
app/models/form/sales/pages/leasehold_charges.rb

@ -2,19 +2,7 @@ class Form::Sales::Pages::LeaseholdCharges < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
end
def copy_key
if form.start_year_2025_or_later?
case @ownershipsch
when 1
"sales.sale_information.leaseholdcharges.shared_ownership"
when 2
"sales.sale_information.leaseholdcharges.discounted_ownership"
end
else
"sales.sale_information.leaseholdcharges"
end
@copy_key = "sales.sale_information.leaseholdcharges"
end
def questions

23
app/models/form/sales/pages/monthly_charges_value_check.rb

@ -1,19 +1,20 @@
class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
def initialize(id, hsh, subsection)
super
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@depends_on = [
{
"monthly_charges_over_soft_max?" => true,
},
]
@copy_key = "sales.soft_validations.monthly_charges_value_check"
@ownershipsch = 2
@title_text = {
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "mscharge",
"i18n_template" => "mscharge",
"arguments_for_key" => monthly_charge_name_from_ownershipsch(ownershipsch),
"i18n_template" => monthly_charge_name_from_ownershipsch(ownershipsch),
},
],
}
@ -23,6 +24,15 @@ class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
}
end
def monthly_charge_name_from_ownershipsch(ownershipsch)
case ownershipsch
when 1
"servicecharge"
when 2
"mscharge"
end
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyChargesValueCheck.new(nil, nil, self),
@ -30,6 +40,11 @@ class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
end
def interruption_screen_question_ids
case @ownershipsch
when 1
%w[type servicecharge proptype]
when 2
%w[type mscharge proptype]
end
end
end

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

@ -15,22 +15,10 @@ 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
def copy_key
if form.start_year_2025_or_later?
case @ownershipsch
when 1
"sales.sale_information.leaseholdcharges.shared_ownership.has_mscharge"
when 2
"sales.sale_information.leaseholdcharges.discounted_ownership.has_mscharge"
end
else
"sales.sale_information.leaseholdcharges.has_mscharge"
end
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
@ -38,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

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

@ -7,26 +7,14 @@ class Form::Sales::Questions::LeaseholdCharges < ::Form::Question
@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
def copy_key
if form.start_year_2025_or_later?
case @ownershipsch
when 1
"sales.sale_information.leaseholdcharges.shared_ownership.mscharge"
when 2
"sales.sale_information.leaseholdcharges.discounted_ownership.mscharge"
end
else
"sales.sale_information.leaseholdcharges.mscharge"
end
end
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

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

@ -40,7 +40,7 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_deposit", nil, self),
Form::Sales::Pages::DiscountedSaleValueCheck.new("discounted_sale_deposit_value_check", nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_discounted_ownership", nil, self, ownershipsch: 2),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_discounted_ownership_value_check", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_discounted_ownership_value_check", nil, self, ownershipsch: 2),
].flatten.compact
end

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

@ -23,7 +23,7 @@ class Form::Sales::Subsections::OutrightSale < ::Form::Subsection
Form::Sales::Pages::DepositValueCheck.new("outright_sale_deposit_joint_purchase_value_check", nil, self, joint_purchase: true),
Form::Sales::Pages::DepositValueCheck.new("outright_sale_deposit_value_check", nil, self, joint_purchase: false),
leasehold_charge_pages,
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_outright_sale_value_check", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_outright_sale_value_check", nil, self, ownershipsch: 3),
].flatten.compact
end

3
app/models/form/sales/subsections/property_information.rb

@ -13,7 +13,8 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection
Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_bedrooms_value_check", nil, self),
(Form::Sales::Pages::PropertyUnitType.new(nil, nil, self) unless form.start_year_2025_or_later?),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self),
# TODO: refactor so that MonthlyChargesValueCheck gets ownershipsch (or so it doesn't need it?)
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self, ownershipsch: 2),
Form::Sales::Pages::PercentageDiscountValueCheck.new("percentage_discount_proptype_value_check", nil, self),
Form::Sales::Pages::PropertyBuildingType.new(nil, nil, self),
(uprn_questions if form.start_date.year == 2023),

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

@ -37,8 +37,8 @@ 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::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::ServiceCharge.new("service_charges_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self, ownershipsch: 1),
Form::Sales::Pages::EstateManagementFee.new("estate_management_fee", nil, self),
].compact
end

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

@ -49,7 +49,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
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::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self, ownershipsch: 1),
].compact
end

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

@ -25,7 +25,7 @@ class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form::
Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self),
Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self, ownershipsch: 1),
].compact
end

4
app/models/sales_log.rb

@ -391,6 +391,10 @@ class SalesLog < Log
has_mscharge&.zero?
end
def no_monthly_service_charges?
has_servicecharge&.zero?
end
def no_buyer_organisation?
pregyrha&.zero? &&
pregla&.zero? &&

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

@ -40,6 +40,10 @@ module Validations::Sales::FinancialValidations
record.errors.add :mortgage, :cannot_be_0, message: I18n.t("validations.sales.financial.mortgage.mortgage_zero") if record.mortgage_used? && record.mortgage&.zero?
end
def validate_monthly_service_charges(record)
record.errors.add :servicecharge, I18n.t("validations.sales.financial.servicecharge.monthly_service_charges.not_zero") if record.servicecharge&.zero?
end
def validate_monthly_leasehold_charges(record)
record.errors.add :mscharge, I18n.t("validations.sales.financial.mscharge.monthly_leasehold_charges.not_zero") if record.mscharge&.zero?
end

18
app/models/validations/sales/soft_validations.rb

@ -149,11 +149,27 @@ module Validations::Sales::SoftValidations
!grant.between?(9_000, 16_000)
end
def service_charges_over_soft_max?
return unless type && servicecharge && proptype
soft_max = old_persons_shared_ownership? ? 550 : 300
servicecharge > soft_max
end
def monthly_charges_over_soft_max?
return unless type && mscharge && proptype
return unless type && proptype && ownershipsch
if discounted_ownership_sale?
return unless mscharge
soft_max = old_persons_shared_ownership? ? 550 : 300
mscharge > soft_max
elsif shared_ownership_scheme?
return unless servicecharge
soft_max = old_persons_shared_ownership? ? 550 : 300
servicecharge > soft_max
end
end
(2..6).each do |person_num|

17
app/services/bulk_upload/sales/year2025/row_parser.rb

@ -727,8 +727,10 @@ private
cashdis: %i[field_92],
mrent: mrent_fields,
has_mscharge: mscharge_fields,
mscharge: mscharge_fields,
has_servicecharge: %i[field_94],
servicecharge: %i[field_94],
has_mscharge: %i[field_121],
mscharge: %i[field_121],
grant: %i[field_114],
discount: %i[field_115],
owning_organisation_id: %i[field_4],
@ -888,7 +890,9 @@ private
attributes["cashdis"] = field_92
attributes["mrent"] = mrent
attributes["mscharge"] = mscharge if mscharge&.positive?
attributes["servicecharge"] = field_94 if field_94&.positive?
attributes["has_servicecharge"] = attributes["servicecharge"].present? ? 1 : 0
attributes["mscharge"] = field_121 if field_121&.positive?
attributes["has_mscharge"] = attributes["mscharge"].present? ? 1 : 0
attributes["grant"] = field_114
attributes["discount"] = field_115
@ -1172,13 +1176,6 @@ private
%i[field_93 field_111]
end
def mscharge_fields
return [:field_94] if shared_ownership?
return [:field_121] if discounted_ownership?
%i[field_94 field_121]
end
def mortlen_fields
return [:field_90] if shared_ownership?
return [:field_118] if discounted_ownership?

2
app/services/csv/sales_log_csv_service.rb

@ -148,6 +148,7 @@ module Csv
"soctenant" => "SOCTEN",
"mortlen" => "MORTLEN1",
"has_mscharge" => "HASMSCHARGE",
"has_servicecharge" => "HASSERVICECHARGES",
"nationalbuy2" => "NATIONAL2",
"uprn_confirmed" => "UPRNCONFIRMED",
}.freeze
@ -212,6 +213,7 @@ module Csv
"managing_organisation_id" => %w[managing_organisation_name],
"value" => %w[value value_value_check],
"mscharge" => %w[mscharge mscharge_value_check],
"servicecharge" => %w[servicecharge mscharge_value_check],
}
unless @user.support? && @year >= 2024
mappings["postcode_full"] = %w[pcode1 pcode2]

4
app/services/exports/sales_log_export_service.rb

@ -71,8 +71,8 @@ module Exports
attribute_hash["previouslaknown"] = sales_log.previous_la_known
attribute_hash["hasmscharge"] = sales_log.discounted_ownership_sale? ? sales_log.has_mscharge : nil
attribute_hash["mscharge"] = sales_log.discounted_ownership_sale? ? sales_log.mscharge : nil
attribute_hash["hasservicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.has_mscharge : nil
attribute_hash["servicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.mscharge : nil
attribute_hash["hasservicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.has_servicecharge : nil
attribute_hash["servicecharges"] = sales_log.shared_ownership_scheme? ? sales_log.servicecharge : nil
attribute_hash["hoday"] = sales_log.hodate&.day
attribute_hash["homonth"] = sales_log.hodate&.month

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

@ -246,19 +246,6 @@ en:
question_text: "What is the basic monthly rent after staircasing?"
leaseholdcharges:
shared_ownership:
page_header: ""
has_mscharge:
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?"
mscharge:
check_answer_label: "Monthly leasehold charges"
check_answer_prompt: ""
hint_text: ""
question_text: "Enter the total monthly charge"
discounted_ownership:
page_header: ""
has_mscharge:
check_answer_label: "Property leasehold charges"
@ -270,6 +257,18 @@ en:
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:

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

@ -49,6 +49,10 @@ en:
monthly_leasehold_charges:
not_zero: "Monthly leasehold charges cannot be £0 if the property has monthly charges."
servicecharge:
monthly_service_charges:
not_zero: "Monthly service charges cannot be £0 if the property has monthly charges."
resale:
equity_over_max: "The maximum initial equity stake is %{max_equity}%."

8
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2025_03_05_092900) do
ActiveRecord::Schema[7.2].define(version: 2025_03_27_125423) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -84,7 +84,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_05_092900) do
t.datetime "last_accessed"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.check_constraint "log_type::text = ANY (ARRAY['lettings'::character varying, 'sales'::character varying]::text[])", name: "log_type_check"
t.check_constraint "log_type::text = ANY (ARRAY['lettings'::character varying::text, 'sales'::character varying::text])", name: "log_type_check"
t.check_constraint "year >= 2000 AND year <= 2099", name: "year_check"
end
@ -373,8 +373,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_05_092900) do
t.integer "partner_under_16_value_check"
t.integer "multiple_partners_value_check"
t.bigint "created_by_id"
t.integer "referral_type"
t.boolean "manual_address_entry_selected", default: false
t.integer "referral_type"
t.index ["assigned_to_id"], name: "index_lettings_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id"
@ -772,6 +772,8 @@ ActiveRecord::Schema[7.2].define(version: 2025_03_05_092900) do
t.datetime "lasttransaction"
t.datetime "initialpurchase"
t.boolean "manual_address_entry_selected", default: false
t.integer "servicecharge_known"
t.decimal "servicecharge", precision: 10, scale: 2
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"

2
spec/factories/sales_log.rb

@ -198,6 +198,8 @@ FactoryBot.define do
socprevten { 3 }
mrent { 900 }
equity { 30 }
has_servicecharge { 1 }
servicecharge { 100 }
ppostcode_full { "SW1A 1AA" }
hodate { Time.zone.today }
end

68
spec/models/validations/sales/soft_validations_spec.rb

@ -933,6 +933,9 @@ RSpec.describe Validations::Sales::SoftValidations do
end
describe "#monthly_charges_over_soft_max?" do
context "discounted ownership" do
let(:record) { FactoryBot.build(:sales_log, :discounted_ownership_setup_complete) }
it "returns false if mscharge is not given" do
record.mscharge = nil
record.proptype = 4
@ -994,6 +997,71 @@ RSpec.describe Validations::Sales::SoftValidations do
end
end
context "shared ownership" do
let(:record) { FactoryBot.build(:sales_log, :shared_2025_completed, startdate: Time.zone.local(2025, 6, 3)) }
it "returns false if servicecharge is not given" do
record.servicecharge = nil
record.proptype = 4
record.type = 2
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns false if proptype is not given" do
record.servicecharge = 999
record.proptype = nil
record.type = 2
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns false if type is not given" do
record.servicecharge = 999
record.proptype = 4
record.type = nil
expect(record).not_to be_monthly_charges_over_soft_max
end
context "with old persons shared ownership" do
it "returns false if the monthly charge is under 550" do
record.servicecharge = 540
record.proptype = 4
record.type = 24
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns true if the monthly charge is over 550" do
record.servicecharge = 999
record.proptype = 4
record.type = 24
expect(record).to be_monthly_charges_over_soft_max
end
end
context "with non old persons type of ownership" do
it "returns false if the monthly charge is under 300" do
record.servicecharge = 280
record.proptype = 4
record.type = 18
expect(record).not_to be_monthly_charges_over_soft_max
end
it "returns true if the monthly charge is over 300" do
record.servicecharge = 400
record.proptype = 4
record.type = 18
expect(record).to be_monthly_charges_over_soft_max
end
end
end
end
describe "#person_2_student_not_child?" do
it "returns false if age is not given" do
record.age2 = nil

Loading…
Cancel
Save