Browse Source

CLDC-4173: add new building height question sales (#3186)

* CLDC-4173: add new buildheightclass db changes

* CLDC-4173: add new buildheightclass page and question

* CLDC-4173: add new buildheightclass bu and export updates

* CLDC-4173: update tests

* CLDC-4173: update var defs

* CLDC-4173: add sales csv export tests for 26 including buildheightclass

* CLDC-4173: add sales csv export tests for 25 and 26 including buildheightclass

* CLDC-4173: update sales export spec

* CLDC-4173: update sales export spec and make sales log csv service future-proof

* CLDC-4173: add missing sales log export fixtures and tests

* CLDC-4173: date refactoring in tests

* CLDC-4173: date refactoring in tests

* CLDC-4173: schema updates

* CLDC-4173: test date updates
pull/3205/head
Nat Dean-Lewis 3 weeks ago committed by GitHub
parent
commit
037f59122c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      app/helpers/bulk_upload/sales_log_to_csv.rb
  2. 17
      app/models/form/sales/pages/building_height_class.rb
  3. 17
      app/models/form/sales/questions/building_height_class.rb
  4. 1
      app/models/form/sales/subsections/property_information.rb
  5. 4
      app/services/bulk_upload/sales/year2026/csv_parser.rb
  6. 4
      app/services/bulk_upload/sales/year2026/row_parser.rb
  7. 2
      app/services/exports/sales_log_export_constants.rb
  8. 7
      config/locales/forms/2026/sales/property_information.en.yml
  9. 5
      db/migrate/20260219093257_add_buildheightclass_to_sales_logs.rb
  10. 9
      db/schema.rb
  11. 2
      spec/factories/sales_log.rb
  12. 154
      spec/fixtures/exports/sales_log_25_26.xml
  13. 161
      spec/fixtures/exports/sales_log_26_27.xml
  14. 18
      spec/fixtures/files/2026_27_sales_bulk_upload.csv
  15. 3
      spec/fixtures/files/sales_logs_csv_export_codes_26.csv
  16. 3
      spec/fixtures/files/sales_logs_csv_export_labels_26.csv
  17. 3
      spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv
  18. 3
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv
  19. 1
      spec/fixtures/variable_definitions/sales_download_26_27.csv
  20. 2
      spec/lib/tasks/log_variable_definitions_spec.rb
  21. 36
      spec/models/form/sales/pages/building_height_class_spec.rb
  22. 39
      spec/models/form/sales/questions/building_height_class_spec.rb
  23. 53
      spec/models/form/sales/subsections/property_information_spec.rb
  24. 1
      spec/services/bulk_upload/sales/year2026/row_parser_spec.rb
  25. 131
      spec/services/csv/sales_log_csv_service_spec.rb
  26. 64
      spec/services/exports/sales_log_export_service_spec.rb

3
app/helpers/bulk_upload/sales_log_to_csv.rb

@ -670,7 +670,8 @@ class BulkUpload::SalesLogToCsv
log.sexrab3,
log.sexrab4,
log.sexrab5,
log.sexrab6, # 127
log.sexrab6,
log.buildheightclass, # 128
]
end

17
app/models/form/sales/pages/building_height_class.rb

@ -0,0 +1,17 @@
class Form::Sales::Pages::BuildingHeightClass < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "building_height_class"
@depends_on = [
{ "proptype" => 1 },
{ "proptype" => 2 },
{ "proptype" => 9 },
]
end
def questions
@questions ||= [
Form::Sales::Questions::BuildingHeightClass.new(nil, nil, self),
]
end
end

