Browse Source

CLDC-1228: Handles household charges (#745)

pull/746/head
Stéphane Meny 2 years ago committed by GitHub
parent
commit
97024c321d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      app/models/case_log.rb
  2. 6
      app/models/derived_variables/case_log_variables.rb
  3. 10
      app/models/location.rb
  4. 68
      app/services/imports/case_logs_import_service.rb
  5. 10
      app/services/imports/scheme_location_import_service.rb
  6. 21
      app/services/postcode_service.rb
  7. 5
      db/migrate/20220715133937_remove_county_from_location.rb
  8. 3
      db/schema.rb
  9. 4
      spec/factories/location.rb
  10. 524
      spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml
  11. 15
      spec/models/case_log_spec.rb
  12. 11
      spec/models/location_spec.rb
  13. 68
      spec/services/imports/case_logs_import_service_spec.rb
  14. 1
      spec/services/imports/scheme_location_import_service_spec.rb

33
app/models/case_log.rb

@ -102,6 +102,22 @@ class CaseLog < ApplicationRecord
attribute_names - AUTOGENERATED_FIELDS attribute_names - AUTOGENERATED_FIELDS
end end
def la
if location
location.location_code
else
super
end
end
def postcode_full
if location
location.postcode
else
super
end
end
def completed? def completed?
status == "completed" status == "completed"
end end
@ -465,7 +481,7 @@ class CaseLog < ApplicationRecord
private private
PIO = Postcodes::IO.new PIO = PostcodeService.new
def update_status! def update_status!
self.status = if all_fields_completed? && errors.empty? self.status = if all_fields_completed? && errors.empty?
@ -587,20 +603,7 @@ private
end end
def get_inferred_la(postcode) def get_inferred_la(postcode)
# Avoid network calls when postcode is invalid PIO.infer_la(postcode)
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = PostcodeService.clean(postcode)
Timeout.timeout(5) { postcode_lookup = PIO.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")
end
if postcode_lookup && postcode_lookup.info.present?
postcode_lookup.codes["admin_district"]
end
end end
def get_has_benefits def get_has_benefits

6
app/models/derived_variables/case_log_variables.rb

@ -73,10 +73,8 @@ module DerivedVariables::CaseLogVariables
self.location = scheme.locations.first self.location = scheme.locations.first
end end
if location if location
self.la = location.county # TODO: Remove and replace with mobility type
self.postcode_full = location.postcode self.wchair = location.wheelchair_adaptation_before_type_cast
wheelchair_adaptation_map = { 1 => 1, 0 => 2 }
self.wchair = wheelchair_adaptation_map[location.wheelchair_adaptation.to_i]
end end
if is_renewal? if is_renewal?
self.voiddate = startdate self.voiddate = startdate

10
app/models/location.rb

@ -2,11 +2,13 @@ class Location < ApplicationRecord
validate :validate_postcode validate :validate_postcode
belongs_to :scheme belongs_to :scheme
before_save :infer_la!, if: :postcode_changed?
attr_accessor :add_another_location attr_accessor :add_another_location
WHEELCHAIR_ADAPTATIONS = { WHEELCHAIR_ADAPTATIONS = {
Yes: 1, Yes: 1,
No: 0, No: 2,
}.freeze }.freeze
enum wheelchair_adaptation: WHEELCHAIR_ADAPTATIONS enum wheelchair_adaptation: WHEELCHAIR_ADAPTATIONS
@ -44,10 +46,16 @@ class Location < ApplicationRecord
private private
PIO = PostcodeService.new
def validate_postcode def validate_postcode
if postcode.nil? || !postcode&.match(POSTCODE_REGEXP) if postcode.nil? || !postcode&.match(POSTCODE_REGEXP)
error_message = I18n.t("validations.postcode") error_message = I18n.t("validations.postcode")
errors.add :postcode, error_message errors.add :postcode, error_message
end end
end end
def infer_la!
self.location_code = PIO.infer_la(postcode)
end
end end

68
app/services/imports/case_logs_import_service.rb

@ -15,6 +15,12 @@ module Imports
private private
FORM_NAME_INDEX = {
start_year: 0,
rent_type: 2,
needs_type: 3,
}.freeze
GN_SH = { GN_SH = {
general_needs: 1, general_needs: 1,
supported_housing: 2, supported_housing: 2,
@ -175,15 +181,20 @@ module Imports
attributes["first_time_property_let_as_social_housing"] = first_time_let(attributes["rsnvac"]) attributes["first_time_property_let_as_social_housing"] = first_time_let(attributes["rsnvac"])
attributes["declaration"] = declaration(xml_doc) attributes["declaration"] = declaration(xml_doc)
# Set charges to 0 if others are partially populated set_partial_charges_to_zero(attributes)
unless attributes["brent"].nil? &&
attributes["scharge"].nil? && # Supported Housing fields
attributes["pscharge"].nil? && if attributes["needstype"] == GN_SH[:supported_housing]
attributes["supcharg"].nil? old_visible_id = safe_string_as_integer(xml_doc, "_1cschemecode")
attributes["brent"] ||= BigDecimal("0.0") location = Location.find_by(old_visible_id:)
attributes["scharge"] ||= BigDecimal("0.0") scheme = location.scheme
attributes["pscharge"] ||= BigDecimal("0.0") # Set the scheme via location, because the scheme old visible ID is not unique
attributes["supcharg"] ||= BigDecimal("0.0") attributes["location_id"] = location.id
attributes["scheme_id"] = scheme.id
attributes["sheltered"] = unsafe_string_as_integer(xml_doc, "Q1e")
attributes["chcharge"] = safe_string_as_decimal(xml_doc, "Q18b")
attributes["household_charge"] = household_charge(xml_doc)
attributes["is_carehome"] = is_carehome(scheme)
end end
# Handles confidential schemes # Handles confidential schemes
@ -306,7 +317,7 @@ module Imports
end end
def needs_type(xml_doc) def needs_type(xml_doc)
gn_sh = get_form_name_component(xml_doc, -1) gn_sh = get_form_name_component(xml_doc, FORM_NAME_INDEX[:needs_type])
case gn_sh case gn_sh
when "GN" when "GN"
GN_SH[:general_needs] GN_SH[:general_needs]
@ -319,7 +330,7 @@ module Imports
# This does not match renttype (CDS) which is derived by case log logic # This does not match renttype (CDS) which is derived by case log logic
def rent_type(xml_doc, lar, irproduct) def rent_type(xml_doc, lar, irproduct)
sr_ar_ir = get_form_name_component(xml_doc, -2) sr_ar_ir = get_form_name_component(xml_doc, FORM_NAME_INDEX[:rent_type])
case sr_ar_ir case sr_ar_ir
when "SR" when "SR"
@ -590,5 +601,40 @@ module Imports
end end
end end
end end
def household_charge(xml_doc)
value = string_or_nil(xml_doc, "Q18c")
start_year = Integer(get_form_name_component(xml_doc, FORM_NAME_INDEX[:start_year]))
if start_year <= 2021
# Yes means that there are no charges (2021 or earlier)
value && value.include?("Yes") ? 1 : 0
else
# Yes means that there are charges (2022 onwards)
value && value.include?("Yes") ? 0 : 1
end
end
def set_partial_charges_to_zero(attributes)
unless attributes["brent"].nil? &&
attributes["scharge"].nil? &&
attributes["pscharge"].nil? &&
attributes["supcharg"].nil?
attributes["brent"] ||= BigDecimal("0.0")
attributes["scharge"] ||= BigDecimal("0.0")
attributes["pscharge"] ||= BigDecimal("0.0")
attributes["supcharg"] ||= BigDecimal("0.0")
end
end
def is_carehome(scheme)
return nil unless scheme
if [2, 3, 4].include?(scheme.registered_under_care_act_before_type_cast)
1
else
0
end
end
end end
end end

10
app/services/imports/scheme_location_import_service.rb

@ -72,7 +72,8 @@ module Imports
attributes["secondary_client_group"] = string_or_nil(xml_doc, "client-group-2") attributes["secondary_client_group"] = string_or_nil(xml_doc, "client-group-2")
attributes["secondary_client_group"] = nil if attributes["primary_client_group"] == attributes["secondary_client_group"] attributes["secondary_client_group"] = nil if attributes["primary_client_group"] == attributes["secondary_client_group"]
attributes["sensitive"] = sensitive(xml_doc) attributes["sensitive"] = sensitive(xml_doc)
attributes["end_date"] = parse_end_date(xml_doc) attributes["start_date"] = parse_date(xml_doc, "start-date")
attributes["end_date"] = parse_date(xml_doc, "end-date")
attributes["location_name"] = string_or_nil(xml_doc, "name") attributes["location_name"] = string_or_nil(xml_doc, "name")
attributes["postcode"] = string_or_nil(xml_doc, "postcode") attributes["postcode"] = string_or_nil(xml_doc, "postcode")
attributes["units"] = safe_string_as_integer(xml_doc, "total-units") attributes["units"] = safe_string_as_integer(xml_doc, "total-units")
@ -94,6 +95,7 @@ module Imports
type_of_unit: attributes["type_of_unit"], type_of_unit: attributes["type_of_unit"],
old_visible_id: attributes["location_old_visible_id"], old_visible_id: attributes["location_old_visible_id"],
old_id: attributes["location_old_id"], old_id: attributes["location_old_id"],
startdate: attributes["start_date"],
scheme:, scheme:,
) )
rescue ActiveRecord::RecordNotUnique rescue ActiveRecord::RecordNotUnique
@ -186,9 +188,9 @@ module Imports
end end
end end
def parse_end_date(xml_doc) def parse_date(xml_doc, attribute)
end_date = string_or_nil(xml_doc, "end-date") date = string_or_nil(xml_doc, attribute)
Time.zone.parse(end_date) if end_date Time.zone.parse(date) if date
end end
end end
end end

21
app/services/postcode_service.rb

@ -1,4 +1,25 @@
class PostcodeService class PostcodeService
def initialize
@pio = Postcodes::IO.new
end
def infer_la(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = self.class.clean(postcode)
Timeout.timeout(5) { postcode_lookup = @pio.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")
end
if postcode_lookup && postcode_lookup.info.present?
postcode_lookup.codes["admin_district"]
end
end
def self.clean(postcode) def self.clean(postcode)
postcode.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "").delete(" ").upcase postcode.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "").delete(" ").upcase
end end

5
db/migrate/20220715133937_remove_county_from_location.rb

@ -0,0 +1,5 @@
class RemoveCountyFromLocation < ActiveRecord::Migration[7.0]
def change
remove_column :locations, :county, :string
end
end

3
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[7.0].define(version: 2022_07_14_080044) do ActiveRecord::Schema[7.0].define(version: 2022_07_15_133937) 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"
@ -243,7 +243,6 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_14_080044) do
t.integer "wheelchair_adaptation" t.integer "wheelchair_adaptation"
t.bigint "scheme_id", null: false t.bigint "scheme_id", null: false
t.string "name" t.string "name"
t.string "county"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "units" t.integer "units"

4
spec/factories/location.rb

@ -1,13 +1,11 @@
FactoryBot.define do FactoryBot.define do
factory :location do factory :location do
location_code { Faker::Name.initials(number: 10) }
postcode { Faker::Address.postcode.delete(" ") } postcode { Faker::Address.postcode.delete(" ") }
name { Faker::Address.street_name } name { Faker::Address.street_name }
type_of_unit { [1, 2, 3, 4, 6, 7].sample } type_of_unit { [1, 2, 3, 4, 6, 7].sample }
type_of_building { "Purpose built" } type_of_building { "Purpose built" }
mobility_type { %w[A M N W X].sample } mobility_type { %w[A M N W X].sample }
wheelchair_adaptation { 0 } wheelchair_adaptation { 2 }
county { Faker::Address.state }
scheme scheme
end end
end end

524
spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml vendored

@ -0,0 +1,524 @@
<Group xmlns="http://data.gov.uk/core/logs/2021-CORE-SR-SH" xmlns:app="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:meta="http://data.gov.uk/core/metadata" xmlns:svc="http://www.w3.org/2007/app" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xfimpl="http://www.w3.org/2002/xforms/implementation" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xxf="http://orbeon.org/oxf/xml/xforms" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<meta:metadata xmlns:es="http://www.ecmascript.org/" xmlns:xqx="http://www.w3.org/2005/XQueryX" xmlns:XSLT="http://www.w3.org/1999/XSL/Transform/compile">
<meta:form-name>2021-CORE-SR-SH</meta:form-name>
<meta:document-id>0b4a68df-30cc-474a-93c0-a56ce8fdad3b</meta:document-id>
<meta:owner-user-id>c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa</meta:owner-user-id>
<meta:owner-institution-id>7c5bd5fb549c09z2c55d9cb90d7ba84927e64618</meta:owner-institution-id>
<meta:managing-institution-id>7c5bd5fb549c09z2c55d9cb90d7ba84927e64618</meta:managing-institution-id>
<meta:created-date>2022-01-05T12:50:20.39153Z</meta:created-date>
<meta:modified-date>2022-01-05T12:50:20.39153Z</meta:modified-date>
<meta:status>submitted-valid</meta:status>
<meta:reporting-year>2021</meta:reporting-year>
<meta:upload-method>Manual Entry</meta:upload-method>
<meta:schema assert-valid="true"/>
<meta:rules assert-valid="true"/>
</meta:metadata>
<Group>
<Qdp>Yes</Qdp>
<KeyDate>2021-11-05</KeyDate>
<FORM>123456</FORM>
<Landlord source-value="2">2 Local Authority</Landlord>
<Group>
<_1cmangroupcode>123</_1cmangroupcode>
<_1cschemecode>10</_1cschemecode>
<schPC/>
<Q1e>3 No</Q1e>
</Group>
</Group>
<Group>
<_2a>2 No</_2a>
<Q2b>1 Secure (inc flexible)</Q2b>
<Q2ba/>
<_2bTenCode>14044912001</_2bTenCode>
<_2cYears>2</_2cYears>
</Group>
<Group>
<P1Age override-field="">72</P1Age>
<P1AR/>
<P1Sex override-field="">Female</P1Sex>
<P1Eco>5) Retired</P1Eco>
<P1Eth>1 White: English/Scottish/Welsh/Northern Irish/British</P1Eth>
<P1Nat>1 UK national resident in UK</P1Nat>
<P2Age override-field="">74</P2Age>
<P2AR/>
<P2Sex override-field="">Male</P2Sex>
<P2Rel>Partner</P2Rel>
<P2Eco>5) Retired</P2Eco>
<P3Age override-field=""/>
<P3AR/>
<P3Sex override-field=""/>
<P3Rel/>
<P3Eco/>
<P4Age override-field=""/>
<P4AR/>
<P4Sex override-field=""/>
<P4Rel/>
<P4Eco/>
<P5Age override-field=""/>
<P5AR/>
<P5Sex override-field=""/>
<P5Rel/>
<P5Eco/>
<P6Age override-field=""/>
<P6AR/>
<P6Sex override-field=""/>
<P6Rel/>
<P6Eco/>
<P7Age override-field=""/>
<P7AR/>
<P7Sex override-field=""/>
<P7Rel/>
<P7Eco/>
<P8Age override-field=""/>
<P8AR/>
<P8Sex override-field=""/>
<P8Rel/>
<P8Eco/>
<Group>
<ArmedF>2 No</ArmedF>
<LeftAF/>
<Inj/>
<Preg override-field="">2 No</Preg>
</Group>
<Group>
<Q6Ben>9 Not in receipt of either UC or HB</Q6Ben>
</Group>
<Group>
<Q7Ben>2 Some</Q7Ben>
<Q8Refused>Refused</Q8Refused>
<Q8Money override-field=""/>
<Q8a/>
</Group>
<Group>
<Q9a>13 Property unsuitable because of ill health / disability</Q9a>
<Q9aa/>
</Group>
<Group>
<_9b override-field="">2 No</_9b>
<Q10-a/>
<Q10-b/>
<Q10-c/>
<Q10-f/>
<Q10-g>Yes</Q10-g>
<Q10-h/>
<Q10ia>1 Yes</Q10ia>
<Q10ib-1/>
<Q10ib-2/>
<Q10ib-3>Yes</Q10ib-3>
<Q10ib-4/>
<Q10ib-5/>
<Q10ib-6/>
<Q10ib-7/>
<Q10ib-8/>
<Q10ib-9/>
<Q10ib-10/>
<Q11 override-field="">26 Owner occupation (private)</Q11>
<Q12a>DLUHC</Q12a>
<Q12aONS>E08000035</Q12aONS>
<Q12b override-field="">S80 4DJ</Q12b>
<Q12bnot/>
<Q12c>5 5 years or more</Q12c>
<Q12d>9 3 years but under 4 years</Q12d>
</Group>
<Group>
<Q13>1 Not homeless</Q13>
<Q14a>2 No</Q14a>
<Q14b1/>
<Q14b2/>
<Q14b3/>
<Q14b4/>
<Q14b5/>
</Group>
<Group>
<Q15CBL>1 Yes</Q15CBL>
<Q15CHR>1 Yes</Q15CHR>
<Q15CAP>1 Yes</Q15CAP>
</Group>
<Group>
<Q16>2 Tenant applied direct (no referral or nomination)</Q16>
</Group>
</Group>
<Group>
<Q17>7 Weekly for 48 weeks</Q17>
<Q18ai override-field="true">125.00</Q18ai>
<Q18aii override-field=""/>
<Q18aiii override-field=""/>
<Q18aiv override-field="">7.00</Q18aiv>
<Q18av override-field="">132.00</Q18av>
<Q18b override-field=""/>
<Q18c/>
<Q18d/>
<Q18dyes override-field=""/>
<Q19void>2021-08-24</Q19void>
<Q19repair/>
<Q19supsch/>
<Q20 override-field="">0</Q20>
<Q21a>14044912</Q21a>
</Group>
<Group>
<Q25>1 Yes</Q25>
<Q27>15 First let of newbuild property</Q27>
</Group>
<Group>
<F1Age>0</F1Age>
<F2Age>0</F2Age>
<F3Age>0</F3Age>
<F4Age>0</F4Age>
<F5Age>0</F5Age>
<F6Age>0</F6Age>
<F7Age>0</F7Age>
<F8Age>0</F8Age>
<FAge>0</FAge>
<F1>1</F1>
<F2>0</F2>
<F3>0</F3>
<F4>0</F4>
<F5>0</F5>
<F6>0</F6>
<F7>0</F7>
<F8>0</F8>
<F>1</F>
<P1100>0</P1100>
<P2100>0</P2100>
<P3100>0</P3100>
<P4100>0</P4100>
<P5100>0</P5100>
<P6100>0</P6100>
<P7100>0</P7100>
<P8100>0</P8100>
<_100>0</_100>
<P170>1</P170>
<P270>1</P270>
<P370>0</P370>
<P470>0</P470>
<P570>0</P570>
<P670>0</P670>
<P770>0</P770>
<P870>0</P870>
<_70>1</_70>
<P1PT>0</P1PT>
<P2PT>0</P2PT>
<P3PT>0</P3PT>
<P4PT>0</P4PT>
<P5PT>0</P5PT>
<P6PT>0</P6PT>
<P7PT>0</P7PT>
<P8PT>0</P8PT>
<PT>0</PT>
<P1FT>0</P1FT>
<P2FT>0</P2FT>
<P3FT>0</P3FT>
<P4FT>0</P4FT>
<P5FT>0</P5FT>
<P6FT>0</P6FT>
<P7FT>0</P7FT>
<P8FT>0</P8FT>
<FT>0</FT>
<P1Stud>0</P1Stud>
<P2Stud>0</P2Stud>
<P3Stud>0</P3Stud>
<P4Stud>0</P4Stud>
<P5Stud>0</P5Stud>
<P6Stud>0</P6Stud>
<P7Stud>0</P7Stud>
<P8Stud>0</P8Stud>
<Stud>0</Stud>
<P2Child>0</P2Child>
<P3Child>0</P3Child>
<P4Child>0</P4Child>
<P5Child>0</P5Child>
<P6Child>0</P6Child>
<P7Child>0</P7Child>
<P8Child>0</P8Child>
<Child>0</Child>
<P2Partner>1</P2Partner>
<P3Partner>0</P3Partner>
<P4Partner>0</P4Partner>
<P5Partner>0</P5Partner>
<P6Partner>0</P6Partner>
<P7Partner>0</P7Partner>
<P8Partner>0</P8Partner>
<Partner>1</Partner>
<Q1cV1>1</Q1cV1>
<Q1cV2>1</Q1cV2>
<Q1cVT>2</Q1cVT>
<P1Adult>1</P1Adult>
<P2Adult>1</P2Adult>
<P3Adult>0</P3Adult>
<P4Adult>0</P4Adult>
<P5Adult>0</P5Adult>
<P6Adult>0</P6Adult>
<P7Adult>0</P7Adult>
<P8Adult>0</P8Adult>
<PAdultT>2</PAdultT>
<P2PAge>74</P2PAge>
<P3PAge>0</P3PAge>
<P4PAge>0</P4PAge>
<P5PAge>0</P5PAge>
<P6PAge>0</P6PAge>
<P7PAge>0</P7PAge>
<P8PAge>0</P8PAge>
<PAGE>74</PAGE>
<P2ChildAge>0</P2ChildAge>
<P3ChildAge>0</P3ChildAge>
<P4ChildAge>0</P4ChildAge>
<P5ChildAge>0</P5ChildAge>
<P6ChildAge>0</P6ChildAge>
<P7ChildAge>0</P7ChildAge>
<P8ChildAge>0</P8ChildAge>
<ChildAgeMin>0</ChildAgeMin>
<AgeDiff1>72</AgeDiff1>
<AgeDiff2>0</AgeDiff2>
<AgeDiff3>74</AgeDiff3>
<TODAY>2022-01-05Z</TODAY>
<FutureLimit>2022-01-20Z</FutureLimit>
<minmax1/>
<minmax2/>
<minmax3/>
<minmax4/>
<minmax5/>
<minmax6/>
<minmax7/>
<minmax8/>
<minmax9/>
<minmax0/>
<minmax10/>
<minmaxT/>
<Q10av>0</Q10av>
<Q10bv>0</Q10bv>
<Q10cv>0</Q10cv>
<Q10fv>0</Q10fv>
<Q10gv>20</Q10gv>
<Q10hv>0</Q10hv>
<Q10Validate>20</Q10Validate>
<Q2bv>A</Q2bv>
<P2Agev>1</P2Agev>
<P2Sexv>1</P2Sexv>
<P2Relv>1</P2Relv>
<P2Ecov>1</P2Ecov>
<P2valid>4</P2valid>
<P3Agev>0</P3Agev>
<P3Sexv>0</P3Sexv>
<P3Relv>0</P3Relv>
<P3Ecov>0</P3Ecov>
<P3valid>0</P3valid>
<P4Agev>0</P4Agev>
<P4Sexv>0</P4Sexv>
<P4Relv>0</P4Relv>
<P4Ecov>0</P4Ecov>
<P4valid>0</P4valid>
<P5Agev>0</P5Agev>
<P5Sexv>0</P5Sexv>
<P5Relv>0</P5Relv>
<P5Ecov>0</P5Ecov>
<P5valid>0</P5valid>
<P6Agev>0</P6Agev>
<P6Sexv>0</P6Sexv>
<P6Relv>0</P6Relv>
<P6Ecov>0</P6Ecov>
<P6valid>0</P6valid>
<P7Agev>0</P7Agev>
<P7Sexv>0</P7Sexv>
<P7Relv>0</P7Relv>
<P7Ecov>0</P7Ecov>
<P7valid>0</P7valid>
<P8Agev>0</P8Agev>
<P8Sexv>0</P8Sexv>
<P8Relv>0</P8Relv>
<P8Ecov>0</P8Ecov>
<P8valid>0</P8valid>
<Q14b1v>0</Q14b1v>
<Q14b2v>0</Q14b2v>
<Q14b3v>0</Q14b3v>
<Q14b4v>0</Q14b4v>
<Q14b5v>0</Q14b5v>
<Q14bv>0</Q14bv>
<P2Other>0</P2Other>
<P3Other>0</P3Other>
<P4Other>0</P4Other>
<P5Other>0</P5Other>
<P6Other>0</P6Other>
<P7Other>0</P7Other>
<P8Other>0</P8Other>
<Other>0</Other>
<P2ARefused>0</P2ARefused>
<P3ARefused>0</P3ARefused>
<P4ARefused>0</P4ARefused>
<P5ARefused>0</P5ARefused>
<P6ARefused>0</P6ARefused>
<P7ARefused>0</P7ARefused>
<P8ARefused>0</P8ARefused>
<TAREUSED>0</TAREUSED>
<P2RRefused>0</P2RRefused>
<P3RRefused>0</P3RRefused>
<P4RRefused>0</P4RRefused>
<P5RRefused>0</P5RRefused>
<P6RRefused>0</P6RRefused>
<P7RRefused>0</P7RRefused>
<P8RRefused>0</P8RRefused>
<TotRRefused>0</TotRRefused>
<TOTREFUSED>0</TOTREFUSED>
</Group>
<Group>
<ChildBen>0.00</ChildBen>
<TOTADULT>2</TOTADULT>
<NEW_OLD>1 New Tenant</NEW_OLD>
<WCHCHRG/>
<VACDAYS>73</VACDAYS>
<HHMEMB>2</HHMEMB>
<HHTYPEP1A>0</HHTYPEP1A>
<HHTYPEP2A>0</HHTYPEP2A>
<HHTYPEP3A>0</HHTYPEP3A>
<HHTYPEP4A>0</HHTYPEP4A>
<HHTYPEP5A>0</HHTYPEP5A>
<HHTYPEP6A>0</HHTYPEP6A>
<HHTYPEP7A>0</HHTYPEP7A>
<HHTYPEP8A>0</HHTYPEP8A>
<TADULT>0</TADULT>
<HHTYPEP1E>1</HHTYPEP1E>
<HHTYPEP2E>1</HHTYPEP2E>
<HHTYPEP3E>0</HHTYPEP3E>
<HHTYPEP4E>0</HHTYPEP4E>
<HHTYPEP5E>0</HHTYPEP5E>
<HHTYPEP6E>0</HHTYPEP6E>
<HHTYPEP7E>0</HHTYPEP7E>
<HHTYPEP8E>0</HHTYPEP8E>
<TELDER>2</TELDER>
<HHTYPEP1C>0</HHTYPEP1C>
<HHTYPEP2C>0</HHTYPEP2C>
<HHTYPEP3C>0</HHTYPEP3C>
<HHTYPEP4C>0</HHTYPEP4C>
<HHTYPEP5C>0</HHTYPEP5C>
<HHTYPEP6C>0</HHTYPEP6C>
<HHTYPEP7C>0</HHTYPEP7C>
<HHTYPEP8C>0</HHTYPEP8C>
<TCHILD>0</TCHILD>
<Q18aValid>1</Q18aValid>
<Q18bValid>0</Q18bValid>
<Q18cValid>0</Q18cValid>
<Q18Valid>1</Q18Valid>
<HHTYPE>2 = 2 Adults at least one is an Elder</HHTYPE>
<WEEKLYINC/>
<INCOME/>
<TYPEHB>15.00</TYPEHB>
<AFFRATE/>
<Weekinc/>
<LETTYPE>2 Local Authority</LETTYPE>
<PLOACODE/>
<OACODE/>
<GOVREG>E12000004</GOVREG>
<OWNINGORGID>1</OWNINGORGID>
<OWNINGORGNAME>DLUHC</OWNINGORGNAME>
<MANINGORGNAME>DLUHC</MANINGORGNAME>
<HCNUM>N/A</HCNUM>
<MANHCNUM>N/A</MANHCNUM>
<LAHA/>
<MANINGORGID>1</MANINGORGID>
<Q28same1>false</Q28same1>
<HBTYPE1/>
<HBTYPE2/>
<HBTYPE3/>
<HBTYPE4/>
<HBTYPE5/>
<HBTYPE6/>
<HBTYPE7/>
<HBTYPE8/>
<HBTYPE9/>
<HBTYPE10/>
<HBTYPE11/>
<HBTYPE12/>
<HBTYPE13/>
<HBTYPE14/>
<HBTYPE15>15</HBTYPE15>
<HBTYPE>15</HBTYPE>
<SCHEME>000001005048</SCHEME>
<MANTYPE>D</MANTYPE>
<UNITS>15</UNITS>
<UNITTYPE>6</UNITTYPE>
<SCHTYPE>7</SCHTYPE>
<REGHOME>1</REGHOME>
<SUPPORT>2</SUPPORT>
<MOBSTAND>A</MOBSTAND>
<INTSTAY>P</INTSTAY>
<CLIGRP1>M</CLIGRP1>
<CLIGRP2/>
<Q28Auth>DLUHC</Q28Auth>
<Q28ONS>E08000035</Q28ONS>
<Q28pc override-field="">S80 4QE</Q28pc>
<Q28same/>
<P1R>0</P1R>
<P2R>0</P2R>
<P3R>0</P3R>
<P4R>0</P4R>
<P5R>0</P5R>
<P6R>0</P6R>
<P7R>0</P7R>
<P8R>0</P8R>
<REFUSEDTOT>0</REFUSEDTOT>
<REFUSED/>
<WTSHORTFALL/>
<WTSHORTFALLHB/>
<WTSHORTFALLHE/>
<WRENT>115.38</WRENT>
<WTCHARGE>121.85</WTCHARGE>
<WSCHARGE/>
<WPSCHRGE/>
<WSUPCHRG>6.46</WSUPCHRG>
<WTSHORTFALL1/>
<WRENT1>115.38</WRENT1>
<WTCHARGE1>121.85</WTCHARGE1>
<WSCHARGE1/>
<WPSCHRGE1/>
<WSUPCHRG1>6.46</WSUPCHRG1>
</Group>
<Group>
<BSa>1</BSa>
<BSb>0</BSb>
<BSc>0</BSc>
<BScm>0</BScm>
<BScf>0</BScf>
<BSd>0</BSd>
<BSdm>0</BSdm>
<BSdf>0</BSdf>
<BSe>0</BSe>
<BSem>0</BSem>
<BSef>0</BSef>
<BSf>0</BSf>
<BSfm>0</BSfm>
<BSff>0</BSff>
<BSfmx>0</BSfmx>
<BSffx>0</BSffx>
<BEDROOMSTAND>1</BEDROOMSTAND>
<BEDMINUSBEDS/>
<WRENTreduced>115.38</WRENTreduced>
<NonDepDeduct>0</NonDepDeduct>
<RENTHB/>
<ChildAllowan>0</ChildAllowan>
<PrsnlAllowan>117.4</PrsnlAllowan>
<HousBenDisAl>10</HousBenDisAl>
<PAIDHB/>
<HCNETAF/>
<ChldAlloCat1>0</ChldAlloCat1>
<ChldAlloCat2>0</ChldAlloCat2>
<P2NnDepDedct>0</P2NnDepDedct>
<P3NnDepDedct>0</P3NnDepDedct>
<P4NnDepDedct>0</P4NnDepDedct>
<P5NnDepDedct>0</P5NnDepDedct>
<P6NnDepDedct>0</P6NnDepDedct>
<P7NnDepDedct>0</P7NnDepDedct>
<P8NnDepDedct>0</P8NnDepDedct>
<DAY>5</DAY>
<MONTH>11</MONTH>
<YEAR>2021</YEAR>
<VDAY>24</VDAY>
<VMONTH>8</VMONTH>
<VYEAR>2021</VYEAR>
<MRCDAY/>
<MRCMONTH/>
<MRCYEAR/>
<PPOSTC1>LS16</PPOSTC1>
<PPOSTC2>6FT</PPOSTC2>
<POSTCODE>LS16</POSTCODE>
<POSTCOD2>6FT</POSTCOD2>
</Group>
</Group>

