Browse Source

Merge branch 'main' into CLDC-497-SharedAccomodationBedrooms

pull/70/head
Milo 3 years ago committed by GitHub
parent
commit
f7a7987f2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      app/models/case_log.rb
  2. 15
      app/models/income_range.rb
  3. 36
      app/validations/financial_validations.rb
  4. 68
      app/validations/household_validations.rb
  5. 8
      app/validations/property_validations.rb
  6. 20
      app/validations/tenancy_validations.rb
  7. 15
      config/forms/2021_2022.json
  8. 9
      db/migrate/20211028083105_change_net_income_type.rb
  9. 7
      db/migrate/20211028095000_add_net_income_known_field.rb
  10. 5
      db/schema.rb
  11. 12
      docs/api/DLUHC-CORE-Data.v1.json
  12. 1
      spec/fixtures/complete_case_log.json
  13. 47
      spec/models/case_log_spec.rb
  14. 8
      spec/requests/case_log_controller_spec.rb

42
app/models/case_log.rb

@ -105,6 +105,12 @@ class CaseLogValidator < ActiveModel::Validator
validate_other_field(record, "tenancy_type", "other_tenancy_type") validate_other_field(record, "tenancy_type", "other_tenancy_type")
end end
# Validations methods need to be called 'validate_' to run on model save
include HouseholdValidations
include PropertyValidations
include FinancialValidations
include TenancyValidations
def validate_shared_housing_rooms(record) def validate_shared_housing_rooms(record)
unless record.property_unit_type.nil? unless record.property_unit_type.nil?
if record.property_unit_type == "Bed-sit" && record.property_number_of_bedrooms != 1 if record.property_unit_type == "Bed-sit" && record.property_number_of_bedrooms != 1
@ -135,9 +141,7 @@ class CaseLogValidator < ActiveModel::Validator
public_send("validate_#{question_to_validate}", record) public_send("validate_#{question_to_validate}", record)
end end
else else
# This assumes that all methods in this class other than this one are validation_methods = public_methods.select { |method| method.starts_with?("validate_") }
# validations to be run
validation_methods = public_methods(false) - [__callee__]
validation_methods.each { |meth| public_send(meth, record) } validation_methods.each { |meth| public_send(meth, record) }
end end
end end
@ -155,14 +159,6 @@ private
record.errors.add other_field.to_sym, "#{other_field_label} must not be provided if #{main_field_label} was not other" record.errors.add other_field.to_sym, "#{other_field_label} must not be provided if #{main_field_label} was not other"
end end
end end
def women_of_child_bearing_age_in_household(record)
(1..8).any? do |n|
next if record["person_#{n}_gender"].nil? || record["person_#{n}_age"].nil?
record["person_#{n}_gender"] == "Female" && record["person_#{n}_age"] >= 16 && record["person_#{n}_age"] <= 50
end
end
end end
class CaseLog < ApplicationRecord class CaseLog < ApplicationRecord
@ -202,6 +198,23 @@ class CaseLog < ApplicationRecord
status == "in_progress" status == "in_progress"
end end
def weekly_net_income
case net_income_frequency
when "Weekly"
net_income
when "Monthly"
((net_income * 12) / 52.0).round(0)
when "Yearly"
(net_income / 12.0).round(0)
end
end
def applicable_income_range
return unless person_1_economic_status
IncomeRange::ALLOWED[person_1_economic_status.to_sym]
end
private private
def update_status! def update_status!
@ -239,10 +252,15 @@ private
dynamically_not_required << "fixed_term_tenancy" dynamically_not_required << "fixed_term_tenancy"
end end
if tenancy_type != "Other" unless tenancy_type == "Other"
dynamically_not_required << "other_tenancy_type" dynamically_not_required << "other_tenancy_type"
end end
unless net_income_known == "Yes"
dynamically_not_required << "net_income"
dynamically_not_required << "net_income_frequency"
end
required.delete_if { |key, _value| dynamically_not_required.include?(key) } required.delete_if { |key, _value| dynamically_not_required.include?(key) }
end end
end end