17
app/models/form/sales/questions/building_height_class.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::BuildingHeightClass < ::Form::Question
def initialize(id, hsh, page)
super
@id = "buildheightclass"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
ANSWER_OPTIONS = {
"1" => { "value" => "High-rise" },
"2" => { "value" => "Low-rise" },
"3" => { "value" => "Don't know" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2026 => 17 }.freeze
end

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

@ -10,6 +10,7 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection
@pages ||= [
(uprn_questions if form.start_date.year >= 2024),
(Form::Sales::Pages::PropertyUnitType.new(nil, nil, self) if form.start_year_2025_or_later?),
(Form::Sales::Pages::BuildingHeightClass.new(nil, nil, self) if form.start_year_2026_or_later?),
Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_bedrooms_value_check", nil, self),
(Form::Sales::Pages::PropertyUnitType.new(nil, nil, self) unless form.start_year_2025_or_later?),

4
app/services/bulk_upload/sales/year2026/csv_parser.rb

@ -4,7 +4,7 @@ class BulkUpload::Sales::Year2026::CsvParser
include CollectionTimeHelper
# TODO: CLDC-4162: Update when 2026 format is known
FIELDS = 127
FIELDS = 128
FORM_YEAR = 2026
attr_reader :path
@ -27,7 +27,7 @@ class BulkUpload::Sales::Year2026::CsvParser
def cols
# TODO: CLDC-4162: Update when 2026 format is known
@cols ||= ("A".."DR").to_a
@cols ||= ("A".."DS").to_a
end
def row_parsers

4
app/services/bulk_upload/sales/year2026/row_parser.rb

@ -142,6 +142,7 @@ class BulkUpload::Sales::Year2026::RowParser
field_125: "Person 4's sex, as registered at birth",
field_126: "Person 5's sex, as registered at birth",
field_127: "Person 6's sex, as registered at birth",
field_128: "What is the building height classification?",
}.freeze
ERROR_BASE_KEY = "validations.sales.2026.bulk_upload".freeze
@ -288,6 +289,7 @@ class BulkUpload::Sales::Year2026::RowParser
attribute :field_125, :string
attribute :field_126, :string
attribute :field_127, :string
attribute :field_128, :integer
validates :field_1,
presence: {
@ -801,6 +803,7 @@ private
sexrab4: %i[field_125],
sexrab5: %i[field_126],
sexrab6: %i[field_127],
buildheightclass: %i[field_128],
}
end
@ -842,6 +845,7 @@ private
attributes["sexrab4"] = field_125
attributes["sexrab5"] = field_126
attributes["sexrab6"] = field_127
attributes["buildheightclass"] = field_128
attributes["relat2"] = relationship_from_is_partner(field_34)
attributes["relat3"] = relationship_from_is_partner(field_42)

2
app/services/exports/sales_log_export_constants.rb

@ -146,7 +146,7 @@ module Exports::SalesLogExportConstants
ALL_YEAR_EXPORT_FIELDS << "RELAT#{index}"
end
YEAR_2026_EXPORT_FIELDS = Set[]
YEAR_2026_EXPORT_FIELDS = Set["BUILDHEIGHTCLASS"]
(1..6).each do |index|
YEAR_2026_EXPORT_FIELDS << "SEXRAB#{index}"

7
config/locales/forms/2026/sales/property_information.en.yml

@ -59,6 +59,13 @@ en:
hint_text: ""
question_text: "What type of unit is the property?"
buildheightclass:
page_header: ""
check_answer_label: "Building height classification"
check_answer_prompt: ""
hint_text: "High-rise residential buildings are those containing 2 or more residential units and either have 7 or more storeys or are at least 18 metres in height. If unsure, answer based on the number of storeys."
question_text: "What is the building height classification?"
builtype:
page_header: ""
check_answer_label: "Type of building"

5
db/migrate/20260219093257_add_buildheightclass_to_sales_logs.rb

@ -0,0 +1,5 @@
class AddBuildheightclassToSalesLogs < ActiveRecord::Migration[7.2]
def change
add_column :sales_logs, :buildheightclass, :integer
end
end

9
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2026_02_12_091506) do
ActiveRecord::Schema[7.2].define(version: 2026_02_19_093257) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -376,6 +376,9 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_12_091506) do
t.boolean "manual_address_entry_selected", default: false
t.integer "referral_type"
t.integer "working_situation_illness_check"
t.integer "referral_register"
t.integer "referral_noms"
t.integer "referral_org"
t.string "sexrab1"
t.string "sexrab2"
t.string "sexrab3"
@ -400,9 +403,6 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_12_091506) do
t.string "gender_description6"
t.string "gender_description7"
t.string "gender_description8"
t.integer "referral_register"
t.integer "referral_noms"
t.integer "referral_org"
t.integer "tenancyother_value_check"
t.index ["assigned_to_id"], name: "index_lettings_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
@ -822,6 +822,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_12_091506) do
t.string "sexrab4"
t.string "sexrab5"
t.string "sexrab6"
t.integer "buildheightclass"
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"