15
spec/models/case_log_spec.rb

@ -1702,9 +1702,9 @@ RSpec.describe CaseLog do
context "and not renewal" do context "and not renewal" do
let(:scheme) { FactoryBot.create(:scheme) } let(:scheme) { FactoryBot.create(:scheme) }
let(:location) { FactoryBot.create(:location, scheme:, county: "E07000041", type_of_unit: 1, type_of_building: "Purpose built") } let(:location) { FactoryBot.create(:location, scheme:, postcode: "M11AE", type_of_unit: 1, type_of_building: "Purpose built") }
let!(:supported_housing_case_log) do let(:supported_housing_case_log) do
described_class.create!({ described_class.create!({
managing_organisation: owning_organisation, managing_organisation: owning_organisation,
owning_organisation:, owning_organisation:,
@ -1716,14 +1716,21 @@ RSpec.describe CaseLog do
}) })
end end
before do
stub_request(:get, /api.postcodes.io/)
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\",\"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
end
it "correctly infers and saves la" do it "correctly infers and saves la" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT la from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0] record_from_db = ActiveRecord::Base.connection.execute("SELECT la from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["la"]).to eq(location.county) expect(record_from_db["la"]).to be_nil
expect(supported_housing_case_log.la).to eq("E08000003")
end end
it "correctly infers and saves postcode" do it "correctly infers and saves postcode" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT postcode_full from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0] record_from_db = ActiveRecord::Base.connection.execute("SELECT postcode_full from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["postcode_full"]).to eq(location.postcode) expect(record_from_db["postcode_full"]).to be_nil
expect(supported_housing_case_log.postcode_full).to eq("M11AE")
end end
it "unittype_sh method returns the type_of_unit of the location" do it "unittype_sh method returns the type_of_unit of the location" do

11
spec/models/location_spec.rb

@ -4,9 +4,20 @@ RSpec.describe Location, type: :model do
describe "#new" do describe "#new" do
let(:location) { FactoryBot.build(:location) } let(:location) { FactoryBot.build(:location) }
before do
stub_request(:get, /api.postcodes.io/)
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\",\"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
end
it "belongs to an organisation" do it "belongs to an organisation" do
expect(location.scheme).to be_a(Scheme) expect(location.scheme).to be_a(Scheme)
end end
it "infers the local authority" do
location.postcode = "M1 1AE"
location.save!
expect(location.location_code).to eq("E08000003")
end
end end
describe "#validate_postcode" do describe "#validate_postcode" do

68
spec/services/imports/case_logs_import_service_spec.rb

@ -16,6 +16,9 @@ RSpec.describe Imports::CaseLogsImportService do
end end
before do before do
WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/LS166FT/)
.to_return(status: 200, body: '{"status":200,"result":{"codes":{"admin_district":"E08000035"}}}', headers: {})
allow(Organisation).to receive(:find_by).and_return(nil) allow(Organisation).to receive(:find_by).and_return(nil)
allow(Organisation).to receive(:find_by).with(old_visible_id: organisation.old_visible_id.to_i).and_return(organisation) allow(Organisation).to receive(:find_by).with(old_visible_id: organisation.old_visible_id.to_i).and_return(organisation)
@ -23,12 +26,17 @@ RSpec.describe Imports::CaseLogsImportService do
FactoryBot.create(:user, old_user_id: "c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa", organisation:) FactoryBot.create(:user, old_user_id: "c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa", organisation:)
FactoryBot.create(:user, old_user_id: "e29c492473446dca4d50224f2bb7cf965a261d6f", organisation:) FactoryBot.create(:user, old_user_id: "e29c492473446dca4d50224f2bb7cf965a261d6f", organisation:)
# Scheme and Location
scheme = FactoryBot.create(:scheme, old_visible_id: 123)
FactoryBot.create(:location,
old_visible_id: 10,
scheme_id: scheme.id,
wheelchair_adaptation: 1,
postcode: "LS166FT")
# Stub the form handler to use the real form # Stub the form handler to use the real form
allow(FormHandler.instance).to receive(:get_form).with("2021_2022").and_return(real_2021_2022_form) allow(FormHandler.instance).to receive(:get_form).with("2021_2022").and_return(real_2021_2022_form)
allow(FormHandler.instance).to receive(:get_form).with("2022_2023").and_return(real_2022_2023_form) allow(FormHandler.instance).to receive(:get_form).with("2022_2023").and_return(real_2022_2023_form)
WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/LS166FT/)
.to_return(status: 200, body: '{"status":200,"result":{"codes":{"admin_district":"E08000035"}}}', headers: {})
end end
context "when importing case logs" do context "when importing case logs" do
@ -36,11 +44,12 @@ RSpec.describe Imports::CaseLogsImportService do
let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" } let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" }
let(:case_log_id2) { "166fc004-392e-47a8-acb8-1c018734882b" } let(:case_log_id2) { "166fc004-392e-47a8-acb8-1c018734882b" }
let(:case_log_id3) { "00d2343e-d5fa-4c89-8400-ec3854b0f2b4" } let(:case_log_id3) { "00d2343e-d5fa-4c89-8400-ec3854b0f2b4" }
let(:case_log_id4) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" }
before do before do
# Stub the S3 file listing and download # Stub the S3 file listing and download
allow(storage_service).to receive(:list_files) allow(storage_service).to receive(:list_files)
.and_return(%W[#{remote_folder}/#{case_log_id}.xml #{remote_folder}/#{case_log_id2}.xml #{remote_folder}/#{case_log_id3}.xml]) .and_return(%W[#{remote_folder}/#{case_log_id}.xml #{remote_folder}/#{case_log_id2}.xml #{remote_folder}/#{case_log_id3}.xml #{remote_folder}/#{case_log_id4}.xml])
allow(storage_service).to receive(:get_file_io) allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id}.xml") .with("#{remote_folder}/#{case_log_id}.xml")
.and_return(open_file(fixture_directory, case_log_id), open_file(fixture_directory, case_log_id)) .and_return(open_file(fixture_directory, case_log_id), open_file(fixture_directory, case_log_id))
@ -50,6 +59,9 @@ RSpec.describe Imports::CaseLogsImportService do
allow(storage_service).to receive(:get_file_io) allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id3}.xml") .with("#{remote_folder}/#{case_log_id3}.xml")
.and_return(open_file(fixture_directory, case_log_id3), open_file(fixture_directory, case_log_id3)) .and_return(open_file(fixture_directory, case_log_id3), open_file(fixture_directory, case_log_id3))
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id4}.xml")
.and_return(open_file(fixture_directory, case_log_id4), open_file(fixture_directory, case_log_id4))
end end
it "successfully create all case logs" do it "successfully create all case logs" do
@ -57,45 +69,45 @@ RSpec.describe Imports::CaseLogsImportService do
expect(logger).not_to receive(:warn) expect(logger).not_to receive(:warn)
expect(logger).not_to receive(:info) expect(logger).not_to receive(:info)
expect { case_log_service.create_logs(remote_folder) } expect { case_log_service.create_logs(remote_folder) }
.to change(CaseLog, :count).by(3) .to change(CaseLog, :count).by(4)
end end
it "only updates existing case logs" do it "only updates existing case logs" do
expect(logger).not_to receive(:error) expect(logger).not_to receive(:error)
expect(logger).not_to receive(:warn) expect(logger).not_to receive(:warn)
expect(logger).to receive(:info).with(/Updating case log/).exactly(3).times expect(logger).to receive(:info).with(/Updating case log/).exactly(4).times
expect { 2.times { case_log_service.create_logs(remote_folder) } } expect { 2.times { case_log_service.create_logs(remote_folder) } }
.to change(CaseLog, :count).by(3) .to change(CaseLog, :count).by(4)
end end
context "when there are status discrepancies" do context "when there are status discrepancies" do
let(:case_log_id4) { "893ufj2s-lq77-42m4-rty6-ej09gh585uy1" } let(:case_log_id5) { "893ufj2s-lq77-42m4-rty6-ej09gh585uy1" }
let(:case_log_id5) { "5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd" } let(:case_log_id6) { "5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd" }
let(:case_log_file) { open_file(fixture_directory, case_log_id4) } let(:case_log_file) { open_file(fixture_directory, case_log_id5) }
let(:case_log_xml) { Nokogiri::XML(case_log_file) } let(:case_log_xml) { Nokogiri::XML(case_log_file) }
before do before do
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id4}.xml")
.and_return(open_file(fixture_directory, case_log_id4), open_file(fixture_directory, case_log_id4))
allow(storage_service).to receive(:get_file_io) allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id5}.xml") .with("#{remote_folder}/#{case_log_id5}.xml")
.and_return(open_file(fixture_directory, case_log_id5), open_file(fixture_directory, case_log_id5)) .and_return(open_file(fixture_directory, case_log_id5), open_file(fixture_directory, case_log_id5))
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id6}.xml")
.and_return(open_file(fixture_directory, case_log_id6), open_file(fixture_directory, case_log_id6))
end end
it "the logger logs a warning with the case log's old id/filename" do it "the logger logs a warning with the case log's old id/filename" do
expect(logger).to receive(:warn).with(/is not completed/).once expect(logger).to receive(:warn).with(/is not completed/).once
expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id4} is incomplete but status should be complete/).once expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id5} is incomplete but status should be complete/).once
case_log_service.send(:create_log, case_log_xml) case_log_service.send(:create_log, case_log_xml)
end end
it "on completion the ids of all logs with status discrepancies are logged in a warning" do it "on completion the ids of all logs with status discrepancies are logged in a warning" do
allow(storage_service).to receive(:list_files) allow(storage_service).to receive(:list_files)
.and_return(%W[#{remote_folder}/#{case_log_id4}.xml #{remote_folder}/#{case_log_id5}.xml]) .and_return(%W[#{remote_folder}/#{case_log_id5}.xml #{remote_folder}/#{case_log_id6}.xml])
allow(logger).to receive(:warn).with(/is not completed/) expect(logger).to receive(:warn).with(/is not completed/).twice
allow(logger).to receive(:warn).with(/is incomplete but status should be complete/) expect(logger).to receive(:warn).with(/is incomplete but status should be complete/).twice
expect(logger).to receive(:warn).with(/The following case logs had status discrepancies: \[893ufj2s-lq77-42m4-rty6-ej09gh585uy1, 5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd\]/).once expect(logger).to receive(:warn).with(/The following case logs had status discrepancies: \[893ufj2s-lq77-42m4-rty6-ej09gh585uy1, 5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd\]/)
case_log_service.create_logs(remote_folder) case_log_service.create_logs(remote_folder)
end end
@ -111,8 +123,8 @@ RSpec.describe Imports::CaseLogsImportService do
before { case_log_xml.at_xpath("//xmlns:VYEAR").content = 2023 } before { case_log_xml.at_xpath("//xmlns:VYEAR").content = 2023 }
it "does not import the voiddate" do it "does not import the voiddate" do
allow(logger).to receive(:warn).with(/is not completed/) expect(logger).to receive(:warn).with(/is not completed/)
allow(logger).to receive(:warn).with(/Case log with old id:#{case_log_id} is incomplete but status should be complete/) expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id} is incomplete but status should be complete/)
case_log_service.send(:create_log, case_log_xml) case_log_service.send(:create_log, case_log_xml)
@ -138,7 +150,7 @@ RSpec.describe Imports::CaseLogsImportService do
it "sets the economic status to child under 16" do it "sets the economic status to child under 16" do
# The update is done when calculating derived variables # The update is done when calculating derived variables
allow(logger).to receive(:warn).with(/Differences found when saving log/) expect(logger).to receive(:warn).with(/Differences found when saving log/)
case_log_service.send(:create_log, case_log_xml) case_log_service.send(:create_log, case_log_xml)
case_log = CaseLog.where(old_id: case_log_id).first case_log = CaseLog.where(old_id: case_log_id).first
@ -203,5 +215,19 @@ RSpec.describe Imports::CaseLogsImportService do
end end
end end
end end
context "and this is a supported housing log" do
let(:case_log_id) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" }
it "sets the scheme and location values" do
expect(logger).not_to receive(:warn)
case_log_service.send(:create_log, case_log_xml)
case_log = CaseLog.find_by(old_id: case_log_id)
expect(case_log.scheme_id).not_to be_nil
expect(case_log.location_id).not_to be_nil
expect(case_log.status).to eq("completed")
end
end
end end
end end

1
spec/services/imports/scheme_location_import_service_spec.rb

@ -138,6 +138,7 @@ RSpec.describe Imports::SchemeLocationImportService do
expect(location.type_of_unit).to eq("Bungalow") expect(location.type_of_unit).to eq("Bungalow")
expect(location.old_id).to eq(first_location_id) expect(location.old_id).to eq(first_location_id)
expect(location.old_visible_id).to eq(10) expect(location.old_visible_id).to eq(10)
expect(location.startdate).to eq("1900-01-01")
expect(location.scheme).to eq(scheme) expect(location.scheme).to eq(scheme)
end end

Loading…
Cancel
Save