From e0e09a22d27465d174187eea84a26da46d36d942 Mon Sep 17 00:00:00 2001
From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com>
Date: Fri, 24 Feb 2023 15:16:28 +0000
Subject: [PATCH] CLDS-1938 Add sales logs importer (#1331)
* Create test fixtures
* Add old id column
* Add sales logs importer
* Save and update completed discounted ownership log without postcode
* Update fixtures to be incomplete
* Import a completed shared ownership example
* Test with a non homebuy shared ownership example
* Add privacynotice
* Update hholdcount and confirmed fields
* Add buyer stilll serving mapping
* Add totadult/totchild and outright sale examples
* Update log fixtures
* Extract shared methods into logs import service and lint
* Add sales logs import rake task
* Update noint, xml examples and some mappings
* Add tests for checking that all required questions are answered
* Update tests, clean up import
* Map mortgage lender and mortgage lender other
* Infer Mscharge known as no for outright sale
* refactor setting default values
* when the armedforcesspouse is not answered set is as don't know
* Refactor tests: change log id names
* set savings to not known if not given
* Refactor tests: change nesting
* Backfill default household characteristics for completed log
* Add more default mapping
* Typo
* Improve logging and refactor tests
* Adjust test data to fit with the mappings that are known so far
* Rename fixture files
---
.../imports/lettings_logs_import_service.rb | 113 +---
app/services/imports/logs_import_service.rb | 127 ++++
.../imports/sales_logs_import_service.rb | 450 +++++++++++++
...20230215112932_add_old_id_to_sales_logs.rb | 8 +
db/schema.rb | 10 +-
lib/tasks/data_import.rake | 2 +
.../discounted_ownership_sales_log.xml | 351 +++++++++++
.../sales_logs/outright_sale_sales_log.xml | 333 ++++++++++
.../sales_logs/shared_ownership_sales_log.xml | 333 ++++++++++
.../shared_ownership_sales_log2.xml | 333 ++++++++++
.../shared_ownership_sales_log3.xml | 333 ++++++++++
.../shared_ownership_sales_log4.xml | 333 ++++++++++
spec/lib/tasks/data_import_spec.rb | 18 +
.../imports/sales_logs_import_service_spec.rb | 595 ++++++++++++++++++
14 files changed, 3223 insertions(+), 116 deletions(-)
create mode 100644 app/services/imports/logs_import_service.rb
create mode 100644 app/services/imports/sales_logs_import_service.rb
create mode 100644 db/migrate/20230215112932_add_old_id_to_sales_logs.rb
create mode 100644 spec/fixtures/imports/sales_logs/discounted_ownership_sales_log.xml
create mode 100644 spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml
create mode 100644 spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml
create mode 100644 spec/fixtures/imports/sales_logs/shared_ownership_sales_log2.xml
create mode 100644 spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
create mode 100644 spec/fixtures/imports/sales_logs/shared_ownership_sales_log4.xml
create mode 100644 spec/services/imports/sales_logs_import_service_spec.rb
diff --git a/app/services/imports/lettings_logs_import_service.rb b/app/services/imports/lettings_logs_import_service.rb
index 8fe71d3dc..a215e8f5a 100644
--- a/app/services/imports/lettings_logs_import_service.rb
+++ b/app/services/imports/lettings_logs_import_service.rb
@@ -1,5 +1,5 @@
module Imports
- class LettingsLogsImportService < ImportService
+ class LettingsLogsImportService < LogsImportService
def initialize(storage_service, logger = Rails.logger)
@logs_with_discrepancies = Set.new
@logs_overridden = Set.new
@@ -318,43 +318,6 @@ module Imports
end
end
- # Safe: A string that represents only an integer (or empty/nil)
- def safe_string_as_integer(xml_doc, attribute)
- str = field_value(xml_doc, "xmlns", attribute)
- Integer(str, exception: false)
- end
-
- # Safe: A string that represents only a decimal (or empty/nil)
- def safe_string_as_decimal(xml_doc, attribute)
- str = string_or_nil(xml_doc, attribute)
- if str.nil?
- nil
- else
- BigDecimal(str, exception: false)
- end
- end
-
- # Unsafe: A string that has more than just the integer value
- def unsafe_string_as_integer(xml_doc, attribute)
- str = string_or_nil(xml_doc, attribute)
- if str.nil?
- nil
- else
- str.to_i
- end
- end
-
- def compose_date(xml_doc, day_str, month_str, year_str)
- day = Integer(field_value(xml_doc, "xmlns", day_str), exception: false)
- month = Integer(field_value(xml_doc, "xmlns", month_str), exception: false)
- year = Integer(field_value(xml_doc, "xmlns", year_str), exception: false)
- if day.nil? || month.nil? || year.nil?
- nil
- else
- Time.zone.local(year, month, day)
- end
- end
-
def get_form_name_component(xml_doc, index)
form_name = meta_field_value(xml_doc, "form-name")
form_type_components = form_name.split("-")
@@ -399,42 +362,6 @@ module Imports
end
end
- def find_organisation_id(xml_doc, id_field)
- old_visible_id = string_or_nil(xml_doc, id_field)
- organisation = Organisation.find_by(old_visible_id:)
- raise "Organisation not found with legacy ID #{old_visible_id}" if organisation.nil?
-
- organisation.id
- end
-
- def sex(xml_doc, index)
- sex = string_or_nil(xml_doc, "P#{index}Sex")
- case sex
- when "Male"
- "M"
- when "Female"
- "F"
- when "Other", "Non-binary"
- "X"
- when "Refused"
- "R"
- end
- end
-
- def relat(xml_doc, index)
- relat = string_or_nil(xml_doc, "P#{index}Rel")
- case relat
- when "Child"
- "C"
- when "Partner"
- "P"
- when "Other", "Non-binary"
- "X"
- when "Refused"
- "R"
- end
- end
-
def age_known(xml_doc, index, hhmemb)
return nil if hhmemb.present? && index > hhmemb
@@ -473,16 +400,6 @@ module Imports
end
end
- def compose_postcode(xml_doc, outcode, incode)
- outcode_value = string_or_nil(xml_doc, outcode)
- incode_value = string_or_nil(xml_doc, incode)
- if outcode_value.nil? || incode_value.nil? || !"#{outcode_value} #{incode_value}".match(POSTCODE_REGEXP)
- nil
- else
- "#{outcode_value} #{incode_value}"
- end
- end
-
def london_affordable_rent(xml_doc)
lar = unsafe_string_as_integer(xml_doc, "LAR")
if lar == 1
@@ -502,34 +419,6 @@ module Imports
end
end
- def string_or_nil(xml_doc, attribute)
- str = field_value(xml_doc, "xmlns", attribute)
- str.presence
- end
-
- def ethnic_group(ethnic)
- case ethnic
- when 1, 2, 3, 18
- # White
- 0
- when 4, 5, 6, 7
- # Mixed
- 1
- when 8, 9, 10, 11, 15
- # Asian
- 2
- when 12, 13, 14
- # Black
- 3
- when 16, 19
- # Others
- 4
- when 17
- # Refused
- 17
- end
- end
-
# Letters should be lowercase to match case
def housing_needs(xml_doc, letter)
housing_need = string_or_nil(xml_doc, "Q10-#{letter}")
diff --git a/app/services/imports/logs_import_service.rb b/app/services/imports/logs_import_service.rb
new file mode 100644
index 000000000..0a3d2df87
--- /dev/null
+++ b/app/services/imports/logs_import_service.rb
@@ -0,0 +1,127 @@
+module Imports
+ class LogsImportService < ImportService
+ private
+
+ # Safe: A string that represents only an integer (or empty/nil)
+ def safe_string_as_integer(xml_doc, attribute)
+ str = field_value(xml_doc, "xmlns", attribute)
+ Integer(str, exception: false)
+ end
+
+ # Unsafe: A string that has more than just the integer value
+ def unsafe_string_as_integer(xml_doc, attribute)
+ str = string_or_nil(xml_doc, attribute)
+ if str.nil?
+ nil
+ else
+ str.to_i
+ end
+ end
+
+ def compose_date(xml_doc, day_str, month_str, year_str)
+ day = Integer(field_value(xml_doc, "xmlns", day_str), exception: false)
+ month = Integer(field_value(xml_doc, "xmlns", month_str), exception: false)
+ year = Integer(field_value(xml_doc, "xmlns", year_str), exception: false)
+ if day.nil? || month.nil? || year.nil?
+ nil
+ else
+ Time.zone.local(year, month, day)
+ end
+ end
+
+ def find_organisation_id(xml_doc, id_field)
+ old_visible_id = string_or_nil(xml_doc, id_field)
+ organisation = Organisation.find_by(old_visible_id:)
+ raise "Organisation not found with legacy ID #{old_visible_id}" if organisation.nil?
+
+ organisation.id
+ end
+
+ def string_or_nil(xml_doc, attribute)
+ str = field_value(xml_doc, "xmlns", attribute)
+ str.presence
+ end
+
+ def ethnic_group(ethnic)
+ case ethnic
+ when 1, 2, 3, 18
+ # White
+ 0
+ when 4, 5, 6, 7
+ # Mixed
+ 1
+ when 8, 9, 10, 11, 15
+ # Asian
+ 2
+ when 12, 13, 14
+ # Black
+ 3
+ when 16, 19
+ # Others
+ 4
+ when 17
+ # Refused
+ 17
+ end
+ end
+
+ # Safe: A string that represents only a decimal (or empty/nil)
+ def safe_string_as_decimal(xml_doc, attribute)
+ str = string_or_nil(xml_doc, attribute)
+ if str.nil?
+ nil
+ else
+ BigDecimal(str, exception: false)
+ end
+ end
+
+ def compose_postcode(xml_doc, outcode, incode)
+ outcode_value = string_or_nil(xml_doc, outcode)
+ incode_value = string_or_nil(xml_doc, incode)
+ if outcode_value.nil? || incode_value.nil? || !"#{outcode_value} #{incode_value}".match(POSTCODE_REGEXP)
+ nil
+ else
+ "#{outcode_value} #{incode_value}"
+ end
+ end
+
+ def previous_postcode_known(xml_doc, previous_postcode, prevloc)
+ previous_postcode_known = string_or_nil(xml_doc, "Q7UnknownPostcode")
+ if previous_postcode_known == "If postcode not known tick" || (previous_postcode.nil? && prevloc.present?)
+ 1
+ elsif previous_postcode.nil?
+ nil
+ else
+ 0
+ end
+ end
+
+ def sex(xml_doc, index)
+ sex = string_or_nil(xml_doc, "P#{index}Sex")
+ case sex
+ when "Male"
+ "M"
+ when "Female"
+ "F"
+ when "Other", "Non-binary"
+ "X"
+ when "Refused"
+ "R"
+ end
+ end
+
+ def relat(xml_doc, index)
+ relat = string_or_nil(xml_doc, "P#{index}Rel")
+ case relat
+ when "Child"
+ "C"
+ when "Partner"
+ "P"
+ when "Other", "Non-binary"
+ "X"
+ when "Refused", "Buyer prefers not to say"
+ "R"
+ end
+ end
+ end
+end
diff --git a/app/services/imports/sales_logs_import_service.rb b/app/services/imports/sales_logs_import_service.rb
new file mode 100644
index 000000000..25f8148b8
--- /dev/null
+++ b/app/services/imports/sales_logs_import_service.rb
@@ -0,0 +1,450 @@
+module Imports
+ class SalesLogsImportService < LogsImportService
+ def initialize(storage_service, logger = Rails.logger)
+ @logs_with_discrepancies = Set.new
+ @logs_overridden = Set.new
+ super
+ end
+
+ def create_logs(folder)
+ import_from(folder, :create_log)
+ if @logs_with_discrepancies.count.positive?
+ @logger.warn("The following sales logs had status discrepancies: [#{@logs_with_discrepancies.join(', ')}]")
+ end
+ end
+
+ private
+
+ def create_log(xml_doc)
+ attributes = {}
+
+ previous_status = meta_field_value(xml_doc, "status")
+
+ # Required fields for status complete or logic to work
+ # Note: order matters when we derive from previous values (attributes parameter)
+
+ attributes["saledate"] = compose_date(xml_doc, "DAY", "MONTH", "YEAR")
+ attributes["owning_organisation_id"] = find_organisation_id(xml_doc, "OWNINGORGID")
+ attributes["type"] = unsafe_string_as_integer(xml_doc, "DerSaleType")
+ attributes["old_id"] = meta_field_value(xml_doc, "document-id")
+ attributes["created_at"] = Time.zone.parse(meta_field_value(xml_doc, "created-date"))
+ attributes["updated_at"] = Time.zone.parse(meta_field_value(xml_doc, "modified-date"))
+ attributes["purchid"] = string_or_nil(xml_doc, "PurchaserCode")
+ attributes["ownershipsch"] = unsafe_string_as_integer(xml_doc, "Ownership")
+ attributes["othtype"] = string_or_nil(xml_doc, "Q38OtherSale")
+ attributes["jointmore"] = unsafe_string_as_integer(xml_doc, "JointMore")
+ attributes["jointpur"] = unsafe_string_as_integer(xml_doc, "joint")
+ attributes["beds"] = safe_string_as_integer(xml_doc, "Q11Bedrooms")
+ attributes["companybuy"] = unsafe_string_as_integer(xml_doc, "company") if attributes["ownershipsch"] == 3
+ attributes["hhmemb"] = safe_string_as_integer(xml_doc, "HHMEMB")
+ (1..6).each do |index|
+ attributes["age#{index}"] = safe_string_as_integer(xml_doc, "P#{index}Age")
+ attributes["sex#{index}"] = sex(xml_doc, index)
+ attributes["ecstat#{index}"] = unsafe_string_as_integer(xml_doc, "P#{index}Eco")
+ attributes["age#{index}_known"] = age_known(xml_doc, index, attributes["hhmemb"], attributes["age#{index}"])
+ end
+ (2..6).each do |index|
+ attributes["relat#{index}"] = relat(xml_doc, index)
+ attributes["details_known_#{index}"] = details_known(index, attributes)
+ end
+ attributes["national"] = unsafe_string_as_integer(xml_doc, "P1Nat")
+ attributes["othernational"] = nil
+ attributes["ethnic"] = unsafe_string_as_integer(xml_doc, "P1Eth")
+ attributes["ethnic_group"] = ethnic_group(attributes["ethnic"])
+ attributes["buy1livein"] = unsafe_string_as_integer(xml_doc, "LiveInBuyer1")
+ attributes["buylivein"] = unsafe_string_as_integer(xml_doc, "LiveInBuyer") if attributes["ownershipsch"] == 3
+ attributes["builtype"] = unsafe_string_as_integer(xml_doc, "Q13BuildingType")
+ attributes["proptype"] = unsafe_string_as_integer(xml_doc, "Q12PropertyType")
+ attributes["privacynotice"] = 1 if string_or_nil(xml_doc, "Qdp") == "Yes"
+ attributes["noint"] = unsafe_string_as_integer(xml_doc, "PartAPurchaser")
+ attributes["buy2livein"] = unsafe_string_as_integer(xml_doc, "LiveInBuyer2")
+ attributes["wheel"] = unsafe_string_as_integer(xml_doc, "Q10Wheelchair")
+ attributes["hholdcount"] = safe_string_as_integer(xml_doc, "LiveInOther")
+ attributes["la"] = string_or_nil(xml_doc, "Q14ONSLACode")
+ attributes["income1"] = safe_string_as_integer(xml_doc, "Q2Person1Income")
+ attributes["income1nk"] = income_known(unsafe_string_as_integer(xml_doc, "P1IncKnown"))
+ attributes["inc1mort"] = unsafe_string_as_integer(xml_doc, "Q2Person1Mortgage")
+ attributes["income2"] = safe_string_as_integer(xml_doc, "Q2Person2Income")
+ attributes["income2nk"] = income_known(unsafe_string_as_integer(xml_doc, "P2IncKnown"))
+ attributes["savings"] = safe_string_as_integer(xml_doc, "Q3Savings")
+ attributes["savingsnk"] = savings_known(xml_doc)
+ attributes["prevown"] = unsafe_string_as_integer(xml_doc, "Q4PrevOwnedProperty")
+ attributes["mortgage"] = safe_string_as_decimal(xml_doc, "CALCMORT")
+ attributes["inc2mort"] = unsafe_string_as_integer(xml_doc, "Q2Person2MortApplication")
+ attributes["hb"] = unsafe_string_as_integer(xml_doc, "Q2a")
+ attributes["frombeds"] = safe_string_as_integer(xml_doc, "Q20Bedrooms")
+ attributes["staircase"] = unsafe_string_as_integer(xml_doc, "Q17aStaircase")
+ attributes["stairbought"] = safe_string_as_integer(xml_doc, "PercentBought")
+ attributes["stairowned"] = safe_string_as_integer(xml_doc, "PercentOwns") if attributes["staircase"] == 1
+ attributes["mrent"] = safe_string_as_decimal(xml_doc, "Q28MonthlyRent")
+ attributes["exdate"] = compose_date(xml_doc, "EXDAY", "EXMONTH", "EXYEAR")
+ attributes["exday"] = safe_string_as_integer(xml_doc, "EXDAY")
+ attributes["exmonth"] = safe_string_as_integer(xml_doc, "EXMONTH")
+ attributes["exyear"] = safe_string_as_integer(xml_doc, "EXYEAR")
+ attributes["resale"] = unsafe_string_as_integer(xml_doc, "Q17Resale")
+ attributes["deposit"] = deposit(xml_doc, attributes)
+ attributes["cashdis"] = safe_string_as_decimal(xml_doc, "Q27SocialHomeBuy")
+ attributes["disabled"] = unsafe_string_as_integer(xml_doc, "Disability")
+ attributes["lanomagr"] = unsafe_string_as_integer(xml_doc, "Q19Rehoused")
+ attributes["value"] = purchase_price(xml_doc, attributes)
+ attributes["equity"] = safe_string_as_decimal(xml_doc, "Q23Equity")
+ attributes["discount"] = safe_string_as_decimal(xml_doc, "Q33Discount")
+ attributes["grant"] = safe_string_as_decimal(xml_doc, "Q32Reductions")
+ attributes["pregyrha"] = 1 if string_or_nil(xml_doc, "PREGYRHA") == "Yes"
+ attributes["pregla"] = 1 if string_or_nil(xml_doc, "PREGLA") == "Yes"
+ attributes["pregghb"] = 1 if string_or_nil(xml_doc, "PREGHBA") == "Yes"
+ attributes["pregother"] = 1 if string_or_nil(xml_doc, "PREGOTHER") == "Yes"
+ attributes["ppostcode_full"] = compose_postcode(xml_doc, "PPOSTC1", "PPOSTC2")
+ attributes["prevloc"] = string_or_nil(xml_doc, "Q7ONSLACode")
+ attributes["ppcodenk"] = previous_postcode_known(xml_doc, attributes["ppostcode_full"], attributes["prevloc"]) # Q7UNKNOWNPOSTCODE check mapping
+ attributes["ppostc1"] = string_or_nil(xml_doc, "PPOSTC1")
+ attributes["ppostc2"] = string_or_nil(xml_doc, "PPOSTC2")
+ attributes["previous_la_known"] = nil
+ attributes["hhregres"] = unsafe_string_as_integer(xml_doc, "ArmedF")
+ attributes["hhregresstill"] = still_serving(xml_doc)
+ attributes["proplen"] = safe_string_as_integer(xml_doc, "Q16aProplen2")
+ attributes["mscharge"] = monthly_charges(xml_doc, attributes)
+ attributes["mscharge_known"] = 1 if attributes["mscharge"].present?
+ attributes["prevten"] = unsafe_string_as_integer(xml_doc, "Q6PrevTenure")
+ attributes["mortgageused"] = unsafe_string_as_integer(xml_doc, "MORTGAGEUSED")
+ attributes["wchair"] = unsafe_string_as_integer(xml_doc, "Q15Wheelchair")
+ attributes["armedforcesspouse"] = unsafe_string_as_integer(xml_doc, "ARMEDFORCESSPOUSE")
+ attributes["hodate"] = compose_date(xml_doc, "HODAY", "HOMONTH", "HOYEAR")
+ attributes["hoday"] = safe_string_as_integer(xml_doc, "HODAY")
+ attributes["homonth"] = safe_string_as_integer(xml_doc, "HOMONTH")
+ attributes["hoyear"] = safe_string_as_integer(xml_doc, "HOYEAR")
+ attributes["fromprop"] = unsafe_string_as_integer(xml_doc, "Q21PropertyType")
+ attributes["socprevten"] = unsafe_string_as_integer(xml_doc, "PrevRentType")
+ attributes["mortgagelender"] = mortgage_lender(xml_doc, attributes)
+ attributes["mortgagelenderother"] = mortgage_lender_other(xml_doc, attributes)
+ attributes["mortlen"] = mortgage_length(xml_doc, attributes)
+ attributes["extrabor"] = borrowing(xml_doc, attributes)
+ attributes["totadult"] = safe_string_as_integer(xml_doc, "TOTADULT") # would get overridden
+ attributes["totchild"] = safe_string_as_integer(xml_doc, "TOTCHILD") # would get overridden
+ attributes["hhtype"] = unsafe_string_as_integer(xml_doc, "HHTYPE")
+ attributes["pcode1"] = string_or_nil(xml_doc, "PCODE1")
+ attributes["pcode2"] = string_or_nil(xml_doc, "PCODE2")
+ attributes["postcode_full"] = compose_postcode(xml_doc, "PCODE1", "PCODE2")
+ attributes["pcodenk"] = 0 if attributes["postcode_full"].present? # known if given
+ attributes["soctenant"] = soctenant(attributes)
+ attributes["ethnic_group2"] = nil # 23/24 variable
+ attributes["ethnicbuy2"] = nil # 23/24 variable
+ attributes["prevshared"] = nil # 23/24 variable
+ attributes["staircasesale"] = nil # 23/24 variable
+
+ # Required for our form invalidated questions (not present in import)
+ attributes["previous_la_known"] = 1 if attributes["prevloc"].present? && attributes["ppostcode_full"].blank?
+ attributes["la_known"] = 1 if attributes["la"].present? && attributes["postcode_full"].blank?
+
+ # Sets the log creator
+ owner_id = meta_field_value(xml_doc, "owner-user-id").strip
+ if owner_id.present?
+ user = LegacyUser.find_by(old_user_id: owner_id)&.user
+ @logger.warn "Missing user! We expected to find a legacy user with old_user_id #{owner_id}" unless user
+
+ attributes["created_by"] = user
+ end
+
+ set_default_values(attributes) if previous_status.include?("submitted")
+ sales_log = save_sales_log(attributes, previous_status)
+ compute_differences(sales_log, attributes)
+ check_status_completed(sales_log, previous_status) unless @logs_overridden.include?(sales_log.old_id)
+ end
+
+ def save_sales_log(attributes, previous_status)
+ sales_log = SalesLog.new(attributes)
+ begin
+ sales_log.save!
+ sales_log
+ rescue ActiveRecord::RecordNotUnique
+ legacy_id = attributes["old_id"]
+ record = SalesLog.find_by(old_id: legacy_id)
+ @logger.info "Updating sales log #{record.id} with legacy ID #{legacy_id}"
+ record.update!(attributes)
+ record
+ rescue ActiveRecord::RecordInvalid => e
+ rescue_validation_or_raise(sales_log, attributes, previous_status, e)
+ end
+ end
+
+ def rescue_validation_or_raise(sales_log, _attributes, _previous_status, exception)
+ @logger.error("Log #{sales_log.old_id}: Failed to import")
+ raise exception
+ end
+
+ def compute_differences(sales_log, attributes)
+ differences = []
+ attributes.each do |key, value|
+ sales_log_value = sales_log.send(key.to_sym)
+ next if fields_not_present_in_softwire_data.include?(key)
+
+ if value != sales_log_value
+ differences.push("#{key} #{value.inspect} #{sales_log_value.inspect}")
+ end
+ end
+ @logger.warn "Differences found when saving log #{sales_log.old_id}: #{differences}" unless differences.empty?
+ end
+
+ def fields_not_present_in_softwire_data
+ %w[created_by
+ income1_value_check
+ mortgage_value_check
+ savings_value_check
+ deposit_value_check
+ wheel_value_check
+ retirement_value_check
+ extrabor_value_check
+ deposit_and_mortgage_value_check
+ shared_ownership_deposit_value_check
+ grant_value_check
+ value_value_check
+ old_persons_shared_ownership_value_check
+ staircase_bought_value_check
+ monthly_charges_value_check
+ hodate_check
+ saledate_check]
+ end
+
+ def check_status_completed(sales_log, previous_status)
+ if previous_status.include?("submitted") && sales_log.status != "completed"
+ @logger.warn "sales log #{sales_log.id} is not completed. The following answers are missing: #{missing_answers(sales_log).join(', ')}"
+ @logger.warn "sales log with old id:#{sales_log.old_id} is incomplete but status should be complete"
+ @logs_with_discrepancies << sales_log.old_id
+ end
+ end
+
+ def age_known(_xml_doc, index, hhmemb, age)
+ return nil if hhmemb.present? && index > hhmemb
+
+ return 0 if age.present?
+ end
+
+ def details_known(index, attributes)
+ return nil if attributes["hhmemb"].nil? || index > attributes["hhmemb"]
+ return nil if attributes["jointpur"] == 1 && index == 2
+
+ if attributes["age#{index}_known"] != 0 &&
+ attributes["sex#{index}"] == "R" &&
+ attributes["relat#{index}"] == "R" &&
+ attributes["ecstat#{index}"] == 10
+ 2 # No
+ else
+ 1 # Yes
+ end
+ end
+
+ MORTGAGE_LENDER_OPTIONS = {
+ "atom bank" => 1,
+ "barclays bank plc" => 2,
+ "bath building society" => 3,
+ "buckinghamshire building society" => 4,
+ "cambridge building society" => 5,
+ "coventry building society" => 6,
+ "cumberland building society" => 7,
+ "darlington building society" => 8,
+ "dudley building society" => 9,
+ "ecology building society" => 10,
+ "halifax" => 11,
+ "hanley economic building society" => 12,
+ "hinckley and rugby building society" => 13,
+ "holmesdale building society" => 14,
+ "ipswich building society" => 15,
+ "leeds building society" => 16,
+ "lloyds bank" => 17,
+ "mansfield building society" => 18,
+ "market harborough building society" => 19,
+ "melton mowbray building society" => 20,
+ "nationwide building society" => 21,
+ "natwest" => 22,
+ "nedbank private wealth" => 23,
+ "newbury building society" => 24,
+ "oneSavings bank" => 25,
+ "parity trust" => 26,
+ "penrith building society" => 27,
+ "pepper homeloans" => 28,
+ "royal bank of scotland" => 29,
+ "santander" => 30,
+ "skipton building society" => 31,
+ "teachers building society" => 32,
+ "the co-operative bank" => 33,
+ "tipton & coseley building society" => 34,
+ "tss" => 35,
+ "ulster bank" => 36,
+ "virgin money" => 37,
+ "west bromwich building society" => 38,
+ "yorkshire building society" => 39,
+ "other" => 40,
+ }.freeze
+
+ # this comes through as a string, need to map to a corresponding integer
+ def mortgage_lender(xml_doc, attributes)
+ lender = case attributes["ownershipsch"]
+ when 1
+ string_or_nil(xml_doc, "Q24aMortgageLender")
+ when 2
+ string_or_nil(xml_doc, "Q34a")
+ when 3
+ string_or_nil(xml_doc, "Q41aMortgageLender")
+ end
+ return if lender.blank?
+
+ MORTGAGE_LENDER_OPTIONS[lender.downcase] || MORTGAGE_LENDER_OPTIONS["other"]
+ end
+
+ def mortgage_lender_other(xml_doc, attributes)
+ return unless attributes["mortgagelender"] == MORTGAGE_LENDER_OPTIONS["other"]
+
+ case attributes["ownershipsch"]
+ when 1
+ string_or_nil(xml_doc, "Q24aMortgageLender")
+ when 2
+ string_or_nil(xml_doc, "Q34a")
+ when 3
+ string_or_nil(xml_doc, "Q41aMortgageLender")
+ end
+ end
+
+ def mortgage_length(xml_doc, attributes)
+ case attributes["ownershipsch"]
+ when 1
+ unsafe_string_as_integer(xml_doc, "Q24b")
+ when 2
+ unsafe_string_as_integer(xml_doc, "Q34b")
+ when 3
+ unsafe_string_as_integer(xml_doc, "Q41b")
+ end
+ end
+
+ def savings_known(xml_doc)
+ case unsafe_string_as_integer(xml_doc, "savingsKnown")
+ when 1 # known
+ 0
+ when 2 # unknown
+ 1
+ end
+ end
+
+ def soctenant(attributes)
+ return nil unless attributes["ownershipsch"] == 1
+
+ if attributes["frombeds"].blank? && attributes["fromprop"].blank? && attributes["socprevten"].blank?
+ 2
+ else
+ 1
+ end
+ # NO (2) if FROMBEDS, FROMPROP and socprevten are blank, and YES(1) if they are completed
+ end
+
+ def still_serving(xml_doc)
+ case unsafe_string_as_integer(xml_doc, "LeftArmedF")
+ when 4
+ 4
+ when 5, 6
+ 5
+ end
+ end
+
+ def income_known(value)
+ case value
+ when 1 # known
+ 0
+ when 2 # unknown
+ 1
+ end
+ end
+
+ def borrowing(xml_doc, attributes)
+ case attributes["ownershipsch"]
+ when 1
+ unsafe_string_as_integer(xml_doc, "Q25Borrowing")
+ when 2
+ unsafe_string_as_integer(xml_doc, "Q35Borrowing")
+ when 3
+ unsafe_string_as_integer(xml_doc, "Q42Borrowing")
+ end
+ end
+
+ def purchase_price(xml_doc, attributes)
+ case attributes["ownershipsch"]
+ when 1
+ safe_string_as_decimal(xml_doc, "Q22PurchasePrice")
+ when 2
+ safe_string_as_decimal(xml_doc, "Q31PurchasePrice")
+ when 3
+ safe_string_as_decimal(xml_doc, "Q40PurchasePrice")
+ end
+ end
+
+ def deposit(xml_doc, attributes)
+ case attributes["ownershipsch"]
+ when 1
+ safe_string_as_decimal(xml_doc, "Q26CashDeposit")
+ when 2
+ safe_string_as_decimal(xml_doc, "Q36CashDeposit")
+ when 3
+ safe_string_as_decimal(xml_doc, "Q43CashDeposit")
+ end
+ end
+
+ def monthly_charges(xml_doc, attributes)
+ safe_string_as_decimal(xml_doc, "Q29MonthlyCharges")
+ case attributes["ownershipsch"]
+ when 1
+ safe_string_as_decimal(xml_doc, "Q29MonthlyCharges")
+ when 2
+ safe_string_as_decimal(xml_doc, "Q37MonthlyCharges")
+ end
+ end
+
+ def set_default_values(attributes)
+ attributes["mscharge_known"] ||= 0 if attributes["ownershipsch"] == 3
+ attributes["mscharge"] ||= 0 if attributes["ownershipsch"] == 3
+ attributes["armedforcesspouse"] ||= 7
+ attributes["hhregres"] ||= 8
+ attributes["disabled"] ||= 3
+ attributes["wheel"] ||= 3
+ attributes["hb"] ||= 4
+ attributes["prevown"] ||= 3
+ attributes["savingsnk"] ||= attributes["savings"].present? ? 0 : 1
+ # attributes["noint"] = 1 # not interviewed
+
+ # buyer 1 characteristics
+ attributes["age1_known"] ||= 1
+ attributes["sex1"] ||= "R"
+ attributes["ethnic_group"] ||= 17
+ attributes["ethnic"] ||= 17
+ attributes["national"] ||= 13
+ attributes["ecstat1"] ||= 10
+ attributes["income1nk"] ||= attributes["income1"].present? ? 0 : 1
+ attributes["hholdcount"] ||= default_household_count(attributes) # just for testing, might need to change
+
+ # buyer 2 characteristics
+ if attributes["jointpur"] == 1
+ attributes["age2_known"] ||= 1
+ attributes["sex2"] ||= "R"
+ attributes["ecstat2"] ||= 10
+ attributes["income2nk"] ||= attributes["income2"].present? ? 0 : 1
+ end
+
+ # other household members characteristics
+ (2..attributes["hhmemb"]).each do |index|
+ attributes["age#{index}_known"] ||= 1
+ attributes["sex#{index}"] ||= "R"
+ attributes["ecstat#{index}"] ||= 10
+ attributes["relat#{index}"] ||= "R"
+ end
+ end
+
+ def missing_answers(sales_log)
+ applicable_questions = sales_log.form.subsections.map { |s| s.applicable_questions(sales_log) }.flatten
+ applicable_questions.filter { |q| q.unanswered?(sales_log) }.map(&:id)
+ end
+
+ # just for testing, logic might need to change
+ def default_household_count(attributes)
+ return 0 if attributes["hhmemb"].zero? || attributes["hhmemb"].blank?
+
+ attributes["jointpur"] == 1 ? attributes["hhmemb"] - 2 : attributes["hhmemb"] - 1
+ end
+ end
+end
diff --git a/db/migrate/20230215112932_add_old_id_to_sales_logs.rb b/db/migrate/20230215112932_add_old_id_to_sales_logs.rb
new file mode 100644
index 000000000..cf61a968c
--- /dev/null
+++ b/db/migrate/20230215112932_add_old_id_to_sales_logs.rb
@@ -0,0 +1,8 @@
+class AddOldIdToSalesLogs < ActiveRecord::Migration[7.0]
+ def change
+ change_table :sales_logs, bulk: true do |t|
+ t.column :old_id, :string
+ end
+ add_index :sales_logs, :old_id, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1747fa6c5..d1668b738 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_02_13_140932) do
+ActiveRecord::Schema[7.0].define(version: 2023_02_15_112932) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -522,16 +522,18 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_13_140932) do
t.integer "old_persons_shared_ownership_value_check"
t.integer "staircase_bought_value_check"
t.integer "monthly_charges_value_check"
+ t.integer "saledate_check"
t.integer "details_known_5"
t.integer "details_known_6"
- t.integer "saledate_check"
- t.integer "prevshared"
- t.integer "staircasesale"
t.integer "ethnic_group2"
t.integer "ethnicbuy2"
t.integer "proplen_asked"
+ t.integer "prevshared"
+ t.integer "staircasesale"
+ t.string "old_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"
+ t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true
t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"
t.index ["updated_by_id"], name: "index_sales_logs_on_updated_by_id"
end
diff --git a/lib/tasks/data_import.rake b/lib/tasks/data_import.rake
index 0b3881388..0dede82a3 100644
--- a/lib/tasks/data_import.rake
+++ b/lib/tasks/data_import.rake
@@ -22,6 +22,8 @@ namespace :core do
Imports::OrganisationRentPeriodImportService.new(storage_service).create_organisation_rent_periods(path)
when "lettings-logs"
Imports::LettingsLogsImportService.new(storage_service).create_logs(path)
+ when "sales-logs"
+ Imports::SalesLogsImportService.new(storage_service).create_logs(path)
else
raise "Type #{type} is not supported by data_import"
end
diff --git a/spec/fixtures/imports/sales_logs/discounted_ownership_sales_log.xml b/spec/fixtures/imports/sales_logs/discounted_ownership_sales_log.xml
new file mode 100644
index 000000000..575f93887
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/discounted_ownership_sales_log.xml
@@ -0,0 +1,351 @@
+
+
+ 2022-CORE-Sales
+ discounted_ownership_sales_log
+ c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa
+ 7c5bd5fb549c09z2c55d9cb90d7ba84927e64618
+ 7c5bd5fb549c09z2c55d9cb90d7ba84927e64618
+ 2023-02-21T11:54:51.786722Z
+ 2023-02-22T10:59:45.88188Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+
+
+
+ 2023-02-01
+ Discount ownership example
+ 2 Yes - a discount ownership scheme
+
+ 14 Preserved Right to Buy (PRTB)
+
+
+
+
+ 2 No
+
+ 1 No
+
+
+ 3
+ 3 House
+ 1 Purpose built
+ SW1A 1AA
+
+
+ Cheltenham
+ E07000078
+ 3 Don’t know
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 134750
+ 1
+ 0
+ 0
+ 0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 1
+ 0
+ 1
+ 0
+ 0
+ 0
+ 1
+ 2
+ 2023
+
+
+
+
+
+
+ GL51
+ 9EX
+
+
+ GL51
+ 9EX
+ 1
+
+
+
+
+ 3 Private tenant
+ GL51 9EX
+
+ Cheltenham
+ E07000078
+
+
+ Yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 275000
+
+ 51
+ 1 Yes
+ 134750
+ Halifax
+
+
+ 33
+ 2 No
+ 0
+ 0.00
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 0
+
+
+ 9 = other
+
+
+
+
+ 14 Preserved Right to Buy (PRTB)
+
+
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml b/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml
new file mode 100644
index 000000000..4bbdc9981
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/outright_sale_sales_log.xml
@@ -0,0 +1,333 @@
+
+
+ 2022-CORE-Sales
+ outright_sale_sales_log
+ c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 2023-02-21T12:09:45.809134Z
+ 2023-02-22T10:59:01.709949Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+ Yes
+ 2023-01-16
+ Outright ownership example
+ 3 No - this is an outright or other sale
+
+
+ 10 Outright
+
+ 2 No
+ 1 Yes
+ 2 No
+
+ 2 Yes
+
+
+ 1
+ 1 Flat or maisonette
+ 1 Purpose built
+ SW1A 1AA
+ Westminster
+ E09000033
+ 2 No
+
+
+ 75
+ Female
+ 5 Retired
+ 1 White: English/Scottish/Welsh/Northern Irish/British
+ 18 United Kingdom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 2
+ 0
+ 0
+ 0
+ 0
+
+
+
+
+
+
+
+ B
+
+
+
+ B
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 0
+ 1
+ 1
+ 0
+ 0
+ 0
+ 16
+ 1
+ 2023
+
+
+
+
+
+
+ B95
+ 5HZ
+ SW1A
+ 1AA
+
+
+
+ 3 Private tenant
+ B95 5HZ
+
+ Stratford-on-Avon
+ E07000221
+
+
+ Yes
+
+
+
+
+ 7 No
+
+
+ 2 No
+ 2 No
+
+
+ 2 No
+
+
+
+ 2 No
+
+
+
+ 4 Don’t know
+
+
+ 2 No
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 300000
+ 2 No
+
+
+
+
+ 300000
+
+
+ 1
+ 1
+ 0
+
+
+ 1 = 1 elder
+
+
+ 10 Outright
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 1
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml
new file mode 100644
index 000000000..6e0c11174
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log.xml
@@ -0,0 +1,333 @@
+
+
+ 2022-CORE-Sales
+ shared_ownership_sales_log
+ c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 2023-02-21T11:48:28.255968Z
+ 2023-02-22T11:00:06.575832Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+ Yes
+ 2023-01-17
+ Shared ownership example
+ 1 Yes - a shared ownership scheme
+ 2 Shared Ownership
+
+
+
+ 2 No
+ 1 Yes
+ 2 No
+
+ 2 Yes
+
+
+ 2
+ 1 Flat or maisonette
+ 1 Purpose built
+ SW1A 1AA
+ Westminster
+ E09000033
+ 3 Don’t know
+
+
+ 30
+ Male
+ 1 Full Time - 30 hours or more a week
+ 2 White: Irish
+ 18 United Kingdom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 76000
+ 1
+ 47000
+ 0
+ 235000
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 1
+ 2
+ 3
+ 17
+ 1
+ 2023
+ 6
+ 9
+ 2022
+ 8
+ 1
+ 2023
+ SW14
+ 7QP
+ SW1A
+ 1AA
+
+
+
+ 2 Private registered provider (PRP) or housing association tenant
+ SW14 7QP
+
+ Richmond-upon-Thames
+ E09000027
+ Yes
+
+
+
+
+
+
+ 8 Don’t know
+
+
+ 2 No
+ 2 No
+
+
+ 1 Yes
+ 47000
+ 1 Yes
+
+
+
+ 4 Don’t know
+ 1 Yes
+ 89000
+ 1 Yes
+
+
+
+
+ 1
+ 2 No
+
+ 30
+ 2 No
+ 2023-01-08
+ 2022-09-06
+ 2 No
+
+
+
+ 550000
+ 30
+ 1 Yes
+ 76000
+ Nationwide
+ 33
+ 2 No
+ 89000
+
+ 912.00
+ 134.24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 0
+
+
+ 3 = 1 adult
+
+
+ 2 Shared Ownership
+
+
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 1
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log2.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log2.xml
new file mode 100644
index 000000000..6334674a2
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log2.xml
@@ -0,0 +1,333 @@
+
+
+ 2022-CORE-Sales
+ shared_ownership_sales_log2
+ c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 2023-02-21T11:48:28.255968Z
+ 2023-02-22T11:00:06.575832Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+ Yes
+ 2023-01-17
+ Shared ownership example
+ 1 Yes - a shared ownership scheme
+ 2 Shared Ownership
+
+
+
+ 2 No
+ 1 Yes
+ 2 No
+
+ 2 Yes
+
+
+ 2
+ 1 Flat or maisonette
+ 1 Purpose built
+ SW1A 1AA
+ Westminster
+ E09000033
+ 3 Don’t know
+
+
+ 30
+ Male
+ 1 Full Time - 30 hours or more a week
+ 2 White: Irish
+ 18 United Kingdom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 76000
+ 1
+ 47000
+ 0
+ 235000
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 1
+ 2
+ 3
+ 17
+ 1
+ 2023
+ 6
+ 9
+ 2022
+ 8
+ 1
+ 2023
+ SW14
+ 7QP
+ SW1A
+ 1AA
+
+
+
+ 2 Private registered provider (PRP) or housing association tenant
+ SW14 7QP
+
+ Richmond-upon-Thames
+ E09000027
+ Yes
+
+
+
+
+
+
+ 8 Don’t know
+
+
+ 2 No
+ 2 No
+
+
+ 1 Yes
+ 47000
+ 1 Yes
+
+
+
+ 4 Don’t know
+ 1 Yes
+ 89000
+ 1 Yes
+
+
+
+
+ 1
+ 2 No
+
+ 30
+ 2 No
+ 2023-01-08
+ 2022-09-06
+ 2 No
+
+
+
+ 550000
+ 30
+ 1 Yes
+ 76000
+ Nationwide
+ 33
+ 2 No
+ 89000
+
+ 912.00
+ 134.24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 0
+
+
+ 3 = 1 adult
+
+
+ 2 Shared Ownership
+
+
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 1
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
new file mode 100644
index 000000000..b5dabde97
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log3.xml
@@ -0,0 +1,333 @@
+
+
+ 2022-CORE-Sales
+ shared_ownership_sales_log3
+ c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 2023-02-21T11:48:28.255968Z
+ 2023-02-22T11:00:06.575832Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+ Yes
+ 2023-01-17
+ Shared ownership example
+ 1 Yes - a shared ownership scheme
+ 2 Shared Ownership
+
+
+
+ 2 No
+ 1 Yes
+ 2 No
+
+ 2 Yes
+
+
+ 2
+ 1 Flat or maisonette
+ 1 Purpose built
+ SW1A 1AA
+ Westminster
+ E09000033
+ 3 Don’t know
+
+
+ 30
+ Male
+ 1 Full Time - 30 hours or more a week
+ 2 White: Irish
+ 18 United Kingdom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 76000
+ 1
+ 47000
+ 0
+ 235000
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 1
+ 2
+ 3
+ 17
+ 1
+ 2023
+ 6
+ 9
+ 2022
+ 8
+ 1
+ 2023
+ SW14
+ 7QP
+ SW1A
+ 1AA
+
+
+
+ 2 Private registered provider (PRP) or housing association tenant
+ SW14 7QP
+
+ Richmond-upon-Thames
+ E09000027
+ Yes
+
+
+
+
+
+
+ 8 Don’t know
+
+
+ 2 No
+ 2 No
+
+
+ 1 Yes
+ 47000
+
+
+
+
+ 4 Don’t know
+ 1 Yes
+ 89000
+ 1 Yes
+
+
+
+
+ 1
+ 2 No
+
+ 30
+ 2 No
+ 2023-01-08
+ 2022-09-06
+ 2 No
+
+
+
+ 550000
+ 30
+ 1 Yes
+ 76000
+ Nationwide
+ 33
+ 2 No
+ 89000
+
+ 912.00
+ 134.24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 0
+
+
+ 3 = 1 adult
+
+
+ 2 Shared Ownership
+
+
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 1
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/fixtures/imports/sales_logs/shared_ownership_sales_log4.xml b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log4.xml
new file mode 100644
index 000000000..98342cf3c
--- /dev/null
+++ b/spec/fixtures/imports/sales_logs/shared_ownership_sales_log4.xml
@@ -0,0 +1,333 @@
+
+
+ 2022-CORE-Sales
+ shared_ownership_sales_log4
+ e29c492473446dca4d50224f2bb7cf965a261d6f
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 7c5bd5fb549c09a2c55d7cb90d7ba84927e64618
+ 2023-02-21T11:48:28.255968Z
+ 2023-02-22T11:00:06.575832Z
+ submitted-valid
+ 2022
+ Manual Entry
+
+
+
+
+ Yes
+ 2023-01-17
+ Shared ownership example
+ 1 Yes - a shared ownership scheme
+ 2 Shared Ownership
+
+
+
+ 2 No
+ 1 Yes
+ 2 No
+
+ 2 Yes
+
+
+ 2
+ 1 Flat or maisonette
+ 1 Purpose built
+ SW1A 1AA
+ Westminster
+ E09000033
+ 3 Don’t know
+
+
+ 30
+ Male
+ 1 Full Time - 30 hours or more a week
+ 2 White: Irish
+ 18 United Kingdom
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1 Yes
+
+
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+
+
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 76000
+ 1
+ 47000
+ 0
+ 235000
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 0
+ 0
+ 1
+ 1
+ 2
+ 3
+ 17
+ 1
+ 2023
+ 6
+ 9
+ 2022
+ 8
+ 1
+ 2023
+ SW14
+ 7QP
+ SW1A
+ 1AA
+
+
+
+ 2 Private registered provider (PRP) or housing association tenant
+ SW14 7QP
+
+ Richmond-upon-Thames
+ E09000027
+ Yes
+
+
+
+
+
+
+ 8 Don’t know
+
+
+ 2 No
+ 2 No
+
+
+ 1 Yes
+ 47000
+ 1 Yes
+
+
+
+ 4 Don’t know
+ 1 Yes
+ 89000
+ 1 Yes
+
+
+
+
+ 1
+ 2 No
+
+ 30
+ 2 No
+ 2023-01-08
+ 2022-09-06
+ 2 No
+
+
+
+ 550000
+ 30
+ 1 Yes
+ 76000
+ Nationwide
+ 33
+ 2 No
+ 89000
+
+ 912.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 0
+
+
+ 3 = 1 adult
+
+
+ 2 Shared Ownership
+
+
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 0
+ 1
+ 0
+ 0
+ 0
+ 0
+ 1
+
+
+
+
+ E12000007
+ 1
+ 1 Test
+ 655
+
+
diff --git a/spec/lib/tasks/data_import_spec.rb b/spec/lib/tasks/data_import_spec.rb
index ad1887d16..462f2740e 100644
--- a/spec/lib/tasks/data_import_spec.rb
+++ b/spec/lib/tasks/data_import_spec.rb
@@ -109,6 +109,24 @@ describe "rake core:data_import", type: :task do
end
end
+ context "when importing sales logs" do
+ let(:type) { "sales-logs" }
+ let(:import_service) { instance_double(Imports::SalesLogsImportService) }
+ let(:fixture_path) { "spec/fixtures/imports/sales_logs" }
+
+ before do
+ allow(Imports::SalesLogsImportService).to receive(:new).and_return(import_service)
+ end
+
+ it "creates sales logs from the given XML file" do
+ expect(Storage::S3Service).to receive(:new).with(paas_config_service, instance_name)
+ expect(Imports::SalesLogsImportService).to receive(:new).with(storage_service)
+ expect(import_service).to receive(:create_logs).with(fixture_path)
+
+ task.invoke(type, fixture_path)
+ end
+ end
+
context "when importing scheme data" do
let(:type) { "scheme" }
let(:import_service) { instance_double(Imports::SchemeImportService) }
diff --git a/spec/services/imports/sales_logs_import_service_spec.rb b/spec/services/imports/sales_logs_import_service_spec.rb
new file mode 100644
index 000000000..b556d3e68
--- /dev/null
+++ b/spec/services/imports/sales_logs_import_service_spec.rb
@@ -0,0 +1,595 @@
+require "rails_helper"
+
+RSpec.describe Imports::SalesLogsImportService do
+ subject(:sales_log_service) { described_class.new(storage_service, logger) }
+
+ let(:storage_service) { instance_double(Storage::S3Service) }
+ let(:logger) { instance_double(ActiveSupport::Logger) }
+
+ let(:fixture_directory) { "spec/fixtures/imports/sales_logs" }
+
+ let(:organisation) { FactoryBot.create(:organisation, old_visible_id: "1", provider_type: "PRP") }
+ let(:managing_organisation) { FactoryBot.create(:organisation, old_visible_id: "2", provider_type: "PRP") }
+ let(:remote_folder) { "sales_logs" }
+
+ def open_file(directory, filename)
+ File.open("#{directory}/#{filename}.xml")
+ end
+
+ before do
+ { "GL519EX" => "E07000078",
+ "SW1A2AA" => "E09000033",
+ "SW1A1AA" => "E09000033",
+ "SW147QP" => "E09000027",
+ "B955HZ" => "E07000221" }.each do |postcode, district_code|
+ WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/#{postcode}/).to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"#{district_code}\",\"codes\":{\"admin_district\":\"#{district_code}\"}}}", headers: {})
+ end
+
+ allow(Organisation).to receive(:find_by).and_return(nil)
+ allow(Organisation).to receive(:find_by).with(old_visible_id: organisation.old_visible_id).and_return(organisation)
+ allow(Organisation).to receive(:find_by).with(old_visible_id: managing_organisation.old_visible_id).and_return(managing_organisation)
+
+ # Created by users
+ FactoryBot.create(:user, old_user_id: "c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa", organisation:)
+ FactoryBot.create(:user, old_user_id: "e29c492473446dca4d50224f2bb7cf965a261d6f", organisation:)
+ end
+
+ context "when importing sales logs" do
+ before do
+ # Stub the S3 file listing and download
+ allow(storage_service).to receive(:list_files)
+ .and_return(%W[#{remote_folder}/shared_ownership_sales_log.xml #{remote_folder}/shared_ownership_sales_log2.xml #{remote_folder}/outright_sale_sales_log.xml #{remote_folder}/discounted_ownership_sales_log.xml])
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/shared_ownership_sales_log.xml")
+ .and_return(open_file(fixture_directory, "shared_ownership_sales_log"), open_file(fixture_directory, "shared_ownership_sales_log"))
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/shared_ownership_sales_log2.xml")
+ .and_return(open_file(fixture_directory, "shared_ownership_sales_log2"), open_file(fixture_directory, "shared_ownership_sales_log2"))
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/outright_sale_sales_log.xml")
+ .and_return(open_file(fixture_directory, "outright_sale_sales_log"), open_file(fixture_directory, "outright_sale_sales_log"))
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/discounted_ownership_sales_log.xml")
+ .and_return(open_file(fixture_directory, "discounted_ownership_sales_log"), open_file(fixture_directory, "discounted_ownership_sales_log"))
+ end
+
+ it "successfully creates all sales logs" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect(logger).not_to receive(:info)
+ expect { sales_log_service.create_logs(remote_folder) }
+ .to change(SalesLog, :count).by(4)
+ end
+
+ it "only updates existing sales logs" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect(logger).to receive(:info).with(/Updating sales log/).exactly(4).times
+ expect { 2.times { sales_log_service.create_logs(remote_folder) } }
+ .to change(SalesLog, :count).by(4)
+ end
+
+ context "when there are status discrepancies" do
+ let(:sales_log_file) { open_file(fixture_directory, "shared_ownership_sales_log3") }
+ let(:sales_log_xml) { Nokogiri::XML(sales_log_file) }
+
+ before do
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/shared_ownership_sales_log3.xml")
+ .and_return(open_file(fixture_directory, "shared_ownership_sales_log3"), open_file(fixture_directory, "shared_ownership_sales_log3"))
+ allow(storage_service).to receive(:get_file_io)
+ .with("#{remote_folder}/shared_ownership_sales_log4.xml")
+ .and_return(open_file(fixture_directory, "shared_ownership_sales_log4"), open_file(fixture_directory, "shared_ownership_sales_log4"))
+ end
+
+ it "the logger logs a warning with the sales log's old id/filename" do
+ expect(logger).to receive(:warn).with(/is not completed/).once
+ expect(logger).to receive(:warn).with(/sales log with old id:shared_ownership_sales_log3 is incomplete but status should be complete/).once
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "on completion the ids of all logs with status discrepancies are logged in a warning" do
+ allow(storage_service).to receive(:list_files)
+ .and_return(%W[#{remote_folder}/shared_ownership_sales_log3.xml #{remote_folder}/shared_ownership_sales_log4.xml])
+ expect(logger).to receive(:warn).with(/is not completed/).twice
+ expect(logger).to receive(:warn).with(/is incomplete but status should be complete/).twice
+ expect(logger).to receive(:warn).with(/The following sales logs had status discrepancies: \[shared_ownership_sales_log3, shared_ownership_sales_log4\]/)
+
+ sales_log_service.create_logs(remote_folder)
+ end
+ end
+ end
+
+ context "when importing a specific log" do
+ let(:sales_log_file) { open_file(fixture_directory, sales_log_id) }
+ let(:sales_log_xml) { Nokogiri::XML(sales_log_file) }
+
+ context "and the organisation legacy ID does not exist" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ before { sales_log_xml.at_xpath("//xmlns:OWNINGORGID").content = 99_999 }
+
+ it "raises an exception" do
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .to raise_error(RuntimeError, "Organisation not found with legacy ID 99999")
+ end
+ end
+
+ context "when the mortgage lender is set to an existing option" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q34a").content = "halifax"
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "correctly sets mortgage lender" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgagelender).to be(11)
+ end
+ end
+
+ context "when the mortgage lender is set to a non existing option" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q34a").content = "something else"
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "correctly sets mortgage lender and mortgage lender other" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.mortgagelender).to be(40)
+ expect(sales_log&.mortgagelenderother).to eq("something else")
+ end
+ end
+
+ context "with shared ownership type" do
+ let(:sales_log_id) { "shared_ownership_sales_log" }
+
+ it "successfully creates a completed shared ownership log" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect(logger).not_to receive(:info)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .to change(SalesLog, :count).by(1)
+ end
+ end
+
+ context "with discounted ownership type" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ it "successfully creates a completed discounted ownership log" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect(logger).not_to receive(:info)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .to change(SalesLog, :count).by(1)
+ end
+ end
+
+ context "with outright sale type" do
+ let(:sales_log_id) { "outright_sale_sales_log" }
+
+ it "successfully creates a completed outright sale log" do
+ expect(logger).not_to receive(:error)
+ expect(logger).not_to receive(:warn)
+ expect(logger).not_to receive(:info)
+ expect { sales_log_service.send(:create_log, sales_log_xml) }
+ .to change(SalesLog, :count).by(1)
+ end
+ end
+
+ context "when inferring default answers for completed sales logs" do
+ context "when the armedforcesspouse is not answered" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:ARMEDFORCESSPOUSE").content = ""
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets armedforcesspouse to don't know" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.armedforcesspouse).to be(7)
+ end
+ end
+
+ context "when the savings not known is not answered and savings is not given" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:savingsKnown").content = ""
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets savingsnk to not know" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.savingsnk).to be(1)
+ end
+ end
+
+ context "when the savings not known is not answered and savings is given" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:Q3Savings").content = "10000"
+ sales_log_xml.at_xpath("//xmlns:savingsKnown").content = ""
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets savingsnk to know" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.savingsnk).to be(0)
+ end
+ end
+
+ context "and it's an outright sale" do
+ let(:sales_log_id) { "outright_sale_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "infers mscharge_known as no" do
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log.mscharge_known).to eq(0)
+ end
+ end
+
+ context "when inferring age known" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:P1Age").content = ""
+ sales_log_xml.at_xpath("//xmlns:P2Age").content = ""
+ sales_log_xml.at_xpath("//xmlns:P3Age").content = "22"
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets age known to no if age not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.age1_known).to be(1) # unknown
+ expect(sales_log&.age2_known).to be(1) # unknown
+ end
+
+ it "sets age known to yes if age answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.age3_known).to be(0) # known
+ end
+ end
+
+ context "when inferring gender" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:P1Sex").content = ""
+ sales_log_xml.at_xpath("//xmlns:P2Sex").content = ""
+ sales_log_xml.at_xpath("//xmlns:P3Sex").content = "Female"
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets gender to prefers not to say if not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.sex1).to eq("R")
+ expect(sales_log&.sex2).to eq("R")
+ end
+
+ it "sets the gender correctly if answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.sex3).to eq("F")
+ end
+ end
+
+ context "when inferring ethnic group" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "1"
+ sales_log_xml.at_xpath("//xmlns:P1Eth").content = ""
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets ethnic group to prefers not to say if not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.ethnic_group).to eq(17)
+ end
+ end
+
+ context "when inferring nationality" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "1"
+ sales_log_xml.at_xpath("//xmlns:P1Nat").content = ""
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets nationality to prefers not to say if not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.national).to eq(13)
+ end
+ end
+
+ context "when inferring economic status" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:P1Eco").content = ""
+ sales_log_xml.at_xpath("//xmlns:P2Eco").content = ""
+ sales_log_xml.at_xpath("//xmlns:P3Eco").content = "3"
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets economic status to prefers not to say if not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.ecstat1).to eq(10)
+ expect(sales_log&.ecstat2).to eq(10)
+ end
+
+ it "sets the economic status correctly if answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.ecstat3).to eq(3)
+ end
+ end
+
+ context "when inferring relationship" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:P2Rel").content = ""
+ sales_log_xml.at_xpath("//xmlns:P3Rel").content = "Partner"
+ allow(logger).to receive(:warn).and_return(nil)
+
+ sales_log_service.send(:create_log, sales_log_xml)
+ end
+
+ it "sets relationship to prefers not to say if not answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.relat2).to eq("R")
+ end
+
+ it "sets the relationship correctly if answered" do
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.relat3).to eq("P")
+ end
+ end
+
+ context "when inferring armed forces" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets hhregres to don't know if not answered" do
+ sales_log_xml.at_xpath("//xmlns:ArmedF").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hhregres).to eq(8)
+ end
+
+ it "sets hhregres correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:ArmedF").content = "7 No"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hhregres).to eq(7)
+ end
+ end
+
+ context "when inferring disability" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets disabled to don't know if not answered" do
+ sales_log_xml.at_xpath("//xmlns:Disability").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.disabled).to eq(3)
+ end
+
+ it "sets disabled correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:Disability").content = "2 No"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.disabled).to eq(2)
+ end
+ end
+
+ context "when inferring wheelchair" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets wheel to don't know if not answered" do
+ sales_log_xml.at_xpath("//xmlns:Q10Wheelchair").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.wheel).to eq(3)
+ end
+
+ it "sets wheel correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:Q10Wheelchair").content = "2 No"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.wheel).to eq(2)
+ end
+ end
+
+ context "when inferring housing benefit" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets hb to don't know if not answered" do
+ sales_log_xml.at_xpath("//xmlns:Q2a").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hb).to eq(4)
+ end
+
+ it "sets hb correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:Q2a").content = "2 Housing Benefit"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hb).to eq(2)
+ end
+ end
+
+ context "when inferring income not known" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ sales_log_xml.at_xpath("//xmlns:joint").content = "1 Yes"
+ sales_log_xml.at_xpath("//xmlns:JointMore").content = "2 No"
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets income to not known if not answered and income is not given" do
+ sales_log_xml.at_xpath("//xmlns:P1IncKnown").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q2Person1Income").content = ""
+ sales_log_xml.at_xpath("//xmlns:P2IncKnown").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q2Person2Income").content = ""
+
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.income1nk).to eq(1)
+ expect(sales_log&.income2nk).to eq(1)
+ end
+
+ it "sets income to known if not answered but the income is given" do
+ sales_log_xml.at_xpath("//xmlns:P1IncKnown").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q2Person1Income").content = "30000"
+ sales_log_xml.at_xpath("//xmlns:P2IncKnown").content = ""
+ sales_log_xml.at_xpath("//xmlns:Q2Person2Income").content = "40000"
+
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.income1nk).to eq(0)
+ expect(sales_log&.income2nk).to eq(0)
+ end
+
+ it "sets income known correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:P1IncKnown").content = "1 Yes"
+ sales_log_xml.at_xpath("//xmlns:P2IncKnown").content = "2 No"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.income1nk).to eq(0)
+ expect(sales_log&.income2nk).to eq(1)
+ end
+ end
+
+ context "when inferring prevown" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets prevown to don't know if not answered" do
+ sales_log_xml.at_xpath("//xmlns:Q4PrevOwnedProperty").content = ""
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.prevown).to eq(3)
+ end
+
+ it "sets prevown correctly if answered" do
+ sales_log_xml.at_xpath("//xmlns:Q4PrevOwnedProperty").content = "2 No"
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.prevown).to eq(2)
+ end
+ end
+
+ context "when inferring household count" do
+ let(:sales_log_id) { "discounted_ownership_sales_log" }
+
+ before do
+ allow(logger).to receive(:warn).and_return(nil)
+ end
+
+ it "sets hholdcount to hhmemb - 1 if not answered and not joint purchase" do
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:joint").content = "2 No"
+ sales_log_xml.at_xpath("//xmlns:LiveInOther").content = ""
+
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hholdcount).to eq(2)
+ end
+
+ it "sets hholdcount to hhmemb - 2 if not answered and joint purchase" do
+ sales_log_xml.at_xpath("//xmlns:joint").content = "1 Yes"
+ sales_log_xml.at_xpath("//xmlns:JointMore").content = "2 No"
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "3"
+ sales_log_xml.at_xpath("//xmlns:LiveInOther").content = ""
+
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hholdcount).to eq(1)
+ end
+
+ it "sets hholdcount to 0 if HHMEMB is 0" do
+ sales_log_xml.at_xpath("//xmlns:joint").content = "1 Yes"
+ sales_log_xml.at_xpath("//xmlns:JointMore").content = "2 No"
+ sales_log_xml.at_xpath("//xmlns:HHMEMB").content = "0"
+ sales_log_xml.at_xpath("//xmlns:LiveInOther").content = ""
+
+ sales_log_service.send(:create_log, sales_log_xml)
+
+ sales_log = SalesLog.find_by(old_id: sales_log_id)
+ expect(sales_log&.hholdcount).to eq(0)
+ end
+ end
+ end
+ end
+end