2
spec/factories/sales_log.rb

@ -90,6 +90,7 @@ FactoryBot.define do
buy1livein { 1 }
relat2 { "P" }
proptype { 1 }
buildheightclass { 2 }
age2_known { 0 }
age2 { Faker::Number.within(range: 25..45) }
builtype { 1 }
@ -293,6 +294,7 @@ FactoryBot.define do
buy1livein { 1 }
relat2 { "P" }
proptype { 1 }
buildheightclass { 2 }
age2_known { 0 }
age2 { 33 }
builtype { 1 }

154
spec/fixtures/exports/sales_log_25_26.xml vendored

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<forms>
<form>
<ID>{id}</ID>
<STATUS>1</STATUS>
<PURCHID>123</PURCHID>
<TYPE>8</TYPE>
<JOINTMORE>1</JOINTMORE>
<BEDS>2</BEDS>
<AGE1>27</AGE1>
<SEX1>F</SEX1>
<ETHNIC>17</ETHNIC>
<BUILTYPE>1</BUILTYPE>
<PROPTYPE>1</PROPTYPE>
<AGE2>33</AGE2>
<RELAT2>P</RELAT2>
<SEX2>X</SEX2>
<NOINT>2</NOINT>
<ECSTAT2>1</ECSTAT2>
<PRIVACYNOTICE>1</PRIVACYNOTICE>
<ECSTAT1>1</ECSTAT1>
<WHEEL>1</WHEEL>
<HHOLDCOUNT>4</HHOLDCOUNT>
<AGE3>14</AGE3>
<LA>E09000033</LA>
<INCOME1>10000</INCOME1>
<AGE4>18</AGE4>
<AGE5>40</AGE5>
<AGE6>40</AGE6>
<INC1MORT>1</INC1MORT>
<INCOME2>10000</INCOME2>
<SAVINGSNK>1</SAVINGSNK>
<SAVINGS/>
<PREVOWN>1</PREVOWN>
<SEX3>F</SEX3>
<MORTGAGE>20000.0</MORTGAGE>
<INC2MORT>1</INC2MORT>
<ECSTAT3>9</ECSTAT3>
<ECSTAT4>3</ECSTAT4>
<ECSTAT5>2</ECSTAT5>
<ECSTAT6>1</ECSTAT6>
<RELAT3>X</RELAT3>
<RELAT4>X</RELAT4>
<RELAT5>R</RELAT5>
<RELAT6>R</RELAT6>
<HB>4</HB>
<SEX4>X</SEX4>
<SEX5>M</SEX5>
<SEX6>X</SEX6>
<FROMBEDS/>
<STAIRCASE/>
<STAIRBOUGHT/>
<STAIROWNED/>
<MRENT/>
<RESALE/>
<DEPOSIT>80000.0</DEPOSIT>
<CASHDIS/>
<DISABLED>1</DISABLED>
<VALUE>110000.0</VALUE>
<EQUITY/>
<DISCOUNT/>
<GRANT>10000.0</GRANT>
<PPCODENK>0</PPCODENK>
<PPOSTC1>SW1A</PPOSTC1>
<PPOSTC2>1AA</PPOSTC2>
<PREVLOC>E09000033</PREVLOC>
<HHREGRES>7</HHREGRES>
<HHREGRESSTILL/>
<PROPLEN/>
<MSCHARGE>100.0</MSCHARGE>
<PREVTEN>1</PREVTEN>
<MORTGAGEUSED>1</MORTGAGEUSED>
<WCHAIR>1</WCHAIR>
<ARMEDFORCESSPOUSE>5</ARMEDFORCESSPOUSE>
<HODAY/>
<HOMONTH/>
<HOYEAR/>
<FROMPROP/>
<SOCPREVTEN/>
<EXTRABOR>1</EXTRABOR>
<HHTYPE>6</HHTYPE>
<VALUE_VALUE_CHECK/>
<PREVSHARED>2</PREVSHARED>
<BUY2LIVING>3</BUY2LIVING>
<UPRN/>
<COUNTY/>
<ADDRESS_SEARCH_VALUE_CHECK/>
<FIRSTSTAIR/>
<NUMSTAIR/>
<MRENTPRESTAIRCASING/>
<DAY>1</DAY>
<MONTH>4</MONTH>
<YEAR>2025</YEAR>
<CREATEDDATE>2025-04-01T00:00:00+01:00</CREATEDDATE>
<CREATEDBY>{created_by_email}</CREATEDBY>
<CREATEDBYID>{created_by_id}</CREATEDBYID>
<USERNAME>{assigned_to_email}</USERNAME>
<USERNAMEID>{assigned_to_id}</USERNAMEID>
<UPLOADDATE>2025-04-01T00:00:00+01:00</UPLOADDATE>
<AMENDEDBY/>
<AMENDEDBYID/>
<OWNINGORGID>{owning_org_id}</OWNINGORGID>
<OWNINGORGNAME>{owning_org_name}</OWNINGORGNAME>
<MANINGORGID>{managing_org_id}</MANINGORGID>
<MANINGORGNAME>{managing_org_name}</MANINGORGNAME>
<CREATIONMETHOD>1</CREATIONMETHOD>
<BULKUPLOADID/>
<COLLECTIONYEAR>2025</COLLECTIONYEAR>
<OWNERSHIP>2</OWNERSHIP>
<JOINT>1</JOINT>
<ETHNICGROUP1>17</ETHNICGROUP1>
<ETHNICGROUP2>17</ETHNICGROUP2>
<PREVIOUSLAKNOWN>1</PREVIOUSLAKNOWN>
<HASMSCHARGE>1</HASMSCHARGE>
<HASSERVICECHARGES/>
<SERVICECHARGES/>
<INC1NK>0</INC1NK>
<INC2NK>0</INC2NK>
<POSTCODE>SW1A 1AA</POSTCODE>
<ISLAINFERRED>true</ISLAINFERRED>
<MORTLEN1>10</MORTLEN1>
<ETHNIC2/>
<PREVTEN2/>
<ADDRESS1>Address line 1</ADDRESS1>
<ADDRESS2/>
<TOWNCITY>City</TOWNCITY>
<LANAME>Westminster</LANAME>
<ADDRESS1INPUT>Address line 1</ADDRESS1INPUT>
<POSTCODEINPUT>SW1A 1AA</POSTCODEINPUT>
<UPRNSELECTED/>
<BULKADDRESS1/>
<BULKADDRESS2/>
<BULKTOWNCITY/>
<BULKCOUNTY/>
<BULKPOSTCODE/>
<BULKLA/>
<NATIONALITYALL1>826</NATIONALITYALL1>
<NATIONALITYALL2>826</NATIONALITYALL2>
<PREVLOCNAME>Westminster</PREVLOCNAME>
<LIVEINBUYER1>1</LIVEINBUYER1>
<LIVEINBUYER2>1</LIVEINBUYER2>
<HASESTATEFEE/>
<ESTATEFEE/>
<STAIRLASTDAY/>
<STAIRLASTMONTH/>
<STAIRLASTYEAR/>
<STAIRINITIALDAY/>
<STAIRINITIALMONTH/>
<STAIRINITIALYEAR/>
<MSCHARGE_VALUE_CHECK/>
<DUPLICATESET/>
<STAIRCASETOSALE/>
</form>
</forms>