15
app/models/income_range.rb

@ -0,0 +1,15 @@
class IncomeRange
ALLOWED = {
"Full-time - 30 hours or more": OpenStruct.new(soft_min: 143, soft_max: 730, hard_min: 90, hard_max: 1230),
"Part-time - Less than 30 hours": OpenStruct.new(soft_min: 67, soft_max: 620, hard_min: 50, hard_max: 950),
"In government training into work, such as New Deal": OpenStruct.new(soft_min: 80, soft_max: 480, hard_min: 40, hard_max: 990),
"Jobseeker": OpenStruct.new(soft_min: 50, soft_max: 370, hard_min: 10, hard_max: 450),
"Retired": OpenStruct.new(soft_min: 50, soft_max: 380, hard_min: 10, hard_max: 690),
"Not seeking work": OpenStruct.new(soft_min: 53, soft_max: 540, hard_min: 10, hard_max: 890),
"Full-time student": OpenStruct.new(soft_min: 47, soft_max: 460, hard_min: 10, hard_max: 1300),
"Unable to work because of long term sick or disability": OpenStruct.new(soft_min: 54, soft_max: 460, hard_min: 10, hard_max: 820),
"Child under 16": OpenStruct.new(soft_min: 50, soft_max: 450, hard_min: 10, hard_max: 750),
"Other": OpenStruct.new(soft_min: 50, soft_max: 580, hard_min: 10, hard_max: 1040),
"Prefer not to say": OpenStruct.new(soft_min: 47, soft_max: 730, hard_min: 10, hard_max: 1300),
}.freeze
end

36
app/validations/financial_validations.rb

@ -0,0 +1,36 @@
module FinancialValidations
# Validations methods need to be called 'validate_' to run on model save
def validate_outstanding_rent_amount(record)
if record.outstanding_rent_or_charges == "Yes" && record.outstanding_amount.blank?
record.errors.add :outstanding_amount, "You must answer the oustanding amout question if you have outstanding rent or charges."
end
if record.outstanding_rent_or_charges == "No" && record.outstanding_amount.present?
record.errors.add :outstanding_amount, "You must not answer the oustanding amout question if you don't have outstanding rent or charges."
end
end
EMPLOYED_STATUSES = ["Full-time - 30 hours or more", "Part-time - Less than 30 hours"].freeze
def validate_net_income_uc_proportion(record)
(1..8).any? do |n|
economic_status = record["person_#{n}_economic_status"]
is_employed = EMPLOYED_STATUSES.include?(economic_status)
relationship = record["person_#{n}_relationship"]
is_partner_or_main = relationship == "Partner" || (relationship.nil? && economic_status.present?)
if is_employed && is_partner_or_main && record.net_income_uc_proportion == "All"
record.errors.add :net_income_uc_proportion, "income is from Universal Credit, state pensions or benefits cannot be All if the tenant or the partner works part or full time"
end
end
end
def validate_net_income(record)
return unless record.person_1_economic_status && record.weekly_net_income
if record.weekly_net_income > record.applicable_income_range.hard_max
record.errors.add :net_income, "Net income cannot be greater than #{record.applicable_income_range.hard_max} given the tenant's working situation"
end
if record.weekly_net_income < record.applicable_income_range.hard_min
record.errors.add :net_income, "Net income cannot be less than #{record.applicable_income_range.hard_min} given the tenant's working situation"
end
end
end

68
app/validations/household_validations.rb

