Browse Source

Cldc 491 rent and charges validations (#411)

* Add rent ranges table and basic hard range validation

Co-authored-by: baarkerlounger  <baarkerlounger@users.noreply.github.com>

* Add import rent ranges rake task

Co-authored-by: baarkerlounger  <baarkerlounger@users.noreply.github.com>

* Update the rent range validation to validate all related fields and check for the correct collection year

* Add ranges data

* add error messages

* Add readme step

* extract get collection year method

* rename method

* create instead of find and create laRentRange record

Co-authored-by: baarkerlounger  <baarkerlounger@users.noreply.github.com>
pull/414/head
kosiakkatrina 3 years ago committed by GitHub
parent
commit
612b29122b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      README.md
  2. 12
      app/models/case_log.rb
  3. 2
      app/models/la_rent_range.rb
  4. 24
      app/models/validations/financial_validations.rb
  5. 12
      config/locales/en.yml
  6. 7726
      config/rent_range_data/2021.csv
  7. 18
      db/migrate/20220321093810_create_la_rent_ranges.rb
  8. 15
      db/schema.rb
  9. 29
      lib/tasks/rent_ranges.rake
  10. 6
      spec/fixtures/files/rent_ranges.csv
  11. 51
      spec/lib/tasks/rent_range_import_spec.rb
  12. 87
      spec/models/validations/financial_validations_spec.rb

10
README.md

@ -34,10 +34,16 @@ Pre-requisites:
5. Seed the database if required:\
`rake db:seed`
6. Install the frontend depenencies:\
6. Seed the database with rent ranges if required (~7000 rows per year):\
`rake "data_import:rent_ranges[<start_year>,<rent_ranges_path>]"`
For 2021-2022 ranges run:\
`rake "data_import:rent_ranges[2021,config/rent_range_data/2021.csv]"`
7. Install the frontend depenencies:\
`yarn install`
7. Start the dev servers using foreman:\
8. Start the dev servers using foreman:\
`./bin/dev`
Or start them individually:\

12
app/models/case_log.rb

@ -48,15 +48,15 @@ class CaseLog < ApplicationRecord
FormHandler.instance.get_form(form_name) || FormHandler.instance.forms.first.second
end
def collection_start_year
window_end_date = Time.zone.local(startdate.year, 4, 1)
startdate < window_end_date ? startdate.year - 1 : startdate.year
end
def form_name
return unless startdate
window_end_date = Time.zone.local(startdate.year, 4, 1)
if startdate < window_end_date
"#{startdate.year - 1}_#{startdate.year}"
else
"#{startdate.year}_#{startdate.year + 1}"
end
"#{collection_start_year}_#{collection_start_year + 1}"
end
def self.editable_fields

2
app/models/la_rent_range.rb

@ -0,0 +1,2 @@
class LaRentRange < ApplicationRecord
end

24
app/models/validations/financial_validations.rb

@ -65,7 +65,7 @@ module Validations::FinancialValidations
record.errors.add :tshortfall, I18n.t("validations.financial.tshortfall.more_than_rent")
end
if record.tcharge.present? && weekly_value_in_range(record, "tcharge", 9.99)
if record.tcharge.present? && weekly_value_in_range(record, "tcharge", 0, 9.99)
record.errors.add :tcharge, I18n.t("validations.financial.tcharge.under_10")
end
@ -77,6 +77,7 @@ module Validations::FinancialValidations
end
validate_charges(record)
validate_rent_range(record)
end
private
@ -121,13 +122,28 @@ private
%i[scharge pscharge supcharg].each do |charge|
maximum = CHARGE_MAXIMUMS.dig(charge, LANDLORD_VALUES[record.landlord], NEEDSTYPE_VALUES[record.needstype])
if maximum.present? && !weekly_value_in_range(record, charge, maximum)
if maximum.present? && !weekly_value_in_range(record, charge, 0, maximum)
record.errors.add charge, I18n.t("validations.financial.rent.#{charge}.#{LANDLORD_VALUES[record.landlord]}.#{NEEDSTYPE_VALUES[record.needstype]}")
end
end
end
def weekly_value_in_range(record, field, max)
record[field].present? && record.weekly_value(record[field]).present? && record.weekly_value(record[field]).between?(0, max)
def weekly_value_in_range(record, field, min, max)
record[field].present? && record.weekly_value(record[field]).present? && record.weekly_value(record[field]).between?(min, max)
end
def validate_rent_range(record)
return if record.startdate.blank?
collection_year = record.collection_start_year
rent_range = LaRentRange.find_by(start_year: collection_year, la: record.la, beds: record.beds, lettype: record.lettype)
if rent_range.present? && !weekly_value_in_range(record, "brent", rent_range.hard_min, rent_range.hard_max)
record.errors.add :brent, I18n.t("validations.financial.brent.not_in_range")
record.errors.add :beds, I18n.t("validations.financial.brent.beds.not_in_range")
record.errors.add :la, I18n.t("validations.financial.brent.la.not_in_range")
record.errors.add :rent_type, I18n.t("validations.financial.brent.rent_type.not_in_range")
record.errors.add :needstype, I18n.t("validations.financial.brent.needstype.not_in_range")
end
end
end

12
config/locales/en.yml

@ -47,7 +47,7 @@ en:
taken: "Email already exists"
invalid: "Enter an email address in the correct format, like name@example.com"
blank: "Enter an email address"
setup:
intermediate_rent_product_name:
blank: "Enter name of other intermediate rent product"
@ -118,6 +118,16 @@ en:
other_landlord:
general_needs: "Support charge must be between £0 and £60 per week if the landlord is another RP and it is a general needs letting"
supported_housing: "Support charge must be between £0 and £120 per week if the landlord is another RP and it is a suported housing letting"
brent:
not_in_range: "Basic rent is outside of the expected range based on the lettings type, local authority and number of bedrooms"
la:
not_in_range: "Basic rent is outside of the expected range based on this local authority"
beds:
not_in_range: "Basic rent is outside of the expected range based on this number of bedrooms"
needstype:
not_in_range: "Basic rent is outside of the expected range based on this lettings type"
rent_type:
not_in_range: "Basic rent is outside of the expected range based on this lettings type"
charges:
complete_1_of_3: 'Only one question out of "Total charge", "Charges for carehomes" and "Does the household pay rent or charges?" needs to be selected and completed'
tcharge:

7726
config/rent_range_data/2021.csv

File diff suppressed because it is too large Load Diff

18
db/migrate/20220321093810_create_la_rent_ranges.rb

@ -0,0 +1,18 @@
class CreateLaRentRanges < ActiveRecord::Migration[7.0]
def change
create_table :la_rent_ranges do |t|
t.integer :ranges_rent_id
t.integer :lettype
t.string :la
t.integer :beds
t.decimal :soft_min, precision: 10, scale: 2
t.decimal :soft_max, precision: 10, scale: 2
t.decimal :hard_min, precision: 10, scale: 2
t.decimal :hard_max, precision: 10, scale: 2
t.integer :start_year
t.index %i[start_year lettype beds la], unique: true
t.timestamps
end
end
end

15
db/schema.rb

@ -233,6 +233,21 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.index ["owning_organisation_id"], name: "index_case_logs_on_owning_organisation_id"
end
create_table "la_rent_ranges", force: :cascade do |t|
t.integer "ranges_rent_id"
t.integer "lettype"
t.string "la"
t.integer "beds"
t.decimal "soft_min", precision: 10, scale: 2
t.decimal "soft_max", precision: 10, scale: 2
t.decimal "hard_min", precision: 10, scale: 2
t.decimal "hard_max", precision: 10, scale: 2
t.integer "start_year"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["start_year", "lettype", "beds", "la"], name: "index_la_rent_ranges_on_start_year_and_lettype_and_beds_and_la", unique: true
end
create_table "organisations", force: :cascade do |t|
t.string "name"
t.string "phone"

29
lib/tasks/rent_ranges.rake

@ -0,0 +1,29 @@
require "csv"
namespace :data_import do
desc "Import annual rent range data"
task :rent_ranges, %i[start_year path] => :environment do |_task, args|
start_year = args[:start_year]
path = args[:path]
count = 0
raise "Usage: rake data_import:rent_ranges[start_year,'path/to/csv_file']" if path.blank? || start_year.blank?
CSV.foreach(path, headers: true) do |row|
LaRentRange.upsert(
{ ranges_rent_id: row["ranges_rent_id"],
lettype: row["lettype"],
beds: row["beds"],
start_year:,
la: row["la"],
soft_min: row["soft_min"],
soft_max: row["soft_max"],
hard_min: row["hard_min"],
hard_max: row["hard_max"] },
unique_by: %i[start_year lettype beds la],
)
count += 1
end
pp "Created/updated #{count} records"
end
end

6
spec/fixtures/files/rent_ranges.csv vendored

@ -0,0 +1,6 @@
ranges_rent_id,lettype,la,beds,soft_min,soft_max,hard_min,hard_max
1,1,E07000223,1,58.37,125.59,26.36,179.73
327,1,E07000223,2,72.5,149.4,26.36,190.57
653,1,E07000223,3,82.85,164.58,26.36,207.89
979,1,E07000223,4,91.33,181.9,26.36,226.27
1305,3,E07000223,1,52.73,118.01,26.36,162.4
1 ranges_rent_id lettype la beds soft_min soft_max hard_min hard_max
2 1 1 E07000223 1 58.37 125.59 26.36 179.73
3 327 1 E07000223 2 72.5 149.4 26.36 190.57
4 653 1 E07000223 3 82.85 164.58 26.36 207.89
5 979 1 E07000223 4 91.33 181.9 26.36 226.27
6 1305 3 E07000223 1 52.73 118.01 26.36 162.4

51
spec/lib/tasks/rent_range_import_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
require "rake"
RSpec.describe "data_import" do
describe ":rent_ranges", type: :task do
subject(:task) { Rake::Task["data_import:rent_ranges"] }
before do
Rake.application.rake_require("tasks/rent_ranges")
Rake::Task.define_task(:environment)
task.reenable
end
context "when the rake task is run" do
let(:start_year) { 2021 }
let(:rent_ranges_file_path) { "./spec/fixtures/files/rent_ranges.csv" }
let(:wrong_file_path) { "/test/no_csv_here.csv" }
before do
LaRentRange.delete_all
end
it "creates new rent range records" do
expect { task.invoke(start_year, rent_ranges_file_path) }.to change(LaRentRange, :count).by(5)
expect(LaRentRange.where(ranges_rent_id: 1).exists?).to be true
end
it "raises an error when no path is given" do
expect { task.invoke(start_year, nil) }.to raise_error(RuntimeError, "Usage: rake data_import:rent_ranges[start_year,'path/to/csv_file']")
end
it "raises an error when no file exists at the given path" do
expect { task.invoke(start_year, wrong_file_path) }.to raise_error(Errno::ENOENT)
end
it "asks for a start year if it is not given" do
expect { task.invoke(nil, rent_ranges_file_path) }.to raise_error(RuntimeError, "Usage: rake data_import:rent_ranges[start_year,'path/to/csv_file']")
end
context "when a record already exists with a matching index of la, beds, start year and lettype" do
let!(:rent_range) { LaRentRange.create(lettype: 1, la: "E07000223", beds: 2, soft_min: 53.5, soft_max: 149.4, hard_min: 20.36, hard_max: 200.57, start_year: 2021) }
it "updates rent ranges if the record is matched on la, beds, start year and lettype" do
task.invoke(start_year, rent_ranges_file_path)
rent_range.reload
expect(rent_range.hard_max).to eq(190.57)
end
end
end
end
end

87
spec/models/validations/financial_validations_spec.rb

@ -713,6 +713,93 @@ RSpec.describe Validations::FinancialValidations do
expect(record.errors["household_charge"])
.to be_empty
end
context "when validating ranges based on LA and needstype" do
before do
LaRentRange.create(
ranges_rent_id: "1",
la: "E07000223",
beds: 1,
lettype: 1,
soft_min: 12.41,
soft_max: 89.54,
hard_min: 9.87,
hard_max: 100.99,
start_year: 2021,
)
end
it "validates hard minimum" do
record.lettype = 1
record.period = 1
record.la = "E07000223"
record.beds = 1
record.year = 2021
record.startdate = Time.zone.local(2021, 9, 17)
record.brent = 9.17
financial_validator.validate_rent_amount(record)
expect(record.errors["brent"])
.to include(match I18n.t("validations.financial.brent.not_in_range"))
end
it "validates hard max" do
record.lettype = 1
record.period = 1
record.la = "E07000223"
record.beds = 1
record.startdate = Time.zone.local(2021, 9, 17)
record.year = 2021
record.brent = 200
financial_validator.validate_rent_amount(record)
expect(record.errors["brent"])
.to include(match I18n.t("validations.financial.brent.not_in_range"))
expect(record.errors["beds"])
.to include(match I18n.t("validations.financial.brent.beds.not_in_range"))
expect(record.errors["la"])
.to include(match I18n.t("validations.financial.brent.la.not_in_range"))
expect(record.errors["rent_type"])
.to include(match I18n.t("validations.financial.brent.rent_type.not_in_range"))
expect(record.errors["needstype"])
.to include(match I18n.t("validations.financial.brent.needstype.not_in_range"))
end
it "validates hard max for correct collection year" do
record.lettype = 1
record.period = 1
record.la = "E07000223"
record.startdate = Time.zone.local(2022, 2, 5)
record.beds = 1
record.year = 2022
record.month = 2
record.day = 5
record.brent = 200
financial_validator.validate_rent_amount(record)
expect(record.errors["brent"])
.to include(match I18n.t("validations.financial.brent.not_in_range"))
expect(record.errors["beds"])
.to include(match I18n.t("validations.financial.brent.beds.not_in_range"))
expect(record.errors["la"])
.to include(match I18n.t("validations.financial.brent.la.not_in_range"))
expect(record.errors["rent_type"])
.to include(match I18n.t("validations.financial.brent.rent_type.not_in_range"))
expect(record.errors["needstype"])
.to include(match I18n.t("validations.financial.brent.needstype.not_in_range"))
end
it "does not error if some of the fields are missing" do
record.managing_organisation.provider_type = 2
record.year = 2021
record.startdate = Time.zone.local(2021, 9, 17)
record.brent = 200
financial_validator.validate_rent_amount(record)
expect(record.errors["brent"])
.to be_empty
end
end
end
end
end

Loading…
Cancel
Save