161
spec/fixtures/exports/sales_log_26_27.xml vendored

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<forms>
<form>
<ID>{id}</ID>
<STATUS>1</STATUS>
<PURCHID>123</PURCHID>
<TYPE>8</TYPE>
<JOINTMORE>1</JOINTMORE>
<BEDS>2</BEDS>
<AGE1>27</AGE1>
<SEX1>F</SEX1>
<ETHNIC>17</ETHNIC>
<BUILTYPE>1</BUILTYPE>
<PROPTYPE>1</PROPTYPE>
<AGE2>33</AGE2>
<RELAT2>P</RELAT2>
<SEX2>X</SEX2>
<NOINT>2</NOINT>
<ECSTAT2>1</ECSTAT2>
<PRIVACYNOTICE>1</PRIVACYNOTICE>
<ECSTAT1>1</ECSTAT1>
<WHEEL>1</WHEEL>
<HHOLDCOUNT>4</HHOLDCOUNT>
<AGE3>14</AGE3>
<LA>E09000033</LA>
<INCOME1>10000</INCOME1>
<AGE4>18</AGE4>
<AGE5>40</AGE5>
<AGE6>40</AGE6>
<INC1MORT>1</INC1MORT>
<INCOME2>10000</INCOME2>
<SAVINGSNK>1</SAVINGSNK>
<SAVINGS/>
<PREVOWN>1</PREVOWN>
<SEX3>F</SEX3>
<MORTGAGE>20000.0</MORTGAGE>
<INC2MORT>1</INC2MORT>
<ECSTAT3>9</ECSTAT3>
<ECSTAT4>3</ECSTAT4>
<ECSTAT5>2</ECSTAT5>
<ECSTAT6>1</ECSTAT6>
<RELAT3>X</RELAT3>
<RELAT4>X</RELAT4>
<RELAT5>R</RELAT5>
<RELAT6>R</RELAT6>
<HB>4</HB>
<SEX4>X</SEX4>
<SEX5>M</SEX5>
<SEX6>X</SEX6>
<FROMBEDS/>
<STAIRCASE/>
<STAIRBOUGHT/>
<STAIROWNED/>
<MRENT/>
<RESALE/>
<DEPOSIT>80000.0</DEPOSIT>
<CASHDIS/>
<DISABLED>1</DISABLED>
<VALUE>110000.0</VALUE>
<EQUITY/>
<DISCOUNT/>
<GRANT>10000.0</GRANT>
<PPCODENK>0</PPCODENK>
<PPOSTC1>SW1A</PPOSTC1>
<PPOSTC2>1AA</PPOSTC2>
<PREVLOC>E09000033</PREVLOC>
<HHREGRES>7</HHREGRES>
<HHREGRESSTILL/>
<PROPLEN/>
<MSCHARGE>100.0</MSCHARGE>
<PREVTEN>1</PREVTEN>
<MORTGAGEUSED>1</MORTGAGEUSED>
<WCHAIR>1</WCHAIR>
<ARMEDFORCESSPOUSE>5</ARMEDFORCESSPOUSE>
<HODAY/>
<HOMONTH/>
<HOYEAR/>
<FROMPROP/>
<SOCPREVTEN/>
<EXTRABOR>1</EXTRABOR>
<HHTYPE>6</HHTYPE>
<VALUE_VALUE_CHECK/>
<PREVSHARED>2</PREVSHARED>
<BUY2LIVING>3</BUY2LIVING>
<UPRN/>
<COUNTY/>
<ADDRESS_SEARCH_VALUE_CHECK/>
<FIRSTSTAIR/>
<NUMSTAIR/>
<MRENTPRESTAIRCASING/>
<SEXRAB1>F</SEXRAB1>
<SEXRAB2/>
<SEXRAB3>F</SEXRAB3>
<SEXRAB4/>
<SEXRAB5>M</SEXRAB5>
<SEXRAB6/>
<BUILDHEIGHTCLASS>2</BUILDHEIGHTCLASS>
<DAY>1</DAY>
<MONTH>4</MONTH>
<YEAR>2026</YEAR>
<CREATEDDATE>2026-04-01T00:00:00+01:00</CREATEDDATE>
<CREATEDBY>{created_by_email}</CREATEDBY>
<CREATEDBYID>{created_by_id}</CREATEDBYID>
<USERNAME>{assigned_to_email}</USERNAME>
<USERNAMEID>{assigned_to_id}</USERNAMEID>
<UPLOADDATE>2026-04-01T00:00:00+01:00</UPLOADDATE>
<AMENDEDBY/>
<AMENDEDBYID/>
<OWNINGORGID>{owning_org_id}</OWNINGORGID>
<OWNINGORGNAME>{owning_org_name}</OWNINGORGNAME>
<MANINGORGID>{managing_org_id}</MANINGORGID>
<MANINGORGNAME>{managing_org_name}</MANINGORGNAME>
<CREATIONMETHOD>1</CREATIONMETHOD>
<BULKUPLOADID/>
<COLLECTIONYEAR>2026</COLLECTIONYEAR>
<OWNERSHIP>2</OWNERSHIP>
<JOINT>1</JOINT>
<ETHNICGROUP1>17</ETHNICGROUP1>
<ETHNICGROUP2>17</ETHNICGROUP2>
<PREVIOUSLAKNOWN>1</PREVIOUSLAKNOWN>
<HASMSCHARGE>1</HASMSCHARGE>
<HASSERVICECHARGES/>
<SERVICECHARGES/>
<INC1NK>0</INC1NK>
<INC2NK>0</INC2NK>
<POSTCODE>SW1A 1AA</POSTCODE>
<ISLAINFERRED>true</ISLAINFERRED>
<MORTLEN1>10</MORTLEN1>
<ETHNIC2/>
<PREVTEN2/>
<ADDRESS1>Address line 1</ADDRESS1>
<ADDRESS2/>
<TOWNCITY>City</TOWNCITY>
<LANAME>Westminster</LANAME>
<ADDRESS1INPUT>Address line 1</ADDRESS1INPUT>
<POSTCODEINPUT>SW1A 1AA</POSTCODEINPUT>
<UPRNSELECTED/>
<BULKADDRESS1/>
<BULKADDRESS2/>
<BULKTOWNCITY/>
<BULKCOUNTY/>
<BULKPOSTCODE/>
<BULKLA/>
<NATIONALITYALL1>826</NATIONALITYALL1>
<NATIONALITYALL2>826</NATIONALITYALL2>
<PREVLOCNAME>Westminster</PREVLOCNAME>
<LIVEINBUYER1>1</LIVEINBUYER1>
<LIVEINBUYER2>1</LIVEINBUYER2>
<HASESTATEFEE/>
<ESTATEFEE/>
<STAIRLASTDAY/>
<STAIRLASTMONTH/>
<STAIRLASTYEAR/>
<STAIRINITIALDAY/>
<STAIRINITIALMONTH/>
<STAIRINITIALYEAR/>
<MSCHARGE_VALUE_CHECK/>
<DUPLICATESET/>
<STAIRCASETOSALE/>
</form>
</forms>