@ -0,0 +1,68 @@
module HouseholdValidations
# Validations methods need to be called 'validate_' to run on model save
def validate_person_1_age(record)
if record.person_1_age && !/^[1-9][0-9]?$|^120$/.match?(record.person_1_age.to_s)
record.errors.add :person_1_age, "Tenant age must be between 0 and 120"
end
end
def validate_reasonable_preference(record)
if record.homelessness == "No" && record.reasonable_preference == "Yes"
record.errors.add :reasonable_preference, "Can not be Yes if Not Homeless immediately prior to this letting has been selected"
elsif record.reasonable_preference == "Yes"
if !record.reasonable_preference_reason_homeless && !record.reasonable_preference_reason_unsatisfactory_housing && !record.reasonable_preference_reason_medical_grounds && !record.reasonable_preference_reason_avoid_hardship && !record.reasonable_preference_reason_do_not_know
record.errors.add :reasonable_preference_reason, "If reasonable preference is Yes, a reason must be given"
end
elsif record.reasonable_preference == "No"
if record.reasonable_preference_reason_homeless || record.reasonable_preference_reason_unsatisfactory_housing || record.reasonable_preference_reason_medical_grounds || record.reasonable_preference_reason_avoid_hardship || record.reasonable_preference_reason_do_not_know
record.errors.add :reasonable_preference_reason, "If reasonable preference is No, no reasons should be given"
end
end
end
def validate_other_reason_for_leaving_last_settled_home(record)
validate_other_field(record, "reason_for_leaving_last_settled_home", "other_reason_for_leaving_last_settled_home")
end
def validate_reason_for_leaving_last_settled_home(record)
if record.reason_for_leaving_last_settled_home == "Do not know" && record.benefit_cap_spare_room_subsidy != "Do not know"
record.errors.add :benefit_cap_spare_room_subsidy, "must be do not know if tenant’s main reason for leaving is do not know"
end
end
def validate_armed_forces_injured(record)
if (record.armed_forces == "Yes - a regular" || record.armed_forces == "Yes - a reserve") && record.armed_forces_injured.blank?
record.errors.add :armed_forces_injured, "You must answer the armed forces injury question if the tenant has served in the armed forces"
end
if (record.armed_forces == "No" || record.armed_forces == "Prefer not to say") && record.armed_forces_injured.present?
record.errors.add :armed_forces_injured, "You must not answer the armed forces injury question if the tenant has not served in the armed forces or prefer not to say was chosen"
end
end
def validate_armed_forces_active_response(record)
if record.armed_forces == "Yes - a regular" && record.armed_forces_active.blank?
record.errors.add :armed_forces_active, "You must answer the armed forces active question if the tenant has served as a regular in the armed forces"
end
if record.armed_forces != "Yes - a regular" && record.armed_forces_active.present?
record.errors.add :armed_forces_active, "You must not answer the armed forces active question if the tenant has not served as a regular in the armed forces"
end
end
def validate_household_pregnancy(record)
if (record.pregnancy == "Yes" || record.pregnancy == "Prefer not to say") && !women_of_child_bearing_age_in_household(record)
record.errors.add :pregnancy, "You must answer no as there are no female tenants aged 16-50 in the property"
end
end
private
def women_of_child_bearing_age_in_household(record)
(1..8).any? do |n|
next if record["person_#{n}_gender"].nil? || record["person_#{n}_age"].nil?
record["person_#{n}_gender"] == "Female" && record["person_#{n}_age"] >= 16 && record["person_#{n}_age"] <= 50
end
end
end

8
app/validations/property_validations.rb

@ -0,0 +1,8 @@
module PropertyValidations
# Validations methods need to be called 'validate_' to run on model save
def validate_property_number_of_times_relet(record)
if record.property_number_of_times_relet && !/^[1-9]$|^0[1-9]$|^1[0-9]$|^20$/.match?(record.property_number_of_times_relet.to_s)
record.errors.add :property_number_of_times_relet, "Must be between 0 and 20"
end
end
end

20
app/validations/tenancy_validations.rb