18
spec/fixtures/files/2026_27_sales_bulk_upload.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_codes_26.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_labels_26.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv vendored

File diff suppressed because one or more lines are too long

1
spec/fixtures/variable_definitions/sales_download_26_27.csv vendored

@ -4,3 +4,4 @@ sexrab3,What was person 3's sex at birth?
sexrab4,What was person 4's sex at birth?
sexrab5,What was person 5's sex at birth?
sexrab6,What was person 6's sex at birth?
buildheightclass, What is the building height classification?

1 sexrab1 What was buyer 1's sex at birth?
4 sexrab4 What was person 4's sex at birth?
5 sexrab5 What was person 5's sex at birth?
6 sexrab6 What was person 6's sex at birth?
7 buildheightclass What is the building height classification?

2
spec/lib/tasks/log_variable_definitions_spec.rb

@ -6,7 +6,7 @@ RSpec.describe "log_variable_definitions" do
subject(:task) { Rake::Task["data_import:add_variable_definitions"] }
let(:path) { "spec/fixtures/variable_definitions" }
let(:total_variable_definitions_count) { 450 }
let(:total_variable_definitions_count) { 451 }
before do
Rake.application.rake_require("tasks/log_variable_definitions")

36
spec/models/form/sales/pages/building_height_class_spec.rb

@ -0,0 +1,36 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::BuildingHeightClass, type: :model do
include CollectionTimeHelper
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: current_collection_start_date)) }
let(:sales_log) { FactoryBot.create(:sales_log, :completed) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[buildheightclass])
end
it "has the correct id" do
expect(page.id).to eq("building_height_class")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has the correct depends_on" do
expect(page.depends_on).to eq([
{ "proptype" => 1 },
{ "proptype" => 2 },
{ "proptype" => 9 },
])
end
end

39
spec/models/form/sales/questions/building_height_class_spec.rb

@ -0,0 +1,39 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::BuildingHeightClass, type: :model do
include CollectionTimeHelper
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: current_collection_start_date))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("buildheightclass")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "High-rise" },
"2" => { "value" => "Low-rise" },
"3" => { "value" => "Don't know" },
})
end
it "has the correct question_number" do
expect(question.question_number).to eq(17)
end
end

53
spec/models/form/sales/subsections/property_information_spec.rb

@ -1,6 +1,8 @@
require "rails_helper"
RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
include CollectionTimeHelper
subject(:property_information) { described_class.new(nil, nil, section) }
let(:section) { instance_double(Form::Sales::Sections::PropertyInformation) }
@ -11,14 +13,16 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
describe "pages" do
let(:section) { instance_double(Form::Sales::Sections::Household, form:) }
let(:form) { instance_double(Form, start_date:) }
before do
allow(form).to receive_messages(start_year_2024_or_later?: false, start_year_2025_or_later?: false)
end
let(:start_year_2024_or_later?) { true }
let(:start_year_2025_or_later?) { true }
let(:start_year_2026_or_later?) { true }
let(:form) { instance_double(Form, start_date:, start_year_2024_or_later?: start_year_2024_or_later?, start_year_2025_or_later?: start_year_2025_or_later?, start_year_2026_or_later?: start_year_2026_or_later?) }
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
let(:start_date) { collection_start_date_for_year(2023) }
let(:start_year_2024_or_later?) { false }
let(:start_year_2025_or_later?) { false }
let(:start_year_2026_or_later?) { false }
it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq(
@ -44,11 +48,9 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
end
context "when 2024" do
let(:start_date) { Time.utc(2024, 2, 8) }
before do
allow(form).to receive_messages(start_year_2024_or_later?: true, start_year_2025_or_later?: false)
end
let(:start_date) { collection_start_date_for_year(2024) }
let(:start_year_2025_or_later?) { false }
let(:start_year_2026_or_later?) { false }
it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq(
@ -73,11 +75,33 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
end
context "when 2025" do
let(:start_date) { Time.utc(2025, 2, 8) }
let(:start_date) { collection_start_date_for_year(2025) }
let(:start_year_2026_or_later?) { false }
before do
allow(form).to receive_messages(start_year_2024_or_later?: true, start_year_2025_or_later?: true)
it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq(
%w[
address_search
address
property_local_authority
local_authority_buyer_1_income_max_value_check
local_authority_buyer_2_income_max_value_check
local_authority_combined_income_max_value_check
about_price_la_value_check
property_unit_type
property_number_of_bedrooms
about_price_bedrooms_value_check
monthly_charges_property_type_value_check
percentage_discount_proptype_value_check
property_building_type
property_wheelchair_accessible
],
)
end
end
context "when 2026" do
let(:start_date) { collection_start_date_for_year(2026) }
it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq(
@ -90,6 +114,7 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
local_authority_combined_income_max_value_check
about_price_la_value_check
property_unit_type
building_height_class
property_number_of_bedrooms
about_price_bedrooms_value_check
monthly_charges_property_type_value_check

1
spec/services/bulk_upload/sales/year2026/row_parser_spec.rb

@ -118,6 +118,7 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
field_125: "M",
field_126: "R",
field_127: "R",
field_128: "1",
}
end

131
spec/services/csv/sales_log_csv_service_spec.rb

@ -21,17 +21,22 @@ RSpec.describe Csv::SalesLogCsvService do
purchid: nil,
hholdcount: 3,
age1: 30,
sexrab1: "F",
sex1: "X",
age2: 35,
sexrab2: "M",
sex2: "X",
sexrab3: "F",
sex3: "X",
age4_known: 1,
sexrab4: "R",
sex4: "X",
details_known_5: 2,
age6_known: nil,
age6: nil,
ecstat6: nil,
relat6: nil,
sexrab6: nil,
sex6: nil,
town_or_city: "Town or city",
address_line1_as_entered: "address line 1 as entered",
@ -192,6 +197,21 @@ RSpec.describe Csv::SalesLogCsvService do
expect(la_label_value).to eq "Westminster"
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
it "exports the CSV with the 2023 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_labels_23.csv")
values_to_delete = %w[ID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
context "when the requested form is 2024" do
let(:now) { Time.zone.local(2024, 5, 1) }
let(:year) { 2024 }
@ -232,18 +252,23 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
context "when the requested form is 2026" do
let(:now) { Time.zone.local(2026, 5, 1) }
let(:year) { 2026 }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
it "exports the CSV with the 2023 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_labels_23.csv")
values_to_delete = %w[ID]
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)
end
it "exports the CSV with the 2026 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_labels_26.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
@ -300,23 +325,18 @@ RSpec.describe Csv::SalesLogCsvService do
expect(la_label_value).to eq "Westminster"
end
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
let(:year) { 2025 }
before do
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_25.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_23.csv")
values_to_delete = %w[ID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
expect(csv).to eq expected_content
end
end
@ -340,18 +360,43 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
let(:year) { 2025 }
before do
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_23.csv")
values_to_delete = %w[ID]
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_25.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
context "when the requested form is 2026" do
let(:now) { Time.zone.local(2026, 5, 1) }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
let(:year) { 2026 }
before do
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_26.csv")
values_to_delete = %w[ID OWNINGORGID MANINGORGID CREATEDBYID USERNAMEID AMENDEDBYID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
@ -422,5 +467,43 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
end
context "and the requested form is 2026" do
let(:year) { 2026 }
let(:now) { Time.zone.local(2026, 5, 1) }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)
end
context "and exporting with labels" do
let(:service) { described_class.new(user:, export_type: "labels", year:) }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv")
values_to_delete = %w[id owning_organisation_id managing_organisation_id assigned_to_id updated_by_id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
context "and exporting with codes" do
let(:service) { described_class.new(user:, export_type: "codes", year:) }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv")
values_to_delete = %w[id owning_organisation_id managing_organisation_id assigned_to_id updated_by_id]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
end
end
end
end
end

64
spec/services/exports/sales_log_export_service_spec.rb

@ -365,6 +365,70 @@ RSpec.describe Exports::SalesLogExportService do
end
end
context "when exporting only 25/26 collection period" do
let(:start_time) { Time.zone.local(2025, 4, 1) }
before do
Timecop.freeze(start_time)
Singleton.__init__(FormHandler)
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "and one sales log is available for export" do
let!(:sales_log) { FactoryBot.create(:sales_log, :export) }
let(:expected_zip_filename) { "core_sales_2025_2026_apr_mar_f0001_inc0001.zip" }
let(:expected_data_filename) { "core_sales_2025_2026_apr_mar_f0001_inc0001_pt001.xml" }
let(:xml_export_file) { File.open("spec/fixtures/exports/sales_log_25_26.xml", "r:UTF-8") }
it "generates an XML export file with the expected content within the ZIP file" do
expected_content = replace_entity_ids(sales_log, xml_export_file.read)
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
expect(entry).not_to be_nil
expect(entry.get_input_stream.read).to have_same_xml_contents_as(expected_content)
end
export_service.export_xml_sales_logs(full_update: true, collection_year: 2025)
end
end
end
context "when exporting only 26/27 collection period" do
let(:start_time) { Time.zone.local(2026, 4, 1) }
before do
Timecop.freeze(start_time)
Singleton.__init__(FormHandler)
end
after do
Timecop.unfreeze
Singleton.__init__(FormHandler)
end
context "and one sales log is available for export" do
let!(:sales_log) { FactoryBot.create(:sales_log, :export) }
let(:expected_zip_filename) { "core_sales_2026_2027_apr_mar_f0001_inc0001.zip" }
let(:expected_data_filename) { "core_sales_2026_2027_apr_mar_f0001_inc0001_pt001.xml" }
let(:xml_export_file) { File.open("spec/fixtures/exports/sales_log_26_27.xml", "r:UTF-8") }
it "generates an XML export file with the expected content within the ZIP file" do
expected_content = replace_entity_ids(sales_log, xml_export_file.read)
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
expect(entry).not_to be_nil
expect(entry.get_input_stream.read).to have_same_xml_contents_as(expected_content)
end
export_service.export_xml_sales_logs(full_update: true, collection_year: 2026)
end
end
end
context "when exporting various fees, correctly maps the values" do
context "with discounted ownership and mscharge" do
let!(:sales_log) { FactoryBot.create(:sales_log, :export, mscharge: 123) }

Loading…
Cancel
Save