@ -0,0 +1,20 @@
module TenancyValidations
# Validations methods need to be called 'validate_' to run on model save
def validate_fixed_term_tenancy(record)
is_present = record.fixed_term_tenancy.present?
is_in_range = record.fixed_term_tenancy.to_i.between?(2, 99)
is_secure = record.tenancy_type == "Fixed term – Secure"
is_ast = record.tenancy_type == "Fixed term – Assured Shorthold Tenancy (AST)"
conditions = [
{ condition: !(is_secure || is_ast) && is_present, error: "You must only answer the fixed term tenancy length question if the tenancy type is fixed term" },
{ condition: is_ast && !is_in_range, error: "Fixed term – Assured Shorthold Tenancy (AST) should be between 2 and 99 years" },
{ condition: is_secure && (!is_in_range && is_present), error: "Fixed term – Secure should be between 2 and 99 years or not specified" },
]
conditions.each { |condition| condition[:condition] ? (record.errors.add :fixed_term_tenancy, condition[:error]) : nil }
end
def validate_other_tenancy_type(record)
validate_other_field(record, "tenancy_type", "other_tenancy_type")
end
end

15
config/forms/2021_2022.json

@ -1458,6 +1458,21 @@
"header": "", "header": "",
"description": "", "description": "",
"questions": { "questions": {
"net_income_known": {
"check_answer_label": "Income known",
"header": "Do you know the tenant and their partner's net income?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": "Yes",
"1": "No",
"2": "Tenant prefers not to say"
},
"conditional_for": {
"net_income": ["Yes"],
"net_income_frequency": ["Yes"]
}
},
"net_income": { "net_income": {
"check_answer_label": "Income", "check_answer_label": "Income",
"header": "What is the tenant’s /and partner’s combined income after tax?", "header": "What is the tenant’s /and partner’s combined income after tax?",

9
db/migrate/20211028083105_change_net_income_type.rb

@ -0,0 +1,9 @@
class ChangeNetIncomeType < ActiveRecord::Migration[6.1]
def up
change_column :case_logs, :net_income, "integer USING CAST(property_number_of_times_relet AS integer)"
end
def down
change_column :case_logs, :net_income, :string
end
end

7
db/migrate/20211028095000_add_net_income_known_field.rb

@ -0,0 +1,7 @@
class AddNetIncomeKnownField < ActiveRecord::Migration[6.1]
def change
change_table :case_logs, bulk: true do |t|
t.column :net_income_known, :string
end
end
end

5
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_10_27_123535) do ActiveRecord::Schema.define(version: 2021_10_28_095000) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -86,7 +86,7 @@ ActiveRecord::Schema.define(version: 2021_10_27_123535) do
t.string "property_major_repairs_date" t.string "property_major_repairs_date"
t.integer "property_number_of_times_relet" t.integer "property_number_of_times_relet"
t.string "property_wheelchair_accessible" t.string "property_wheelchair_accessible"
t.string "net_income" t.integer "net_income"
t.string "net_income_frequency" t.string "net_income_frequency"
t.string "net_income_uc_proportion" t.string "net_income_uc_proportion"
t.string "housing_benefit" t.string "housing_benefit"
@ -133,6 +133,7 @@ ActiveRecord::Schema.define(version: 2021_10_27_123535) do
t.boolean "reasonable_preference_reason_do_not_know" t.boolean "reasonable_preference_reason_do_not_know"
t.datetime "discarded_at" t.datetime "discarded_at"
t.string "other_tenancy_type" t.string "other_tenancy_type"
t.string "net_income_known"
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" t.index ["discarded_at"], name: "index_case_logs_on_discarded_at"
end end

12
docs/api/DLUHC-CORE-Data.v1.json

@ -937,6 +937,15 @@
"property_wheelchair_accessible": { "property_wheelchair_accessible": {
"type": "boolean" "type": "boolean"
}, },
"net_income_known": {
"type": "string",
"minLength": 1,
"enum": [
"Yes",
"No",
"Tenant prefers not to say"
]
},
"net_income": { "net_income": {
"type": "number" "type": "number"
}, },
@ -1208,7 +1217,8 @@
"reasonable_preference_reason_medical_grounds", "reasonable_preference_reason_medical_grounds",
"reasonable_preference_reason_avoid_hardship", "reasonable_preference_reason_avoid_hardship",
"reasonable_preference_reason_do_not_know", "reasonable_preference_reason_do_not_know",
"other_tenancy-type" "other_tenancy-type",
"net_income_known"
] ]
} }
}, },

1
spec/fixtures/complete_case_log.json vendored

@ -68,6 +68,7 @@
"property_major_repairs_date": "05/05/2020", "property_major_repairs_date": "05/05/2020",
"property_number_of_times_relet": 2, "property_number_of_times_relet": 2,
"property_wheelchair_accessible": true, "property_wheelchair_accessible": true,
"net_income_known": "Yes",
"net_income": 0, "net_income": 0,
"net_income_frequency": null, "net_income_frequency": null,
"net_income_uc_proportion": "Some", "net_income_uc_proportion": "Some",

47
spec/models/case_log_spec.rb

@ -237,10 +237,11 @@ RSpec.describe Form, type: :model do
# Crossover over tests here as injured must be answered as well for no error # Crossover over tests here as injured must be answered as well for no error
it "must be answered if ever served in the forces as a regular" do it "must be answered if ever served in the forces as a regular" do
expect { expect do
CaseLog.create!(armed_forces: "Yes - a regular", CaseLog.create!(armed_forces: "Yes - a regular",
armed_forces_active: "Yes", armed_forces_active: "Yes",
armed_forces_injured: "Yes")} armed_forces_injured: "Yes")
end
end end
end end
@ -269,6 +270,28 @@ RSpec.describe Form, type: :model do
}.not_to raise_error }.not_to raise_error
end end
end end
context "income ranges" do
it "validates net income maximum" do
expect {
CaseLog.create!(
person_1_economic_status: "Full-time - 30 hours or more",
net_income: 5000,
net_income_frequency: "Weekly",
)
}.to raise_error(ActiveRecord::RecordInvalid)
end
it "validates net income minimum" do
expect {
CaseLog.create!(
person_1_economic_status: "Full-time - 30 hours or more",
net_income: 1,
net_income_frequency: "Weekly",
)
}.to raise_error(ActiveRecord::RecordInvalid)
end
end
end end
describe "status" do describe "status" do
@ -287,4 +310,24 @@ RSpec.describe Form, type: :model do
expect(in_progress_case_log.completed?).to be(false) expect(in_progress_case_log.completed?).to be(false)
end end
end end
describe "weekly_net_income" do
let(:net_income) { 5000 }
let(:case_log) { FactoryBot.build(:case_log, net_income: net_income) }
it "returns input income if frequency is already weekly" do
case_log.net_income_frequency = "Weekly"
expect(case_log.weekly_net_income).to eq(net_income)
end
it "calculates the correct weekly income from monthly income" do
case_log.net_income_frequency = "Monthly"
expect(case_log.weekly_net_income).to eq(1154)
end
it "calculates the correct weekly income from yearly income" do
case_log.net_income_frequency = "Yearly"
expect(case_log.weekly_net_income).to eq(417)
end
end
end end

8
spec/requests/case_log_controller_spec.rb

@ -115,6 +115,14 @@ RSpec.describe CaseLogsController, type: :request do
json_response = JSON.parse(response.body) json_response = JSON.parse(response.body)
expect(json_response["status"]).to eq(case_log.status) expect(json_response["status"]).to eq(case_log.status)
end end
context "invalid case log id" do
let(:id) { (CaseLog.order(:id).last&.id || 0) + 1 }
it "returns 404" do
expect(response).to have_http_status(:not_found)
end
end
end end
describe "PATCH" do describe "PATCH" do

Loading…
Cancel
Save