From f1d53bfe018503721b2f5ae90fe0d83ad36555d8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 26 Feb 2026 12:44:52 +0000 Subject: [PATCH 1/7] CLDC-4203: Update bulk upload field numbers (#3189) * CLDC-4141: copy person questions refactor from CLDC-4142 * CLDC-4141: remove gender same as sex and replace gender identity q * CLDC-4141: replace sex with sexrab where relevant for 2026 * CLDC-4141: update download order * CLDC-4140: remove sex from rowparser entirely * CLDC-4140: keep sex in factory for now * CLDC-4140: update household characteristics test * CLDC-4140: update log variable spec * CLDC-4140: update csv export tests * CLDC-4140: update validator tests * CLDC-4140: update row-parser spec * CLDC-4140: update csv-parser spec * CLDC-4140: update export spec * CLDC-4140: update log var defs spec * CLDC-4140: update export spec * CLDC-4140: update row parser spec * CLDC-4140: update validator spec * CLDC-4140: update csv parser spec * CLDC-4143: Update gender retirement validations rename functions as the new wording is more related to those that identify as male and any other option * CLDC-4143: Add 2026 tests * CLDC-4143: Update legacy json files causes issues with some old tests * CLDC-4143: Add error mappings for gender fields * CLDC-4143: Add new gender age retirement soft validation replaces the many check pages from previous years with a single one shows if either of the previous ones would show has agreed wording on ticket * fixup! CLDC-4143: Update gender retirement validations improve gender comment wording * CLDC-4143: Update non males validation name reference to 'soft validation' we found confusing * fixup! CLDC-4143: Add new gender age retirement soft validation update subsection specs * CLDC-4140: update parsers post merge * CLDC-4140: update tests after field renumbering * CLDC-4140: update tests after field renumbering * CLDC-4203: Remove builtype from BU * CLDC-4203: Renumber sex/gender fields * CLDC-4203: Renumber source of referral fields * CLDC-4203: Renumber lettings_log_to_csv to_2026_row comments * CLDC-4140: update spacing in to_2026_row * CLDC-4140: update row parser spec field numbers * CLDC-4140: update row parser field types * CLDC-4203: Swap age and relat fields as age is now asked first * CLDC-4203: Update error messages for fields to match BU * CLDC-4203: Add a new example BU file * CLDC-4203: Update tests * CLDC-4164: Fix spacing in lettings_log_to_csv * CLDC-4203: Fix error description q order Co-authored-by: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> * CLDC-4203: Fix attribute ordering in row_parser --------- Co-authored-by: Nat Dean-Lewis Co-authored-by: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> --- .../bulk_upload/lettings_log_to_csv.rb | 111 +- .../lettings/year2026/csv_parser.rb | 2 +- .../lettings/year2026/row_parser.rb | 1089 ++++++++--------- .../files/2026_27_lettings_bulk_upload.csv | 70 +- .../bulk_upload/lettings/validator_spec.rb | 4 +- .../lettings/year2026/csv_parser_spec.rb | 8 +- .../lettings/year2026/row_parser_spec.rb | 1089 ++++++++--------- 7 files changed, 1184 insertions(+), 1189 deletions(-) diff --git a/app/helpers/bulk_upload/lettings_log_to_csv.rb b/app/helpers/bulk_upload/lettings_log_to_csv.rb index 18a524b0d..11f60773f 100644 --- a/app/helpers/bulk_upload/lettings_log_to_csv.rb +++ b/app/helpers/bulk_upload/lettings_log_to_csv.rb @@ -105,8 +105,8 @@ class BulkUpload::LettingsLogToCsv renewal, log.startdate&.day, log.startdate&.month, - log.startdate&.strftime("%y"), # 10 + log.startdate&.strftime("%y"), # 10 rent_type, log.irproduct_other, log.tenancycode, @@ -116,20 +116,19 @@ class BulkUpload::LettingsLogToCsv log.unitletas, log.uprn, log.address_line1&.tr(",", " "), - log.address_line2&.tr(",", " "), # 20 + log.address_line2&.tr(",", " "), # 20 log.town_or_city&.tr(",", " "), log.county&.tr(",", " "), ((log.postcode_full || "").split(" ") || [""]).first, ((log.postcode_full || "").split(" ") || [""]).last, log.la, log.unittype_gn, - log.builtype, log.wchair, log.beds, - log.voiddate&.day, # 30 + log.voiddate&.day, - log.voiddate&.month, + log.voiddate&.month, # 30 log.voiddate&.strftime("%y"), log.mrcdate&.day, log.mrcdate&.month, @@ -138,98 +137,119 @@ class BulkUpload::LettingsLogToCsv log.joint, log.startertenancy, log.tenancy, - log.tenancyother, # 40 + log.tenancyother, - log.tenancylength, + log.tenancylength, # 40 log.age1 || overrides[:age1], log.sexrab1, + log.gender_same_as_sex1, + log.gender_description1, log.ethnic, log.nationality_all_group, log.ecstat1, - relat_number(log.relat2), log.age2 || overrides[:age2], - log.sexrab2, - log.ecstat2, # 50 + relat_number(log.relat2), - relat_number(log.relat3), + log.sexrab2, # 50 + log.gender_same_as_sex2, + log.gender_description2, + log.ecstat2, log.age3 || overrides[:age3], + relat_number(log.relat3), log.sexrab3, + log.gender_same_as_sex3, + log.gender_description3, log.ecstat3, + + log.age4 || overrides[:age4], # 60 relat_number(log.relat4), - log.age4 || overrides[:age4], log.sexrab4, + log.gender_same_as_sex4, + log.gender_description4, log.ecstat4, + log.age5 || overrides[:age5], relat_number(log.relat5), - log.age5 || overrides[:age5], # 60 - log.sexrab5, + log.gender_same_as_sex5, + + log.gender_description5, # 70 log.ecstat5, - relat_number(log.relat6), log.age6 || overrides[:age6], + relat_number(log.relat6), log.sexrab6, + log.gender_same_as_sex6, + log.gender_description6, log.ecstat6, - relat_number(log.relat7), log.age7 || overrides[:age7], - log.sexrab7, - log.ecstat7, # 70 + relat_number(log.relat7), - relat_number(log.relat8), + log.sexrab7, # 80 + log.gender_same_as_sex7, + log.gender_description7, + log.ecstat7, log.age8 || overrides[:age8], + relat_number(log.relat8), log.sexrab8, + log.gender_same_as_sex8, + log.gender_description8, log.ecstat8, - log.armedforces, + + log.armedforces, # 90 log.leftreg, log.reservist, log.preg_occ, log.housingneeds_a, - log.housingneeds_b, # 80 - + log.housingneeds_b, log.housingneeds_c, log.housingneeds_f, log.housingneeds_g, log.housingneeds_h, - overrides[:illness] || log.illness, + + overrides[:illness] || log.illness, # 100 log.illness_type_1, log.illness_type_2, log.illness_type_3, log.illness_type_4, - log.illness_type_5, # 90 - + log.illness_type_5, log.illness_type_6, log.illness_type_7, log.illness_type_8, log.illness_type_9, - log.illness_type_10, + + log.illness_type_10, # 110 log.layear, log.waityear, log.reason, log.reasonother, - log.prevten, # 100 - + log.prevten, homeless, previous_postcode_known, ((log.ppostcode_full || "").split(" ") || [""]).first, ((log.ppostcode_full || "").split(" ") || [""]).last, - log.prevloc, + + log.prevloc, # 120 log.reasonpref, log.rp_homeless, log.rp_insan_unsat, log.rp_medwel, - log.rp_hardship, # 110 - + log.rp_hardship, log.rp_dontknow, cbl, chr, cap, - accessible_register, + + accessible_register, # 130 log.owning_organisation.la? ? log.referral_register : nil, + log.owning_organisation.prp? ? log.referral_register : nil, + log.referral_noms, + log.referral_org, net_income_known, log.incfreq, log.earnings, - log.hb, # 120 - + log.hb, log.benefits, - log.household_charge, + + log.household_charge, # 140 log.period, log.brent, log.scharge, @@ -237,27 +257,6 @@ class BulkUpload::LettingsLogToCsv log.supcharg, log.hbrentshortfall, log.tshortfall, - log.gender_same_as_sex1, # 130 - - log.gender_description1, - log.gender_same_as_sex2, - log.gender_description2, - log.gender_same_as_sex3, - log.gender_description3, - log.gender_same_as_sex4, - log.gender_description4, - log.gender_same_as_sex5, - log.gender_description5, - log.gender_same_as_sex6, # 140 - - log.gender_description6, - log.gender_same_as_sex7, - log.gender_description7, - log.gender_same_as_sex8, - log.gender_description8, - log.owning_organisation.prp? ? log.referral_register : nil, - log.referral_noms, - log.referral_org, # 148 ] end diff --git a/app/services/bulk_upload/lettings/year2026/csv_parser.rb b/app/services/bulk_upload/lettings/year2026/csv_parser.rb index 643990c8b..d2f1ab7b1 100644 --- a/app/services/bulk_upload/lettings/year2026/csv_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/csv_parser.rb @@ -4,7 +4,7 @@ class BulkUpload::Lettings::Year2026::CsvParser include CollectionTimeHelper # TODO: CLDC-4162: Update when 2026 format is known - FIELDS = 148 + FIELDS = 147 FORM_YEAR = 2026 attr_reader :path diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index c8fc4c891..547ced717 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -11,150 +11,147 @@ class BulkUpload::Lettings::Year2026::RowParser field_4: "What is the needs type?", field_5: "What scheme does this letting belong to?", field_6: "Which location is this letting for?", - field_7: "Is this letting a renewal?", - field_8: "What is the tenancy start date?", - field_9: "What is the tenancy start date?", - field_10: "What is the tenancy start date?", + field_7: "Is this letting a renewal of social housing to the same tenant in the same property?", + field_8: "What is the tenancy start date? - day DD?", + field_9: "What is the tenancy start date? - month MM?", + field_10: "What is the tenancy start date? - year YY?", field_11: "What is the rent type?", field_12: "Which 'Other' type of Intermediate Rent is this letting?", field_13: "What is the tenant code?", field_14: "What is the property reference?", - field_15: "Has tenant seen the MHCLG privacy notice?", + field_15: "Has the tenant seen or been given access to the MHCLG privacy notice?", field_16: "What is the reason for the property being vacant?", field_17: "What type was the property most recently let as?", field_18: "If known, provide this property’s UPRN", - field_19: "Address line 1", - field_20: "Address line 2", + field_19: "Address Line 1", + field_20: "Address Line 2", field_21: "Town or city", field_22: "County", - field_23: "Part 1 of the property’s postcode", - field_24: "Part 2 of the property’s postcode", - field_25: "What is the property’s local authority?", + field_23: "Part 1 of the property's postcode", + field_24: "Part 2 of the property's postcode", + field_25: "What is the property's local authority?", field_26: "What type of unit is the property?", - field_27: "Which type of building is the property?", - field_28: "Is the property built or adapted to wheelchair-user standards?", - field_29: "How many bedrooms does the property have?", - field_30: "What is the void date?", - field_31: "What is the void date?", - field_32: "What is the void date?", - field_33: "What date were any major repairs completed on?", - field_34: "What date were any major repairs completed on?", - field_35: "What date were any major repairs completed on?", - field_36: "Is this letting sheltered accommodation?", - field_37: "Is this a joint tenancy?", - field_38: "Is this a starter tenancy?", - field_39: "What is the type of tenancy?", - field_40: "If 'Other', what is the type of tenancy?", - field_41: "What is the length of the fixed-term tenancy to the nearest year?", - field_42: "What is the lead tenant’s age?", - field_43: "Lead tenant's sex, as registered at birth", - field_44: "Which of these best describes the lead tenant’s ethnic background?", - field_45: "What is the lead tenant’s nationality?", - field_46: "Which of these best describes the lead tenant’s working situation?", - field_47: "Is person 2 the partner of the lead tenant?", - field_48: "What is person 2’s age?", - field_49: "Person 2's sex, as registered at birth", - field_50: "Which of these best describes person 2’s working situation?", - field_51: "Is person 3 the partner of the lead tenant?", - field_52: "What is person 3’s age?", - field_53: "Person 3's sex, as registered at birth", - field_54: "Which of these best describes person 3’s working situation?", - field_55: "Is person 4 the partner of the lead tenant?", - field_56: "What is person 4’s age?", - field_57: "Person 4's sex, as registered at birth", - field_58: "Which of these best describes person 4’s working situation?", - field_59: "Is person 5 the partner of the lead tenant?", - field_60: "What is person 5’s age?", - field_61: "Person 5's sex, as registered at birth", - field_62: "Which of these best describes person 5’s working situation?", - field_63: "Is person 6 the partner of the lead tenant?", - field_64: "What is person 6’s age?", - field_65: "Person 6's sex, as registered at birth", - field_66: "Which of these best describes person 6’s working situation?", - field_67: "Is person 7 the partner of the lead tenant?", - field_68: "What is person 7’s age?", - field_69: "Person 7's sex, as registered at birth", - field_70: "Which of these best describes person 7’s working situation?", - field_71: "Is person 8 the partner of the lead tenant?", - field_72: "What is person 8’s age?", - field_73: "Person 8's sex, as registered at birth", - field_74: "Which of these best describes person 8’s working situation?", - field_75: "Does anybody in the household have links to the UK armed forces?", - field_76: "Is this person still serving in the UK armed forces?", - field_77: "Was this person seriously injured or ill as a result of serving in the UK armed forces?", - field_78: "Is anybody in the household pregnant?", - field_79: "Does anybody in the household have any disabled access needs?", - field_80: "Does anybody in the household have any disabled access needs?", - field_81: "Does anybody in the household have any disabled access needs?", - field_82: "Does anybody in the household have any disabled access needs?", - field_83: "Does anybody in the household have any disabled access needs?", - field_84: "Does anybody in the household have any disabled access needs?", - field_85: "Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more?", - field_86: "Does this person’s condition affect their dexterity?", - field_87: "Does this person’s condition affect their learning or understanding or concentrating?", - field_88: "Does this person’s condition affect their hearing?", - field_89: "Does this person’s condition affect their memory?", - field_90: "Does this person’s condition affect their mental health?", - field_91: "Does this person’s condition affect their mobility?", - field_92: "Does this person’s condition affect them socially or behaviourally?", - field_93: "Does this person’s condition affect their stamina or breathing or fatigue?", - field_94: "Does this person’s condition affect their vision?", - field_95: "Does this person’s condition affect them in another way?", - field_96: "How long has the household continuously lived in the local authority area of the new letting?", - field_97: "How long has the household been on the local authority waiting list for the new letting?", - field_98: "What is the tenant’s main reason for the household leaving their last settled home?", - field_99: "If 'Other', what was the main reason for leaving their last settled home?", - field_100: "Where was the household immediately before this letting?", - field_101: "Did the household experience homelessness immediately before this letting?", - field_102: "Do you know the postcode of the household’s last settled home?", - field_103: "What is the postcode of the household’s last settled home?", - field_104: "What is the postcode of the household’s last settled home?", - field_105: "What is the local authority of the household’s last settled home?", - field_106: "Was the household given 'reasonable preference' by the local authority?", - field_107: "Reasonable preference reason - They were homeless or about to lose their home (within 56 days)", - field_108: "Reasonable preference reason - They were living in insanitary, overcrowded or unsatisfactory housing", - field_109: "Reasonable preference reason - They needed to move on medical and welfare reasons (including disability)", - field_110: "Reasonable preference reason - They needed to move to avoid hardship to themselves or others", - field_111: "Reasonable preference reason - Don’t know", - field_112: "Was the letting made under the Choice-Based Lettings (CBL)?", - field_113: "Was the letting made under the Common Allocation Policy (CAP)?", - field_114: "Was the letting made under the Common Housing Register (CHR)?", - field_115: "Was the letting made under the Accessible Register?", - field_116: "What was the source of referral for this letting? - LA properties", - field_117: "Do you know the household’s combined total income after tax?", - field_118: "How often does the household receive income?", - field_119: "How much income does the household have in total?", - field_120: "Is the tenant likely to be receiving any of these housing-related benefits?", - field_121: "How much of the household’s income is from Universal Credit, state pensions or benefits?", - field_122: "Does the household pay rent or other charges for the accommodation?", - field_123: "How often does the household pay rent and other charges?", - field_124: "What is the basic rent?", - field_125: "What is the service charge?", - field_126: "What is the personal service charge?", - field_127: "What is the support charge?", - field_128: "After the household has received any housing-related benefits, will they still need to pay for rent and charges?", - field_129: "What do you expect the outstanding amount to be?", - - field_130: "Is the gender the lead tenant identifies with the same as their sex registered at birth?", - field_131: "If 'No', enter the lead tenant's gender identity", - field_132: "Is the gender person 2 identifies with the same as their sex registered at birth?", - field_133: "If 'No', enter person 2's gender identity", - field_134: "Is the gender person 3 identifies with the same as their sex registered at birth?", - field_135: "If 'No', enter person 3's gender identity", - field_136: "Is the gender person 4 identifies with the same as their sex registered at birth?", - field_137: "If 'No', enter person 4's gender identity", - field_138: "Is the gender person 5 identifies with the same as their sex registered at birth?", - field_139: "If 'No', enter person 5's gender identity", - field_140: "Is the gender person 6 identifies with the same as their sex registered at birth?", - field_141: "If 'No', enter person 6's gender identity", - field_142: "Is the gender person 7 identifies with the same as their sex registered at birth?", - field_143: "If 'No', enter person 7's gender identity", - field_144: "Is the gender person 8 identifies with the same as their sex registered at birth?", - field_145: "If 'No', enter person 8's gender identity", - - field_146: "What was the source of referral for this letting? - PRP properties part 1", - field_147: "What was the source of referral for this letting? - PRP properties part 2", - field_148: "What was the source of referral for this letting? - PRP properties part 3", + field_27: "Is the property built or adapted to wheelchair-user standards?", + field_28: "How many bedrooms does the property have?", + field_29: "What is the void date? - day DD", + field_30: "What is the void date? - month MM", + field_31: "What is the void date? - year YY", + field_32: "What date were any major repairs completed on? - day DD", + field_33: "What date were any major repairs completed on? - month MM", + field_34: "What date were any major repairs completed on? - year YY", + field_35: "Is this property older people's housing?", + field_36: "Is this a joint tenancy?", + field_37: "Is this a starter tenancy?", + field_38: "What is the type of tenancy?", + field_39: "If 'Other', what is the type of tenancy?", + field_40: "What is the length of the fixed-term tenancy to the nearest year?", + field_41: "What is the lead tenant’s age?", + field_42: "What is the lead tenant's sex?", + field_43: "Is the gender the lead tenant identifies with the same as their sex registered at birth?", + field_44: "If 'No', enter lead tenant's gender identity", + field_45: "Which of these best describes the lead tenant's ethnic background?", + field_46: "What is the lead tenant’s nationality?", + field_47: "Which of these best describes the lead tenant’s working situation?", + field_48: "What is person 2's age?", + field_49: "Is person 2 the partner of the lead tenant?", + field_50: "What is person 2's sex?", + field_51: "Is the gender the person 2 identifies with the same as their sex registered at birth?", + field_52: "If 'No', enter person 2's gender identity", + field_53: "Which of these best describes person 2's working situation?", + field_54: "What is person 3's age?", + field_55: "Is person 3 the partner of the lead tenant?", + field_56: "What is person 3's sex?", + field_57: "Is the gender the person 3 identifies with the same as their sex registered at birth?", + field_58: "If 'No', enter person 3's gender identity", + field_59: "Which of these best describes person 3's working situation?", + field_60: "What is person 4's age?", + field_61: "Is person 4 the partner of the lead tenant?", + field_62: "What is person 4's sex?", + field_63: "Is the gender the person 4 identifies with the same as their sex registered at birth?", + field_64: "If 'No', enter person 4's gender identity", + field_65: "Which of these best describes person 4's working situation?", + field_66: "What is person 5's age?", + field_67: "Is person 5 the partner of the lead tenant?", + field_68: "What is person 5's sex?", + field_69: "Is the gender the person 5 identifies with the same as their sex registered at birth?", + field_70: "If 'No', enter person 5's gender identity", + field_71: "Which of these best describes person 5's working situation?", + field_72: "What is person 6's age?", + field_73: "Is person 6 the partner of the lead tenant?", + field_74: "What is person 6's sex?", + field_75: "Is the gender the person 6 identifies with the same as their sex registered at birth?", + field_76: "If 'No', enter person 6's gender identity", + field_77: "Which of these best describes person 6's working situation?", + field_78: "What is person 7's age?", + field_79: "Is person 7 the partner of the lead tenant?", + field_80: "What is person 7's sex?", + field_81: "Is the gender the person 7 identifies with the same as their sex registered at birth?", + field_82: "If 'No', enter person 7's gender identity", + field_83: "Which of these best describes person 7's working situation?", + field_84: "What is person 8's age?", + field_85: "Is person 8 the partner of the lead tenant?", + field_86: "What is person 8's sex?", + field_87: "Is the gender the person 8 identifies with the same as their sex registered at birth?", + field_88: "If 'No', enter person 8's gender identity", + field_89: "Which of these best describes person 8's working situation?", + field_90: "Does anybody in the household have links to the UK armed forces?", + field_91: "Is this person still serving in the UK armed forces?", + field_92: "Was this person seriously injured or ill as a result of serving in the UK armed forces?", + field_93: "Is anybody in the household pregnant?", + field_94: "Disabled access needs — a) Fully wheelchair-accessible housing", + field_95: "Disabled access needs — b) Wheelchair access to essential rooms", + field_96: "Disabled access needs — c) Level access housing", + field_97: "Disabled access needs — f) Other disabled access needs", + field_98: "Disabled access needs — g) No disabled access needs", + field_99: "Disabled access needs — h) Don’t know", + field_100: "Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more?", + field_101: "Does this person's condition affect their dexterity?", + field_102: "Does this person's condition affect their learning or understanding or concentrating?", + field_103: "Does this person's condition affect their hearing?", + field_104: "Does this person's condition affect their memory?", + field_105: "Does this person's condition affect their mental health?", + field_106: "Does this person's condition affect their mobility?", + field_107: "Does this person's condition affect them socially or behaviourally?", + field_108: "Does this person's condition affect their stamina or breathing or fatigue?", + field_109: "Does this person's condition affect their vision?", + field_110: "Does this person's condition affect them in another way?", + field_111: "How long has the household continuously lived in the local authority area of the new letting?", + field_112: "How long has the household been on the local authority housing register (or waiting list) for the area of the new letting?", + field_113: "What is the tenant’s main reason for the household leaving their last settled home?", + field_114: "If 'Other', what was the main reason for leaving their last settled home?", + field_115: "Where was the household immediately before this letting?", + field_116: "Did the household experience homelessness immediately before this letting?", + field_117: "Do you know the postcode of the household's last settled home?", + field_118: "Part 1 of postcode of last settled home", + field_119: "Part 2 of postcode of last settled home", + field_120: "What is the local authority of the household's last settled home?", + field_121: "Was the household given 'reasonable preference' by the local authority?", + field_122: "Reasonable preference reason — They were homeless or about to lose their home (within 56 days)", + field_123: "Reasonable preference reason — They were living in unsanitary, overcrowded or unsatisfactory housing", + field_124: "Reasonable preference reason — They needed to move due to medical and welfare reasons (including disability)", + field_125: "Reasonable preference reason — They needed to move to avoid hardship to themselves or others", + field_126: "Reasonable preference reason — Don't know", + field_127: "How was this letting allocated? — Choice based Lettings (CBL)", + field_128: "How was this letting allocated? — Common Allocations Policy (CAP)", + field_129: "How was this letting allocated? — Common Housing Register (CHR)", + field_130: "How was this letting allocated? — Accessible Housing Register", + field_131: "What was the source of referral for this letting? - LA properties", + field_132: "What was the source of referral for this letting? - PRP properties part 1", + field_133: "What was the source of referral for this letting? - PRP properties part 2", + field_134: "What was the source of referral for this letting? - PRP properties part 3", + field_135: "Do you know the household's combined total income after tax?", + field_136: "How often does the household receive income?", + field_137: "How much income does the household have in total?", + field_138: "Is the tenant likely to be receiving any of these housing-related benefits?", + field_139: "How much of the household's income is from Universal Credit, state pensions or benefits?", + field_140: "Does the household pay rent or other charges for the accommodation?", + field_141: "How often does the household pay rent and other charges?", + field_142: "What is the basic rent?", + field_143: "What is the service charge?", + field_144: "What is the personal service charge?", + field_145: "What is the support charge?", + field_146: "After the household has received any housing-related benefits, will they still need to pay for rent and charges?", + field_147: "What do you expect the outstanding amount to be?", }.freeze RENT_TYPE_BU_MAPPING = { @@ -178,6 +175,8 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_2, :string attribute :field_3, :string attribute :field_4, :integer + attribute :field_5, :string + attribute :field_6, :string attribute :field_7, :integer attribute :field_8, :integer attribute :field_9, :integer @@ -186,8 +185,9 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_12, :string attribute :field_13, :string attribute :field_14, :string - attribute :field_5, :string - attribute :field_6, :string + attribute :field_15, :integer + attribute :field_16, :integer + attribute :field_17, :integer attribute :field_18, :string attribute :field_19, :string attribute :field_20, :string @@ -196,8 +196,6 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_23, :string attribute :field_24, :string attribute :field_25, :string - attribute :field_17, :integer - attribute :field_16, :integer attribute :field_26, :integer attribute :field_27, :integer attribute :field_28, :integer @@ -208,60 +206,59 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_33, :integer attribute :field_34, :integer attribute :field_35, :integer + attribute :field_36, :integer attribute :field_37, :integer attribute :field_38, :integer - attribute :field_39, :integer - attribute :field_40, :string - attribute :field_41, :integer - attribute :field_36, :integer - attribute :field_15, :integer + attribute :field_39, :string + attribute :field_40, :integer + attribute :field_41, :string attribute :field_42, :string attribute :field_43, :string - attribute :field_44, :integer + attribute :field_44, :string attribute :field_45, :integer attribute :field_46, :integer attribute :field_47, :integer attribute :field_48, :string - attribute :field_49, :string - attribute :field_50, :integer - attribute :field_51, :integer + attribute :field_49, :integer + attribute :field_50, :string + attribute :field_51, :string attribute :field_52, :string - attribute :field_53, :string - attribute :field_54, :integer + attribute :field_53, :integer + attribute :field_54, :string attribute :field_55, :integer attribute :field_56, :string attribute :field_57, :string - attribute :field_58, :integer + attribute :field_58, :string attribute :field_59, :integer attribute :field_60, :string - attribute :field_61, :string - attribute :field_62, :integer - attribute :field_63, :integer + attribute :field_61, :integer + attribute :field_62, :string + attribute :field_63, :string attribute :field_64, :string - attribute :field_65, :string - attribute :field_66, :integer + attribute :field_65, :integer + attribute :field_66, :string attribute :field_67, :integer attribute :field_68, :string - attribute :field_69, :string - attribute :field_70, :integer + attribute :field_69, :integer + attribute :field_70, :string attribute :field_71, :integer attribute :field_72, :string - attribute :field_73, :string - attribute :field_74, :integer + attribute :field_73, :integer + attribute :field_74, :string attribute :field_75, :integer - attribute :field_76, :integer + attribute :field_76, :string attribute :field_77, :integer - attribute :field_78, :integer + attribute :field_78, :string attribute :field_79, :integer - attribute :field_80, :integer + attribute :field_80, :string attribute :field_81, :integer - attribute :field_82, :integer + attribute :field_82, :string attribute :field_83, :integer - attribute :field_84, :integer + attribute :field_84, :string attribute :field_85, :integer - attribute :field_86, :integer + attribute :field_86, :string attribute :field_87, :integer - attribute :field_88, :integer + attribute :field_88, :string attribute :field_89, :integer attribute :field_90, :integer attribute :field_91, :integer @@ -272,13 +269,13 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_96, :integer attribute :field_97, :integer attribute :field_98, :integer - attribute :field_99, :string + attribute :field_99, :integer attribute :field_100, :integer attribute :field_101, :integer attribute :field_102, :integer - attribute :field_103, :string - attribute :field_104, :string - attribute :field_105, :string + attribute :field_103, :integer + attribute :field_104, :integer + attribute :field_105, :integer attribute :field_106, :integer attribute :field_107, :integer attribute :field_108, :integer @@ -287,43 +284,40 @@ class BulkUpload::Lettings::Year2026::RowParser attribute :field_111, :integer attribute :field_112, :integer attribute :field_113, :integer - attribute :field_114, :integer + attribute :field_114, :string attribute :field_115, :integer attribute :field_116, :integer attribute :field_117, :integer - attribute :field_118, :integer - attribute :field_119, :decimal - attribute :field_120, :integer + attribute :field_118, :string + attribute :field_119, :string + attribute :field_120, :string attribute :field_121, :integer attribute :field_122, :integer attribute :field_123, :integer - attribute :field_124, :decimal - attribute :field_125, :decimal - attribute :field_126, :decimal - attribute :field_127, :decimal + attribute :field_124, :integer + attribute :field_125, :integer + attribute :field_126, :integer + attribute :field_127, :integer attribute :field_128, :integer - attribute :field_129, :decimal - + attribute :field_129, :integer attribute :field_130, :integer - attribute :field_131, :string + attribute :field_131, :integer attribute :field_132, :integer - attribute :field_133, :string + attribute :field_133, :integer attribute :field_134, :integer - attribute :field_135, :string + attribute :field_135, :integer attribute :field_136, :integer - attribute :field_137, :string + attribute :field_137, :decimal attribute :field_138, :integer - attribute :field_139, :string + attribute :field_139, :integer attribute :field_140, :integer - attribute :field_141, :string - attribute :field_142, :integer - attribute :field_143, :string - attribute :field_144, :integer - attribute :field_145, :string - + attribute :field_141, :integer + attribute :field_142, :decimal + attribute :field_143, :decimal + attribute :field_144, :decimal + attribute :field_145, :decimal attribute :field_146, :integer - attribute :field_147, :integer - attribute :field_148, :integer + attribute :field_147, :decimal validate :validate_valid_radio_option, on: :before_log @@ -390,7 +384,7 @@ class BulkUpload::Lettings::Year2026::RowParser }, on: :after_log - validates :field_112, + validates :field_127, presence: { message: I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "was the letting made under the Choice-Based Lettings (CBL)?"), category: :not_answered, @@ -398,11 +392,11 @@ class BulkUpload::Lettings::Year2026::RowParser inclusion: { in: [1, 2], message: I18n.t("#{ERROR_BASE_KEY}.invalid_option", question: "was the letting made under the Choice-Based Lettings (CBL)?"), - if: -> { field_112.present? }, + if: -> { field_127.present? }, }, on: :after_log - validates :field_113, + validates :field_128, presence: { message: I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "was the letting made under the Common Allocation Policy (CAP)?"), category: :not_answered, @@ -410,11 +404,11 @@ class BulkUpload::Lettings::Year2026::RowParser inclusion: { in: [1, 2], message: I18n.t("#{ERROR_BASE_KEY}.invalid_option", question: "was the letting made under the Common Allocation Policy (CAP)?"), - if: -> { field_113.present? }, + if: -> { field_128.present? }, }, on: :after_log - validates :field_114, + validates :field_129, presence: { message: I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "was the letting made under the Common Housing Register (CHR)?"), category: :not_answered, @@ -422,11 +416,11 @@ class BulkUpload::Lettings::Year2026::RowParser inclusion: { in: [1, 2], message: I18n.t("#{ERROR_BASE_KEY}.invalid_option", question: "was the letting made under the Common Housing Register (CHR)?"), - if: -> { field_114.present? }, + if: -> { field_129.present? }, }, on: :after_log - validates :field_115, + validates :field_130, presence: { message: I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "was the letting made under the Accessible Register?"), category: :not_answered, @@ -434,18 +428,18 @@ class BulkUpload::Lettings::Year2026::RowParser inclusion: { in: [1, 2], message: I18n.t("#{ERROR_BASE_KEY}.invalid_option", question: "was the letting made under the Accessible Register?"), - if: -> { field_115.present? }, + if: -> { field_130.present? }, }, on: :after_log - validates :field_42, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 1) }, on: :after_log + validates :field_41, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 1) }, on: :after_log validates :field_48, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 2) }, on: :after_log, if: proc { details_known?(2).zero? } - validates :field_52, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 3) }, on: :after_log, if: proc { details_known?(3).zero? } - validates :field_56, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 4) }, on: :after_log, if: proc { details_known?(4).zero? } - validates :field_60, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 5) }, on: :after_log, if: proc { details_known?(5).zero? } - validates :field_64, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 6) }, on: :after_log, if: proc { details_known?(6).zero? } - validates :field_68, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 7) }, on: :after_log, if: proc { details_known?(7).zero? } - validates :field_72, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 8) }, on: :after_log, if: proc { details_known?(8).zero? } + validates :field_54, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 3) }, on: :after_log, if: proc { details_known?(3).zero? } + validates :field_60, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 4) }, on: :after_log, if: proc { details_known?(4).zero? } + validates :field_66, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 5) }, on: :after_log, if: proc { details_known?(5).zero? } + validates :field_72, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 6) }, on: :after_log, if: proc { details_known?(6).zero? } + validates :field_78, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 7) }, on: :after_log, if: proc { details_known?(7).zero? } + validates :field_84, format: { with: /\A\d{1,3}\z|\AR\z/, message: I18n.t("#{ERROR_BASE_KEY}.age.invalid", person_num: 8) }, on: :after_log, if: proc { details_known?(8).zero? } validate :validate_needs_type_present, on: :after_log validate :validate_data_types, on: :after_log @@ -580,12 +574,12 @@ class BulkUpload::Lettings::Year2026::RowParser !general_needs? ? :field_6.to_s : nil, # location # TODO: CLDC-4119: remove location from hash !supported_housing? ? "field_23" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing !supported_housing? ? "field_24" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing - "field_42", # age1 - "field_43", # sexrab1 - "field_46", # ecstat1 + "field_41", # age1 + "field_42", # sexrab1 + "field_47", # ecstat1 ) - if [field_124, field_125, field_126, field_127].all?(&:present?) - hash.merge({ "tcharge" => [field_124, field_125, field_126, field_127].sum }) + if [field_142, field_143, field_144, field_145].all?(&:present?) + hash.merge({ "tcharge" => [field_142, field_143, field_144, field_145].sum }) else hash end @@ -594,7 +588,7 @@ class BulkUpload::Lettings::Year2026::RowParser def add_duplicate_found_in_spreadsheet_errors spreadsheet_duplicate_hash.each_key do |field| if field == "tcharge" - %w[field_124 field_125 field_126 field_127].each do |sub_field| + %w[field_142 field_143 field_144 field_145].each do |sub_field| errors.add(sub_field, I18n.t("#{ERROR_BASE_KEY}.spreadsheet_dupe"), category: :setup) end else @@ -703,14 +697,14 @@ private end def validate_nationality - if field_45.present? && !valid_nationality_options.include?(field_45.to_s) - errors.add(:field_45, I18n.t("#{ERROR_BASE_KEY}.nationality.invalid")) + if field_46.present? && !valid_nationality_options.include?(field_46.to_s) + errors.add(:field_46, I18n.t("#{ERROR_BASE_KEY}.nationality.invalid")) end end def validate_reasonpref_reason_values valid_reasonpref_reason_options = %w[0 1] - %w[field_107 field_108 field_109 field_110 field_111].each do |field| + %w[field_122 field_123 field_124 field_125 field_126].each do |field| next unless send(field).present? && !valid_reasonpref_reason_options.include?(send(field).to_s) question_text = QUESTIONS[field.to_sym] @@ -721,9 +715,9 @@ private def validate_prevten_value_when_renewal return unless renewal? - return if field_100.blank? || [6, 30, 31, 32, 33, 34, 35, 38].include?(field_100) + return if field_115.blank? || [6, 30, 31, 32, 33, 34, 35, 38].include?(field_115) - errors.add(:field_100, I18n.t("#{ERROR_BASE_KEY}.prevten.invalid")) + errors.add(:field_115, I18n.t("#{ERROR_BASE_KEY}.prevten.invalid")) end def duplicate_check_fields @@ -748,41 +742,41 @@ private end def validate_no_and_dont_know_disabled_needs_conjunction - if field_83 == 1 && field_84 == 1 - errors.add(:field_83, I18n.t("#{ERROR_BASE_KEY}.housingneeds.no_and_dont_know_disabled_needs_conjunction")) - errors.add(:field_84, I18n.t("#{ERROR_BASE_KEY}.housingneeds.no_and_dont_know_disabled_needs_conjunction")) + if field_98 == 1 && field_99 == 1 + errors.add(:field_98, I18n.t("#{ERROR_BASE_KEY}.housingneeds.no_and_dont_know_disabled_needs_conjunction")) + errors.add(:field_99, I18n.t("#{ERROR_BASE_KEY}.housingneeds.no_and_dont_know_disabled_needs_conjunction")) end end def validate_dont_know_disabled_needs_conjunction - if field_84 == 1 && [field_79, field_80, field_81, field_82].count(1).positive? - %i[field_84 field_79 field_80 field_81 field_82].each do |field| + if field_99 == 1 && [field_94, field_95, field_96, field_97].count(1).positive? + %i[field_99 field_94 field_95 field_96 field_97].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.housingneeds.dont_know_disabled_needs_conjunction")) if send(field) == 1 end end end def validate_no_disabled_needs_conjunction - if field_83 == 1 && [field_79, field_80, field_81, field_82].count(1).positive? - %i[field_83 field_79 field_80 field_81 field_82].each do |field| + if field_98 == 1 && [field_94, field_95, field_96, field_97].count(1).positive? + %i[field_98 field_94 field_95 field_96 field_97].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.housingneeds.no_disabled_needs_conjunction")) if send(field) == 1 end end end def validate_only_one_housing_needs_type - if [field_79, field_80, field_81].count(1) > 1 - %i[field_79 field_80 field_81].each do |field| + if [field_94, field_95, field_96].count(1) > 1 + %i[field_94 field_95 field_96].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.housingneeds_type.only_one_option_permitted")) if send(field) == 1 end end end def validate_no_housing_needs_questions_answered - if [field_79, field_80, field_81, field_82, field_83, field_84].all?(&:blank?) - errors.add(:field_83, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "anybody with disabled access needs.")) - errors.add(:field_82, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "other access needs.")) - %i[field_79 field_80 field_81].each do |field| + if [field_94, field_95, field_96, field_97, field_98, field_99].all?(&:blank?) + errors.add(:field_98, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "anybody with disabled access needs.")) + errors.add(:field_97, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "other access needs.")) + %i[field_94 field_95 field_96].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "disabled access needs type.")) end end @@ -790,16 +784,16 @@ private def validate_reasonable_preference_dont_know if rp_dontknow_conflict? - errors.add(:field_111, I18n.t("#{ERROR_BASE_KEY}.reasonpref.conflict.dont_know")) - %i[field_107 field_108 field_109 field_110].each do |field| + errors.add(:field_126, I18n.t("#{ERROR_BASE_KEY}.reasonpref.conflict.dont_know")) + %i[field_122 field_123 field_124 field_125].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.reasonpref.conflict.other")) if send(field) == 1 end end end def validate_reasonable_preference_homeless - reason_fields = %i[field_107 field_108 field_109 field_110 field_111] - if field_106 == 1 && reason_fields.all? { |field| attributes[field.to_s].blank? } + reason_fields = %i[field_122 field_123 field_124 field_125 field_126] + if field_121 == 1 && reason_fields.all? { |field| attributes[field.to_s].blank? } reason_fields.each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "reason for reasonable preference.")) end @@ -807,7 +801,7 @@ private end def validate_condition_effects - illness_option_fields = %i[field_94 field_88 field_91 field_86 field_87 field_89 field_90 field_93 field_92 field_95] + illness_option_fields = %i[field_109 field_103 field_106 field_101 field_102 field_104 field_105 field_108 field_107 field_110] if household_no_illness? illness_option_fields.each do |field| if attributes[field.to_s] == 1 @@ -822,12 +816,12 @@ private end def household_no_illness? - field_85 != 1 + field_100 != 1 end def validate_leaving_reason_for_renewal - if renewal? && ![50, 51, 52, 53].include?(field_98) - errors.add(:field_98, I18n.t("#{ERROR_BASE_KEY}.reason.renewal_reason_needed")) + if renewal? && ![50, 51, 52, 53].include?(field_113) + errors.add(:field_113, I18n.t("#{ERROR_BASE_KEY}.reason.renewal_reason_needed")) end end @@ -968,13 +962,13 @@ private end def validate_all_charges_given - return if supported_housing? && field_124 == 1 + return if supported_housing? && field_142 == 1 blank_charge_fields, other_charge_fields = { - field_124: "basic rent", - field_125: "service charge", - field_126: "personal service charge", - field_127: "support charge", + field_142: "basic rent", + field_143: "service charge", + field_144: "personal service charge", + field_145: "support charge", }.partition { |field, _| public_send(field).blank? }.map(&:to_h) blank_charge_fields.each do |field, charge| @@ -990,7 +984,7 @@ private end def all_charges_given? - field_124.present? && field_125.present? && field_126.present? && field_127.present? + field_142.present? && field_143.present? && field_144.present? && field_145.present? end def setup_question?(question) @@ -1011,52 +1005,52 @@ private errors.add(:field_23, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing errors.add(:field_24, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing errors.add(:field_25, error_message) unless supported_housing? # la # TODO: CLDC-4119: add LA to error fields for supported housing - errors.add(:field_42, error_message) # age1 - errors.add(:field_43, error_message) # sexrab1 - errors.add(:field_46, error_message) # ecstat1 - errors.add(:field_122, error_message) unless general_needs? # household_charge - errors.add(:field_124, error_message) # brent - errors.add(:field_125, error_message) # scharge - errors.add(:field_126, error_message) # pscharge - errors.add(:field_127, error_message) # chcharge + errors.add(:field_41, error_message) # age1 + errors.add(:field_42, error_message) # sexrab1 + errors.add(:field_47, error_message) # ecstat1 + errors.add(:field_140, error_message) unless general_needs? # household_charge + errors.add(:field_142, error_message) # brent + errors.add(:field_143, error_message) # scharge + errors.add(:field_144, error_message) # pscharge + errors.add(:field_145, error_message) # chcharge end end def field_referral_register_la_valid? if owning_organisation&.la? - [1, 2, 3, 4].include?(field_116) + [1, 2, 3, 4].include?(field_131) else - field_116.blank? + field_131.blank? end end def field_referral_register_prp_valid? if owning_organisation&.prp? - [5, 6, 7, 8, 9].include?(field_146) + [5, 6, 7, 8, 9].include?(field_132) else - field_146.blank? + field_132.blank? end end def field_referral_noms_valid? - case field_146 + case field_132 when 6 - [1, 2, 3, 4].include?(field_147) + [1, 2, 3, 4].include?(field_133) when 7 - [5, 6, 7, 8].include?(field_147) + [5, 6, 7, 8].include?(field_133) else - field_147.blank? + field_133.blank? end end def field_referral_org_valid? - case field_147 + case field_133 when 1 - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].include?(field_148) + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].include?(field_134) when 7 - [11, 12, 13, 14, 15, 16, 17, 18, 19, 20].include?(field_148) + [11, 12, 13, 14, 15, 16, 17, 18, 19, 20].include?(field_134) else - field_148.blank? + field_134.blank? end end @@ -1068,7 +1062,7 @@ private return if renewal? return if referral_fields_valid? - %i[field_116 field_146 field_147 field_148].each do |field| + %i[field_131 field_132 field_133 field_134].each do |field| errors.add(field, I18n.t("#{ERROR_BASE_KEY}.referral.invalid_option")) end end @@ -1094,140 +1088,139 @@ private rent_type: %i[field_11], startdate: %i[field_8 field_9 field_10], unittype_gn: %i[field_26], - builtype: %i[field_27], - wchair: %i[field_28], - beds: %i[field_29], - joint: %i[field_37], - startertenancy: %i[field_38], - tenancy: %i[field_39], - tenancyother: %i[field_40], - tenancylength: %i[field_41], + wchair: %i[field_27], + beds: %i[field_28], + joint: %i[field_36], + startertenancy: %i[field_37], + tenancy: %i[field_38], + tenancyother: %i[field_39], + tenancylength: %i[field_40], declaration: %i[field_15], - age1_known: %i[field_42], - age1: %i[field_42], + age1_known: %i[field_41], + age1: %i[field_41], age2_known: %i[field_48], age2: %i[field_48], - age3_known: %i[field_52], - age3: %i[field_52], - age4_known: %i[field_56], - age4: %i[field_56], - age5_known: %i[field_60], - age5: %i[field_60], - age6_known: %i[field_64], - age6: %i[field_64], - age7_known: %i[field_68], - age7: %i[field_68], - age8_known: %i[field_72], - age8: %i[field_72], - - sexrab1: %i[field_43], - sexrab2: %i[field_49], - sexrab3: %i[field_53], - sexrab4: %i[field_57], - sexrab5: %i[field_61], - sexrab6: %i[field_65], - sexrab7: %i[field_69], - sexrab8: %i[field_73], - - ethnic_group: %i[field_44], - ethnic: %i[field_44], - nationality_all: %i[field_45], - nationality_all_group: %i[field_45], - - relat2: %i[field_47], - relat3: %i[field_51], - relat4: %i[field_55], - relat5: %i[field_59], - relat6: %i[field_63], - relat7: %i[field_67], - relat8: %i[field_71], - - ecstat1: %i[field_46], - ecstat2: %i[field_50], - ecstat3: %i[field_54], - ecstat4: %i[field_58], - ecstat5: %i[field_62], - ecstat6: %i[field_66], - ecstat7: %i[field_70], - ecstat8: %i[field_74], - - armedforces: %i[field_75], - leftreg: %i[field_76], - reservist: %i[field_77], - preg_occ: %i[field_78], - housingneeds: %i[field_78], - - illness: %i[field_85], - - layear: %i[field_96], - waityear: %i[field_97], - reason: %i[field_98], - reasonother: %i[field_99], - prevten: %i[field_100], - homeless: %i[field_101], - - prevloc: %i[field_105], - previous_la_known: %i[field_105], - ppcodenk: %i[field_102], - ppostcode_full: %i[field_103 field_104], - - reasonpref: %i[field_106], - rp_homeless: %i[field_107], - rp_insan_unsat: %i[field_108], - rp_medwel: %i[field_109], - rp_hardship: %i[field_110], - rp_dontknow: %i[field_111], - - cbl: %i[field_112], - cap: %i[field_113], - chr: %i[field_114], - accessible_register: %i[field_115], - letting_allocation: %i[field_112 field_113 field_114 field_115], - - referral_register: %i[field_116 field_146], - referral_noms: %i[field_147], - referral_org: %i[field_148], - - net_income_known: %i[field_117], - incfreq: %i[field_118], - earnings: %i[field_119], - hb: %i[field_120], - benefits: %i[field_121], - - period: %i[field_123], - brent: %i[field_124], - scharge: %i[field_125], - pscharge: %i[field_126], - supcharg: %i[field_127], - tcharge: %i[field_124 field_125 field_126 field_127], - household_charge: %i[field_122], - hbrentshortfall: %i[field_128], - tshortfall: %i[field_129], + age3_known: %i[field_54], + age3: %i[field_54], + age4_known: %i[field_60], + age4: %i[field_60], + age5_known: %i[field_66], + age5: %i[field_66], + age6_known: %i[field_72], + age6: %i[field_72], + age7_known: %i[field_78], + age7: %i[field_78], + age8_known: %i[field_84], + age8: %i[field_84], + + sexrab1: %i[field_42], + sexrab2: %i[field_50], + sexrab3: %i[field_56], + sexrab4: %i[field_62], + sexrab5: %i[field_68], + sexrab6: %i[field_74], + sexrab7: %i[field_80], + sexrab8: %i[field_86], + + ethnic_group: %i[field_45], + ethnic: %i[field_45], + nationality_all: %i[field_46], + nationality_all_group: %i[field_46], + + relat2: %i[field_49], + relat3: %i[field_55], + relat4: %i[field_61], + relat5: %i[field_67], + relat6: %i[field_73], + relat7: %i[field_79], + relat8: %i[field_85], + + ecstat1: %i[field_47], + ecstat2: %i[field_53], + ecstat3: %i[field_59], + ecstat4: %i[field_65], + ecstat5: %i[field_71], + ecstat6: %i[field_77], + ecstat7: %i[field_83], + ecstat8: %i[field_89], + + armedforces: %i[field_90], + leftreg: %i[field_91], + reservist: %i[field_92], + preg_occ: %i[field_93], + housingneeds: %i[field_93], + + illness: %i[field_100], + + layear: %i[field_111], + waityear: %i[field_112], + reason: %i[field_113], + reasonother: %i[field_114], + prevten: %i[field_115], + homeless: %i[field_116], + + prevloc: %i[field_120], + previous_la_known: %i[field_120], + ppcodenk: %i[field_117], + ppostcode_full: %i[field_118 field_119], + + reasonpref: %i[field_121], + rp_homeless: %i[field_122], + rp_insan_unsat: %i[field_123], + rp_medwel: %i[field_124], + rp_hardship: %i[field_125], + rp_dontknow: %i[field_126], + + cbl: %i[field_127], + cap: %i[field_128], + chr: %i[field_129], + accessible_register: %i[field_130], + letting_allocation: %i[field_127 field_128 field_129 field_130], + + referral_register: %i[field_131 field_132], + referral_noms: %i[field_133], + referral_org: %i[field_134], + + net_income_known: %i[field_135], + incfreq: %i[field_136], + earnings: %i[field_137], + hb: %i[field_138], + benefits: %i[field_139], + + period: %i[field_141], + brent: %i[field_142], + scharge: %i[field_143], + pscharge: %i[field_144], + supcharg: %i[field_145], + tcharge: %i[field_142 field_143 field_144 field_145], + household_charge: %i[field_140], + hbrentshortfall: %i[field_146], + tshortfall: %i[field_147], unitletas: %i[field_17], rsnvac: %i[field_16], - sheltered: %i[field_36], - - illness_type_1: %i[field_94], - illness_type_2: %i[field_88], - illness_type_3: %i[field_91], - illness_type_4: %i[field_86], - illness_type_5: %i[field_87], - illness_type_6: %i[field_89], - illness_type_7: %i[field_90], - illness_type_8: %i[field_93], - illness_type_9: %i[field_92], - illness_type_10: %i[field_95], + sheltered: %i[field_35], + + illness_type_1: %i[field_109], + illness_type_2: %i[field_103], + illness_type_3: %i[field_106], + illness_type_4: %i[field_101], + illness_type_5: %i[field_102], + illness_type_6: %i[field_104], + illness_type_7: %i[field_105], + illness_type_8: %i[field_108], + illness_type_9: %i[field_107], + illness_type_10: %i[field_110], irproduct_other: %i[field_12], propcode: %i[field_14], - majorrepairs: %i[field_33 field_34 field_35], - mrcdate: %i[field_33 field_34 field_35], + majorrepairs: %i[field_32 field_33 field_34], + mrcdate: %i[field_32 field_33 field_34], - voiddate: %i[field_30 field_31 field_32], + voiddate: %i[field_29 field_30 field_31], uprn: [:field_18], address_line1: [:field_19], @@ -1236,22 +1229,23 @@ private county: [:field_22], uprn_selection: [:field_19], - gender_same_as_sex1: %i[field_130], - gender_same_as_sex2: %i[field_132], - gender_same_as_sex3: %i[field_134], - gender_same_as_sex4: %i[field_136], - gender_same_as_sex5: %i[field_138], - gender_same_as_sex6: %i[field_140], - gender_same_as_sex7: %i[field_142], - gender_same_as_sex8: %i[field_144], - gender_description1: %i[field_131], - gender_description2: %i[field_133], - gender_description3: %i[field_135], - gender_description4: %i[field_137], - gender_description5: %i[field_139], - gender_description6: %i[field_141], - gender_description7: %i[field_143], - gender_description8: %i[field_145], + gender_same_as_sex1: %i[field_43], + gender_description1: %i[field_44], + gender_same_as_sex2: %i[field_51], + gender_description2: %i[field_52], + gender_same_as_sex3: %i[field_57], + gender_description3: %i[field_58], + gender_same_as_sex4: %i[field_63], + gender_description4: %i[field_64], + gender_same_as_sex5: %i[field_69], + gender_description5: %i[field_70], + gender_same_as_sex6: %i[field_75], + gender_description6: %i[field_76], + gender_same_as_sex7: %i[field_81], + gender_description7: %i[field_82], + gender_same_as_sex8: %i[field_87], + gender_description8: %i[field_88], + }.compact end @@ -1279,70 +1273,69 @@ private attributes["rent_type"] = RENT_TYPE_BU_MAPPING[field_11] attributes["startdate"] = startdate attributes["unittype_gn"] = field_26 - attributes["builtype"] = field_27 - attributes["wchair"] = field_28 - attributes["beds"] = field_26 == 2 ? 1 : field_29 - attributes["joint"] = field_37 - attributes["startertenancy"] = field_38 - attributes["tenancy"] = field_39 - attributes["tenancyother"] = field_40 - attributes["tenancylength"] = field_41 + attributes["wchair"] = field_27 + attributes["beds"] = field_26 == 2 ? 1 : field_28 + attributes["joint"] = field_36 + attributes["startertenancy"] = field_37 + attributes["tenancy"] = field_38 + attributes["tenancyother"] = field_39 + attributes["tenancylength"] = field_40 attributes["declaration"] = field_15 attributes["age1_known"] = age1_known? - attributes["age1"] = field_42 if attributes["age1_known"]&.zero? && field_42&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age1"] = field_41 if attributes["age1_known"]&.zero? && field_41&.match(/\A\d{1,3}\z|\AR\z/) attributes["age2_known"] = age2_known? attributes["age2"] = field_48 if attributes["age2_known"]&.zero? && field_48&.match(/\A\d{1,3}\z|\AR\z/) attributes["age3_known"] = age3_known? - attributes["age3"] = field_52 if attributes["age3_known"]&.zero? && field_52&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age3"] = field_54 if attributes["age3_known"]&.zero? && field_54&.match(/\A\d{1,3}\z|\AR\z/) attributes["age4_known"] = age4_known? - attributes["age4"] = field_56 if attributes["age4_known"]&.zero? && field_56&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age4"] = field_60 if attributes["age4_known"]&.zero? && field_60&.match(/\A\d{1,3}\z|\AR\z/) attributes["age5_known"] = age5_known? - attributes["age5"] = field_60 if attributes["age5_known"]&.zero? && field_60&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age5"] = field_66 if attributes["age5_known"]&.zero? && field_66&.match(/\A\d{1,3}\z|\AR\z/) attributes["age6_known"] = age6_known? - attributes["age6"] = field_64 if attributes["age6_known"]&.zero? && field_64&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age6"] = field_72 if attributes["age6_known"]&.zero? && field_72&.match(/\A\d{1,3}\z|\AR\z/) attributes["age7_known"] = age7_known? - attributes["age7"] = field_68 if attributes["age7_known"]&.zero? && field_68&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age7"] = field_78 if attributes["age7_known"]&.zero? && field_78&.match(/\A\d{1,3}\z|\AR\z/) attributes["age8_known"] = age8_known? - attributes["age8"] = field_72 if attributes["age8_known"]&.zero? && field_72&.match(/\A\d{1,3}\z|\AR\z/) + attributes["age8"] = field_84 if attributes["age8_known"]&.zero? && field_84&.match(/\A\d{1,3}\z|\AR\z/) - attributes["sexrab1"] = field_43 - attributes["sexrab2"] = field_49 - attributes["sexrab3"] = field_53 - attributes["sexrab4"] = field_57 - attributes["sexrab5"] = field_61 - attributes["sexrab6"] = field_65 - attributes["sexrab7"] = field_69 - attributes["sexrab8"] = field_73 + attributes["sexrab1"] = field_42 + attributes["sexrab2"] = field_50 + attributes["sexrab3"] = field_56 + attributes["sexrab4"] = field_62 + attributes["sexrab5"] = field_68 + attributes["sexrab6"] = field_74 + attributes["sexrab7"] = field_80 + attributes["sexrab8"] = field_86 attributes["ethnic_group"] = ethnic_group_from_ethnic - attributes["ethnic"] = field_44 - attributes["nationality_all"] = field_45 if field_45.present? && valid_nationality_options.include?(field_45.to_s) + attributes["ethnic"] = field_45 + attributes["nationality_all"] = field_46 if field_46.present? && valid_nationality_options.include?(field_46.to_s) attributes["nationality_all_group"] = nationality_group(attributes["nationality_all"]) - attributes["relat2"] = relationship_from_input_value(field_47) - attributes["relat3"] = relationship_from_input_value(field_51) - attributes["relat4"] = relationship_from_input_value(field_55) - attributes["relat5"] = relationship_from_input_value(field_59) - attributes["relat6"] = relationship_from_input_value(field_63) - attributes["relat7"] = relationship_from_input_value(field_67) - attributes["relat8"] = relationship_from_input_value(field_71) - - attributes["ecstat1"] = field_46 - attributes["ecstat2"] = field_50 - attributes["ecstat3"] = field_54 - attributes["ecstat4"] = field_58 - attributes["ecstat5"] = field_62 - attributes["ecstat6"] = field_66 - attributes["ecstat7"] = field_70 - attributes["ecstat8"] = field_74 + attributes["relat2"] = relationship_from_input_value(field_49) + attributes["relat3"] = relationship_from_input_value(field_55) + attributes["relat4"] = relationship_from_input_value(field_61) + attributes["relat5"] = relationship_from_input_value(field_67) + attributes["relat6"] = relationship_from_input_value(field_73) + attributes["relat7"] = relationship_from_input_value(field_79) + attributes["relat8"] = relationship_from_input_value(field_85) + + attributes["ecstat1"] = field_47 + attributes["ecstat2"] = field_53 + attributes["ecstat3"] = field_59 + attributes["ecstat4"] = field_65 + attributes["ecstat5"] = field_71 + attributes["ecstat6"] = field_77 + attributes["ecstat7"] = field_83 + attributes["ecstat8"] = field_89 attributes["details_known_2"] = details_known?(2) attributes["details_known_3"] = details_known?(3) @@ -1352,36 +1345,36 @@ private attributes["details_known_7"] = details_known?(7) attributes["details_known_8"] = details_known?(8) - attributes["armedforces"] = field_75 + attributes["armedforces"] = field_90 attributes["leftreg"] = leftreg - attributes["reservist"] = field_77 + attributes["reservist"] = field_92 - attributes["preg_occ"] = field_78 + attributes["preg_occ"] = field_93 attributes["housingneeds"] = housingneeds attributes["housingneeds_type"] = housingneeds_type attributes["housingneeds_other"] = housingneeds_other - attributes["illness"] = field_85 + attributes["illness"] = field_100 - attributes["layear"] = field_96 - attributes["waityear"] = field_97 - attributes["reason"] = field_98 - attributes["reasonother"] = field_99 if reason_is_other? - attributes["prevten"] = field_100 - attributes["homeless"] = field_101 + attributes["layear"] = field_111 + attributes["waityear"] = field_112 + attributes["reason"] = field_113 + attributes["reasonother"] = field_114 if reason_is_other? + attributes["prevten"] = field_115 + attributes["homeless"] = field_116 attributes["prevloc"] = prevloc attributes["previous_la_known"] = previous_la_known attributes["ppcodenk"] = ppcodenk attributes["ppostcode_full"] = ppostcode_full - attributes["reasonpref"] = field_106 - attributes["rp_homeless"] = field_107 unless rp_dontknow_conflict? - attributes["rp_insan_unsat"] = field_108 unless rp_dontknow_conflict? - attributes["rp_medwel"] = field_109 unless rp_dontknow_conflict? - attributes["rp_hardship"] = field_110 unless rp_dontknow_conflict? - attributes["rp_dontknow"] = field_111 unless rp_dontknow_conflict? + attributes["reasonpref"] = field_121 + attributes["rp_homeless"] = field_122 unless rp_dontknow_conflict? + attributes["rp_insan_unsat"] = field_123 unless rp_dontknow_conflict? + attributes["rp_medwel"] = field_124 unless rp_dontknow_conflict? + attributes["rp_hardship"] = field_125 unless rp_dontknow_conflict? + attributes["rp_dontknow"] = field_126 unless rp_dontknow_conflict? attributes["cbl"] = cbl attributes["chr"] = chr @@ -1395,36 +1388,36 @@ private attributes["net_income_known"] = net_income_known attributes["earnings"] = earnings - attributes["incfreq"] = field_118 - attributes["hb"] = field_120 - attributes["benefits"] = field_121 - - attributes["period"] = field_123 - attributes["brent"] = field_124 if all_charges_given? - attributes["scharge"] = field_125 if all_charges_given? - attributes["pscharge"] = field_126 if all_charges_given? - attributes["supcharg"] = field_127 if all_charges_given? - attributes["household_charge"] = supported_housing? ? field_122 : nil - attributes["hbrentshortfall"] = field_128 + attributes["incfreq"] = field_136 + attributes["hb"] = field_138 + attributes["benefits"] = field_139 + + attributes["period"] = field_141 + attributes["brent"] = field_142 if all_charges_given? + attributes["scharge"] = field_143 if all_charges_given? + attributes["pscharge"] = field_144 if all_charges_given? + attributes["supcharg"] = field_145 if all_charges_given? + attributes["household_charge"] = supported_housing? ? field_140 : nil + attributes["hbrentshortfall"] = field_146 attributes["tshortfall_known"] = tshortfall_known - attributes["tshortfall"] = field_129 + attributes["tshortfall"] = field_147 attributes["hhmemb"] = hhmemb attributes["unitletas"] = field_17 attributes["rsnvac"] = rsnvac - attributes["sheltered"] = field_36 - - attributes["illness_type_1"] = field_94 - attributes["illness_type_2"] = field_88 - attributes["illness_type_3"] = field_91 - attributes["illness_type_4"] = field_86 - attributes["illness_type_5"] = field_87 - attributes["illness_type_6"] = field_89 - attributes["illness_type_7"] = field_90 - attributes["illness_type_8"] = field_93 - attributes["illness_type_9"] = field_92 - attributes["illness_type_10"] = field_95 + attributes["sheltered"] = field_35 + + attributes["illness_type_1"] = field_109 + attributes["illness_type_2"] = field_103 + attributes["illness_type_3"] = field_106 + attributes["illness_type_4"] = field_101 + attributes["illness_type_5"] = field_102 + attributes["illness_type_6"] = field_104 + attributes["illness_type_7"] = field_105 + attributes["illness_type_8"] = field_108 + attributes["illness_type_9"] = field_107 + attributes["illness_type_10"] = field_110 attributes["irproduct_other"] = field_12 if RENT_TYPE_BU_MAPPING[field_11] == 5 @@ -1459,22 +1452,22 @@ private attributes["postcode_full_input"] = postcode_full attributes["select_best_address_match"] = true if field_18.blank? - attributes["gender_same_as_sex1"] = field_130 - attributes["gender_description1"] = field_131 - attributes["gender_same_as_sex2"] = field_132 - attributes["gender_description2"] = field_133 - attributes["gender_same_as_sex3"] = field_134 - attributes["gender_description3"] = field_135 - attributes["gender_same_as_sex4"] = field_136 - attributes["gender_description4"] = field_137 - attributes["gender_same_as_sex5"] = field_138 - attributes["gender_description5"] = field_139 - attributes["gender_same_as_sex6"] = field_140 - attributes["gender_description6"] = field_141 - attributes["gender_same_as_sex7"] = field_142 - attributes["gender_description7"] = field_143 - attributes["gender_same_as_sex8"] = field_144 - attributes["gender_description8"] = field_145 + attributes["gender_same_as_sex1"] = field_43 + attributes["gender_description1"] = field_44 + attributes["gender_same_as_sex2"] = field_51 + attributes["gender_description2"] = field_52 + attributes["gender_same_as_sex3"] = field_57 + attributes["gender_description3"] = field_58 + attributes["gender_same_as_sex4"] = field_63 + attributes["gender_description4"] = field_64 + attributes["gender_same_as_sex5"] = field_69 + attributes["gender_description5"] = field_70 + attributes["gender_same_as_sex6"] = field_75 + attributes["gender_description6"] = field_76 + attributes["gender_same_as_sex7"] = field_81 + attributes["gender_description7"] = field_82 + attributes["gender_same_as_sex8"] = field_87 + attributes["gender_description8"] = field_88 attributes end @@ -1538,9 +1531,9 @@ private end def ethnic_group_from_ethnic - return nil if field_44.blank? + return nil if field_45.blank? - case field_44 + case field_45 when 1, 2, 3, 18, 20 0 when 4, 5, 6, 7 @@ -1557,19 +1550,19 @@ private end def age1_known? - return 1 if field_42 == "R" + return 1 if field_41 == "R" 0 end [ { person: 2, field: :field_48 }, - { person: 3, field: :field_52 }, - { person: 4, field: :field_56 }, - { person: 5, field: :field_60 }, - { person: 6, field: :field_64 }, - { person: 7, field: :field_68 }, - { person: 8, field: :field_72 }, + { person: 3, field: :field_54 }, + { person: 4, field: :field_60 }, + { person: 5, field: :field_66 }, + { person: 6, field: :field_72 }, + { person: 7, field: :field_78 }, + { person: 8, field: :field_84 }, ].each do |hash| define_method("age#{hash[:person]}_known?") do return 1 if public_send(hash[:field]) == "R" @@ -1583,53 +1576,53 @@ private end def person_2_present? - field_47.present? || field_48.present? || field_49.present? || field_132.present? || field_133.present? + field_49.present? || field_48.present? || field_50.present? || field_51.present? || field_52.present? end def person_3_present? - field_51.present? || field_52.present? || field_53.present? || field_134.present? || field_135.present? + field_55.present? || field_54.present? || field_56.present? || field_57.present? || field_58.present? end def person_4_present? - field_55.present? || field_56.present? || field_57.present? || field_136.present? || field_137.present? + field_61.present? || field_60.present? || field_62.present? || field_63.present? || field_64.present? end def person_5_present? - field_59.present? || field_60.present? || field_61.present? || field_138.present? || field_139.present? + field_67.present? || field_66.present? || field_68.present? || field_69.present? || field_70.present? end def person_6_present? - field_63.present? || field_64.present? || field_65.present? || field_140.present? || field_141.present? + field_73.present? || field_72.present? || field_74.present? || field_75.present? || field_76.present? end def person_7_present? - field_67.present? || field_68.present? || field_69.present? || field_142.present? || field_143.present? + field_79.present? || field_78.present? || field_80.present? || field_81.present? || field_82.present? end def person_8_present? - field_71.present? || field_72.present? || field_73.present? || field_144.present? || field_145.present? + field_85.present? || field_84.present? || field_86.present? || field_87.present? || field_88.present? end def leftreg - field_76 + field_91 end def housingneeds - if field_83 == 1 + if field_98 == 1 2 - elsif field_84 == 1 + elsif field_99 == 1 3 - elsif field_83.blank? || field_83&.zero? + elsif field_98.blank? || field_98&.zero? 1 end end def housingneeds_type - if field_79 == 1 + if field_94 == 1 0 - elsif field_80 == 1 + elsif field_95 == 1 1 - elsif field_81 == 1 + elsif field_96 == 1 2 else 3 @@ -1637,13 +1630,13 @@ private end def housingneeds_other - return 1 if field_82 == 1 + return 1 if field_97 == 1 - 0 if [field_79, field_80, field_81].include?(1) + 0 if [field_94, field_95, field_96].include?(1) end def prevloc - field_105 + field_120 end def previous_la_known @@ -1651,7 +1644,7 @@ private end def ppcodenk - case field_102 + case field_117 when 1 0 when 2 @@ -1660,11 +1653,11 @@ private end def ppostcode_full - "#{field_103} #{field_104}".strip.gsub(/\s+/, " ") + "#{field_118} #{field_119}".strip.gsub(/\s+/, " ") end def cbl - case field_112 + case field_127 when 2 0 when 1 @@ -1673,7 +1666,7 @@ private end def cap - case field_113 + case field_128 when 2 0 when 1 @@ -1682,7 +1675,7 @@ private end def chr - case field_114 + case field_129 when 2 0 when 1 @@ -1691,7 +1684,7 @@ private end def accessible_register - case field_115 + case field_130 when 2 0 when 1 @@ -1704,7 +1697,7 @@ private end def net_income_known - case field_117 + case field_135 when 1 0 when 2 @@ -1715,11 +1708,11 @@ private end def earnings - field_119.presence&.round + field_137.presence&.round end def tshortfall_known - field_128 == 1 ? 0 : 1 + field_146 == 1 ? 0 : 1 end def hhmemb @@ -1739,15 +1732,15 @@ private end def mrcdate - year = field_35.to_s.strip.length.between?(1, 2) ? field_35 + 2000 : field_35 - Date.new(year, field_34, field_33) if field_35.present? && field_34.present? && field_33.present? + year = field_34.to_s.strip.length.between?(1, 2) ? field_34 + 2000 : field_34 + Date.new(year, field_33, field_32) if field_34.present? && field_33.present? && field_32.present? rescue Date::Error Date.new end def voiddate - year = field_32.to_s.strip.length.between?(1, 2) ? field_32 + 2000 : field_32 - Date.new(year, field_31, field_30) if field_32.present? && field_31.present? && field_30.present? + year = field_31.to_s.strip.length.between?(1, 2) ? field_31 + 2000 : field_31 + Date.new(year, field_30, field_29) if field_31.present? && field_30.present? && field_29.present? rescue Date::Error Date.new end @@ -1774,7 +1767,7 @@ private end def reason_is_other? - field_98 == 20 + field_113 == 20 end def bulk_upload_organisation @@ -1793,10 +1786,10 @@ private end def rp_dontknow_conflict? - other_reason_fields = %i[field_107 field_108 field_109 field_110] - if field_106 == 1 + other_reason_fields = %i[field_122 field_123 field_124 field_125] + if field_121 == 1 selected_reasons = other_reason_fields.select { |field| send(field) == 1 } - dont_know_selected = field_111 == 1 + dont_know_selected = field_126 == 1 return true if selected_reasons.any? && dont_know_selected end @@ -1812,9 +1805,9 @@ private return unless referral_fields_valid? if owning_organisation.la? - field_116 + field_131 else - field_146 + field_132 end end @@ -1823,7 +1816,7 @@ private return unless referral_fields_valid? if owning_organisation.prp? - field_147 + field_133 end end @@ -1832,7 +1825,7 @@ private return unless referral_fields_valid? if owning_organisation.prp? - field_148 + field_134 end end end diff --git a/spec/fixtures/files/2026_27_lettings_bulk_upload.csv b/spec/fixtures/files/2026_27_lettings_bulk_upload.csv index 4b66a7c71..2c9d3dc38 100644 --- a/spec/fixtures/files/2026_27_lettings_bulk_upload.csv +++ b/spec/fixtures/files/2026_27_lettings_bulk_upload.csv @@ -1,5 +1,5 @@ -Section,Setting up this lettings log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What scheme does this letting belong to?,Which location is this letting for?,Is this letting a renewal of social housing to the same tenant in the same property?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,What is the rent type?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,Has the tenant seen or been given access to the MHCLG privacy notice?,What is the reason for the property being vacant?,What type was the property most recently let as?,"If known, provide this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type of unit is the property?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this property older people's housing?,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,What is the lead tenant’s age?,Which of these best describes the lead tenant’s gender identity? ,Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,Is person 2 the partner of the lead tenant?,What is person 2's age?,Which of these best describes person 2's gender identity?,Which of these best describes person 2's working situation?,Is person 3 the partner of the lead tenant?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3's working situation?,Is person 4 the partner of the lead tenant?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4's working situation?,Is person 5 the partner of the lead tenant?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5's working situation?,Is person 6 the partner of the lead tenant?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6's working situation?,Is person 7 the partner of the lead tenant?,What is person 7's age?,Which of these best describes person 7's gender identity?,Which of these best describes person 7's working situation?,Is person 8 the partner of the lead tenant?,What is person 8's age?,Which of these best describes person 8's gender identity?,Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs +Section,Setting up this lettings log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,, +Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What scheme does this letting belong to?,Which location is this letting for?,Is this letting a renewal of social housing to the same tenant in the same property?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,What is the rent type?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,Has the tenant seen or been given access to the MHCLG privacy notice?,What is the reason for the property being vacant?,What type was the property most recently let as?,"If known, provide this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type of unit is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this property older people's housing?,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,What is the lead tenant’s age?,What is the lead tenant's sex?,Is the gender the lead tenant identifies with the same as their sex registered at birth?,"If 'No', enter lead tenant's gender identity",Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,What is person 2's age?,Is person 2 the partner of the lead tenant?,What is person 2's sex?,Is the gender the person 2 identifies with the same as their sex registered at birth?,"If 'No', enter person 2's gender identity",Which of these best describes person 2's working situation?,What is person 3's age?,Is person 3 the partner of the lead tenant?,What is person 3's sex?,Is the gender the person 3 identifies with the same as their sex registered at birth?,"If 'No', enter person 3's gender identity",Which of these best describes person 3's working situation?,What is person 4's age?,Is person 4 the partner of the lead tenant?,What is person 4's sex?,Is the gender the person 4 identifies with the same as their sex registered at birth?,"If 'No', enter person 4's gender identity",Which of these best describes person 4's working situation?,What is person 5's age?,Is person 5 the partner of the lead tenant?,What is person 5's sex?,Is the gender the person 5 identifies with the same as their sex registered at birth?,"If 'No', enter person 5's gender identity",Which of these best describes person 5's working situation?,What is person 6's age?,Is person 6 the partner of the lead tenant?,What is person 6's sex?,Is the gender the person 6 identifies with the same as their sex registered at birth?,"If 'No', enter person 6's gender identity",Which of these best describes person 6's working situation?,What is person 7's age?,Is person 7 the partner of the lead tenant?,What is person 7's sex?,Is the gender the person 7 identifies with the same as their sex registered at birth?,"If 'No', enter person 7's gender identity",Which of these best describes person 7's working situation?,What is person 8's age?,Is person 8 the partner of the lead tenant?,What is person 8's sex?,Is the gender the person 8 identifies with the same as their sex registered at birth?,"If 'No', enter person 8's gender identity",Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs a) Fully wheelchair-accessible housing","Disabled access needs @@ -29,59 +29,63 @@ Common Allocations Policy (CAP)","How was this letting allocated? Common Housing Register (CHR)","How was this letting allocated? -Accessible Housing Register",What was the source of referral for this letting? - LA properties,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be?,What was the lead tenant's sex at birth?,What was person 2's sex registered at birth?,What was person 3's sex registered at birth?,What was person 4's sex registered at birth?,What was person 5's sex registered at birth?,What was person 6's sex registered at birth?,What was person 7's sex registered at birth?,What was person 8's sex registered at birth?,Is the gender the lead tenant identifies with the same as their sex registered at birth?,"If 'No', enter lead tenant's gender identity",Is the gender the person 2 identifies with the same as their sex registered at birth?,"If 'No', enter person 2's gender identity",Is the gender the person 3 identifies with the same as their sex registered at birth?,"If 'No', enter person 3's gender identity",Is the gender the person 4 identifies with the same as their sex registered at birth?,"If 'No', enter person 4's gender identity",Is the gender the person 5 identifies with the same as their sex registered at birth?,"If 'No', enter person 5's gender identity",Is the gender the person 6 identifies with the same as their sex registered at birth?,"If 'No', enter person 6's gender identity",Is the gender the person 7 identifies with the same as their sex registered at birth?,"If 'No', enter person 7's gender identity",Is the gender the person 8 identifies with the same as their sex registered at birth?,"If 'No', enter person 8's gender identity",What was the source of referral for this letting? - PRP properties part 1,What was the source of referral for this letting? - PRP properties part 2,What was the source of referral for this letting? - PRP properties part 3 -Additional info,"You can find the org ID on the CORE service under 'Stock owners' or, if your organisation is the stock owner, under 'About your organisation'","You can find the org ID on the CORE service under 'Managing agents' or, if your organisation is the managing agent, under 'About your organisation'","If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing includes direct access hostels, group homes, residential care and nursing homes.","Scheme code. Include the 'S' at the beginning if it has one. +Accessible Housing Register",What was the source of referral for this letting? - LA properties,What was the source of referral for this letting? - PRP properties part 1,What was the source of referral for this letting? - PRP properties part 2,What was the source of referral for this letting? - PRP properties part 3,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be? +Additional info,"You can find the org ID on the CORE service under 'Stock owners' or, if your organisation is the stock owner, under 'About your organisation'","You can find the org ID on the CORE service under 'Managing agents' or, if your organisation is the managing agent, under 'About your organisation'","If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. + +Supported housing is housing with special design facilities or features targeted at a specific client group requiring support, for example housing designed for older people, sheltered accommodation, extra care housing. It can include direct access hostels, group homes, and purpose-built self-contained housing. We do not require CORE logs for residential care or nursing homes.","Scheme code. Include the 'S' at the beginning if it has one. You can find the scheme code on the CORE service under 'Schemes', either by searching for the specific scheme or downloading a csv.","Location code. You can find the location code on the CORE service under 'Schemes', either by searching for the specific location or downloading a csv.","If the property was previously being used as temporary accommodation, then answer 'no'.",,,,See specification for definitions,,This is how you usually refer to this tenancy on your own systems.,This is how you usually refer to this property on your own systems.,"Make sure the lead tenant has seen or been given access to the Ministry of Housing, Communities and Local Government (MHCLG) privacy notice before completing this log. This is a legal requirement under data protection legislation.","Internal transfer - Where a tenant moved from one social housing property to another property. Their landlord may be the same or may have changed. Renewal of a fixed term tenancy - to the same tenant in the same property, except if was previously used as temporary accommodation.",This is the rent type of the previous tenancy in this property.,"The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example UPRN is 10010457355. -The UPRN may not be the same as the property reference assigned by your organisation.",,,,,Combined with field 22 it should be a postcode which lies within the local authority given in field 25.,Combined with field 21 it should be a postcode which lies within the local authority given in field 25.,,,,"This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property.","If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.","Date the property was (legally/contractually) available to let, or for: +The UPRN may not be the same as the property reference assigned by your organisation.",,,,,Combined with field 22 it should be a postcode which lies within the local authority given in field 25.,Combined with field 21 it should be a postcode which lies within the local authority given in field 25.,,,"This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property.","If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.","Date the property was (legally/contractually) available to let, or for: - re-lets: the day after previous tenant’s contract end - new-builds: the day the landlord legally owned the property ('completion date’) - new conversions or acquisitions: the completion date, or the day after any rehabilitation work ended -- new leases: the day the landlord got contractual property rights, and could let it out to tenants.",,,"Major repairs are works that could not be reasonably carried out with a tenant living at the property. For example, structural repairs.",,,"This includes retirement living, sheltered housing and extra care housing. There is no national set limit for “older people”, please answer based on your own policies. +- new leases: the day the landlord got contractual property rights, and could let it out to tenants.",,,"Major repairs are works which could not reasonably be carried out with a tenant in occupation, and which need to be carried out in a property while it is vacant. They involve remedial works that are necessary for the property to remain habitable and include structural repairs, site works and service installations.",,,"This includes retirement living, sheltered housing and extra care housing. There is no national set limit for “older people”, please answer based on your own policies. Extra care housing is for tenants with medium to high care and support needs, often with 24 hour access to support staff provided by an agency registered with the Care Quality Commission.",This is where two or more people are named on the tenancy agreement.,"If the tenancy has an ‘introductory period’ answer ‘yes’. -You should submit a CORE log at the beginning of the starter tenancy or introductory period, with the best information you have at the time. You do not need to submit a log when a tenant later rolls onto the main tenancy.",This is about the main tenancy after any starter or introductory period. See specification for definitions.,,Do not include the starter or introductory period. The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years.,"This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member.",This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter.","This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member.",,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,Answer 1 for children aged under 1 year old,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"This excludes national service. -If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety","For example, walking short distances or climbing stairs","For example, anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"The tenant's ‘last settled home' is their last long-standing home. For tenants who had temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living previously.",,,,"This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.","Combined with field 104, it should be a postcode which lies within the local authority given in field 105.","Combined with field 103, it should be a postcode which lies within the local authority given in field 105.","This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.",Households may be given ‘reasonable preference’ for social housing under one or more specific category by the local authority. This is also known as ‘priority need’.,,,,,,Where available vacant properties are advertised and applicants are able to bid for specific properties.,Where a common system agreed between a group of housing providers is used to determine applicants' priority for housing.,Where a single waiting list is used by a group of housing providers to receive and process housing applications. Providers may use different approaches to determine priority.,Where the 'access category' or another descriptor of whether an available vacant property meets a range of access needs is displayed to applicants during the allocations process.,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.","This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive.",,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,"This is the amount paid before any charges are added for services (for example, hot water or cleaning). Households may receive housing benefit or Universal Credit towards basic rent.","For example, cleaning. Households may get household benefits towards the service charge.",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any charges made to fund support services included in the tenancy agreement.,Also known as the 'outstanding amount',You only need to give an approximate figure.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,,,,,,,,,,,,,,,,,,, -Values,Alphanumeric,,Email format,01-Feb,Alphanumeric,Numeric,01-Feb,Jan-31,01-Dec,25 - 26,01-Jul,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1,"5 - 6, or 8 - 22",1 - 3 or 5 - 9,Numeric,Alphanumeric,,Text,,"Alphanumeric, +You should submit a CORE log at the beginning of the starter tenancy or introductory period, with the best information you have at the time. You do not need to submit a log when a tenant later rolls onto the main tenancy.",This is about the main tenancy after any starter or introductory period. See specification for definitions.,,Do not include the starter or introductory period. The minimum period is 2 years for social or affordable rent general needs logs. You do not need to submit CORE logs for these types of tenancies if they are shorter than 2 years.,"This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member.",This is their sex as registered at birth. The next question will ask about gender identity.,,,,"If the lead tenant is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, the tenant should decide which country to enter.","This is the household member who does the most paid work. If several people do the same amount of paid work, it's the oldest household member. + +People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".",Answer 1 for children aged under 1 year old,,This is their sex as registered at birth. The next question will ask about gender identity.,,,"People providing informal care for family and friends who are not in formal employment (but may receive carer's allowance) should answer ""Not seeking work"".","This excludes national service. +If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety","For example, walking short distances or climbing stairs","For example, anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"The tenant's ‘last settled home' is their last long-standing home. For tenants who had temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living immediately before that period.",,This is where the household was the night before they moved into this new let.,,"This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.","Combined with field 119, it should be a postcode which lies within the local authority given in field 120.","Combined with field 118, it should be a postcode which lies within the local authority given in field 120.","This is the tenant’s last long-standing home. It is where the tenant was living before any period in temporary accommodation, sleeping rough or otherwise homeless.",Households may be given ‘reasonable preference’ for social housing under one or more specific category by the local authority. This is also known as ‘priority need’.,,,,,,Where available vacant properties are advertised and applicants are able to bid for specific properties.,Where a common system agreed between a group of housing providers is used to determine applicants' priority for housing.,Where a single waiting list is used by a group of housing providers to receive and process housing applications. Providers may use different approaches to determine priority.,Where the 'access category' or another descriptor of whether an available vacant property meets a range of access needs is displayed to applicants during the allocations process.,,,Use code 3 for internal transfers from a housing register had LA involvement (if field 131 = 6) and code 5 for internal transfers from a housing register had no LA involvement (if field 132 = 7).,Use codes 1-10 for lettings from a housing register that had LA involvement (if field 131 = 6) and codes 11-20 for lettings from a housing register with no LA involvement (if field 132= 7).,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.","This is about when the tenant is in their new let. If they are unsure about the situation for their new let and their financial and working situation hasn’t changed significantly, answer based on what housing-related benefits they currently receive.",,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.","Only options relevant to your organisation will be accepted. To change these, go to the 'About your organisation' page or contact the CORE helpdesk.","This is the amount paid before any charges are added for services (for example, hot water or cleaning). Households may receive housing benefit or Universal Credit towards basic rent.","For example, cleaning. Households may get household benefits towards the service charge.",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any charges made to fund support services included in the tenancy agreement.,Also known as the 'outstanding amount',You only need to give an approximate figure. +Values,Alphanumeric,,Email format,1 - 2,Alphanumeric,Numeric,1 - 2,1 - 31,1 - 12,26 - 27,1 - 7,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1,"5 - 6, or 8 - 22",1 - 3 or 5 - 9,Numeric,Alphanumeric,,Text,,"Alphanumeric, 2 - 4 characters","Alphanumeric, - 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ","1 - 2, 4 or 6 - 10",01-Feb,01-Feb,01-Jul,Jan-31,01-Dec,Jun-26,Jan-31,01-Dec,Jun-26,"2 - 4, 7 - 8",01-Mar,01-Feb,02-Aug,Text,"1 - 99, see specification for more detail",16 - 120 or R,"F, M, X or R",Jan-20,"3 digit ISO country code, see specification",0 - 10,01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') + 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ","1 - 2, 4 or 6 - 10",1 - 2,1 - 7,1 - 31,1 - 12,07 - 27,1 - 31,1 - 12,07 - 27,"2 - 4, 7 - 8",1 - 3,1 - 2,2 - 8,Text,"1 - 99, see specification for more detail",16 - 120 or R,"F, M or R",1 - 3,Text,Text,"3 digit ISO country code, see specification",0 - 10,"Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 -Must be 9 if age <16",01-Mar,"Numeric, range 1 - 120 or text (upper case 'R') +Must be 9 if age <16","Numeric, range 1 - 120 or text (upper case 'R') Must be >= 16 if working situation = 1 - 8 or 0 -Must be <16 if working situation = 9","F, M, X or R","0 - 10 - -Must be 9 if age <16",01-Jun,03-Jun,01-Mar,,1 or empty,,,,,,01-Mar,1 or empty,,,,,,,,,,1 - 2 or 6 - 12,2 or 6 - 13,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31, 34 or 44 - 55",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21, 23 - 33, 35, 37 - 39 ",1 or 11,01-Feb,"Alphanumeric, 2 - 4 characters","Alphanumeric, -3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",01-Mar,1 or empty,,,,,01-Feb,,,,,01-Mar,01-Mar,0 - 99999,"1, 3, 6, 9 or 10",01-Apr,0 - 1,01-Oct,xxxx.xx,,,,01-Mar,xxxx.xx,"F, M or R","F, M or R","F, M or R","F, M or R","F, M or R","F, M or R","F, M or R","F, M or R",1 - 3,Text,1 - 3,Text,1 - 3,Text,1 - 3,Text,1 - 3,Text,1 - 3,Text,1 - 3,Text,1 - 3,Text,,, -Can be empty?,No,,Yes,No,"Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1)",No,,,,,"Yes, if letting is not 'Other intermediate rent product' (if field 11 is not 6)",Yes,,No,"Yes, if letting is a renewal (if field 7 = 1)","Yes, if letting is a renewal (if field 7 = 1) or a first-time let (if field 16 = 15 - 17)","Yes, if letting is supported housing (if field 4 = 2) or if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",,"Yes, if letting is supported housing (if field 4 = 2)",,,,,"Yes, if letting is a renewal (if field 7 = 1)",,,Yes,,,"Yes, if letting is general needs (if field 4 = 1)",No,,,"Yes, if 'Other' is not selected for tenancy type (if field 39 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 39 is not 4 or 6)",No,,,,,"Yes, if all fields about person 2 are empty (fields 47 - 50)",,,,"Yes, if all fields about person 3 are empty (fields 51 - 54)",,,,"Yes, if all fields about person 4 are empty (fields 55 - 58)",,,,"Yes, if all fields about person 5 are empty (fields 59 - 62)",,,,"Yes, if all fields about person 6 are empty (fields 63 - 66)",,,,"Yes, if all fields about person 7 are empty (fields 67 - 70)",,,,"Yes, if all fields about person 8 are empty (fields 71 - 74)",,,,No,"Yes, if no one in the household is a current or former regular (if field 75 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 75 is not 1 or 4)",No,"Yes, if no household members have access needs or if it is unknown (if field 83 or 84 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 79 to 82 = 1)",,No,"Yes, if a household member has an access need (if at least one of fields 79 to 82 = 1) -If someone in the household does have such a condition (if field 89 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 7 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 98 is not 20)",No,No,,"Yes, if postcode of household's last settled home is not known (if 102 = 2)",,Yes,No,"If household was given 'reasonable preference' (if field 107 = 1), at least one of these fields must be 1 -If household was not given 'reasonable preference' (if field 106 = 2 or 3), these fields will be ignored.",,,,,No,,,,,No,"Yes, if household's income is unknown (if field 117 = 2 or 3)",,No,,"Yes, if letting is supported housing (if field 4 = 2)",No,"Yes, if the household does not pay rent (if field 122 = 1)",,,,"Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 120 = 3, 9 or 10)","Yes, if the household does not need to pay rent or charges after receiving housing benefits (if field 128 is not 1)",,,,,,,,,,,,,,,,,,,,,,,,,,, -Type of letting the question applies to,,,,,Supported housing only,,,,,,,Other Intermediate Rent only,,,,,,General needs only,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Duplicate check field?,Yes,,,,Yes,,,Yes,,,,,Yes,,,,,,,,,,Yes,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156 -,ORG1,ORG1,support@example.com,1,,,2,1,4,26,1,,1,1,1,5,1,,a,a,a,a,a1,1aa,E09000001,1,1,1,1,1,4,25,,,,,3,1,2,,,20,F,1,GBR,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,6,3,1,1,,,,,,1,1,,,,,,,,,,1,2,50,,30,1,2,,,,1,1,,,,,1,1,1,1,1,2,,,1,2,1,1,50,0,0,0,3,,F,,,,,,,,1,,,,,,,,,,,,,,,,,, +Must be <16 if working situation = 9",1 - 3,"F, M or R",1 - 3,Text,"0 - 10 + +Must be 9 if age <16",1 - 6,3 - 6,1 - 3,,1 or empty,,,,,,1 - 3,1 or empty,,,,,,,,,,1 - 2 or 6 - 12,2 or 6 - 13,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31, 34 or 44 - 57",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21, 23 - 33, 35, 37 - 40",1 or 11,1 - 2,"Alphanumeric, 2 - 4 characters","Alphanumeric, +3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",1 - 3,1 or empty,,,,,1 - 2,,,,1 - 4,5 - 9,1 - 8,1 - 20,1 - 3,1 - 3,0 - 99999,"1, 3, 6, 9 or 10",1 - 4,0 - 1,1 - 10,xxxx.xx,,,,1 - 3,xxxx.xx +Can be empty?,No,,Yes,No,"Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1)",No,,,,,"Yes, if letting is not 'Other intermediate rent product' (if field 11 is not 6)",Yes,,No,"Yes, if letting is a renewal (if field 7 = 1)","Yes, if letting is a renewal (if field 7 = 1) or a first-time let (if field 16 = 15 - 17)","Yes, if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",No,"Yes, if letting is supported housing (if field 4 = 2)",,,,"Yes, if letting is a renewal (if field 7 = 1)",,,Yes,,,"Yes, if letting is general needs (if field 4 = 1)",No,,,"Yes, if 'Other' is not selected for tenancy type (if field 38 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 38 is not 4 or 6)",No,,,"Yes, if 'No' is not selected for gender same as sex (if field 43 is not 2)",No,,,"Yes, if all fields about person 2 are empty (fields 48 - 53)","Yes, if person 2 is aged under 16 (if field 46 < 16) or if all fields about person 2 are empty (fields 48 - 53)","Yes, if all fields about person 2 are empty (fields 48 - 53)",,"Yes, if 'No' is not selected for gender same as sex (if field 51 is not 2) or if all fields about person 2 are empty (fields 48 - 53)","Yes, if all fields about person 2 are empty (fields 48 - 53)","Yes, if all fields about person 3 are empty (fields 54 - 59)","Yes, if person 3 is aged under 16 (if field 54 < 16) or if all fields about person 2 are empty (fields 54 - 59)","Yes, if all fields about person 3 are empty (fields 54 - 59)",,"Yes, if 'No' is not selected for gender same as sex (if field 57 is not 2) or if all fields about person 3 are empty (fields 54 - 59)","Yes, if all fields about person 3 are empty (fields 54 - 59)","Yes, if all fields about person 4 are empty (fields 60 - 65)","Yes, if person 4 is aged under 16 (if field 60 < 16) or if all fields about person 2 are empty (fields 54 - 59)","Yes, if all fields about person 4 are empty (fields 60 - 65)",,"Yes, if 'No' is not selected for gender same as sex (if field 63 is not 2) or if all fields about person 4 are empty (fields 60 - 65)","Yes, if all fields about person 4 are empty (fields 60 - 65)","Yes, if all fields about person 5 are empty (fields 59 - 62)","Yes, if person 5 is aged under 16 (if field 66 < 16) or if all fields about person 5 are empty (fields 66 - 71)","Yes, if all fields about person 5 are empty (fields 59 - 62)",,"Yes, if 'No' is not selected for gender same as sex (if field 69 is not 2) or if all fields about person 5 are empty (fields 66 - 71)","Yes, if all fields about person 5 are empty (fields 59 - 62)","Yes, if all fields about person 6 are empty (fields 63 - 66)","Yes, if person 6 is aged under 16 (if field 72 < 16) or if all fields about person 6 are empty (fields 72 - 77)","Yes, if all fields about person 6 are empty (fields 63 - 66)",,"Yes, if 'No' is not selected for gender same as sex (if field 75 is not 2) or if all fields about person 6 are empty (fields 72 - 77)","Yes, if all fields about person 6 are empty (fields 63 - 66)","Yes, if all fields about person 7 are empty (fields 67 - 70)","Yes, if person 7 is aged under 16 (if field 78 < 16) or if all fields about person 7 are empty (fields 78 - 83)","Yes, if all fields about person 7 are empty (fields 67 - 70)",,"Yes, if 'No' is not selected for gender same as sex (if field 81 is not 2) or if all fields about person 7 are empty (fields 78 - 83)","Yes, if all fields about person 7 are empty (fields 67 - 70)","Yes, if all fields about person 8 are empty (fields 71 - 74)","Yes, if person 8 is aged under 16 (if field 84 < 16) or if all fields about person 8 are empty (fields 84 - 89)","Yes, if all fields about person 8 are empty (fields 71 - 74)",,"Yes, if 'No' is not selected for gender same as sex (if field 87 is not 2) or if all fields about person 8 are empty (fields 84 - 89)","Yes, if all fields about person 8 are empty (fields 71 - 74)",No,"Yes, if no one in the household is a current or former regular (if field 90 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 90 is not 1 or 4)",No,"Yes, if no household members have access needs or if it is unknown (if field 98 or 99 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 94 to 97 = 1)",,No,"Yes, if no one in the household has a physical or mental health condition (if field 100 is 2 or 3). +If someone in the household does have such a condition (if field 100 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 7 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 113 is not 20)",No,No,,"Yes, if postcode of household's last settled home is not known (if 117 = 2)",,Yes,No,"Yes, if household was not given 'reasonable preference' (if field 121 = 2 or 3) +",,,,,No,,,,"Yes, if the stock owner (field 1) is a PRP, or if the letting is a renewal (if field 7 = 1)","Yes, if the stock owner (field 1) is a local authority, or if the letting is a renewal (if field 7 = 1)","Yes, if the stock owner (field 1) is a local authority, or if the letting is a renewal (if field 7 = 1), or if field 132 is 5 or 8-9","Yes, if the stock owner (field 1) is a local authority, or if the letting is a renewal (if field 7 = 1), or if field 133 is 2-6 or 8",No,"Yes, if household's income is unknown (if field 135 = 2 or 3)",,No,,"Yes, if letting is supported housing (if field 4 = 2)",No,"Yes, if the household does not pay rent (if field 140 = 1)",,,,"Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 139 = 3, 9 or 10)","Yes, if the household does not need to pay rent or charges after receiving housing benefits (if field 146 is not 1)" +Type of letting the question applies to,,,,,Supported housing only,,,,,,,Other Intermediate Rent only,,,,,,,,,,,,,,General needs only,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Local authority owned properties,PRP owned properties,,,,,,,,Supported housing only,,,,,,, +Duplicate check field?,Yes,,,,,,,Yes,,,,,Yes,,,,,"Yes, if UPRN provided instead of address","Yes, if address provided instead of UPRN",,,,"Yes, if address provided instead of UPRN",,,,,,,,,,,,,,,,,,Yes,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,,,,,, +Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147 +,ORG1,ORG1,support@example.com,1,,,2,1,4,26,1,,1,1,1,5,1,,a,a,a,a,a1,1aa,E09000001,1,1,1,1,4,25,,,,,3,1,2,,,20,F,1,,1,GBR,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,6,3,1,1,,,,,,1,1,,,,,,,,,,1,2,50,,30,1,2,,,,1,1,,,,,1,1,1,1,1,,,,2,,,1,2,1,1,100,0,0,0,3, diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 78e0d7f34..ef5386cc9 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -190,8 +190,8 @@ RSpec.describe BulkUpload::Lettings::Validator do expect(error.tenant_code).to eql(log.tenancycode) expect(error.property_ref).to eql(log.propcode) expect(error.row).to eql("2") - expect(error.cell).to eql("EB2") # this may change when adding a new field as the cols are in a random order - expect(error.col).to eql("EB") # this may change when adding a new field as the cols are in a random order + expect(error.cell).to eql("EA2") # this may change when adding a new field as the cols are in a random order + expect(error.col).to eql("EA") # this may change when adding a new field as the cols are in a random order end end end diff --git a/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb index d32a85ee3..0452116be 100644 --- a/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/csv_parser_spec.rb @@ -244,10 +244,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::CsvParser do end it "returns correct column" do - expect(service.column_for_field("field_5")).to eql("AA") - expect(service.column_for_field("field_22")).to eql("BK") - expect(service.column_for_field("field_26")).to eql("BZ") - expect(service.column_for_field("field_25")).to eql("S") + expect(service.column_for_field("field_5")).to eql("T") + expect(service.column_for_field("field_22")).to eql("BG") + expect(service.column_for_field("field_26")).to eql("BX") + expect(service.column_for_field("field_25")).to eql("F") end end end diff --git a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb index f0e05e30a..19847dec5 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -119,7 +119,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when calling the method multiple times" do - let(:attributes) { { bulk_upload:, field_129: 2 } } + let(:attributes) { { bulk_upload:, field_147: 2 } } it "does not add keep adding errors to the pile" do parser.valid? @@ -144,127 +144,126 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do field_26: "2", field_27: "1", field_28: "1", - field_29: "1", - field_37: "2", - field_38: "1", - field_39: "2", + field_36: "2", + field_37: "1", + field_38: "2", field_15: "1", - field_42: "42", + field_41: "42", field_48: "41", - field_52: "17", - field_56: "18", - field_60: "16", - field_64: "14", - field_68: "12", - field_72: "20", - - field_43: "F", - field_49: "M", - field_53: "F", - field_57: "M", - field_61: "F", - field_65: "M", - field_69: "F", - field_73: "M", - - field_44: "17", - field_45: "826", + field_54: "17", + field_60: "18", + field_66: "16", + field_72: "14", + field_78: "12", + field_84: "20", + + field_42: "F", + field_50: "M", + field_56: "F", + field_62: "M", + field_68: "F", + field_74: "M", + field_80: "F", + field_86: "M", + + field_45: "17", + field_46: "826", + + field_49: "1", + field_55: "2", + field_61: "2", + field_67: "3", + field_73: "2", + field_79: "2", + field_85: "2", field_47: "1", - field_51: "2", - field_55: "2", - field_59: "3", - field_63: "2", - field_67: "2", - field_71: "2", - - field_46: "1", - field_50: "2", - field_54: "7", - field_58: "7", - field_62: "8", - field_66: "9", - field_70: "0", - field_74: "10", - - field_75: "1", - field_76: "4", - field_77: "1", - - field_78: "1", - - field_79: "1", - field_80: "0", - field_81: "0", - field_82: "1", + field_53: "2", + field_59: "7", + field_65: "7", + field_71: "8", + field_77: "9", field_83: "0", + field_89: "10", - field_85: "3", + field_90: "1", + field_91: "4", + field_92: "1", - field_96: "11", - field_97: "2", - field_98: "31", - field_100: "3", - field_101: "11", - - field_102: "1", - field_103: "EC1N", - field_104: "2TD", + field_93: "1", - field_106: "1", - field_107: "1", - field_108: "", - field_109: "1", - field_110: "", - field_111: "", + field_94: "1", + field_95: "0", + field_96: "0", + field_97: "1", + field_98: "0", - field_112: "1", - field_113: "2", - field_114: "2", - field_115: "2", + field_100: "3", - field_116: "1", + field_111: "11", + field_112: "2", + field_113: "31", + field_115: "3", + field_116: "11", field_117: "1", - field_118: "2", - field_119: "2300", - field_120: "1", - field_121: "4", - - field_123: "4", - field_124: "1234.56", - field_125: "43.32", - field_126: "13.14", - field_127: "101.11", - field_128: "1", - field_129: "34.56", + field_118: "EC1N", + field_119: "2TD", + + field_121: "1", + field_122: "1", + field_123: "", + field_124: "1", + field_125: "", + field_126: "", + + field_127: "1", + field_128: "2", + field_129: "2", + field_130: "2", + + field_131: "1", + + field_135: "1", + field_136: "2", + field_137: "2300", + field_138: "1", + field_139: "4", + + field_141: "4", + field_142: "1234.56", + field_143: "43.32", + field_144: "13.14", + field_145: "101.11", + field_146: "1", + field_147: "34.56", field_16: "15", - field_30: now.day.to_s, - field_31: now.month.to_s, - field_32: now.strftime("%g"), + field_29: now.day.to_s, + field_30: now.month.to_s, + field_31: now.strftime("%g"), field_4: "1", field_18: "12", - field_130: "1", - field_131: "", - field_132: "2", - field_133: "identity", - field_134: "3", - field_135: "", - field_136: "1", - field_137: "", - field_138: "2", - field_139: "identity", - field_140: "3", - field_141: "", - field_142: "1", - field_143: "", - field_144: "2", - field_145: "identity", + field_43: "1", + field_44: "", + field_51: "2", + field_52: "identity", + field_57: "3", + field_58: "", + field_63: "1", + field_64: "", + field_69: "2", + field_70: "identity", + field_75: "3", + field_76: "", + field_81: "1", + field_82: "", + field_87: "2", + field_88: "identity", } end @@ -314,13 +313,13 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_23, # postcode_full :field_24, # postcode_full :field_25, # LA - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 - :field_124, # brent - :field_125, # scharge - :field_126, # pscharge - :field_127, # supcharg + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 + :field_142, # brent + :field_143, # scharge + :field_144, # pscharge + :field_145, # supcharg ].each do |field| expect(parser.errors[field]).to include(error_message) end @@ -345,7 +344,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when a supported housing log already exists in the db" do # TODO: CLDC-4119: Beware! The `postcode_full` method in the `LettingsLog` class may cause issues with these supported housing log duplicate detection tests after postcode is added. See comment on the `postcode_full` method for details. - let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_36: 3, field_122: 0 }) } + let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_35: 3, field_140: 0 }) } before do parser.log.save! @@ -368,13 +367,13 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_10, # startdate :field_13, # tenancycode :field_6, # location - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 - :field_124, # brent - :field_125, # scharge - :field_126, # pscharge - :field_127, # supcharg + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 + :field_142, # brent + :field_143, # scharge + :field_144, # pscharge + :field_145, # supcharg ].each do |field| expect(parser.errors[field]).to include(error_message) end @@ -410,13 +409,13 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_10, # startdate :field_13, # tenancycode :field_6, # location - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 - :field_124, # brent - :field_125, # scharge - :field_126, # pscharge - :field_127, # supcharg + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 + :field_142, # brent + :field_143, # scharge + :field_144, # pscharge + :field_145, # supcharg ].each do |field| expect(parser.errors[field]).to include(error_message) end @@ -453,9 +452,9 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_10, # startdate :field_13, # tenancycode :field_6, # location - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 ].each do |field| expect(parser.errors[field]).to include(error_message) end @@ -474,8 +473,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do field_11: "2", field_6: location.id, field_1: owning_org.old_visible_id, - field_122: 0, - field_36: 4 }) + field_140: 0, + field_35: 4 }) end before do @@ -499,10 +498,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_10, # startdate :field_13, # tenancycode :field_6, # location - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 - :field_122, # household_charge + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 + :field_140, # household_charge ].each do |field| expect(parser.errors[field]).to include(error_message) end @@ -518,9 +517,9 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do let(:attributes) do setup_section_params.merge({ field_5: nil, field_6: nil, - field_124: 300, - field_123: 1, - field_29: 1, + field_142: 300, + field_141: 1, + field_28: 1, field_4: 1, field_11: "2", field_25: "E09000008" }) @@ -553,9 +552,9 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_23, # postcode_full :field_24, # postcode_full :field_25, # LA - :field_42, # age1 - :field_43, # sexrab1 - :field_46, # ecstat1 + :field_41, # age1 + :field_42, # sexrab1 + :field_47, # ecstat1 ].each do |field| expect(parser.errors[field]).to be_blank end @@ -588,8 +587,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "with a valid ethnic value" do - context "when field_44 is 20" do - let(:attributes) { valid_attributes.merge({ field_44: "20" }) } + context "when field_45 is 20" do + let(:attributes) { valid_attributes.merge({ field_45: "20" }) } it "is correctly sets ethnic and ethnic group" do expect(parser.log.ethnic).to eq(20) @@ -609,43 +608,43 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do context "when there is a :skip_bu_error error" do let(:managing_org) { create(:organisation, :with_old_visible_id, rent_periods: [4, 1]) } - let(:attributes) { valid_attributes.merge({ field_123: 3, field_127: 80 }) } + let(:attributes) { valid_attributes.merge({ field_141: 3, field_145: 80 }) } it "does not add that error" do parser.valid? expect(parser.log.errors.map(&:attribute).sort).to eql(%i[managing_organisation_id period]) - expect(parser.errors.map(&:attribute)).to eql(%i[field_123]) + expect(parser.errors.map(&:attribute)).to eql(%i[field_141]) end end end describe "#validate_nulls" do context "when non-setup questions are null" do - let(:attributes) { setup_section_params.merge({ field_43: "" }) } + let(:attributes) { setup_section_params.merge({ field_42: "" }) } it "fetches the question's check_answer_label if it exists" do parser.valid? - expect(parser.errors[:field_43]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "lead tenant’s sex registered at birth.")]) + expect(parser.errors[:field_42]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "lead tenant’s sex registered at birth.")]) end end context "when other null error is added" do - let(:attributes) { setup_section_params.merge({ field_112: nil }) } + let(:attributes) { setup_section_params.merge({ field_127: nil }) } it "only has one error added to the field" do parser.valid? - expect(parser.errors[:field_112]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "was the letting made under the Choice-Based Lettings (CBL)?")]) + expect(parser.errors[:field_127]).to eql([I18n.t("validations.lettings.2026.bulk_upload.not_answered", question: "was the letting made under the Choice-Based Lettings (CBL)?")]) end end context "when an invalid value error has been added" do - let(:attributes) { setup_section_params.merge({ field_115: "100" }) } + let(:attributes) { setup_section_params.merge({ field_130: "100" }) } it "does not add an additional error" do parser.valid? - expect(parser.errors[:field_115].length).to eq(1) - expect(parser.errors[:field_115]).to include(match I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "")) + expect(parser.errors[:field_130].length).to eq(1) + expect(parser.errors[:field_130]).to include(match I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "")) end end end @@ -894,213 +893,213 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#field_98" do # leaving reason + describe "#field_113" do # leaving reason context "when field_7 is 1 meaning it is a renewal" do - context "when field_98 is 50" do - let(:attributes) { { bulk_upload:, field_98: "50", field_7: "1" } } + context "when field_113 is 50" do + let(:attributes) { { bulk_upload:, field_113: "50", field_7: "1" } } it "is permitted" do parser.valid? - expect(parser.errors[:field_98]).to be_blank + expect(parser.errors[:field_113]).to be_blank end end - context "when field_98 is 51" do - let(:attributes) { { bulk_upload:, field_98: "51", field_7: "1" } } + context "when field_113 is 51" do + let(:attributes) { { bulk_upload:, field_113: "51", field_7: "1" } } it "is permitted" do parser.valid? - expect(parser.errors[:field_98]).to be_blank + expect(parser.errors[:field_113]).to be_blank end end - context "when field_98 is 52" do - let(:attributes) { { bulk_upload:, field_98: "52", field_7: "1" } } + context "when field_113 is 52" do + let(:attributes) { { bulk_upload:, field_113: "52", field_7: "1" } } it "is permitted" do parser.valid? - expect(parser.errors[:field_98]).to be_blank + expect(parser.errors[:field_113]).to be_blank end end - context "when field_98 is 53" do - let(:attributes) { { bulk_upload:, field_98: "53", field_7: "1" } } + context "when field_113 is 53" do + let(:attributes) { { bulk_upload:, field_113: "53", field_7: "1" } } it "is permitted" do parser.valid? - expect(parser.errors[:field_98]).to be_blank + expect(parser.errors[:field_113]).to be_blank end end - context "when field_98 is not 50, 51, 52 or 53" do - let(:attributes) { { bulk_upload:, field_98: "1", field_7: "1" } } + context "when field_113 is not 50, 51, 52 or 53" do + let(:attributes) { { bulk_upload:, field_113: "1", field_7: "1" } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_98]).to include(I18n.t("validations.lettings.2026.bulk_upload.reason.renewal_reason_needed")) + expect(parser.errors[:field_113]).to include(I18n.t("validations.lettings.2026.bulk_upload.reason.renewal_reason_needed")) end end end context "when no longer a valid option from previous year" do - let(:attributes) { setup_section_params.merge({ field_98: "7" }) } + let(:attributes) { setup_section_params.merge({ field_113: "7" }) } it "returns an error" do parser.valid? - expect(parser.errors[:field_98]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "what is the tenant’s main reason for the household leaving their last settled home?")) + expect(parser.errors[:field_113]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "what is the tenant’s main reason for the household leaving their last settled home?")) end end end - describe "#field_79, #field_80, #field_81" do + describe "#field_94, #field_95, #field_96" do context "when one item selected" do - let(:attributes) { { bulk_upload:, field_79: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1" } } it "is permitted" do parser.valid? - expect(parser.errors[:field_79]).to be_blank - expect(parser.errors[:field_80]).to be_blank - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_94]).to be_blank + expect(parser.errors[:field_95]).to be_blank + expect(parser.errors[:field_96]).to be_blank end end context "when more than one item selected" do - let(:attributes) { { bulk_upload:, field_79: "1", field_80: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1", field_95: "1" } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_79]).to be_present - expect(parser.errors[:field_80]).to be_present + expect(parser.errors[:field_94]).to be_present + expect(parser.errors[:field_95]).to be_present end end end - describe "#field_83" do + describe "#field_98" do context "when 1 and another disability field selected" do - let(:attributes) { { bulk_upload:, field_83: "1", field_82: "1" } } + let(:attributes) { { bulk_upload:, field_98: "1", field_97: "1" } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_83]).to be_present + expect(parser.errors[:field_98]).to be_present end end end - describe "#field_84" do + describe "#field_99" do context "when 1 and another disability field selected" do - let(:attributes) { { bulk_upload:, field_84: "1", field_82: "1" } } + let(:attributes) { { bulk_upload:, field_99: "1", field_97: "1" } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_84]).to be_present + expect(parser.errors[:field_99]).to be_present end end end - describe "#field_83, #field_84" do + describe "#field_98, #field_99" do context "when both 1" do - let(:attributes) { { bulk_upload:, field_83: "1", field_84: "1" } } + let(:attributes) { { bulk_upload:, field_98: "1", field_99: "1" } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_83]).to be_present - expect(parser.errors[:field_84]).to be_present + expect(parser.errors[:field_98]).to be_present + expect(parser.errors[:field_99]).to be_present end end end - describe "#field_79 - #field_84" do + describe "#field_94 - #field_99" do context "when all blank" do - let(:attributes) { setup_section_params.merge({ field_79: nil, field_80: nil, field_81: nil, field_82: nil, field_83: nil, field_84: nil }) } + let(:attributes) { setup_section_params.merge({ field_94: nil, field_95: nil, field_96: nil, field_97: nil, field_98: nil, field_99: nil }) } it "adds errors to correct fields" do parser.valid? - expect(parser.errors[:field_79]).to be_present - expect(parser.errors[:field_80]).to be_present - expect(parser.errors[:field_81]).to be_present - expect(parser.errors[:field_82]).to be_present - expect(parser.errors[:field_83]).to be_present + expect(parser.errors[:field_94]).to be_present + expect(parser.errors[:field_95]).to be_present + expect(parser.errors[:field_96]).to be_present + expect(parser.errors[:field_97]).to be_present + expect(parser.errors[:field_98]).to be_present end end - context "when one item selected and field_82 is blank" do - let(:attributes) { setup_section_params.merge({ field_79: "1", field_82: nil }) } + context "when one item selected and field_97 is blank" do + let(:attributes) { setup_section_params.merge({ field_94: "1", field_97: nil }) } it "sets other disabled access needs as no" do parser.valid? - expect(parser.errors[:field_79]).to be_blank - expect(parser.errors[:field_82]).to be_blank + expect(parser.errors[:field_94]).to be_blank + expect(parser.errors[:field_97]).to be_blank expect(parser.log.housingneeds_other).to eq(0) end end end - describe "#field_85, field_94 - 99" do + describe "#field_100, field_109 - 99" do context "when no illness but illnesses answered" do - let(:attributes) { { bulk_upload:, field_85: "2", field_86: "1", field_87: "1", field_88: "1" } } + let(:attributes) { { bulk_upload:, field_100: "2", field_101: "1", field_102: "1", field_103: "1" } } it "errors added to correct fields" do parser.valid? - expect(parser.errors[:field_86]).to be_present - expect(parser.errors[:field_87]).to be_present - expect(parser.errors[:field_88]).to be_present - expect(parser.errors[:field_89]).not_to be_present - expect(parser.errors[:field_90]).not_to be_present - expect(parser.errors[:field_91]).not_to be_present - expect(parser.errors[:field_92]).not_to be_present - expect(parser.errors[:field_93]).not_to be_present - expect(parser.errors[:field_94]).not_to be_present - expect(parser.errors[:field_95]).not_to be_present + expect(parser.errors[:field_101]).to be_present + expect(parser.errors[:field_102]).to be_present + expect(parser.errors[:field_103]).to be_present + expect(parser.errors[:field_104]).not_to be_present + expect(parser.errors[:field_105]).not_to be_present + expect(parser.errors[:field_106]).not_to be_present + expect(parser.errors[:field_107]).not_to be_present + expect(parser.errors[:field_108]).not_to be_present + expect(parser.errors[:field_109]).not_to be_present + expect(parser.errors[:field_110]).not_to be_present end end context "when illness but no illnesses answered" do - let(:attributes) { { bulk_upload:, field_85: "1", field_86: nil, field_87: nil, field_88: nil, field_89: nil, field_90: nil, field_91: nil, field_92: nil, field_93: nil, field_94: nil, field_95: nil } } + let(:attributes) { { bulk_upload:, field_100: "1", field_101: nil, field_102: nil, field_103: nil, field_104: nil, field_105: nil, field_106: nil, field_107: nil, field_108: nil, field_109: nil, field_110: nil } } it "errors added to correct fields" do parser.valid? - expect(parser.errors[:field_86]).to be_present - expect(parser.errors[:field_87]).to be_present - expect(parser.errors[:field_88]).to be_present - expect(parser.errors[:field_89]).to be_present - expect(parser.errors[:field_90]).to be_present - expect(parser.errors[:field_91]).to be_present - expect(parser.errors[:field_92]).to be_present - expect(parser.errors[:field_93]).to be_present - expect(parser.errors[:field_94]).to be_present - expect(parser.errors[:field_95]).to be_present + expect(parser.errors[:field_101]).to be_present + expect(parser.errors[:field_102]).to be_present + expect(parser.errors[:field_103]).to be_present + expect(parser.errors[:field_104]).to be_present + expect(parser.errors[:field_105]).to be_present + expect(parser.errors[:field_106]).to be_present + expect(parser.errors[:field_107]).to be_present + expect(parser.errors[:field_108]).to be_present + expect(parser.errors[:field_109]).to be_present + expect(parser.errors[:field_110]).to be_present end end end - describe "#field_100" do + describe "#field_115" do context "when log is a renewal and field 100 is an invalid value" do - let(:attributes) { { bulk_upload:, field_7: 1, field_100: 4 } } + let(:attributes) { { bulk_upload:, field_7: 1, field_115: 4 } } it "adds an error to field 100" do parser.valid? - expect(parser.errors[:field_100]).to be_present - expect(parser.errors[:field_100]).to include(I18n.t("validations.lettings.2026.bulk_upload.prevten.invalid")) + expect(parser.errors[:field_115]).to be_present + expect(parser.errors[:field_115]).to include(I18n.t("validations.lettings.2026.bulk_upload.prevten.invalid")) end end context "when log is a renewal and field 100 is a valid value" do - let(:attributes) { { bulk_upload:, field_7: 1, field_100: 38 } } + let(:attributes) { { bulk_upload:, field_7: 1, field_115: 38 } } it "does not add an error" do parser.valid? - expect(parser.errors[:field_100]).to be_blank + expect(parser.errors[:field_115]).to be_blank end end end - describe "#field_112 - 115 (lettings allocation methods)" do - %i[field_112 field_113 field_114 field_115].each do |field| + describe "#field_127 - 115 (lettings allocation methods)" do + %i[field_127 field_128 field_129 field_130].each do |field| context "when only #{field} is not given" do let(:attributes) do override = {} override[field] = "" - { bulk_upload:, field_112: "2", field_113: "1", field_114: "2", field_115: "1" }.merge(override) + { bulk_upload:, field_127: "2", field_128: "1", field_129: "2", field_130: "1" }.merge(override) end it "adds an error to #{field}" do @@ -1111,35 +1110,35 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#field_101, field_106 - 15" do + describe "#field_116, field_121 - 15" do context "when there is a reasonable preference but none is given" do - let(:attributes) { { bulk_upload:, field_106: "1", field_107: nil, field_108: nil, field_109: nil, field_110: nil, field_111: nil } } + let(:attributes) { { bulk_upload:, field_121: "1", field_122: nil, field_123: nil, field_124: nil, field_125: nil, field_126: nil } } it "is not permitted" do parser.valid? - expect(parser.errors[:field_107]).to be_present - expect(parser.errors[:field_108]).to be_present - expect(parser.errors[:field_109]).to be_present - expect(parser.errors[:field_110]).to be_present - expect(parser.errors[:field_111]).to be_present + expect(parser.errors[:field_122]).to be_present + expect(parser.errors[:field_123]).to be_present + expect(parser.errors[:field_124]).to be_present + expect(parser.errors[:field_125]).to be_present + expect(parser.errors[:field_126]).to be_present end end context "when some reasonable preference options are set as invalid values" do - let(:attributes) { setup_section_params.merge({ bulk_upload:, field_106: "2", field_107: "2", field_108: "3", field_109: "2", field_110: "3", field_111: "-4" }) } + let(:attributes) { setup_section_params.merge({ bulk_upload:, field_121: "2", field_122: "2", field_123: "3", field_124: "2", field_125: "3", field_126: "-4" }) } it "adds errors" do parser.valid? - expect(parser.errors[:field_107]).to be_present - expect(parser.errors[:field_108]).to be_present - expect(parser.errors[:field_109]).to be_present - expect(parser.errors[:field_110]).to be_present - expect(parser.errors[:field_111]).to be_present + expect(parser.errors[:field_122]).to be_present + expect(parser.errors[:field_123]).to be_present + expect(parser.errors[:field_124]).to be_present + expect(parser.errors[:field_125]).to be_present + expect(parser.errors[:field_126]).to be_present end end context "when some reasonable preference options are selected" do - let(:attributes) { setup_section_params.merge({ bulk_upload:, field_106: "1", field_107: "1", field_108: nil, field_109: "1", field_110: nil, field_111: nil }) } + let(:attributes) { setup_section_params.merge({ bulk_upload:, field_121: "1", field_122: "1", field_123: nil, field_124: "1", field_125: nil, field_126: nil }) } it "sets the rest of the options to 0" do parser.valid? @@ -1152,7 +1151,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#field_116, field_146, field_147, field_148" do # referral + describe "#field_131, field_132, field_133, field_134" do # referral context "when org is LA" do let(:owning_org) { create(:organisation, :la, :with_old_visible_id) } @@ -1161,64 +1160,64 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do context "and not renewal" do let(:renewal_attributes) { org_attributes.merge({ field_7: nil }) } - context "and field_116 is valid" do - let(:attributes) { renewal_attributes.merge({ field_116: 1 }) } + context "and field_131 is valid" do + let(:attributes) { renewal_attributes.merge({ field_131: 1 }) } it "does not add an error" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end end - context "and field_116 is invalid" do - let(:attributes) { renewal_attributes.merge({ field_116: 5 }) } # PRP option + context "and field_131 is invalid" do + let(:attributes) { renewal_attributes.merge({ field_131: 5 }) } # PRP option it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_116 is blank" do - let(:attributes) { renewal_attributes.merge({ field_116: nil }) } + context "and field_131 is blank" do + let(:attributes) { renewal_attributes.merge({ field_131: nil }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end context "and other fields are given" do - let(:attributes) { renewal_attributes.merge({ field_116: 1, field_146: 5, field_147: 1, field_144: 1 }) } + let(:attributes) { renewal_attributes.merge({ field_131: 1, field_132: 5, field_133: 1, field_87: 1 }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end end context "and is renewal" do - let(:attributes) { org_attributes.merge({ field_7: 1, field_116: 1, field_146: 5, field_147: 1, field_148: 1 }) } + let(:attributes) { org_attributes.merge({ field_7: 1, field_131: 1, field_132: 5, field_133: 1, field_134: 1 }) } it "does not add an error for referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end end end @@ -1231,141 +1230,141 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do context "and not renewal" do let(:renewal_attributes) { org_attributes.merge({ field_7: nil }) } - context "and field_146 is valid and does not expect an answer for field_147" do - let(:attributes) { renewal_attributes.merge({ field_146: 5 }) } + context "and field_132 is valid and does not expect an answer for field_133" do + let(:attributes) { renewal_attributes.merge({ field_132: 5 }) } it "does not add an error" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end context "and later fields are given" do - let(:attributes) { renewal_attributes.merge({ field_146: 5, field_147: 1, field_148: 1 }) } + let(:attributes) { renewal_attributes.merge({ field_132: 5, field_133: 1, field_134: 1 }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end end - context "and field_146 is invalid" do - let(:attributes) { renewal_attributes.merge({ field_146: 1 }) } # LA option + context "and field_132 is invalid" do + let(:attributes) { renewal_attributes.merge({ field_132: 1 }) } # LA option it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_146 is blank" do - let(:attributes) { renewal_attributes.merge({ field_146: nil }) } + context "and field_132 is blank" do + let(:attributes) { renewal_attributes.merge({ field_132: nil }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_146 is valid and expects an answer for field_147" do - let(:field_146_attributes) { renewal_attributes.merge({ field_146: 6 }) } + context "and field_132 is valid and expects an answer for field_133" do + let(:field_146_attributes) { renewal_attributes.merge({ field_132: 6 }) } - context "and field_147 is valid and does not expect an answer for field_148" do - let(:attributes) { field_146_attributes.merge({ field_147: 2 }) } + context "and field_133 is valid and does not expect an answer for field_134" do + let(:attributes) { field_146_attributes.merge({ field_133: 2 }) } it "does not add an error" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end context "and later fields are given" do - let(:attributes) { field_146_attributes.merge({ field_147: 2, field_148: 1 }) } + let(:attributes) { field_146_attributes.merge({ field_133: 2, field_134: 1 }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end end - context "and field_147 is invalid" do - let(:attributes) { field_146_attributes.merge({ field_147: 5 }) } # needs field_146 to be 7 + context "and field_133 is invalid" do + let(:attributes) { field_146_attributes.merge({ field_133: 5 }) } # needs field_132 to be 7 it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_147 is blank" do - let(:attributes) { field_146_attributes.merge({ field_147: nil }) } + context "and field_133 is blank" do + let(:attributes) { field_146_attributes.merge({ field_133: nil }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_147 is valid and expects an answer for field_148" do - let(:field_147_attributes) { field_146_attributes.merge({ field_147: 1 }) } + context "and field_133 is valid and expects an answer for field_134" do + let(:field_147_attributes) { field_146_attributes.merge({ field_133: 1 }) } - context "and field_148 is valid" do - let(:attributes) { field_147_attributes.merge({ field_148: 1 }) } + context "and field_134 is valid" do + let(:attributes) { field_147_attributes.merge({ field_134: 1 }) } it "does not add an error" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end end - context "and field_148 is invalid" do - let(:attributes) { field_147_attributes.merge({ field_148: 11 }) } # needs field_147 to be 7 + context "and field_134 is invalid" do + let(:attributes) { field_147_attributes.merge({ field_134: 11 }) } # needs field_133 to be 7 it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end - context "and field_148 is blank" do - let(:attributes) { field_147_attributes.merge({ field_148: nil }) } + context "and field_134 is blank" do + let(:attributes) { field_147_attributes.merge({ field_134: nil }) } it "adds errors to all referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_present - expect(parser.errors[:field_146]).to be_present - expect(parser.errors[:field_147]).to be_present - expect(parser.errors[:field_148]).to be_present + expect(parser.errors[:field_131]).to be_present + expect(parser.errors[:field_132]).to be_present + expect(parser.errors[:field_133]).to be_present + expect(parser.errors[:field_134]).to be_present end end end @@ -1373,14 +1372,14 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "and is renewal" do - let(:attributes) { org_attributes.merge({ field_7: 1, field_116: 1, field_146: 5, field_147: 1, field_148: 1 }) } + let(:attributes) { org_attributes.merge({ field_7: 1, field_131: 1, field_132: 5, field_133: 1, field_134: 1 }) } it "does not add an error for referral fields" do parser.valid? - expect(parser.errors[:field_116]).to be_blank - expect(parser.errors[:field_146]).to be_blank - expect(parser.errors[:field_147]).to be_blank - expect(parser.errors[:field_148]).to be_blank + expect(parser.errors[:field_131]).to be_blank + expect(parser.errors[:field_132]).to be_blank + expect(parser.errors[:field_133]).to be_blank + expect(parser.errors[:field_134]).to be_blank end end end @@ -1691,7 +1690,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do it "adds a setup error" do parser.valid? - expect(parser.errors.where(:field_7, category: :setup).map(&:message)).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "is this letting a renewal?")) + expect(parser.errors.where(:field_7, category: :setup).map(&:message)).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "is this letting a renewal of social housing to the same tenant in the same property?")) end end end @@ -1961,7 +1960,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#field_48" do # age2 context "when null but gender given" do - let(:attributes) { setup_section_params.merge({ field_48: "", field_49: "F" }) } + let(:attributes) { setup_section_params.merge({ field_48: "", field_50: "F" }) } it "returns an error" do parser.valid? @@ -1970,9 +1969,9 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - describe "#field_45" do - context "when field_45 is a 3 digit nationality code" do - let(:attributes) { setup_section_params.merge({ field_45: "036" }) } + describe "#field_46" do + context "when field_46 is a 3 digit nationality code" do + let(:attributes) { setup_section_params.merge({ field_46: "036" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(36) @@ -1980,8 +1979,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is a nationality code without the trailing 0s" do - let(:attributes) { setup_section_params.merge({ field_45: "36" }) } + context "when field_46 is a nationality code without the trailing 0s" do + let(:attributes) { setup_section_params.merge({ field_46: "36" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(36) @@ -1989,8 +1988,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is a nationality code with trailing 0s" do - let(:attributes) { setup_section_params.merge({ field_45: "0036" }) } + context "when field_46 is a nationality code with trailing 0s" do + let(:attributes) { setup_section_params.merge({ field_46: "0036" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(36) @@ -1998,8 +1997,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is 0" do - let(:attributes) { setup_section_params.merge({ field_45: "0" }) } + context "when field_46 is 0" do + let(:attributes) { setup_section_params.merge({ field_46: "0" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(0) @@ -2007,8 +2006,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is 000" do - let(:attributes) { setup_section_params.merge({ field_45: "000" }) } + context "when field_46 is 000" do + let(:attributes) { setup_section_params.merge({ field_46: "000" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(0) @@ -2016,8 +2015,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is 0000" do - let(:attributes) { setup_section_params.merge({ field_45: "0000" }) } + context "when field_46 is 0000" do + let(:attributes) { setup_section_params.merge({ field_46: "0000" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(0) @@ -2025,8 +2024,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is 826" do - let(:attributes) { setup_section_params.merge({ field_45: "826" }) } + context "when field_46 is 826" do + let(:attributes) { setup_section_params.merge({ field_46: "826" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(826) @@ -2034,8 +2033,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is 826 with trailing 0s" do - let(:attributes) { setup_section_params.merge({ field_45: "0826" }) } + context "when field_46 is 826 with trailing 0s" do + let(:attributes) { setup_section_params.merge({ field_46: "0826" }) } it "is correctly set" do expect(parser.log.nationality_all).to be(826) @@ -2043,79 +2042,79 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when field_45 is not a valid option" do - let(:attributes) { setup_section_params.merge({ field_45: "123123" }) } + context "when field_46 is not a valid option" do + let(:attributes) { setup_section_params.merge({ field_46: "123123" }) } it "is correctly set" do expect(parser.log.nationality_all).to be_nil expect(parser.log.nationality_all_group).to be_nil end - it "adds an error to field_45" do + it "adds an error to field_46" do parser.valid? - expect(parser.errors["field_45"]).to include(I18n.t("validations.lettings.2026.bulk_upload.nationality.invalid")) + expect(parser.errors["field_46"]).to include(I18n.t("validations.lettings.2026.bulk_upload.nationality.invalid")) end end end describe "soft validations" do context "when soft validation is triggered" do - let(:attributes) { setup_section_params.merge({ field_42: 22, field_46: 5 }) } + let(:attributes) { setup_section_params.merge({ field_41: 22, field_47: 5 }) } it "adds an error to the relevant fields" do parser.valid? - expect(parser.errors.where(:field_42, category: :soft_validation)).to be_present - expect(parser.errors.where(:field_46, category: :soft_validation)).to be_present + expect(parser.errors.where(:field_41, category: :soft_validation)).to be_present + expect(parser.errors.where(:field_47, category: :soft_validation)).to be_present end it "populates with correct error message" do parser.valid? - expect(parser.errors.where(:field_42, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired. The minimum expected retirement age in England is 66.") - expect(parser.errors.where(:field_46, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired. The minimum expected retirement age in England is 66.") + expect(parser.errors.where(:field_41, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired. The minimum expected retirement age in England is 66.") + expect(parser.errors.where(:field_47, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired. The minimum expected retirement age in England is 66.") end end context "when a soft validation is triggered that relates both to fields that are and are not routed to" do - let(:attributes) { setup_section_params.merge({ field_78: "1", field_43: "M", field_49: "M", field_53: "M", field_130: 1, field_132: 1, field_134: 1 }) } + let(:attributes) { setup_section_params.merge({ field_93: "1", field_42: "M", field_50: "M", field_56: "M", field_43: 1, field_51: 1, field_57: 1 }) } it "adds errors to fields that are routed to" do parser.valid? + expect(parser.errors.where(:field_42, category: :soft_validation)).to be_present expect(parser.errors.where(:field_43, category: :soft_validation)).to be_present - expect(parser.errors.where(:field_130, category: :soft_validation)).to be_present - expect(parser.errors.where(:field_49, category: :soft_validation)).to be_present - expect(parser.errors.where(:field_132, category: :soft_validation)).to be_present + expect(parser.errors.where(:field_50, category: :soft_validation)).to be_present + expect(parser.errors.where(:field_51, category: :soft_validation)).to be_present end it "does not add errors to fields that are not routed to" do parser.valid? - expect(parser.errors.where(:field_57, category: :soft_validation)).not_to be_present - expect(parser.errors.where(:field_136, category: :soft_validation)).not_to be_present - expect(parser.errors.where(:field_61, category: :soft_validation)).not_to be_present - expect(parser.errors.where(:field_138, category: :soft_validation)).not_to be_present + expect(parser.errors.where(:field_62, category: :soft_validation)).not_to be_present + expect(parser.errors.where(:field_63, category: :soft_validation)).not_to be_present + expect(parser.errors.where(:field_68, category: :soft_validation)).not_to be_present + expect(parser.errors.where(:field_69, category: :soft_validation)).not_to be_present end end context "when soft validation is triggered and not required" do - let(:attributes) { setup_section_params.merge({ field_124: 120, field_125: 120, field_126: 120, field_127: 120, field_123: 1, field_29: 1, field_4: 1, field_11: "2", field_25: "E09000008" }) } + let(:attributes) { setup_section_params.merge({ field_142: 120, field_143: 120, field_144: 120, field_145: 120, field_141: 1, field_28: 1, field_4: 1, field_11: "2", field_25: "E09000008" }) } it "adds an error to the relevant fields" do parser.valid? - expect(parser.errors.where(:field_124, category: :soft_validation)).to be_present + expect(parser.errors.where(:field_142, category: :soft_validation)).to be_present end it "populates with correct error message" do parser.valid? - expect(parser.errors.where(:field_124, category: :soft_validation).count).to be(1) - expect(parser.errors.where(:field_124, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. This is higher than we would expect.") + expect(parser.errors.where(:field_142, category: :soft_validation).count).to be(1) + expect(parser.errors.where(:field_142, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. This is higher than we would expect.") end end context "when an invalid ecstat1 is given" do - let(:attributes) { setup_section_params.merge({ field_46: 11, field_119: 123, field_118: 1 }) } + let(:attributes) { setup_section_params.merge({ field_47: 11, field_137: 123, field_136: 1 }) } it "does not run net income soft validations validation" do parser.valid? - expect(parser.errors.where(:field_46).count).to be(1) + expect(parser.errors.where(:field_47).count).to be(1) end end end @@ -2232,14 +2231,14 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end [ - %w[age1_known details_known_1 age1 field_42 field_47 field_49], - %w[age2_known details_known_2 age2 field_48 field_47 field_49], - %w[age3_known details_known_3 age3 field_52 field_51 field_53], - %w[age4_known details_known_4 age4 field_56 field_55 field_57], - %w[age5_known details_known_5 age5 field_60 field_59 field_61], - %w[age6_known details_known_6 age6 field_64 field_63 field_65], - %w[age7_known details_known_7 age7 field_68 field_67 field_69], - %w[age8_known details_known_8 age8 field_72 field_71 field_73], + %w[age1_known details_known_1 age1 field_41 field_49 field_50], + %w[age2_known details_known_2 age2 field_48 field_49 field_50], + %w[age3_known details_known_3 age3 field_54 field_55 field_56], + %w[age4_known details_known_4 age4 field_60 field_61 field_62], + %w[age5_known details_known_5 age5 field_66 field_67 field_68], + %w[age6_known details_known_6 age6 field_72 field_73 field_74], + %w[age7_known details_known_7 age7 field_78 field_79 field_80], + %w[age8_known details_known_8 age8 field_84 field_85 field_86], ].each do |known, details_known, age, field, relationship, gender| describe "##{known} and ##{age}" do context "when #{field} is blank" do @@ -2341,141 +2340,141 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#beds" do context "when property is a bedsit" do - let(:attributes) { setup_section_params.merge({ field_26: 2, field_29: 2 }) } + let(:attributes) { setup_section_params.merge({ field_26: 2, field_28: 2 }) } - it "sets value to 1 even if field_29 contradicts this" do + it "sets value to 1 even if field_28 contradicts this" do expect(parser.log.beds).to be(1) end end context "when property is not a bedsit" do - let(:attributes) { setup_section_params.merge({ field_26: 1, field_29: 2 }) } + let(:attributes) { setup_section_params.merge({ field_26: 1, field_28: 2 }) } - it "sets value to field_29" do + it "sets value to field_28" do expect(parser.log.beds).to be(2) end end end describe "#cbl" do - context "when field_112 is yes ie 1" do - let(:attributes) { { bulk_upload:, field_112: 1 } } + context "when field_127 is yes ie 1" do + let(:attributes) { { bulk_upload:, field_127: 1 } } it "sets value to 1" do expect(parser.log.cbl).to be(1) end end - context "when field_112 is no ie 2" do - let(:attributes) { { bulk_upload:, field_112: 2 } } + context "when field_127 is no ie 2" do + let(:attributes) { { bulk_upload:, field_127: 2 } } it "sets value to 0" do expect(parser.log.cbl).to be(0) end end - context "when field_112 is not a permitted value" do - let(:attributes) { { bulk_upload:, field_112: 3 } } + context "when field_127 is not a permitted value" do + let(:attributes) { { bulk_upload:, field_127: 3 } } it "adds an error" do parser.valid? - expect(parser.errors[:field_112]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Choice-Based Lettings (CBL)?")) + expect(parser.errors[:field_127]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Choice-Based Lettings (CBL)?")) end end end describe "#chr" do - context "when field_114 is yes ie 1" do - let(:attributes) { { bulk_upload:, field_114: 1 } } + context "when field_129 is yes ie 1" do + let(:attributes) { { bulk_upload:, field_129: 1 } } it "sets value to 1" do expect(parser.log.chr).to be(1) end end - context "when field_114 is no ie 2" do - let(:attributes) { { bulk_upload:, field_114: 2 } } + context "when field_129 is no ie 2" do + let(:attributes) { { bulk_upload:, field_129: 2 } } it "sets value to 0" do expect(parser.log.chr).to be(0) end end - context "when field_114 is not a permitted value" do - let(:attributes) { { bulk_upload:, field_114: 3 } } + context "when field_129 is not a permitted value" do + let(:attributes) { { bulk_upload:, field_129: 3 } } it "adds an error" do parser.valid? - expect(parser.errors[:field_114]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Common Housing Register (CHR)?")) + expect(parser.errors[:field_129]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Common Housing Register (CHR)?")) end end end describe "#cap" do - context "when field_113 is yes ie 1" do - let(:attributes) { { bulk_upload:, field_113: 1 } } + context "when field_128 is yes ie 1" do + let(:attributes) { { bulk_upload:, field_128: 1 } } it "sets value to 1" do expect(parser.log.cap).to be(1) end end - context "when field_113 is no ie 2" do - let(:attributes) { { bulk_upload:, field_113: 2 } } + context "when field_128 is no ie 2" do + let(:attributes) { { bulk_upload:, field_128: 2 } } it "sets value to 0" do expect(parser.log.cap).to be(0) end end - context "when field_113 is not a permitted value" do - let(:attributes) { { bulk_upload:, field_113: 3 } } + context "when field_128 is not a permitted value" do + let(:attributes) { { bulk_upload:, field_128: 3 } } it "adds an error" do parser.valid? - expect(parser.errors[:field_113]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Common Allocation Policy (CAP)?")) + expect(parser.errors[:field_128]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Common Allocation Policy (CAP)?")) end end end describe "#accessible_register" do - context "when field_115 is yes ie 1" do - let(:attributes) { { bulk_upload:, field_115: 1 } } + context "when field_130 is yes ie 1" do + let(:attributes) { { bulk_upload:, field_130: 1 } } it "sets value to 1" do expect(parser.log.accessible_register).to be(1) end end - context "when field_115 is no ie 2" do - let(:attributes) { { bulk_upload:, field_115: 2 } } + context "when field_130 is no ie 2" do + let(:attributes) { { bulk_upload:, field_130: 2 } } it "sets value to 0" do expect(parser.log.accessible_register).to be(0) end end - context "when field_115 is not a permitted value" do - let(:attributes) { { bulk_upload:, field_115: 3 } } + context "when field_130 is not a permitted value" do + let(:attributes) { { bulk_upload:, field_130: 3 } } it "adds an error" do parser.valid? - expect(parser.errors[:field_115]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Accessible Register?")) + expect(parser.errors[:field_130]).to include(I18n.t("validations.lettings.2026.bulk_upload.invalid_option", question: "was the letting made under the Accessible Register?")) end end end describe "#letting_allocation_unknown" do - context "when field_112, 113, 114, 115 are no ie 2" do - let(:attributes) { { bulk_upload:, field_112: 2, field_113: 2, field_114: 2, field_115: 2 } } + context "when field_127, 113, 114, 115 are no ie 2" do + let(:attributes) { { bulk_upload:, field_127: 2, field_128: 2, field_129: 2, field_130: 2 } } it "sets value to 1" do expect(parser.log.letting_allocation_unknown).to be(1) end end - context "when any one of field_112, 113, 114, 115 is yes ie 1" do - let(:attributes) { { bulk_upload:, field_115: 1 } } + context "when any one of field_127, 113, 114, 115 is yes ie 1" do + let(:attributes) { { bulk_upload:, field_130: 1 } } it "sets value to 0" do expect(parser.log.letting_allocation_unknown).to be(0) @@ -2497,14 +2496,14 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do let(:attributes) do { bulk_upload:, - field_43: "F", - field_49: "M", - field_53: "X", - field_57: "R", - field_61: "F", - field_65: "M", - field_69: "X", - field_73: "R", + field_42: "F", + field_50: "M", + field_56: "X", + field_62: "R", + field_68: "F", + field_74: "M", + field_80: "X", + field_86: "R", } end @@ -2524,14 +2523,14 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do let(:attributes) do { bulk_upload:, - field_46: "1", - field_50: "2", - field_54: "6", - field_58: "7", - field_62: "8", - field_66: "9", - field_70: "0", - field_74: "10", + field_47: "1", + field_53: "2", + field_59: "6", + field_65: "7", + field_71: "8", + field_77: "9", + field_83: "0", + field_89: "10", } end @@ -2551,13 +2550,13 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do let(:attributes) do { bulk_upload:, - field_47: "1", - field_51: "2", + field_49: "1", field_55: "2", - field_59: "3", - field_63: "1", - field_67: "2", - field_71: "2", + field_61: "2", + field_67: "3", + field_73: "1", + field_79: "2", + field_85: "2", } end @@ -2574,7 +2573,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#net_income_known" do context "when 1" do - let(:attributes) { { bulk_upload:, field_117: "1" } } + let(:attributes) { { bulk_upload:, field_135: "1" } } it "sets value from correct mapping" do expect(parser.log.net_income_known).to eq(0) @@ -2582,7 +2581,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when 2" do - let(:attributes) { { bulk_upload:, field_117: "2" } } + let(:attributes) { { bulk_upload:, field_135: "2" } } it "sets value from correct mapping" do expect(parser.log.net_income_known).to eq(1) @@ -2590,7 +2589,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when 3" do - let(:attributes) { { bulk_upload:, field_117: "3" } } + let(:attributes) { { bulk_upload:, field_135: "3" } } it "sets value from correct mapping" do expect(parser.log.net_income_known).to eq(2) @@ -2615,7 +2614,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#sheltered" do - let(:attributes) { { bulk_upload:, field_36: "1" } } + let(:attributes) { { bulk_upload:, field_35: "1" } } it "sets value from correct mapping" do expect(parser.log.sheltered).to eq(1) @@ -2624,16 +2623,16 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "illness fields" do mapping = [ - { attribute: :illness_type_1, field: :field_94 }, - { attribute: :illness_type_2, field: :field_88 }, - { attribute: :illness_type_3, field: :field_91 }, - { attribute: :illness_type_4, field: :field_86 }, - { attribute: :illness_type_5, field: :field_87 }, - { attribute: :illness_type_6, field: :field_89 }, - { attribute: :illness_type_7, field: :field_90 }, - { attribute: :illness_type_8, field: :field_93 }, - { attribute: :illness_type_9, field: :field_92 }, - { attribute: :illness_type_10, field: :field_95 }, + { attribute: :illness_type_1, field: :field_109 }, + { attribute: :illness_type_2, field: :field_103 }, + { attribute: :illness_type_3, field: :field_106 }, + { attribute: :illness_type_4, field: :field_101 }, + { attribute: :illness_type_5, field: :field_102 }, + { attribute: :illness_type_6, field: :field_104 }, + { attribute: :illness_type_7, field: :field_105 }, + { attribute: :illness_type_8, field: :field_108 }, + { attribute: :illness_type_9, field: :field_107 }, + { attribute: :illness_type_10, field: :field_110 }, ] mapping.each do |hash| @@ -2666,7 +2665,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#tenancyother" do - let(:attributes) { { bulk_upload:, field_40: "some other tenancy" } } + let(:attributes) { { bulk_upload:, field_39: "some other tenancy" } } it "sets value to given free text string" do expect(parser.log.tenancyother).to eql("some other tenancy") @@ -2674,7 +2673,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#tenancylength" do - let(:attributes) { { bulk_upload:, field_41: "2" } } + let(:attributes) { { bulk_upload:, field_40: "2" } } it "sets value to given free text string" do expect(parser.log.tenancylength).to eq(2) @@ -2682,7 +2681,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#earnings" do - let(:attributes) { { bulk_upload:, field_119: "104.50" } } + let(:attributes) { { bulk_upload:, field_137: "104.50" } } it "rounds to the nearest whole pound" do expect(parser.log.earnings).to eq(105) @@ -2691,7 +2690,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#reasonother" do context "when reason is 'Other'" do - let(:attributes) { { bulk_upload:, field_98: "20", field_99: "some other reason" } } + let(:attributes) { { bulk_upload:, field_113: "20", field_114: "some other reason" } } it "is set to given free text string" do expect(parser.log.reasonother).to eql("some other reason") @@ -2699,7 +2698,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when reason is not 'Other'" do - let(:attributes) { { bulk_upload:, field_98: "50", field_99: "some other reason" } } + let(:attributes) { { bulk_upload:, field_113: "50", field_114: "some other reason" } } it "is set to nil" do expect(parser.log.reasonother).to be_nil @@ -2708,7 +2707,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#ppcodenk" do - let(:attributes) { { bulk_upload:, field_102: "2" } } + let(:attributes) { { bulk_upload:, field_117: "2" } } it "sets correct value from mapping" do expect(parser.log.ppcodenk).to eq(1) @@ -2717,7 +2716,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#household_charge" do context "when log is general needs" do - let(:attributes) { { bulk_upload:, field_4: 1, field_122: "1" } } + let(:attributes) { { bulk_upload:, field_4: 1, field_140: "1" } } it "sets correct value from mapping" do expect(parser.log.household_charge).to be_nil @@ -2725,7 +2724,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when log is supported housing" do - let(:attributes) { { bulk_upload:, field_4: 2, field_122: "1" } } + let(:attributes) { { bulk_upload:, field_4: 2, field_140: "1" } } it "sets correct value from mapping" do expect(parser.log.household_charge).to eq(1) @@ -2734,14 +2733,14 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#supcharg" do - let(:attributes) { setup_section_params.merge({ field_124: "330", field_125: "0", field_126: "0", field_127: "123.45" }) } + let(:attributes) { setup_section_params.merge({ field_142: "330", field_143: "0", field_144: "0", field_145: "123.45" }) } it "sets value given" do expect(parser.log.supcharg).to eq(123.45) end context "when other charges are not given" do - let(:attributes) { setup_section_params.merge({ field_127: "123.45", field_124: nil, field_125: nil, field_126: nil }) } + let(:attributes) { setup_section_params.merge({ field_145: "123.45", field_142: nil, field_143: nil, field_144: nil }) } it "does not set charges values" do parser.log.save! @@ -2754,15 +2753,15 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do it "adds an error to all missing charges" do parser.valid? - expect(parser.errors[:field_124]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "basic rent")]) - expect(parser.errors[:field_125]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "service charge")]) - expect(parser.errors[:field_126]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "personal service charge")]) - expect(parser.errors[:field_127]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) + expect(parser.errors[:field_142]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "basic rent")]) + expect(parser.errors[:field_143]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "service charge")]) + expect(parser.errors[:field_144]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "personal service charge")]) + expect(parser.errors[:field_145]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) end end context "when supscharg is not given" do - let(:attributes) { setup_section_params.merge({ field_123: 1, field_124: "350.45", field_125: "0", field_126: "0", field_127: nil }) } + let(:attributes) { setup_section_params.merge({ field_141: 1, field_142: "350.45", field_143: "0", field_144: "0", field_145: nil }) } it "does not set charges values" do parser.log.save! @@ -2776,16 +2775,16 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do it "adds an error to all charges" do parser.valid? - expect(parser.errors[:field_124]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) - expect(parser.errors[:field_125]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) - expect(parser.errors[:field_126]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) - expect(parser.errors[:field_127]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "support charge")]) + expect(parser.errors[:field_142]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) + expect(parser.errors[:field_143]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) + expect(parser.errors[:field_144]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.related_to_missing_charge")]) + expect(parser.errors[:field_145]).to eql([I18n.t("validations.lettings.2026.bulk_upload.charges.missing_charges", sentence_fragment: "support charge")]) end end end describe "#pscharge" do - let(:attributes) { { bulk_upload:, field_124: "111.45", field_125: "0", field_126: "123.45", field_127: "0" } } + let(:attributes) { { bulk_upload:, field_142: "111.45", field_143: "0", field_144: "123.45", field_145: "0" } } it "sets value given" do expect(parser.log.pscharge).to eq(123.45) @@ -2793,7 +2792,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#scharge" do - let(:attributes) { { bulk_upload:, field_124: "111.45", field_125: "123.45", field_126: "0", field_127: "0" } } + let(:attributes) { { bulk_upload:, field_142: "111.45", field_143: "123.45", field_144: "0", field_145: "0" } } it "sets value given" do expect(parser.log.scharge).to eq(123.45) @@ -2810,7 +2809,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#mrcdate" do context "when valid" do - let(:attributes) { { bulk_upload:, field_33: "13", field_34: "12", field_35: "22" } } + let(:attributes) { { bulk_upload:, field_32: "13", field_33: "12", field_34: "22" } } it "sets value given" do expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) @@ -2818,7 +2817,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when valid (4 digit year)" do - let(:attributes) { { bulk_upload:, field_33: "13", field_34: "12", field_35: "2022" } } + let(:attributes) { { bulk_upload:, field_32: "13", field_33: "12", field_34: "2022" } } it "sets value given" do expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13)) @@ -2826,7 +2825,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when invalid" do - let(:attributes) { { bulk_upload:, field_33: "13", field_34: "13", field_35: "22" } } + let(:attributes) { { bulk_upload:, field_32: "13", field_33: "13", field_34: "22" } } it "does not raise an error" do expect { parser.log.mrcdate }.not_to raise_error @@ -2836,7 +2835,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#majorrepairs" do context "when mrcdate given" do - let(:attributes) { { bulk_upload:, field_33: "13", field_34: "12", field_35: "22" } } + let(:attributes) { { bulk_upload:, field_32: "13", field_33: "12", field_34: "22" } } it "sets #majorrepairs to 1" do expect(parser.log.majorrepairs).to eq(1) @@ -2844,7 +2843,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when mrcdate not given" do - let(:attributes) { { bulk_upload:, field_33: "", field_34: "", field_35: "", field_4: 1 } } + let(:attributes) { { bulk_upload:, field_32: "", field_33: "", field_34: "", field_4: 1 } } it "sets #majorrepairs to 0" do expect(parser.log.majorrepairs).to eq(0) @@ -2854,7 +2853,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#voiddate" do context "when valid" do - let(:attributes) { { bulk_upload:, field_30: "13", field_31: "12", field_32: "22" } } + let(:attributes) { { bulk_upload:, field_29: "13", field_30: "12", field_31: "22" } } it "sets value given" do expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) @@ -2862,7 +2861,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when valid (4 digit year)" do - let(:attributes) { { bulk_upload:, field_30: "13", field_31: "12", field_32: "2022" } } + let(:attributes) { { bulk_upload:, field_29: "13", field_30: "12", field_31: "2022" } } it "sets value given" do expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13)) @@ -2870,7 +2869,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when invalid" do - let(:attributes) { { bulk_upload:, field_30: "13", field_31: "13", field_32: "22" } } + let(:attributes) { { bulk_upload:, field_29: "13", field_30: "13", field_31: "22" } } it "does not raise an error" do expect { parser.log.voiddate }.not_to raise_error @@ -2911,7 +2910,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#prevloc" do - let(:attributes) { { bulk_upload:, field_105: "E07000223" } } + let(:attributes) { { bulk_upload:, field_120: "E07000223" } } it "sets to given value" do expect(parser.log.prevloc).to eql("E07000223") @@ -2920,7 +2919,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#previous_la_known" do context "when known" do - let(:attributes) { { bulk_upload:, field_105: "E07000223" } } + let(:attributes) { { bulk_upload:, field_120: "E07000223" } } it "sets to 1" do expect(parser.log.previous_la_known).to eq(1) @@ -2928,7 +2927,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when not known" do - let(:attributes) { { bulk_upload:, field_105: "", field_4: 1 } } + let(:attributes) { { bulk_upload:, field_120: "", field_4: 1 } } it "sets to 0" do expect(parser.log.previous_la_known).to eq(0) @@ -2956,7 +2955,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do describe "#housingneeds" do context "when no disabled needs" do - let(:attributes) { { bulk_upload:, field_83: "1" } } + let(:attributes) { { bulk_upload:, field_98: "1" } } it "sets to 2" do expect(parser.log.housingneeds).to eq(2) @@ -2964,7 +2963,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when dont know about disabled needs" do - let(:attributes) { { bulk_upload:, field_84: "1" } } + let(:attributes) { { bulk_upload:, field_99: "1" } } it "sets to 3" do expect(parser.log.housingneeds).to eq(3) @@ -2972,7 +2971,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when housingneeds are given" do - let(:attributes) { { bulk_upload:, field_83: "0", field_81: "1", field_82: "1" } } + let(:attributes) { { bulk_upload:, field_98: "0", field_96: "1", field_97: "1" } } it "sets correct housingneeds" do expect(parser.log.housingneeds).to eq(1) @@ -2981,8 +2980,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when housingneeds are given and field_82 is nil" do - let(:attributes) { { bulk_upload:, field_83: nil, field_81: "1", field_82: "1" } } + context "when housingneeds are given and field_97 is nil" do + let(:attributes) { { bulk_upload:, field_98: nil, field_96: "1", field_97: "1" } } it "sets correct housingneeds" do expect(parser.log.housingneeds).to eq(1) @@ -2992,7 +2991,7 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when housingneeds are not given" do - let(:attributes) { { bulk_upload:, field_79: nil, field_80: nil, field_81: nil, field_83: nil } } + let(:attributes) { { bulk_upload:, field_94: nil, field_95: nil, field_96: nil, field_98: nil } } it "sets correct housingneeds" do expect(parser.log.housingneeds).to eq(1) @@ -3001,106 +3000,106 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end context "when housingneeds a and b are selected" do - let(:attributes) { { bulk_upload:, field_79: "1", field_80: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1", field_95: "1" } } it "sets error on housingneeds a and b" do parser.valid? - expect(parser.errors[:field_79]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_80]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_94]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_95]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_96]).to be_blank end end context "when housingneeds a and c are selected" do - let(:attributes) { { bulk_upload:, field_79: "1", field_81: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1", field_96: "1" } } it "sets error on housingneeds a and c" do parser.valid? - expect(parser.errors[:field_79]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_81]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_80]).to be_blank + expect(parser.errors[:field_94]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_96]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_95]).to be_blank end end context "when housingneeds b and c are selected" do - let(:attributes) { { bulk_upload:, field_80: "1", field_81: "1" } } + let(:attributes) { { bulk_upload:, field_95: "1", field_96: "1" } } it "sets error on housingneeds b and c" do parser.valid? - expect(parser.errors[:field_80]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_81]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) - expect(parser.errors[:field_79]).to be_blank + expect(parser.errors[:field_95]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_96]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds_type.only_one_option_permitted")) + expect(parser.errors[:field_94]).to be_blank end end context "when housingneeds a and g are selected" do - let(:attributes) { { bulk_upload:, field_79: "1", field_83: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1", field_98: "1" } } it "sets error on housingneeds a and g" do parser.valid? - expect(parser.errors[:field_83]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.no_disabled_needs_conjunction")) - expect(parser.errors[:field_79]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.no_disabled_needs_conjunction")) - expect(parser.errors[:field_80]).to be_blank - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_98]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.no_disabled_needs_conjunction")) + expect(parser.errors[:field_94]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.no_disabled_needs_conjunction")) + expect(parser.errors[:field_95]).to be_blank + expect(parser.errors[:field_96]).to be_blank end end context "when only housingneeds g is selected" do - let(:attributes) { { bulk_upload:, field_79: "0", field_83: "1" } } + let(:attributes) { { bulk_upload:, field_94: "0", field_98: "1" } } it "does not add any housingneeds errors" do parser.valid? - expect(parser.errors[:field_55]).to be_blank - expect(parser.errors[:field_79]).to be_blank - expect(parser.errors[:field_80]).to be_blank - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_61]).to be_blank + expect(parser.errors[:field_94]).to be_blank + expect(parser.errors[:field_95]).to be_blank + expect(parser.errors[:field_96]).to be_blank end end context "when housingneeds a and h are selected" do - let(:attributes) { { bulk_upload:, field_79: "1", field_84: "1" } } + let(:attributes) { { bulk_upload:, field_94: "1", field_99: "1" } } it "sets error on housingneeds a and h" do parser.valid? - expect(parser.errors[:field_84]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.dont_know_disabled_needs_conjunction")) - expect(parser.errors[:field_79]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.dont_know_disabled_needs_conjunction")) - expect(parser.errors[:field_80]).to be_blank - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_99]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.dont_know_disabled_needs_conjunction")) + expect(parser.errors[:field_94]).to include(I18n.t("validations.lettings.2026.bulk_upload.housingneeds.dont_know_disabled_needs_conjunction")) + expect(parser.errors[:field_95]).to be_blank + expect(parser.errors[:field_96]).to be_blank end end context "when only housingneeds h is selected" do - let(:attributes) { { bulk_upload:, field_79: "0", field_84: "1" } } + let(:attributes) { { bulk_upload:, field_94: "0", field_99: "1" } } it "does not add any housingneeds errors" do parser.valid? - expect(parser.errors[:field_84]).to be_blank - expect(parser.errors[:field_79]).to be_blank - expect(parser.errors[:field_80]).to be_blank - expect(parser.errors[:field_81]).to be_blank + expect(parser.errors[:field_99]).to be_blank + expect(parser.errors[:field_94]).to be_blank + expect(parser.errors[:field_95]).to be_blank + expect(parser.errors[:field_96]).to be_blank end end end describe "#housingneeds_type" do - context "when field_79 is 1" do - let(:attributes) { { bulk_upload:, field_79: "1" } } + context "when field_94 is 1" do + let(:attributes) { { bulk_upload:, field_94: "1" } } it "set to 0" do expect(parser.log.housingneeds_type).to eq(0) end end - context "when field_80 is 1" do - let(:attributes) { { bulk_upload:, field_80: "1" } } + context "when field_95 is 1" do + let(:attributes) { { bulk_upload:, field_95: "1" } } it "set to 1" do expect(parser.log.housingneeds_type).to eq(1) end end - context "when field_81 is 1" do - let(:attributes) { { bulk_upload:, field_81: "1" } } + context "when field_96 is 1" do + let(:attributes) { { bulk_upload:, field_96: "1" } } it "set to 2" do expect(parser.log.housingneeds_type).to eq(2) @@ -3109,8 +3108,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end describe "#housingneeds_other" do - context "when field_54 is 1" do - let(:attributes) { { bulk_upload:, field_82: "1" } } + context "when field_59 is 1" do + let(:attributes) { { bulk_upload:, field_97: "1" } } it "sets to 1" do expect(parser.log.housingneeds_other).to eq(1) From fce65aabf5e901f615d3cbef89b22c97744e97bd Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 26 Feb 2026 13:14:59 +0000 Subject: [PATCH 2/7] CLDC-4119: Amend lettings duplicate log check (#3179) * CLDC-4119: Update row parser duplicate log check * CLDC-4119: Update main site duplicate logs check adds sexrab1 to the duplicate log attributes needs special care for the location and uprn attributes * CLDC-4119: Update duplicate logs page shows correct fields for 2025/2026 and for whether manual address or address search * CLDC-4119: Update duplicate log errors we no longer need a lot of the tests here as it's all postcode based * CLDC-4119: Add an address line1 to duplicate log trait * fixup! CLDC-4119: Update duplicate log errors * CLDC-4119: Add UPRN to duplicate log trait * fixup! CLDC-4119: Update main site duplicate logs check improve comments * fixup! CLDC-4119: Update duplicate logs page improve duplicate_logs_helper.rb * fixup! CLDC-4119: Update main site duplicate logs check handle sexrab answered properly * fixup! CLDC-4119: Add UPRN to duplicate log trait * Revert "fixup! CLDC-4119: Add UPRN to duplicate log trait" This reverts commit 9ff5f098397ec8e0db9b203a6c16eb102a787661. * Revert "CLDC-4119: Add UPRN to duplicate log trait" This reverts commit d4d1c00c0589bd39c11c62c7143d398b5ebba4f5. * fixup! CLDC-4119: Update duplicate log errors --- app/helpers/duplicate_logs_helper.rb | 23 ++- app/models/lettings_log.rb | 90 ++++++++--- .../lettings/year2026/row_parser.rb | 28 ++-- .../_duplicate_log_check_answers.erb | 2 +- lib/tasks/handle_unpended_logs.rake | 8 +- spec/factories/lettings_log.rb | 1 + .../lettings/year2026/row_parser_spec.rb | 148 ++---------------- 7 files changed, 112 insertions(+), 188 deletions(-) diff --git a/app/helpers/duplicate_logs_helper.rb b/app/helpers/duplicate_logs_helper.rb index 077845857..228b4a6c6 100644 --- a/app/helpers/duplicate_logs_helper.rb +++ b/app/helpers/duplicate_logs_helper.rb @@ -55,16 +55,18 @@ module DuplicateLogsHelper duplicate_sets_count > 1 ? "Review these #{duplicate_sets_count} sets of logs" : "Review this #{duplicate_sets_count} set of logs" end - def duplicate_log_question_label(question) - if question.id == "uprn" + def duplicate_log_question_label(question, log) + if question.id == "uprn" && !log.form.start_year_2026_or_later? "Postcode (from UPRN)" + elsif question.id == "address_line1" + "#{question.question_number_string} - Address line 1" else get_question_label(question) end end def duplicate_log_answer_label(question, log) - if question.id == "uprn" + if question.id == "uprn" && !log.form.start_year_2026_or_later? postcode_question = log.form.get_question("postcode_full", log) get_answer_label(postcode_question, log) else @@ -73,16 +75,21 @@ module DuplicateLogsHelper end def duplicate_log_extra_value(question, log) - if question.id == "uprn" - postcode_question = log.form.get_question("postcode_full", log) - postcode_question.get_extra_check_answer_value(log) + case question.id + when "uprn" + if log.form.start_year_2026_or_later? + "\n\n#{[log.address_line1, log.postcode_full].join("\n")}" + else + postcode_question = log.form.get_question("postcode_full", log) + postcode_question.get_extra_check_answer_value(log) + end else question.get_extra_check_answer_value(log) end end def duplicate_log_answer_label_present(question, log, current_user) - if question.id == "uprn" + if question.id == "uprn" && !log.form.start_year_2026_or_later? postcode_question = log.form.get_question("postcode_full", log) postcode_question.answer_label(log, current_user).present? else @@ -91,7 +98,7 @@ module DuplicateLogsHelper end def duplicate_log_inferred_answers(question, log) - if question.id == "uprn" + if question.id == "uprn" && !log.form.start_year_2026_or_later? postcode_question = log.form.get_question("postcode_full", log) postcode_question.get_inferred_answers(log) else diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index d70d0e012..fb4866fc0 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -21,6 +21,7 @@ class LettingsLog < Log include Validations::DateValidations include Validations::FinancialValidations include MoneyFormattingHelper + include CollectionTimeHelper has_paper_trail @@ -42,6 +43,8 @@ class LettingsLog < Log belongs_to :managing_organisation, class_name: "Organisation", optional: true scope :filter_by_year, ->(year) { where(startdate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) } + scope :filter_by_year_or_later, ->(year) { where("lettings_logs.startdate >= ?", Time.zone.local(year.to_i, 4, 1)) } + scope :filter_by_year_or_earlier, ->(year) { where("lettings_logs.startdate < ?", Time.zone.local(year.to_i + 1, 4, 1)) } scope :filter_by_years_or_nil, lambda { |years, _user = nil| first_year = years.shift query = filter_by_year(first_year) @@ -86,40 +89,48 @@ class LettingsLog < Log scope :age1_answered, -> { where.not(age1: nil).or(where(age1_known: 1)) } scope :tcharge_answered, -> { where.not(tcharge: nil).or(where(household_charge: 1)).or(where(is_carehome: 1)) } scope :chcharge_answered, -> { where.not(chcharge: nil).or(where(is_carehome: [nil, 0])) } - scope :location_for_log_answered, ->(log) { where(location_id: log.location_id).or(where(needstype: 1)) } - scope :postcode_for_log_answered, ->(log) { where(postcode_full: log.postcode_full).or(where(needstype: 2)) } - scope :location_answered, -> { where.not(location_id: nil).or(where(needstype: 1)) } + # once 2025 logs are removed this logic can be simplified + # 2025 and before, match on location if supported, or address if general needs + # 2026 and after, match on address only + scope :location_for_log_answered_as, ->(log) { where(location_id: log.location_id).or(where(needstype: 1)).or(filter_by_year_or_later(2026)) } + scope :address_for_log_answered_as, lambda { |log| + where(postcode_full: log.postcode_full).where(address_line1: log.address_line1).where(uprn: log.uprn) + .or(filter_by_year_or_earlier(2025).where(needstype: 2)) + } + scope :location_answered, -> { where.not(location_id: nil).or(where(needstype: 1)).or(filter_by_year_or_later(2026)) } scope :postcode_answered, -> { where.not(postcode_full: nil).or(where(needstype: 2)) } + scope :sex1_answered, -> { where.not(sex1: nil).filter_by_year_or_earlier(2025).or(where.not(sexrab1: nil).filter_by_year_or_later(2026)) } scope :duplicate_logs, lambda { |log| visible .where.not(id: log.id) .where.not(startdate: nil) - .where.not(sex1: nil) + .sex1_answered .where.not(ecstat1: nil) .where.not(needstype: nil) .age1_answered .tcharge_answered .chcharge_answered - .location_for_log_answered(log) - .postcode_for_log_answered(log) + .location_for_log_answered_as(log) + .address_for_log_answered_as(log) .where(log.slice(*DUPLICATE_LOG_ATTRIBUTES)) } - scope :duplicate_sets, lambda { |assigned_to_id = nil| + scope :duplicate_sets_2025_and_earlier, lambda { |assigned_to_id = nil| scope = visible - .group(*DUPLICATE_LOG_ATTRIBUTES, :postcode_full, :location_id) - .where.not(startdate: nil) - .where.not(sex1: nil) - .where.not(ecstat1: nil) - .where.not(needstype: nil) - .age1_answered - .tcharge_answered - .chcharge_answered - .location_answered - .postcode_answered - .having( - "COUNT(*) > 1", - ) + .filter_by_year_or_earlier(2025) + .group(*DUPLICATE_LOG_ATTRIBUTES, :postcode_full, :location_id, :uprn, :address_line1) + .where.not(startdate: nil) + .where.not(sex1: nil) + .where.not(ecstat1: nil) + .where.not(needstype: nil) + .age1_answered + .tcharge_answered + .chcharge_answered + .location_answered + .postcode_answered + .having( + "COUNT(*) > 1", + ) if assigned_to_id scope = scope.having("MAX(CASE WHEN assigned_to_id = ? THEN 1 ELSE 0 END) >= 1", assigned_to_id) @@ -127,6 +138,34 @@ class LettingsLog < Log scope.pluck("ARRAY_AGG(id)") } + scope :duplicate_sets_2026_and_later, lambda { |assigned_to_id = nil| + scope = visible + .filter_by_year_or_later(2026) + # separate function as location needs to be fully ignored in 2026 + .group(*DUPLICATE_LOG_ATTRIBUTES, :postcode_full, :uprn, :address_line1) + .where.not(startdate: nil) + .where.not(sexrab1: nil) + .where.not(ecstat1: nil) + .where.not(needstype: nil) + .age1_answered + .tcharge_answered + .chcharge_answered + .location_answered + .postcode_answered + .having( + "COUNT(*) > 1", + ) + + if assigned_to_id + scope = scope.having("MAX(CASE WHEN assigned_to_id = ? THEN 1 ELSE 0 END) >= 1", assigned_to_id) + end + scope.pluck("ARRAY_AGG(id)") + } + + scope :duplicate_sets, lambda { |assigned_to_id = nil| + duplicate_sets_2025_and_earlier(assigned_to_id) + duplicate_sets_2026_and_later(assigned_to_id) + } + scope :with_illness_without_type, lambda { where(illness: 1, illness_type_1: false, @@ -151,7 +190,7 @@ class LettingsLog < Log HAS_BENEFITS_OPTIONS = [1, 6, 8, 7].freeze NUM_OF_WEEKS_FROM_PERIOD = { 2 => 26, 3 => 13, 4 => 12, 5 => 50, 6 => 49, 7 => 48, 8 => 47, 9 => 46, 11 => 51, 1 => 52, 10 => 53 }.freeze SUFFIX_FROM_PERIOD = { 2 => "every 2 weeks", 3 => "every 4 weeks", 4 => "every month" }.freeze - DUPLICATE_LOG_ATTRIBUTES = %w[owning_organisation_id tenancycode startdate age1_known age1 sex1 ecstat1 tcharge household_charge chcharge].freeze + DUPLICATE_LOG_ATTRIBUTES = %w[owning_organisation_id tenancycode startdate age1_known age1 sex1 sexrab1 ecstat1 tcharge household_charge chcharge].freeze RENT_TYPE = { social_rent: 0, affordable_rent: 1, @@ -191,7 +230,6 @@ class LettingsLog < Log location.linked_local_authorities.active(form.start_date).first&.code || location.location_code end - # TODO: CLDC-4119: Beware! This method may cause issues when testing supported housing log duplicate detection after postcode is added, as it can return `location.postcode` instead of the actual `postcode_full` stored on the log record (`super`). If this happens, investigate why it isn't returning `super`, as it should when `form.start_year_2026_or_later? && super`. def postcode_full return super unless location return super if form.start_year_2026_or_later? && super @@ -698,11 +736,13 @@ class LettingsLog < Log ["owning_organisation_id", "startdate", "tenancycode", - uprn.blank? ? "postcode_full" : "uprn", + form.start_year_2026_or_later? ? "address_line1" : nil, + "postcode_full", + "uprn", "scheme_id", - "location_id", + form.start_year_2026_or_later? ? nil : "location_id", "age1", - "sex1", + form.start_year_2026_or_later? ? "sexrab1" : "sex1", "ecstat1", household_charge == 1 ? "household_charge" : nil, "tcharge", diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 547ced717..87d6bb526 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -569,12 +569,13 @@ class BulkUpload::Lettings::Year2026::RowParser "field_1", # owning org "field_8", # startdate "field_9", # startdate - "field_10", # startdate - "field_13", # tenancycode - !general_needs? ? :field_6.to_s : nil, # location # TODO: CLDC-4119: remove location from hash - !supported_housing? ? "field_23" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing - !supported_housing? ? "field_24" : nil, # postcode # TODO: CLDC-4119: add postcode to hash for supported housing - "field_41", # age1 + "field_10", # startdate + "field_13", # tenancycode + "field_18", # uprn + "field_19", # address_line1 + "field_23", # postcode first half + "field_24", # postcode second half + "field_41", # age1 "field_42", # sexrab1 "field_47", # ecstat1 ) @@ -728,8 +729,9 @@ private "ecstat1", "owning_organisation", "tcharge", - !supported_housing? ? "postcode_full" : nil, # TODO: CLDC-4119: add postcode to duplicate check fields for supported housing - !general_needs? ? "location" : nil, # TODO: CLDC-4119: remove location from duplicate check fields + "postcode_full", + "address_line1", + "uprn", "tenancycode", log.chcharge.present? ? "chcharge" : nil, ].compact @@ -1000,11 +1002,11 @@ private errors.add(:field_9, error_message) # startdate errors.add(:field_10, error_message) # startdate errors.add(:field_13, error_message) # tenancycode - errors.add(:field_6, error_message) if !general_needs? && :field_6.present? # location # TODO: CLDC-4119: remove location from error fields - errors.add(:field_5, error_message) if !general_needs? && :field_6.blank? # add to Scheme field as unclear whether log uses New or Old CORE ids - errors.add(:field_23, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing - errors.add(:field_24, error_message) unless supported_housing? # postcode_full # TODO: CLDC-4119: add postcode to error fields for supported housing - errors.add(:field_25, error_message) unless supported_housing? # la # TODO: CLDC-4119: add LA to error fields for supported housing + errors.add(:field_18, error_message) # uprn + errors.add(:field_19, error_message) # address_line_1 + errors.add(:field_23, error_message) # postcode_full + errors.add(:field_24, error_message) # postcode_full + errors.add(:field_25, error_message) # la errors.add(:field_41, error_message) # age1 errors.add(:field_42, error_message) # sexrab1 errors.add(:field_47, error_message) # ecstat1 diff --git a/app/views/duplicate_logs/_duplicate_log_check_answers.erb b/app/views/duplicate_logs/_duplicate_log_check_answers.erb index 91deb10c8..8dbe7056f 100644 --- a/app/views/duplicate_logs/_duplicate_log_check_answers.erb +++ b/app/views/duplicate_logs/_duplicate_log_check_answers.erb @@ -3,7 +3,7 @@ <%= govuk_summary_list do |summary_list| %> <% log.duplicate_check_questions(current_user).each do |question| %> <% summary_list.with_row do |row| %> - <% row.with_key { duplicate_log_question_label(question) } %> + <% row.with_key { duplicate_log_question_label(question, log) } %> <% row.with_value do %> <%= simple_format( diff --git a/lib/tasks/handle_unpended_logs.rake b/lib/tasks/handle_unpended_logs.rake index dcc87d117..5c81c61be 100644 --- a/lib/tasks/handle_unpended_logs.rake +++ b/lib/tasks/handle_unpended_logs.rake @@ -6,7 +6,7 @@ task :handle_unpended_logs, %i[perform_updates] => :environment do |_task, args| query = "SELECT \"versions\".* FROM \"versions\" WHERE \"versions\".\"item_type\" = 'LettingsLog' AND whodunnit is null AND ((object_changes like '%status:\n- 3\n- 1%') OR (object_changes like '%status:\n- 3\n- 2%'))" results = pg.execute(query) - duplicate_log_attributes = %w[owning_organisation_id tenancycode startdate age1_known age1 sex1 ecstat1 tcharge household_charge chcharge] + duplicate_log_attributes = %w[owning_organisation_id tenancycode startdate age1_known age1 sex1 sexrab1 ecstat1 tcharge household_charge chcharge] seen = [].to_set @@ -41,14 +41,14 @@ task :handle_unpended_logs, %i[perform_updates] => :environment do |_task, args| # This is the normal query for duplicates but without the check that the logs are visible (i.e. not deleted/pending) duplicates = LettingsLog.where.not(id: log.id) .where.not(startdate: nil) - .where.not(sex1: nil) + .sex1_answered .where.not(ecstat1: nil) .where.not(needstype: nil) .age1_answered .tcharge_answered .chcharge_answered - .location_for_log_answered(log) - .postcode_for_log_answered(log) + .location_for_log_answered_as(log) + .address_for_log_answered_as(log) .where(log.slice(*duplicate_log_attributes)) duplicate_count = duplicates.length diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index 2c1aff71a..38f066bda 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -35,6 +35,7 @@ FactoryBot.define do setup_completed status { 1 } tenancycode { "same tenancy code" } + address_line1 { "same address line 1" } postcode_full { "A1 1AA" } uprn_known { 0 } declaration { 1 } diff --git a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb index 19847dec5..d2e7fb797 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -321,10 +321,11 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_144, # pscharge :field_145, # supcharg ].each do |field| - expect(parser.errors[field]).to include(error_message) + expect(parser.errors[field]).to include(error_message), "field: #{field}" end expect(parser.errors[:field_6]).not_to include(error_message) + expect(parser.errors[:field_43]).not_to include(error_message) end end @@ -343,8 +344,8 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end - context "when a supported housing log already exists in the db" do # TODO: CLDC-4119: Beware! The `postcode_full` method in the `LettingsLog` class may cause issues with these supported housing log duplicate detection tests after postcode is added. See comment on the `postcode_full` method for details. - let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id, field_35: 3, field_140: 0 }) } + context "when a supported housing log already exists in the db" do + let(:attributes) { valid_attributes.merge({ field_4: "2", field_5: "S#{scheme.id}", field_6: location.old_visible_id }) } before do parser.log.save! @@ -366,7 +367,11 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_9, # startdate :field_10, # startdate :field_13, # tenancycode - :field_6, # location + :field_18, # uprn + :field_19, # address_line_1 + :field_23, # postcode_full + :field_24, # postcode_full + :field_25, # la :field_41, # age1 :field_42, # sexrab1 :field_47, # ecstat1 @@ -375,141 +380,10 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do :field_144, # pscharge :field_145, # supcharg ].each do |field| - expect(parser.errors[field]).to include(error_message) - end - - expect(parser.errors[:field_23]).not_to include(error_message) - expect(parser.errors[:field_24]).not_to include(error_message) - expect(parser.errors[:field_25]).not_to include(error_message) - end - end - - context "with old core scheme and location ids" do - context "when a supported housing log already exists in the db" do - let(:attributes) { { bulk_upload:, field_4: "2", field_5: "123" } } - - before do - parser.log.save! - parser.instance_variable_set(:@valid, nil) - end - - it "is not a valid row" do - expect(parser).not_to be_valid - end - - it "adds an error to all the fields used to determine duplicates" do - parser.valid? - - error_message = I18n.t("validations.lettings.2026.bulk_upload.duplicate") - - [ - :field_1, # owning_organisation - :field_8, # startdate - :field_9, # startdate - :field_10, # startdate - :field_13, # tenancycode - :field_6, # location - :field_41, # age1 - :field_42, # sexrab1 - :field_47, # ecstat1 - :field_142, # brent - :field_143, # scharge - :field_144, # pscharge - :field_145, # supcharg - ].each do |field| - expect(parser.errors[field]).to include(error_message) - end - - expect(parser.errors[:field_23]).not_to include(error_message) - expect(parser.errors[:field_24]).not_to include(error_message) - expect(parser.errors[:field_25]).not_to include(error_message) - end - end - end - - context "with new core scheme and location ids" do - context "when a supported housing log already exists in the db" do - let(:attributes) { { bulk_upload:, field_4: "2", field_5: "S123" } } - - before do - parser.log.save! - parser.instance_variable_set(:@valid, nil) + expect(parser.errors[field]).to include(error_message), "field: #{field}" end - it "is not a valid row" do - expect(parser).not_to be_valid - end - - it "adds an error to all the fields used to determine duplicates" do - parser.valid? - - error_message = I18n.t("validations.lettings.2026.bulk_upload.duplicate") - - [ - :field_1, # owning_organisation - :field_8, # startdate - :field_9, # startdate - :field_10, # startdate - :field_13, # tenancycode - :field_6, # location - :field_41, # age1 - :field_42, # sexrab1 - :field_47, # ecstat1 - ].each do |field| - expect(parser.errors[field]).to include(error_message) - end - - expect(parser.errors[:field_23]).not_to include(error_message) - expect(parser.errors[:field_24]).not_to include(error_message) - expect(parser.errors[:field_25]).not_to include(error_message) - end - end - - context "when a supported housing log already exists in the db (2)" do - let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) } - let(:attributes) do - valid_attributes.merge({ field_5: "S#{scheme.id}", - field_4: "2", - field_11: "2", - field_6: location.id, - field_1: owning_org.old_visible_id, - field_140: 0, - field_35: 4 }) - end - - before do - parser.log.save! - parser.instance_variable_set(:@valid, nil) - end - - it "is not a valid row" do - expect(parser).not_to be_valid - end - - it "adds an error to all the fields used to determine duplicates" do - parser.valid? - - error_message = I18n.t("validations.lettings.2026.bulk_upload.duplicate") - - [ - :field_1, # owning_organisation - :field_8, # startdate - :field_9, # startdate - :field_10, # startdate - :field_13, # tenancycode - :field_6, # location - :field_41, # age1 - :field_42, # sexrab1 - :field_47, # ecstat1 - :field_140, # household_charge - ].each do |field| - expect(parser.errors[field]).to include(error_message) - end - - expect(parser.errors[:field_23]).not_to include(error_message) - expect(parser.errors[:field_24]).not_to include(error_message) - expect(parser.errors[:field_25]).not_to include(error_message) - end + expect(parser.errors[:field_6]).not_to include(error_message) end end From e294120cfd76bd0a0bcb726cc55464d0ec6f5687 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:49:41 +0000 Subject: [PATCH 3/7] Bump minimatch from 3.1.2 to 3.1.5 (#3204) Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a19a6d611..ff3fe1709 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3434,9 +3434,9 @@ mini-css-extract-plugin@^2.6.0: tapable "^2.2.1" minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" From 6f0bef3432a3ab45f96423f309f14b60e6e14d36 Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:58:38 +0000 Subject: [PATCH 4/7] CLDC-4174: clone Q88 service charge question for staircasing (#3182) * add the question * CLDC-4174: update question number for 2026 * CLDc-4174: update question number for 2026 * CLDc-4174: update subsection * CLDc-4174: update question specs * CLDC-4174: add subsection spec * CLDc-4174: lint * CLDc-4174: add new page test * CLDC-4241: lint * CLDC-4241: lint * CLDC-4174: copy sales log csv updates from CLDC-4176 --------- Co-authored-by: Samuel Young --- app/models/form/sales/pages/service_charge.rb | 4 +- .../sales/pages/service_charge_staircasing.rb | 13 +++ .../sales/questions/has_service_charge.rb | 15 +++- .../form/sales/questions/service_charge.rb | 16 +++- ...hared_ownership_staircasing_transaction.rb | 1 + app/services/csv/sales_log_csv_service.rb | 11 +-- .../pages/service_charge_staircasing_spec.rb | 29 +++++++ .../questions/has_service_charge_spec.rb | 48 ++++++++++- .../sales/questions/service_charge_spec.rb | 47 +++++++++- ..._ownership_staircasing_transaction_spec.rb | 85 +++++++++++++++++++ 10 files changed, 249 insertions(+), 20 deletions(-) create mode 100644 app/models/form/sales/pages/service_charge_staircasing.rb create mode 100644 spec/models/form/sales/pages/service_charge_staircasing_spec.rb create mode 100644 spec/models/form/sales/subsections/shared_ownership_staircasing_transaction_spec.rb diff --git a/app/models/form/sales/pages/service_charge.rb b/app/models/form/sales/pages/service_charge.rb index a0102eda4..e938a3e56 100644 --- a/app/models/form/sales/pages/service_charge.rb +++ b/app/models/form/sales/pages/service_charge.rb @@ -6,8 +6,8 @@ class Form::Sales::Pages::ServiceCharge < ::Form::Page def questions @questions ||= [ - Form::Sales::Questions::HasServiceCharge.new(nil, nil, self), - Form::Sales::Questions::ServiceCharge.new(nil, nil, self), + Form::Sales::Questions::HasServiceCharge.new(nil, nil, self, staircasing: false), + Form::Sales::Questions::ServiceCharge.new(nil, nil, self, staircasing: false), ] end end diff --git a/app/models/form/sales/pages/service_charge_staircasing.rb b/app/models/form/sales/pages/service_charge_staircasing.rb new file mode 100644 index 000000000..4e4f65c9e --- /dev/null +++ b/app/models/form/sales/pages/service_charge_staircasing.rb @@ -0,0 +1,13 @@ +class Form::Sales::Pages::ServiceChargeStaircasing < ::Form::Page + def initialize(id, hsh, subsection) + super + @copy_key = "sales.sale_information.servicecharges" + end + + def questions + @questions ||= [ + Form::Sales::Questions::HasServiceCharge.new(nil, nil, self, staircasing: true), + Form::Sales::Questions::ServiceCharge.new(nil, nil, self, staircasing: true), + ] + end +end diff --git a/app/models/form/sales/questions/has_service_charge.rb b/app/models/form/sales/questions/has_service_charge.rb index 41bf5f809..708c32f0d 100644 --- a/app/models/form/sales/questions/has_service_charge.rb +++ b/app/models/form/sales/questions/has_service_charge.rb @@ -1,6 +1,6 @@ class Form::Sales::Questions::HasServiceCharge < ::Form::Question - def initialize(id, hsh, subsection) - super + def initialize(id, hsh, subsection, staircasing:) + super(id, hsh, subsection) @id = "has_mscharge" @type = "radio" @answer_options = ANSWER_OPTIONS @@ -15,7 +15,8 @@ class Form::Sales::Questions::HasServiceCharge < ::Form::Question ], } @copy_key = "sales.sale_information.servicecharges.has_servicecharge" - @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + @staircasing = staircasing + @question_number = question_number_from_year[form.start_date.year] || question_number_from_year[question_number_from_year.keys.max] end ANSWER_OPTIONS = { @@ -23,5 +24,11 @@ class Form::Sales::Questions::HasServiceCharge < ::Form::Question "0" => { "value" => "No" }, }.freeze - QUESTION_NUMBER_FROM_YEAR = { 2025 => 88 }.freeze + def question_number_from_year + if @staircasing + { 2026 => 0 }.freeze + else + { 2025 => 88, 2026 => 0 }.freeze + end + end end diff --git a/app/models/form/sales/questions/service_charge.rb b/app/models/form/sales/questions/service_charge.rb index 6b9a76a94..f3aec4a2b 100644 --- a/app/models/form/sales/questions/service_charge.rb +++ b/app/models/form/sales/questions/service_charge.rb @@ -1,16 +1,24 @@ class Form::Sales::Questions::ServiceCharge < ::Form::Question - def initialize(id, hsh, subsection) - super + def initialize(id, hsh, subsection, staircasing:) + super(id, hsh, subsection) @id = "mscharge" @type = "numeric" @min = 1 + @max = 9999.99 @step = 0.01 @width = 5 @prefix = "£" @copy_key = "sales.sale_information.servicecharges.servicecharge" - @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + @staircasing = staircasing + @question_number = question_number_from_year[form.start_date.year] || question_number_from_year[question_number_from_year.keys.max] @strip_commas = true end - QUESTION_NUMBER_FROM_YEAR = { 2025 => 88 }.freeze + def question_number_from_year + if @staircasing + { 2026 => 0 }.freeze + else + { 2025 => 88, 2026 => 0 }.freeze + end + end end diff --git a/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb index cc10ed28c..cfd00e5e1 100644 --- a/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb +++ b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb @@ -25,6 +25,7 @@ class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form:: Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1), Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self), Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self), + (Form::Sales::Pages::ServiceChargeStaircasing.new("service_charge_staircasing", nil, self) if form.start_year_2026_or_later?), Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self), ].compact end diff --git a/app/services/csv/sales_log_csv_service.rb b/app/services/csv/sales_log_csv_service.rb index 07ccad82c..b386b070f 100644 --- a/app/services/csv/sales_log_csv_service.rb +++ b/app/services/csv/sales_log_csv_service.rb @@ -249,7 +249,7 @@ module Csv return @attributes unless @user.support? mappings = SUPPORT_ATTRIBUTE_NAME_MAPPINGS - mappings = mappings.merge(SUPPORT_ATTRIBUTE_NAME_MAPPINGS_2025) if @year == 2025 + mappings = mappings.merge(SUPPORT_ATTRIBUTE_NAME_MAPPINGS_2025) if @year >= 2025 @attributes.map do |attribute| mappings[attribute] || attribute.upcase @@ -297,11 +297,10 @@ module Csv end def attribute_mappings - mappings = case @year - when 2024 - ATTRIBUTE_MAPPINGS.merge(ATTRIBUTE_MAPPINGS_2024) - when 2025 + mappings = if @year >= 2025 ATTRIBUTE_MAPPINGS.merge(ATTRIBUTE_MAPPINGS_2024).merge(ATTRIBUTE_MAPPINGS_2025) + elsif @year == 2024 + ATTRIBUTE_MAPPINGS.merge(ATTRIBUTE_MAPPINGS_2024) else ATTRIBUTE_MAPPINGS end @@ -348,6 +347,8 @@ module Csv %w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo] when 2025 %w[id status duplicate_set_id created_at created_by_id updated_at updated_by_id creation_method bulk_upload_id] + when 2026 + %w[id status duplicate_set_id created_at created_by_id updated_at updated_by_id creation_method bulk_upload_id] else %w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo] end diff --git a/spec/models/form/sales/pages/service_charge_staircasing_spec.rb b/spec/models/form/sales/pages/service_charge_staircasing_spec.rb new file mode 100644 index 000000000..a29df7b82 --- /dev/null +++ b/spec/models/form/sales/pages/service_charge_staircasing_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::ServiceChargeStaircasing, type: :model do + 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: Time.zone.local(2026, 4, 1))) } + + 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[has_mscharge mscharge]) + end + + it "has the correct id" do + expect(page.id).to be_nil + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to be_nil + end +end diff --git a/spec/models/form/sales/questions/has_service_charge_spec.rb b/spec/models/form/sales/questions/has_service_charge_spec.rb index bf0bd9d25..d64609bb0 100644 --- a/spec/models/form/sales/questions/has_service_charge_spec.rb +++ b/spec/models/form/sales/questions/has_service_charge_spec.rb @@ -1,12 +1,14 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::HasServiceCharge, type: :model do - subject(:question) { described_class.new(question_id, question_definition, page) } + subject(:question) { described_class.new(question_id, question_definition, page, staircasing:) } - let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 4)) } let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, id: "shared_ownership", form:)) } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) } + let(:page) { instance_double(Form::Page, subsection:) } + let(:start_date) { Time.utc(2025, 5, 1) } + let(:staircasing) { false } it "has correct page" do expect(question.page).to eq(page) @@ -46,4 +48,44 @@ RSpec.describe Form::Sales::Questions::HasServiceCharge, type: :model do ], }) end + + context "with 2025/26 form" do + let(:start_date) { Time.utc(2025, 4, 1) } + + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + context "when not staircasing" do + let(:staircasing) { false } + + it "has the correct question number" do + expect(question.question_number).to eq(88) + end + end + end + + context "with 2026/27 form" do + let(:start_date) { Time.utc(2026, 4, 1) } + + before do + allow(subsection.form).to receive(:start_year_2026_or_later?).and_return(true) + end + + context "when staircasing" do + let(:staircasing) { true } + + it "has the correct question number" do + expect(question.question_number).to eq(0) + end + end + + context "when not staircasing" do + let(:staircasing) { false } + + it "has the correct question number" do + expect(question.question_number).to eq(0) + end + end + end end diff --git a/spec/models/form/sales/questions/service_charge_spec.rb b/spec/models/form/sales/questions/service_charge_spec.rb index 56dee01cf..21104d2b5 100644 --- a/spec/models/form/sales/questions/service_charge_spec.rb +++ b/spec/models/form/sales/questions/service_charge_spec.rb @@ -1,11 +1,14 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::ServiceCharge, type: :model do - subject(:question) { described_class.new(question_id, question_definition, page) } + subject(:question) { described_class.new(question_id, question_definition, page, staircasing:) } 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: Time.zone.local(2023, 4, 1)))) } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) } + let(:page) { instance_double(Form::Page, subsection:) } + let(:start_date) { Time.utc(2023, 4, 1) } + let(:staircasing) { false } it "has correct page" do expect(question.page).to eq(page) @@ -34,4 +37,44 @@ RSpec.describe Form::Sales::Questions::ServiceCharge, type: :model do it "has the correct prefix" do expect(question.prefix).to eq("£") end + + context "with 2025/26 form" do + let(:start_date) { Time.utc(2025, 4, 1) } + + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + context "when not staircasing" do + let(:staircasing) { false } + + it "has the correct question number" do + expect(question.question_number).to eq(88) + end + end + end + + context "with 2026/27 form" do + let(:start_date) { Time.utc(2026, 4, 1) } + + before do + allow(subsection.form).to receive(:start_year_2026_or_later?).and_return(true) + end + + context "when staircasing" do + let(:staircasing) { true } + + it "has the correct question number" do + expect(question.question_number).to eq(0) + end + end + + context "when not staircasing" do + let(:staircasing) { false } + + it "has the correct question number" do + expect(question.question_number).to eq(0) + end + end + end end diff --git a/spec/models/form/sales/subsections/shared_ownership_staircasing_transaction_spec.rb b/spec/models/form/sales/subsections/shared_ownership_staircasing_transaction_spec.rb new file mode 100644 index 000000000..3efe482d8 --- /dev/null +++ b/spec/models/form/sales/subsections/shared_ownership_staircasing_transaction_spec.rb @@ -0,0 +1,85 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Subsections::SharedOwnershipStaircasingTransaction, type: :model do + subject(:shared_ownership_staircasing_transaction) { described_class.new(nil, nil, section) } + + let(:form) { instance_double(Form, start_year_2026_or_later?: false) } + let(:section) { instance_double(Form::Sales::Sections::SaleInformation, form:) } + + it "has correct section" do + expect(shared_ownership_staircasing_transaction.section).to eq(section) + end + + it "has the correct depends_on" do + expect(shared_ownership_staircasing_transaction.depends_on).to eq([{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 1 }]) + end + + it "has the correct id" do + expect(shared_ownership_staircasing_transaction.id).to eq("shared_ownership_staircasing_transaction") + end + + it "has the correct label" do + expect(shared_ownership_staircasing_transaction.label).to eq("Shared ownership - staircasing transaction") + end + + it "has the correct copy key" do + expect(shared_ownership_staircasing_transaction.copy_key).to eq("sale_information") + end + + context "when the start year is 2025" do + let(:form) { instance_double(Form, start_year_2025_or_later?: true, start_year_2026_or_later?: false, start_date: Time.utc(2025, 4, 1)) } + + it "has correct pages" do + expect(shared_ownership_staircasing_transaction.pages.map(&:id)).to eq( + %w[ + about_staircasing_joint_purchase + about_staircasing_not_joint_purchase + staircase_sale + staircase_bought_value_check + staircase_owned_value_check_joint_purchase + staircase_owned_value_check_not_joint_purchase + staircase_first_time + staircase_previous + staircase_initial_date + value_shared_ownership_staircase + about_price_shared_ownership_value_check_staircasing + staircase_equity + shared_ownership_equity_value_check_staircasing + staircase_mortgage_used_shared_ownership + monthly_rent_staircasing_owned + monthly_rent_staircasing + monthly_charges_shared_ownership_value_check + ], + ) + end + end + + context "when the start year is 2026" do + let(:form) { instance_double(Form, start_year_2025_or_later?: true, start_year_2026_or_later?: true, start_date: Time.utc(2026, 4, 1)) } + + it "has correct pages" do + expect(shared_ownership_staircasing_transaction.pages.map(&:id)).to eq( + %w[ + about_staircasing_joint_purchase + about_staircasing_not_joint_purchase + staircase_sale + staircase_bought_value_check + staircase_owned_value_check_joint_purchase + staircase_owned_value_check_not_joint_purchase + staircase_first_time + staircase_previous + staircase_initial_date + value_shared_ownership_staircase + about_price_shared_ownership_value_check_staircasing + staircase_equity + shared_ownership_equity_value_check_staircasing + staircase_mortgage_used_shared_ownership + monthly_rent_staircasing_owned + monthly_rent_staircasing + service_charge_staircasing + monthly_charges_shared_ownership_value_check + ], + ) + end + end +end From 037f59122cf5e48efe7f73be8516629ed34670a2 Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:20:10 +0000 Subject: [PATCH 5/7] 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 --- app/helpers/bulk_upload/sales_log_to_csv.rb | 3 +- .../form/sales/pages/building_height_class.rb | 17 ++ .../sales/questions/building_height_class.rb | 17 ++ .../sales/subsections/property_information.rb | 1 + .../bulk_upload/sales/year2026/csv_parser.rb | 4 +- .../bulk_upload/sales/year2026/row_parser.rb | 4 + .../exports/sales_log_export_constants.rb | 2 +- .../2026/sales/property_information.en.yml | 7 + ...3257_add_buildheightclass_to_sales_logs.rb | 5 + db/schema.rb | 9 +- spec/factories/sales_log.rb | 2 + spec/fixtures/exports/sales_log_25_26.xml | 154 +++++++++++++++++ spec/fixtures/exports/sales_log_26_27.xml | 161 ++++++++++++++++++ .../files/2026_27_sales_bulk_upload.csv | 18 +- .../files/sales_logs_csv_export_codes_26.csv | 3 + .../files/sales_logs_csv_export_labels_26.csv | 3 + ...s_logs_csv_export_non_support_codes_26.csv | 3 + ..._logs_csv_export_non_support_labels_26.csv | 3 + .../sales_download_26_27.csv | 1 + .../tasks/log_variable_definitions_spec.rb | 2 +- .../sales/pages/building_height_class_spec.rb | 36 ++++ .../questions/building_height_class_spec.rb | 39 +++++ .../subsections/property_information_spec.rb | 53 ++++-- .../sales/year2026/row_parser_spec.rb | 1 + .../csv/sales_log_csv_service_spec.rb | 131 +++++++++++--- .../exports/sales_log_export_service_spec.rb | 64 +++++++ 26 files changed, 687 insertions(+), 56 deletions(-) create mode 100644 app/models/form/sales/pages/building_height_class.rb create mode 100644 app/models/form/sales/questions/building_height_class.rb create mode 100644 db/migrate/20260219093257_add_buildheightclass_to_sales_logs.rb create mode 100644 spec/fixtures/exports/sales_log_25_26.xml create mode 100644 spec/fixtures/exports/sales_log_26_27.xml create mode 100644 spec/fixtures/files/sales_logs_csv_export_codes_26.csv create mode 100644 spec/fixtures/files/sales_logs_csv_export_labels_26.csv create mode 100644 spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv create mode 100644 spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv create mode 100644 spec/models/form/sales/pages/building_height_class_spec.rb create mode 100644 spec/models/form/sales/questions/building_height_class_spec.rb diff --git a/app/helpers/bulk_upload/sales_log_to_csv.rb b/app/helpers/bulk_upload/sales_log_to_csv.rb index 30833c1c7..5b304abdb 100644 --- a/app/helpers/bulk_upload/sales_log_to_csv.rb +++ b/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 diff --git a/app/models/form/sales/pages/building_height_class.rb b/app/models/form/sales/pages/building_height_class.rb new file mode 100644 index 000000000..3e3547225 --- /dev/null +++ b/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 diff --git a/app/models/form/sales/questions/building_height_class.rb b/app/models/form/sales/questions/building_height_class.rb new file mode 100644 index 000000000..016245fbc --- /dev/null +++ b/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 diff --git a/app/models/form/sales/subsections/property_information.rb b/app/models/form/sales/subsections/property_information.rb index e33666208..07464ea1b 100644 --- a/app/models/form/sales/subsections/property_information.rb +++ b/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?), diff --git a/app/services/bulk_upload/sales/year2026/csv_parser.rb b/app/services/bulk_upload/sales/year2026/csv_parser.rb index 78e467807..50faaea28 100644 --- a/app/services/bulk_upload/sales/year2026/csv_parser.rb +++ b/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 diff --git a/app/services/bulk_upload/sales/year2026/row_parser.rb b/app/services/bulk_upload/sales/year2026/row_parser.rb index 08d56fe37..efba9967e 100644 --- a/app/services/bulk_upload/sales/year2026/row_parser.rb +++ b/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) diff --git a/app/services/exports/sales_log_export_constants.rb b/app/services/exports/sales_log_export_constants.rb index 6dfb2b334..cf6a6e079 100644 --- a/app/services/exports/sales_log_export_constants.rb +++ b/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}" diff --git a/config/locales/forms/2026/sales/property_information.en.yml b/config/locales/forms/2026/sales/property_information.en.yml index 70afb55d8..b63ea755c 100644 --- a/config/locales/forms/2026/sales/property_information.en.yml +++ b/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" diff --git a/db/migrate/20260219093257_add_buildheightclass_to_sales_logs.rb b/db/migrate/20260219093257_add_buildheightclass_to_sales_logs.rb new file mode 100644 index 000000000..485de919b --- /dev/null +++ b/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 diff --git a/db/schema.rb b/db/schema.rb index 62d32713f..c5ac9a344 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.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" diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index b98cc6f75..81d679ab6 100644 --- a/spec/factories/sales_log.rb +++ b/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 } diff --git a/spec/fixtures/exports/sales_log_25_26.xml b/spec/fixtures/exports/sales_log_25_26.xml new file mode 100644 index 000000000..e9190c390 --- /dev/null +++ b/spec/fixtures/exports/sales_log_25_26.xml @@ -0,0 +1,154 @@ + + +
+ {id} + 1 + 123 + 8 + 1 + 2 + 27 + F + 17 + 1 + 1 + 33 + P + X + 2 + 1 + 1 + 1 + 1 + 4 + 14 + E09000033 + 10000 + 18 + 40 + 40 + 1 + 10000 + 1 + + 1 + F + 20000.0 + 1 + 9 + 3 + 2 + 1 + X + X + R + R + 4 + X + M + X + + + + + + + 80000.0 + + 1 + 110000.0 + + + 10000.0 + 0 + SW1A + 1AA + E09000033 + 7 + + + 100.0 + 1 + 1 + 1 + 5 + + + + + + 1 + 6 + + 2 + 3 + + + + + + + 1 + 4 + 2025 + 2025-04-01T00:00:00+01:00 + {created_by_email} + {created_by_id} + {assigned_to_email} + {assigned_to_id} + 2025-04-01T00:00:00+01:00 + + + {owning_org_id} + {owning_org_name} + {managing_org_id} + {managing_org_name} + 1 + + 2025 + 2 + 1 + 17 + 17 + 1 + 1 + + + 0 + 0 + SW1A 1AA + true + 10 + + + Address line 1 + + City + Westminster + Address line 1 + SW1A 1AA + + + + + + + + 826 + 826 + Westminster + 1 + 1 + + + + + + + + + + + + +
diff --git a/spec/fixtures/exports/sales_log_26_27.xml b/spec/fixtures/exports/sales_log_26_27.xml new file mode 100644 index 000000000..71278bbda --- /dev/null +++ b/spec/fixtures/exports/sales_log_26_27.xml @@ -0,0 +1,161 @@ + + +
+ {id} + 1 + 123 + 8 + 1 + 2 + 27 + F + 17 + 1 + 1 + 33 + P + X + 2 + 1 + 1 + 1 + 1 + 4 + 14 + E09000033 + 10000 + 18 + 40 + 40 + 1 + 10000 + 1 + + 1 + F + 20000.0 + 1 + 9 + 3 + 2 + 1 + X + X + R + R + 4 + X + M + X + + + + + + + 80000.0 + + 1 + 110000.0 + + + 10000.0 + 0 + SW1A + 1AA + E09000033 + 7 + + + 100.0 + 1 + 1 + 1 + 5 + + + + + + 1 + 6 + + 2 + 3 + + + + + + + F + + F + + M + + 2 + 1 + 4 + 2026 + 2026-04-01T00:00:00+01:00 + {created_by_email} + {created_by_id} + {assigned_to_email} + {assigned_to_id} + 2026-04-01T00:00:00+01:00 + + + {owning_org_id} + {owning_org_name} + {managing_org_id} + {managing_org_name} + 1 + + 2026 + 2 + 1 + 17 + 17 + 1 + 1 + + + 0 + 0 + SW1A 1AA + true + 10 + + + Address line 1 + + City + Westminster + Address line 1 + SW1A 1AA + + + + + + + + 826 + 826 + Westminster + 1 + 1 + + + + + + + + + + + + +
diff --git a/spec/fixtures/files/2026_27_sales_bulk_upload.csv b/spec/fixtures/files/2026_27_sales_bulk_upload.csv index bf7d70748..dba6fb97a 100644 --- a/spec/fixtures/files/2026_27_sales_bulk_upload.csv +++ b/spec/fixtures/files/2026_27_sales_bulk_upload.csv @@ -1,23 +1,23 @@ -Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Shared ownership - initial purchase,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction,,,,,,,,,,,,,,,,Discounted ownership,,,,,,,,,,,,,,, +Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Shared ownership - initial purchase,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction,,,,,,,,,,,,,,,,Discounted ownership,,,,,,,,,,,,,,,, Question,What is the sale completion date? - day DD,What is the sale completion date? - month MM,What is the sale completion date? - year YY,Which organisation owned this property before the sale?,Which organisation is reporting this sale?,What is the CORE username of the account this sale log should be assigned to? ,What is the purchaser code?,Is this a shared ownership or discounted ownership sale?,What is the type of shared ownership sale?,Is this a staircasing transaction?,What is the type of discounted ownership sale?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen or been given access to the MHCLG privacy notice?,"If known, enter this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type of unit is the property?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1’s age?,Which of these best describes buyer 1's gender identity?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1’s working situation? ,Will buyer 1 live in the property?,Is buyer 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,Which of these best describes buyer 2 or person 2's gender identity?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,Which of these best describes buyer 2 or person 2’s working situation? ,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3’s working situation? ,Is person 4 the partner of buyer 1?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4’s working situation? ,Is person 5 the partner of buyer 1?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5’s working situation? ,Is person 6 the partner of buyer 1?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6’s working situation? ,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,What is the local authority of buyer 1's last settled accommodation?,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,"Have any of the buyers ever served as a regular in the UK armed forces? -",Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,What is the day of the practical completion or handover date? - DD,What is the month of the practical completion or handover date? - MM,What is the year of the practical completion or handover date? - YY,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the buyer’s previous tenure?,What is the full purchase price?,What was the initial percentage share purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social HomeBuy?,What is the basic monthly rent?,What are the total monthly service charges for the property?,What are the total monthly estate management fees for the property?,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the buyer has engaged in staircasing in the home?,What was the day of the initial purchase of a share in the property? DD,What was the month of the initial purchase of a share in the property? MM,What was the year of the initial purchase of a share in the property? YYYY,"Including this time, how many times has the shared owner engaged in staircasing in the home?",What was the day of the last staircasing transaction? DD,What was the month of the last staircasing transaction? MM,What was the year of the last staircasing transaction? YYYY,What is the full purchase price for this staircasing transaction?,What was the percentage share purchased in the initial transaction?,Was a mortgage used for this staircasing transaction?,What was the basic monthly rent prior to staircasing?,What is the basic monthly rent after staircasing?,How long did the buyer(s) live in the property before purchasing it?,What is the full purchase price?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,Does this include any extra borrowing?,How much was the cash deposit paid on the property?,What are the total monthly leasehold charges for the property?,What was buyer 1's sex at birth?,What was buyer/person 2's sex registered at birth?,What was person 3's sex registered at birth?,What was person 4's sex registered at birth?,What was person 5's sex registered at birth?,What was person 6's sex registered at birth? +",Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,What is the day of the practical completion or handover date? - DD,What is the month of the practical completion or handover date? - MM,What is the year of the practical completion or handover date? - YY,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the buyer’s previous tenure?,What is the full purchase price?,What was the initial percentage share purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social HomeBuy?,What is the basic monthly rent?,What are the total monthly service charges for the property?,What are the total monthly estate management fees for the property?,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the buyer has engaged in staircasing in the home?,What was the day of the initial purchase of a share in the property? DD,What was the month of the initial purchase of a share in the property? MM,What was the year of the initial purchase of a share in the property? YYYY,"Including this time, how many times has the shared owner engaged in staircasing in the home?",What was the day of the last staircasing transaction? DD,What was the month of the last staircasing transaction? MM,What was the year of the last staircasing transaction? YYYY,What is the full purchase price for this staircasing transaction?,What was the percentage share purchased in the initial transaction?,Was a mortgage used for this staircasing transaction?,What was the basic monthly rent prior to staircasing?,What is the basic monthly rent after staircasing?,How long did the buyer(s) live in the property before purchasing it?,What is the full purchase price?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,Does this include any extra borrowing?,How much was the cash deposit paid on the property?,What are the total monthly leasehold charges for the property?,What was buyer 1's sex at birth?,What was buyer/person 2's sex registered at birth?,What was person 3's sex registered at birth?,What was person 4's sex registered at birth?,What was person 5's sex registered at birth?,What was person 6's sex registered at birth?,What is the building height classification? Additional info,,,,"You can find the org ID on the CORE service under 'Stock owners' or, if your organisation is the stock owner, under 'About your organisation'","You can find the org ID on the CORE service under 'Managing agents' or, if your organisation is the managing agent, under 'About your organisation'","If left empty, the sales log will be assigned to the account used to upload the log.",This is how you usually refer to this buyer on your own systems,"Sales logs are no longer required for outright and other sales. A shared ownership sale is when the purchaser buys an initial share of up to 75% of the property value and pays rent to the Private Registered Provider (PRP) on the remaining portion, or a subsequent staircasing transaction",See specification for full description of each scheme,"A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property.",See specification for full description of each scheme,This is where two or more people are named as legal owners of the property after the purchase.,,You should still try to answer all questions even if the buyer wasn't interviewed in person,"Make sure the buyer has seen or been given access to the Ministry of Housing, Communities and Local Government (MHCLG) privacy notice before completing this log. This is a legal requirement under data protection legislation.","The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and various industries across the UK. An example UPRN is 10010457355. -The UPRN may not be the same as the property reference assigned by your organisation.",,,,,Combined with field 28 it should be a postcode which lies within the local authority given in field 29.,Combined with field 27 it should be a postcode which lies within the local authority given in field 29.,,,"For bedsits, enter ‘1’",,"This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property.","Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.",This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If buyer 1 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, they should decide which country to enter.","Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.",,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If buyer 2 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, they should decide which country to enter.",,,"You can provide details for a maximum of 4 other people if there are 2 buyers, or 5 other people if there is only one buyer",,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,This is also known as the household's 'last settled home',,,,,,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",This includes any long-term health condition that has an impact on the person's day-to-day life,This can be inside or outside the home,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,,Enter their total savings to the nearest £10,,For any buyer,"If the social landlord has previously sold the property to another buyer and is now reselling the property, enter 'yes'. If this is the first time the property has been sold, enter 'no'.","If the buyers haven't been living in the property, enter '0'",This is the date on which the building contractor hands over responsibility for the completed property to the private registered provider (PRP).,,,"For bedsits, enter ‘1’",,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","This is the initial stake purchased. Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments.,,Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage,Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme,Before any charges,"This includes any charges for day-to-day maintenance and repairs, buildings insurance, and any contributions to a sinking or reserve fund. It does not include estate management fees.","Estate management fees are typically used for the maintenance of communal gardens, payments, private roads, car parks or play areas within new build estates.",,,Back-to-back staircasing transactions are used as a way for shared owners who own less than 100% of their property to sell on the open market. It involves the shared owner purchasing the remaining share from their landlord and immediately selling 100% of the property to a buyer on the open market. The landlord is then reimbursed for the staircasing transaction through the proceeds of sale to the buyer.,,,,,,,,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","This is the initial stake purchased. Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,,,,"For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount","For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy","For Right to Buy (RTB), Preserved Right to Buy (PRTB) and Voluntary Right to Buy (VRTB). For capped discount, enter capped %. If property is sold to an existing tenant under RTB, PRTB or VRTB, enter % discount from full market value.",,Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments.,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan.,"For example, service and management charges",This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth. +The UPRN may not be the same as the property reference assigned by your organisation.",,,,,Combined with field 28 it should be a postcode which lies within the local authority given in field 29.,Combined with field 27 it should be a postcode which lies within the local authority given in field 29.,,,"For bedsits, enter ‘1’",,"This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property.","Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.",This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If buyer 1 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, they should decide which country to enter.","Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.",,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,"If buyer 2 is a dual national of the United Kingdom and another country, enter United Kingdom. If they are a dual national of two other countries, they should decide which country to enter.",,,"You can provide details for a maximum of 4 other people if there are 2 buyers, or 5 other people if there is only one buyer",,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,,This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.,,,This is also known as the household's 'last settled home',,,,,,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",This includes any long-term health condition that has an impact on the person's day-to-day life,This can be inside or outside the home,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,,Enter their total savings to the nearest £10,,For any buyer,"If the social landlord has previously sold the property to another buyer and is now reselling the property, enter 'yes'. If this is the first time the property has been sold, enter 'no'.","If the buyers haven't been living in the property, enter '0'",This is the date on which the building contractor hands over responsibility for the completed property to the private registered provider (PRP).,,,"For bedsits, enter ‘1’",,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","This is the initial stake purchased. Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments.,,Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage,Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme,Before any charges,"This includes any charges for day-to-day maintenance and repairs, buildings insurance, and any contributions to a sinking or reserve fund. It does not include estate management fees.","Estate management fees are typically used for the maintenance of communal gardens, payments, private roads, car parks or play areas within new build estates.",,,Back-to-back staircasing transactions are used as a way for shared owners who own less than 100% of their property to sell on the open market. It involves the shared owner purchasing the remaining share from their landlord and immediately selling 100% of the property to a buyer on the open market. The landlord is then reimbursed for the staircasing transaction through the proceeds of sale to the buyer.,,,,,,,,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","This is the initial stake purchased. Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,,,,"For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount","For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy","For Right to Buy (RTB), Preserved Right to Buy (PRTB) and Voluntary Right to Buy (VRTB). For capped discount, enter capped %. If property is sold to an existing tenant under RTB, PRTB or VRTB, enter % discount from full market value.",,Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments.,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan.,"For example, service and management charges",This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth.,This is the sex that was registered at birth., Values,Jan-31,01-Dec,25 - 26,Alphanumeric,Alphanumeric,Email format,"Alphanumeric, punctuation can be used as long as not the field separator. Max 9 characters.",01-Feb,"2, 16, 18, 24, 28 or 30 - 32",01-Feb,"8, 9, 14, 21, 22, 27 or 29",01-Feb,01-Mar,01-Feb,1,"Numeric, max 12 digits",Alphanumeric,,Text,,"Alphanumeric, 2 - 4 characters","Alphanumeric, 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",1 - 4 or 9,01-Sep,01-Feb,01-Mar,"16 - 110 or R","F, M, X or R",Jan-20,"3 digit ISO country code, see specification",0 - 8 or 10,01-Feb,01-Mar,1 - 110 or R,"F, M, X or R",Jan-20,"3 digit ISO country code, see specification",0 - 10,01-Feb,0 - 5,01-Mar,1 - 110 or R,"F, M, X or R",0 - 10,01-Mar,1 - 110 or R,"F, M, X or R",0 - 10,01-Mar,1 - 110 or R,"F, M, X or R",0 - 10,01-Mar,1 - 110 or R,"F, M, X or R",0 - 10,"1 - 7, 9 or R",01-Feb,"Alphanumeric, 2 - 4 characters","Alphanumeric, - 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",01-Mar,"1 - 7, 9 or R","1, 3, 7 or 8",04-Jun,04-Jul,01-Mar,,0 - 99999 or R,01-Feb,0 - 99999 or R,01-Feb,01-Apr,0 - 999990 or R,01-Mar,,01-Feb,Integer <= 80,Jan-31,01-Dec,22 - 26,01-Sep,1 - 4 or 9,1 - 3 or 9 - 10,0 - 999999,0 - 100,01-Feb,0 - 999999,Integer <= 60,0 - 999999,,0 - 9999.99,,,1 - 100,,01-Mar,01-Feb,Jan-31,01-Dec,1980-2026,02-Oct,Jan-31,01-Dec,1980-2026,0 - 999999,0 - 100,01-Mar,0 - 9999.99,,Integer <= 80,0 - 999999,,0 - 100,01-Feb,0 - 999999,Integer <= 60,01-Mar,0 - 999999,0 - 9999.99,"F, M or R","F, M or R","F, M or R","F, M or R","F, M or R","F, M or R" + 3 characters","9 character ONS code, beginning with 'E' (https://www.get-information-schools.service.gov.uk/Guidance/LaNameCodes) ",01-Mar,"1 - 7, 9 or R","1, 3, 7 or 8",04-Jun,04-Jul,01-Mar,,0 - 99999 or R,01-Feb,0 - 99999 or R,01-Feb,01-Apr,0 - 999990 or R,01-Mar,,01-Feb,Integer <= 80,Jan-31,01-Dec,22 - 26,01-Sep,1 - 4 or 9,1 - 3 or 9 - 10,0 - 999999,0 - 100,01-Feb,0 - 999999,Integer <= 60,0 - 999999,,0 - 9999.99,,,1 - 100,,01-Mar,01-Feb,Jan-31,01-Dec,1980-2026,02-Oct,Jan-31,01-Dec,1980-2026,0 - 999999,0 - 100,01-Mar,0 - 9999.99,,Integer <= 80,0 - 999999,,0 - 100,01-Feb,0 - 999999,Integer <= 60,01-Mar,0 - 999999,0 - 9999.99,"F, M or R","F, M or R","F, M or R","F, M or R","F, M or R","F, M or R",1-3 Can be empty?,No,,,No,No,Yes,Yes,No,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",No,"Yes, if the sale is not a joint purchase (if field 12 = 2)",No,No,"Yes, if property's full address is known (if fields 16, 18, 20 and 21 are not empty)","Yes, if property's UPRN is known (if field 16 is not empty)",Yes,"Yes, if property's UPRN is known (if field 16 is not empty)",Yes,"Yes, if property's UPRN and local authority are known (if fields 16 and 23 are not empty",,No,,,"Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,No,,"Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,,,"Yes, if sale is not a joint purchase (if field 12 = 2) and the other fields about this person (fields 35, 36 and 39) are also empty","Yes, if sale is not a joint purchase (if field 12 = 2) and the other fields about this person (fields 35, 36 and 39) are also empty","Yes, if sale is not a joint purchase (if field 12 = 2) and the other fields about this person (fields 35, 36 and 39) are also empty","Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,"Yes, if sale is not a joint purchase (if field 12 = 2) and the other fields about this person (fields 35, 36 and 39) are also empty, or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if purchase is a staircasing transaction (if field 10 = 1)","Yes, if all fields about this person (fields 42 - 45) are also empty, or if the purchase is a staircasing transaction (if field 10 = 1)",,,,"Yes, if all fields about this person (fields 46 - 49) are also empty, or if the purchase is a staircasing transaction (if field 10 = 1)",,,,"Yes, if all fields about this person (fields 50 - 53) are also empty, or if the purchase is a staircasing transaction (if field 10 = 1)",,,,"Yes, if all fields about this person (fields 54 - 57) are also empty, or if the purchase is a staircasing transaction (if field 10 = 1)",,,,"Yes, if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is discounted ownership (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if postcode of buyer 1's last settled accommodation is unknown (if field 59 = 2) Yes, if sale is discounted ownership (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,Yes,"Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2), or if buyer is not known to have been living at the same address as buyer 1 at the time of purchase (if field 63 = 1 or 3), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if none of the buyers are known to have served as a regular in the UK armed forces (if field 65 = 7, 3 or 8), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if buyer 1's income is not known (if field 70 = R), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2) or if buyer 2's income is not known (if field 70 = R), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchasers did not previously own a property or if it is not known (if field 76 = 2 or 3), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2) or if this is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if this is a resale (if field 78 = 1) or if this is a staircasing transaction (if field 10 = 1)",,,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1) -Yes, if the buyer was not a private registered provider, housing association or local authority tenant immediately before sale (if field 58 is not 1 or 2)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1) or if a mortgage was not used (if field 88 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1), or if the type of shared ownership sale is not Social Homebuy (if field 9 is not 18)","Yes, if there are no monthy charges or fees, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers do not own 100% of the property (if field 97 is less than 100)","Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), and if this is the first time the buyer has engaged in staircasing in the home (if field 99 = 1)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers now own 100% of the property (if field 97 = 100)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 9, 14, 27 or 29)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is not PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 8, 21, 22)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if a mortgage was not used (if field 116 = 2)",,,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if there are no leasehold charges or if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,,,,, -Types of sales the question applies to,All,,,,,,,,Shared ownership only,Shared ownership only,Discounted ownership only,All,Joint purchase only,All,,All,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,,All,,Shared ownership - initial purchase and discounted ownership,,,,Joint purchases,,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase and discounted ownership,,,,,,,,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,Shared ownership - initial purchase,,,Shared ownership - initial purchase and discounted ownership,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,,Shared ownership - initial purchase and discounted ownership,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase only,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction only,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,,,, -Duplicate check field?,Yes,,,,,,,,,,,,,,,,,,,,Yes,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 -,4,9,26,ORG1,ORG1,support@example.com,1,2,,,8,2,3,1,1,,a,a,a,a,aa1,1aa,E09000001,1,1,2,3,20,M,20,GBR,10,1,,,,,,,,0,,,,,,,,,,,,,,,,,1,2,,,,,,8,,7,3,3,10000,2,,,4,20000,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30,400000,10000,25,2,,,,390000,0,F,,,,, +Yes, if the buyer was not a private registered provider, housing association or local authority tenant immediately before sale (if field 58 is not 1 or 2)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1) or if a mortgage was not used (if field 88 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1), or if the type of shared ownership sale is not Social Homebuy (if field 9 is not 18)","Yes, if there are no monthy charges or fees, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers do not own 100% of the property (if field 97 is less than 100)","Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), and if this is the first time the buyer has engaged in staircasing in the home (if field 99 = 1)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers now own 100% of the property (if field 97 = 100)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 9, 14, 27 or 29)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is not PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 8, 21, 22)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if a mortgage was not used (if field 116 = 2)",,,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if there are no leasehold charges or if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,,,,,, +Types of sales the question applies to,All,,,,,,,,Shared ownership only,Shared ownership only,Discounted ownership only,All,Joint purchase only,All,,All,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,,All,,Shared ownership - initial purchase and discounted ownership,,,,Joint purchases,,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase and discounted ownership,,,,,,,,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,Shared ownership - initial purchase,,,Shared ownership - initial purchase and discounted ownership,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,,Shared ownership - initial purchase and discounted ownership,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase only,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction only,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,,,,, +Duplicate check field?,Yes,,,,,,,,,,,,,,,,,,,,Yes,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128 +,4,9,26,ORG1,ORG1,support@example.com,1,2,,,8,2,3,1,1,,a,a,a,a,aa1,1aa,E09000001,1,1,2,3,20,M,20,GBR,10,1,,,,,,,,0,,,,,,,,,,,,,,,,,1,2,,,,,,8,,7,3,3,10000,2,,,4,20000,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30,400000,10000,25,2,,,,390000,0,F,,,,,, diff --git a/spec/fixtures/files/sales_logs_csv_export_codes_26.csv b/spec/fixtures/files/sales_logs_csv_export_codes_26.csv new file mode 100644 index 000000000..851034528 --- /dev/null +++ b/spec/fixtures/files/sales_logs_csv_export_codes_26.csv @@ -0,0 +1,3 @@ +Log ID,Status of log,ID of a set of duplicate logs,Which organisation owned this property before the sale?,Which organisation owned this property before the sale? (ID),Which organisation reported the sale?,Which organisation reported the sale? (ID),Time and date the log was created,User that created the log (email),User that created the log (ID),User the log is assigned to (email),User the log is assigned to (ID),Time and date the log was last updated,User that last amended the log (email),User that last amended the log (ID),Was the log submitted in-service or via bulk upload?,ID of a set of bulk uploaded logs,Year collection period opened,Day of sale completion date,Month of sale completion date,Year of sale completion date,What is the purchaser code?,Was this purchase made through an ownership scheme?,What is the type of shared ownership/discounted ownership/outright sale?,Is this a staircasing transaction?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen the MHCLG privacy notice?,What is the UPRN of the property?,Address line 1,Address line 2,Town/City,County,Postcode,The internal value to indicate if the LA was inferred from the postcode,LA name,LA code,UPRN of the address selected,Was the 'No address found' page seen?,Address line 1 input from address matching feature,Postcode input from address matching feature,Address line 1 entered in bulk upload file,Address line 2 entered in bulk upload file,Town or city entered in bulk upload file,County entered in bulk upload file,Postcode entered in bulk upload file,Local authority entered in bulk upload file,What type of unit is the property?,What is the building height classification?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1's age?,What is buyer 1's sex registered at birth?,Which of these best describes buyer 1's gender identity?,What is buyer 1's ethnic group?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1's working situation?,Will buyer 1 live in the property?,Is buyer 2 or person 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,What is buyer 2 or person 2's sex registered at birth?,Which of these best describes buyer 2 or person 2's gender identity?,What is buyer 2's ethnic group?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,What is buyer 2 or person 2's working situation?,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,What is person 3's sex registered at birth?,What is person 3's gender identity?,What is person 3's working situation?,Is person 4 the partner of buyer 1?,What is person 4's age?,What is person 4's sex registered at birth?,What is person 4's gender identity?,What is person 4's working situation?,Is person 5 the partner of buyer 1?,What is person 5's age?,What is person 5's sex registered at birth?,What is person 5's gender identity?,What is person 5's working situation?,Is person 6 the partner of buyer 1?,What is person 6's age?,What is person 6's sex registered at birth?,What is person 6's gender identity?,What is person 6's working situation?,Household type,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,Do you know the local authority of buyer 1's last settled accommodation?,The local authority code of buyer 1's last settled accommodation,The local authority name of buyer 1's last settled accommodation,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,Have any of the buyers ever served as a regular in the UK armed forces?,Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,Is buyer 1's annual income known?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,Is buyer 1's annual income known?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,Is the the total amount the buyers had in savings known?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,Day of the practical completion or handover date,Month of the practical completion or handover date,Year of the practical completion or handover date,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the rent type of buyer's previous tenure?,What is the full purchase price?,Populated if a soft validation is confirmed.,What was the initial percentage equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social Homebuy?,What is the basic monthly rent?,Does the property have any service charges?,Monthly service charges amount,Does the property have an estate management fee?,Monthly estate management fee amount,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the shared owner has engaged in staircasing in the home?,"Including this time, how many times has the shared owner engaged in staircasing in the home?",Day of last staircasing transaction,Month of last staircasing transaction,Year of last staircasing transaction,Day of initial staircasing transaction,Month of initial staircasing transaction,Year of initial staircasing transaction,What was the basic monthly rent prior to staircasing?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Does this include any extra borrowing?,Does the property have any monthly leasehold charges?,What are the total monthly leasehold charges for the property?,Populated if a soft validation is confirmed. +ID,STATUS,DUPLICATESET,OWNINGORGNAME,OWNINGORGID,MANINGORGNAME,MANINGORGID,CREATEDDATE,CREATEDBY,CREATEDBYID,USERNAME,USERNAMEID,UPLOADDATE,AMENDEDBY,AMENDEDBYID,CREATIONMETHOD,BULKUPLOADID,COLLECTIONYEAR,DAY,MONTH,YEAR,PURCHID,OWNERSHIP,TYPE,STAIRCASE,JOINT,JOINTMORE,NOINT,PRIVACYNOTICE,UPRN,ADDRESS1,ADDRESS2,TOWNCITY,COUNTY,POSTCODE,ISLAINFERRED,LANAME,LA,UPRNSELECTED,ADDRESS_SEARCH_VALUE_CHECK,ADDRESS1INPUT,POSTCODEINPUT,BULKADDRESS1,BULKADDRESS2,BULKTOWNCITY,BULKCOUNTY,BULKPOSTCODE,BULKLA,PROPTYPE,BUILDHEIGHTCLASS,BEDS,BUILTYPE,WCHAIR,AGE1,SEXRAB1,SEX1,ETHNICGROUP1,ETHNIC,NATIONALITYALL1,ECSTAT1,LIVEINBUYER1,RELAT2,AGE2,SEXRAB2,SEX2,ETHNICGROUP2,ETHNIC2,NATIONALITYALL2,ECSTAT2,LIVEINBUYER2,HHOLDCOUNT,RELAT3,AGE3,SEXRAB3,SEX3,ECSTAT3,RELAT4,AGE4,SEXRAB4,SEX4,ECSTAT4,RELAT5,AGE5,SEXRAB5,SEX5,ECSTAT5,RELAT6,AGE6,SEXRAB6,SEX6,ECSTAT6,HHTYPE,PREVTEN,PPCODENK,PPOSTC1,PPOSTC2,PREVIOUSLAKNOWN,PREVLOC,PREVLOCNAME,BUY2LIVING,PREVTEN2,HHREGRES,HHREGRESSTILL,ARMEDFORCESSPOUSE,DISABLED,WHEEL,INC1NK,INCOME1,INC1MORT,INC2NK,INCOME2,INC2MORT,HB,SAVINGSNK,SAVINGS,PREVOWN,PREVSHARED,RESALE,PROPLEN,HODAY,HOMONTH,HOYEAR,FROMBEDS,FROMPROP,SOCPREVTEN,VALUE,VALUE_VALUE_CHECK,EQUITY,MORTGAGEUSED,MORTGAGE,MORTLEN1,DEPOSIT,CASHDIS,MRENT,HASSERVICECHARGES,SERVICECHARGES,HASESTATEFEE,ESTATEFEE,STAIRBOUGHT,STAIROWNED,STAIRCASETOSALE,FIRSTSTAIR,NUMSTAIR,STAIRLASTDAY,STAIRLASTMONTH,STAIRLASTYEAR,STAIRINITIALDAY,STAIRINITIALMONTH,STAIRINITIALYEAR,MRENTPRESTAIRCASING,GRANT,DISCOUNT,EXTRABOR,HASMSCHARGE,MSCHARGE,MSCHARGE_VALUE_CHECK +,completed,,MHCLG,,MHCLG,,2026-05-01T00:00:00+01:00,billyboy@eyeklaud.com,,billyboy@eyeklaud.com,,2026-05-01T00:00:00+01:00,,,1,,2026,1,5,2026,,2,8,,1,1,2,1,1,"1, Test Street",,Test Town,,AA1 1AA,true,Westminster,E09000033,1,,,,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,1,2,2,1,1,30,F,X,17,17,826,1,1,P,35,M,X,17,,826,1,1,3,X,14,F,X,9,X,-9,R,X,3,R,-9,R,R,10,,,,,,6,1,0,SW1A,1AA,1,E09000033,Westminster,3,,1,4,5,1,1,0,13400,1,0,13400,1,4,1,,1,2,,10,,,,,,,110000.0,,,1,20000.0,10,80000.0,,,,,,,,,,,,,,,,,,,10000.0,,1,1,100.0, diff --git a/spec/fixtures/files/sales_logs_csv_export_labels_26.csv b/spec/fixtures/files/sales_logs_csv_export_labels_26.csv new file mode 100644 index 000000000..cf4dbdf20 --- /dev/null +++ b/spec/fixtures/files/sales_logs_csv_export_labels_26.csv @@ -0,0 +1,3 @@ +Log ID,Status of log,ID of a set of duplicate logs,Which organisation owned this property before the sale?,Which organisation owned this property before the sale? (ID),Which organisation reported the sale?,Which organisation reported the sale? (ID),Time and date the log was created,User that created the log (email),User that created the log (ID),User the log is assigned to (email),User the log is assigned to (ID),Time and date the log was last updated,User that last amended the log (email),User that last amended the log (ID),Was the log submitted in-service or via bulk upload?,ID of a set of bulk uploaded logs,Year collection period opened,Day of sale completion date,Month of sale completion date,Year of sale completion date,What is the purchaser code?,Shared or discounted ownership,Type of ownership,Is this a staircasing transaction?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen the MHCLG privacy notice?,What is the UPRN of the property?,Address line 1,Address line 2,Town/City,County,Postcode,The internal value to indicate if the LA was inferred from the postcode,LA name,LA code,UPRN of the address selected,Was the 'No address found' page seen?,Address line 1 input from address matching feature,Postcode input from address matching feature,Address line 1 entered in bulk upload file,Address line 2 entered in bulk upload file,Town or city entered in bulk upload file,County entered in bulk upload file,Postcode entered in bulk upload file,Local authority entered in bulk upload file,What type of unit is the property?,What is the building height classification?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1's age?,What is buyer 1's sex registered at birth?,Which of these best describes buyer 1's gender identity?,What is buyer 1's ethnic group?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1's working situation?,Will buyer 1 live in the property?,Is buyer 2 or person 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,What is buyer 2 or person 2's sex registered at birth?,Which of these best describes buyer 2 or person 2's gender identity?,What is buyer 2's ethnic group?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,What is buyer 2 or person 2's working situation?,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,What is person 3's sex registered at birth?,What is person 3's gender identity?,What is person 3's working situation?,Is person 4 the partner of buyer 1?,What is person 4's age?,What is person 4's sex registered at birth?,What is person 4's gender identity?,What is person 4's working situation?,Is person 5 the partner of buyer 1?,What is person 5's age?,What is person 5's sex registered at birth?,What is person 5's gender identity?,What is person 5's working situation?,Is person 6 the partner of buyer 1?,What is person 6's age?,What is person 6's sex registered at birth?,What is person 6's gender identity?,What is person 6's working situation?,Household type,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,Do you know the local authority of buyer 1's last settled accommodation?,The local authority code of buyer 1's last settled accommodation,The local authority name of buyer 1's last settled accommodation,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,Have any of the buyers ever served as a regular in the UK armed forces?,Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,Is buyer 1's annual income known?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,Is buyer 1's annual income known?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,Is the the total amount the buyers had in savings known?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,Day of the practical completion or handover date,Month of the practical completion or handover date,Year of the practical completion or handover date,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the rent type of buyer's previous tenure?,What is the full purchase price?,Populated if a soft validation is confirmed.,What was the initial percentage equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social Homebuy?,What is the basic monthly rent?,Does the property have any service charges?,Monthly service charges amount,Does the property have an estate management fee?,Monthly estate management fee amount,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the shared owner has engaged in staircasing in the home?,"Including this time, how many times has the shared owner engaged in staircasing in the home?",Day of last staircasing transaction,Month of last staircasing transaction,Year of last staircasing transaction,Day of initial staircasing transaction,Month of initial staircasing transaction,Year of initial staircasing transaction,What was the basic monthly rent prior to staircasing?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Does this include any extra borrowing?,Does the property have any monthly leasehold charges?,What are the total monthly leasehold charges for the property?,Populated if a soft validation is confirmed +ID,STATUS,DUPLICATESET,OWNINGORGNAME,OWNINGORGID,MANINGORGNAME,MANINGORGID,CREATEDDATE,CREATEDBY,CREATEDBYID,USERNAME,USERNAMEID,UPLOADDATE,AMENDEDBY,AMENDEDBYID,CREATIONMETHOD,BULKUPLOADID,COLLECTIONYEAR,DAY,MONTH,YEAR,PURCHID,OWNERSHIP,TYPE,STAIRCASE,JOINT,JOINTMORE,NOINT,PRIVACYNOTICE,UPRN,ADDRESS1,ADDRESS2,TOWNCITY,COUNTY,POSTCODE,ISLAINFERRED,LANAME,LA,UPRNSELECTED,ADDRESS_SEARCH_VALUE_CHECK,ADDRESS1INPUT,POSTCODEINPUT,BULKADDRESS1,BULKADDRESS2,BULKTOWNCITY,BULKCOUNTY,BULKPOSTCODE,BULKLA,PROPTYPE,BUILDHEIGHTCLASS,BEDS,BUILTYPE,WCHAIR,AGE1,SEXRAB1,SEX1,ETHNICGROUP1,ETHNIC,NATIONALITYALL1,ECSTAT1,LIVEINBUYER1,RELAT2,AGE2,SEXRAB2,SEX2,ETHNICGROUP2,ETHNIC2,NATIONALITYALL2,ECSTAT2,LIVEINBUYER2,HHOLDCOUNT,RELAT3,AGE3,SEXRAB3,SEX3,ECSTAT3,RELAT4,AGE4,SEXRAB4,SEX4,ECSTAT4,RELAT5,AGE5,SEXRAB5,SEX5,ECSTAT5,RELAT6,AGE6,SEXRAB6,SEX6,ECSTAT6,HHTYPE,PREVTEN,PPCODENK,PPOSTC1,PPOSTC2,PREVIOUSLAKNOWN,PREVLOC,PREVLOCNAME,BUY2LIVING,PREVTEN2,HHREGRES,HHREGRESSTILL,ARMEDFORCESSPOUSE,DISABLED,WHEEL,INC1NK,INCOME1,INC1MORT,INC2NK,INCOME2,INC2MORT,HB,SAVINGSNK,SAVINGS,PREVOWN,PREVSHARED,RESALE,PROPLEN,HODAY,HOMONTH,HOYEAR,FROMBEDS,FROMPROP,SOCPREVTEN,VALUE,VALUE_VALUE_CHECK,EQUITY,MORTGAGEUSED,MORTGAGE,MORTLEN1,DEPOSIT,CASHDIS,MRENT,HASSERVICECHARGES,SERVICECHARGES,HASESTATEFEE,ESTATEFEE,STAIRBOUGHT,STAIROWNED,STAIRCASETOSALE,FIRSTSTAIR,NUMSTAIR,STAIRLASTDAY,STAIRLASTMONTH,STAIRLASTYEAR,STAIRINITIALDAY,STAIRINITIALMONTH,STAIRINITIALYEAR,MRENTPRESTAIRCASING,GRANT,DISCOUNT,EXTRABOR,HASMSCHARGE,MSCHARGE,MSCHARGE_VALUE_CHECK +,completed,,MHCLG,,MHCLG,,2026-05-01T00:00:00+01:00,billyboy@eyeklaud.com,,billyboy@eyeklaud.com,,2026-05-01T00:00:00+01:00,,,single log,,2026,1,5,2026,,Discounted Ownership,Right to Acquire (RTA),,Yes,Yes,Yes,1,1,"1, Test Street",,Test Town,,AA1 1AA,Yes,Westminster,E09000033,1,,,,address line 1 as entered,address line 2 as entered,town or city as entered,county as entered,AB1 2CD,la as entered,Flat or maisonette,Low-rise,2,Purpose built,Yes,30,Female,Non-binary,Buyer prefers not to say,17,United Kingdom,Full-time – 30 hours or more per week,Yes,Yes,35,Male,Non-binary,Buyer prefers not to say,,United Kingdom,Full-time – 30 hours or more per week,Yes,3,No,14,Female,Non-binary,Child under 16,No,Not known,Prefers not to say,Non-binary,In government training into work,Prefers not to say,Not known,Prefers not to say,Prefers not to say,Prefers not to say,,,,,,6,Local authority tenant,Yes,SW1A,1AA,Yes,E09000033,Westminster,Don’t know,,Yes,Yes,No,Yes,Yes,Yes,13400,Yes,Yes,13400,Yes,Don’t know ,No,,Yes,No,,10,,,,,,,110000.0,,,Yes,20000.0,10,80000.0,,,,,,,,,,,,,,,,,,,10000.0,,Yes,Yes,100.0, diff --git a/spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv b/spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv new file mode 100644 index 000000000..5902de596 --- /dev/null +++ b/spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv @@ -0,0 +1,3 @@ +Log ID,Status of log,ID of a set of duplicate logs,Which organisation owned this property before the sale?,Which organisation owned this property before the sale? (ID),Which organisation reported the sale?,Which organisation reported the sale? (ID),Time and date the log was created,User the log is assigned to (email),User the log is assigned to (ID),Time and date the log was last updated,User that last amended the log (email),User that last amended the log (ID),Was the log submitted in-service or via bulk upload?,ID of a set of bulk uploaded logs,Year collection period opened,Day of sale completion date,Month of sale completion date,Year of sale completion date,What is the purchaser code?,Shared or discounted ownership,Type of ownership,Is this a staircasing transaction?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen the MHCLG privacy notice?,What is the UPRN of the property?,We found an address that might be this property. Is this the property address?,Address line 1 input from address matching feature,Postcode input from address matching feature,UPRN of the address selected,Address line 1,Address line 2,Town/City,County,Part 1 of the property's postcode,Part 2 of the property's postcode,LA code,LA name,What type of unit is the property?,What is the building height classification?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1's age?,What is buyer 1's sex registered at birth?,Which of these best describes buyer 1's gender identity?,What is buyer 1's ethnic group?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1's working situation?,Will buyer 1 live in the property?,Is buyer 2 or person 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,What is buyer 2 or person 2's sex registered at birth?,Which of these best describes buyer 2 or person 2's gender identity?,What is buyer 2's ethnic group?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,What is buyer 2 or person 2's working situation?,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,What is person 3's sex registered at birth?,What is person 3's gender identity?,What is person 3's working situation?,Is person 4 the partner of buyer 1?,What is person 4's age?,What is person 4's sex registered at birth?,What is person 4's gender identity?,What is person 4's working situation?,Is person 5 the partner of buyer 1?,What is person 5's age?,What is person 5's sex registered at birth?,What is person 5's gender identity?,What is person 5's working situation?,Is person 6 the partner of buyer 1?,What is person 6's age?,What is person 6's sex registered at birth?,What is person 6's gender identity?,What is person 6's working situation?,Household type,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,Do you know the local authority of buyer 1's last settled accommodation?,The local authority code of buyer 1's last settled accommodation,The local authority name of buyer 1's last settled accommodation,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,Have any of the buyers ever served as a regular in the UK armed forces?,Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,Is buyer 1's annual income known?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,Is buyer 1's annual income known?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,Is the the total amount the buyers had in savings known?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,Day of the practical completion or handover date,Month of the practical completion or handover date,Year of the practical completion or handover date,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the rent type of buyer's previous tenure?,What is the full purchase price?,What was the initial percentage equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social Homebuy?,What is the basic monthly rent?,Does the property have any service charges?,Monthly service charges amount,Does the property have an estate management fee?,Monthly estate management fee amount,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the shared owner has engaged in staircasing in the home?,"Including this time, how many times has the shared owner engaged in staircasing in the home?",Day of last staircasing transaction,Month of last staircasing transaction,Year of last staircasing transaction,Day of initial staircasing transaction,Month of initial staircasing transaction,Year of initial staircasing transaction,What was the basic monthly rent prior to staircasing?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Does this include any extra borrowing?,Does the property have any monthly leasehold charges?,What are the total monthly leasehold charges for the property? +id,status,duplicate_set_id,owning_organisation_name,owning_organisation_id,managing_organisation_name,managing_organisation_id,created_at,assigned_to,assigned_to_id,updated_at,updated_by,updated_by_id,creation_method,bulk_upload_id,collection_start_year,day,month,year,purchid,ownershipsch,type,staircase,jointpur,jointmore,noint,privacynotice,uprn,uprn_confirmed,address_line1_input,postcode_full_input,uprn_selection,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la,la_label,proptype,buildheightclass,beds,builtype,wchair,age1,sexrab1,sex1,ethnic_group,ethnic,nationality_all,ecstat1,buy1livein,relat2,age2,sexrab2,sex2,ethnic_group2,ethnicbuy2,nationality_all_buyer2,ecstat2,buy2livein,hholdcount,relat3,age3,sexrab3,sex3,ecstat3,relat4,age4,sexrab4,sex4,ecstat4,relat5,age5,sexrab5,sex5,ecstat5,relat6,age6,sexrab6,sex6,ecstat6,hhtype,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,resale,proplen,hoday,homonth,hoyear,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortlen,deposit,cashdis,mrent,has_servicecharges,servicecharges,has_management_fee,management_fee,stairbought,stairowned,staircasesale,firststair,numstair,stairlastday,stairlastmonth,stairlastyear,stairinitialday,stairinitialmonth,stairinitialyear,mrentprestaircasing,grant,discount,extrabor,has_mscharge,mscharge +,completed,,MHCLG,,MHCLG,,2026-05-01T00:00:00+01:00,billyboy@eyeklaud.com,,2026-05-01T00:00:00+01:00,,,1,,2026,1,5,2026,,2,8,,1,1,2,1,1,1,,,1,"1, Test Street",,Test Town,,SW1A,1AA,E09000033,Westminster,1,2,2,1,1,30,F,X,17,17,826,1,1,P,35,M,X,17,,826,1,1,3,X,14,F,X,9,X,-9,R,X,3,R,-9,R,R,10,,,,,,6,1,0,SW1A,1AA,1,E09000033,Westminster,3,,1,4,5,1,1,0,13400,1,0,13400,1,4,1,,1,2,,10,,,,,,,110000.0,,1,20000.0,10,80000.0,,,,,,,,,,,,,,,,,,,10000.0,,1,1,100.0 diff --git a/spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv b/spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv new file mode 100644 index 000000000..aa86f4f02 --- /dev/null +++ b/spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv @@ -0,0 +1,3 @@ +Log ID,Status of log,ID of a set of duplicate logs,Which organisation owned this property before the sale?,Which organisation owned this property before the sale? (ID),Which organisation reported the sale?,Which organisation reported the sale? (ID),Time and date the log was created,User the log is assigned to (email),User the log is assigned to (ID),Time and date the log was last updated,User that last amended the log (email),User that last amended the log (ID),Was the log submitted in-service or via bulk upload?,ID of a set of bulk uploaded logs,Year collection period opened,Day of sale completion date,Month of sale completion date,Year of sale completion date,What is the purchaser code?,Shared or discounted ownership,Type of ownership,Is this a staircasing transaction?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen the MHCLG privacy notice?,What is the UPRN of the property?,We found an address that might be this property. Is this the property address?,Address line 1 input from address matching feature,Postcode input from address matching feature,UPRN of the address selected,Address line 1,Address line 2,Town/City,County,Part 1 of the property's postcode,Part 2 of the property's postcode,LA code,LA name,What type of unit is the property?,What is the building height classification?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1's age?,What is buyer 1's sex registered at birth?,Which of these best describes buyer 1's gender identity?,What is buyer 1's ethnic group?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1's working situation?,Will buyer 1 live in the property?,Is buyer 2 or person 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,What is buyer 2 or person 2's sex registered at birth?,Which of these best describes buyer 2 or person 2's gender identity?,What is buyer 2's ethnic group?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,What is buyer 2 or person 2's working situation?,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,What is person 3's sex registered at birth?,What is person 3's gender identity?,What is person 3's working situation?,Is person 4 the partner of buyer 1?,What is person 4's age?,What is person 4's sex registered at birth?,What is person 4's gender identity?,What is person 4's working situation?,Is person 5 the partner of buyer 1?,What is person 5's age?,What is person 5's sex registered at birth?,What is person 5's gender identity?,What is person 5's working situation?,Is person 6 the partner of buyer 1?,What is person 6's age?,What is person 6's sex registered at birth?,What is person 6's gender identity?,What is person 6's working situation?,Household type,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,Do you know the local authority of buyer 1's last settled accommodation?,The local authority code of buyer 1's last settled accommodation,The local authority name of buyer 1's last settled accommodation,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,Have any of the buyers ever served as a regular in the UK armed forces?,Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,Is buyer 1's annual income known?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,Is buyer 1's annual income known?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,Is the the total amount the buyers had in savings known?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,Day of the practical completion or handover date,Month of the practical completion or handover date,Year of the practical completion or handover date,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the rent type of buyer's previous tenure?,What is the full purchase price?,What was the initial percentage equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social Homebuy?,What is the basic monthly rent?,Does the property have any service charges?,Monthly service charges amount,Does the property have an estate management fee?,Monthly estate management fee amount,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the shared owner has engaged in staircasing in the home?,"Including this time, how many times has the shared owner engaged in staircasing in the home?",Day of last staircasing transaction,Month of last staircasing transaction,Year of last staircasing transaction,Day of initial staircasing transaction,Month of initial staircasing transaction,Year of initial staircasing transaction,What was the basic monthly rent prior to staircasing?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Does this include any extra borrowing?,Does the property have any monthly leasehold charges?,What are the total monthly leasehold charges for the property? +id,status,duplicate_set_id,owning_organisation_name,owning_organisation_id,managing_organisation_name,managing_organisation_id,created_at,assigned_to,assigned_to_id,updated_at,updated_by,updated_by_id,creation_method,bulk_upload_id,collection_start_year,day,month,year,purchid,ownershipsch,type,staircase,jointpur,jointmore,noint,privacynotice,uprn,uprn_confirmed,address_line1_input,postcode_full_input,uprn_selection,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la,la_label,proptype,buildheightclass,beds,builtype,wchair,age1,sexrab1,sex1,ethnic_group,ethnic,nationality_all,ecstat1,buy1livein,relat2,age2,sexrab2,sex2,ethnic_group2,ethnicbuy2,nationality_all_buyer2,ecstat2,buy2livein,hholdcount,relat3,age3,sexrab3,sex3,ecstat3,relat4,age4,sexrab4,sex4,ecstat4,relat5,age5,sexrab5,sex5,ecstat5,relat6,age6,sexrab6,sex6,ecstat6,hhtype,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,resale,proplen,hoday,homonth,hoyear,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortlen,deposit,cashdis,mrent,has_servicecharges,servicecharges,has_management_fee,management_fee,stairbought,stairowned,staircasesale,firststair,numstair,stairlastday,stairlastmonth,stairlastyear,stairinitialday,stairinitialmonth,stairinitialyear,mrentprestaircasing,grant,discount,extrabor,has_mscharge,mscharge +,completed,,MHCLG,,MHCLG,,2026-05-01T00:00:00+01:00,billyboy@eyeklaud.com,,2026-05-01T00:00:00+01:00,,,single log,,2026,1,5,2026,,Discounted Ownership,Right to Acquire (RTA),,Yes,Yes,Yes,1,1,Yes,,,1,"1, Test Street",,Test Town,,SW1A,1AA,E09000033,Westminster,Flat or maisonette,Low-rise,2,Purpose built,Yes,30,Female,Non-binary,Buyer prefers not to say,17,United Kingdom,Full-time – 30 hours or more per week,Yes,Yes,35,Male,Non-binary,Buyer prefers not to say,,United Kingdom,Full-time – 30 hours or more per week,Yes,3,No,14,Female,Non-binary,Child under 16,No,Not known,Prefers not to say,Non-binary,In government training into work,Prefers not to say,Not known,Prefers not to say,Prefers not to say,Prefers not to say,,,,,,6,Local authority tenant,Yes,SW1A,1AA,Yes,E09000033,Westminster,Don’t know,,Yes,Yes,No,Yes,Yes,Yes,13400,Yes,Yes,13400,Yes,Don’t know ,No,,Yes,No,,10,,,,,,,110000.0,,Yes,20000.0,10,80000.0,,,,,,,,,,,,,,,,,,,10000.0,,Yes,Yes,100.0 diff --git a/spec/fixtures/variable_definitions/sales_download_26_27.csv b/spec/fixtures/variable_definitions/sales_download_26_27.csv index b26e3abbd..24a96252f 100644 --- a/spec/fixtures/variable_definitions/sales_download_26_27.csv +++ b/spec/fixtures/variable_definitions/sales_download_26_27.csv @@ -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? diff --git a/spec/lib/tasks/log_variable_definitions_spec.rb b/spec/lib/tasks/log_variable_definitions_spec.rb index 73ec882c2..e4ec36831 100644 --- a/spec/lib/tasks/log_variable_definitions_spec.rb +++ b/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") diff --git a/spec/models/form/sales/pages/building_height_class_spec.rb b/spec/models/form/sales/pages/building_height_class_spec.rb new file mode 100644 index 000000000..2387020b3 --- /dev/null +++ b/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 diff --git a/spec/models/form/sales/questions/building_height_class_spec.rb b/spec/models/form/sales/questions/building_height_class_spec.rb new file mode 100644 index 000000000..e7ab032d4 --- /dev/null +++ b/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 diff --git a/spec/models/form/sales/subsections/property_information_spec.rb b/spec/models/form/sales/subsections/property_information_spec.rb index acc5b56a3..dee0f0c7e 100644 --- a/spec/models/form/sales/subsections/property_information_spec.rb +++ b/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 diff --git a/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb index b7505d7b8..9b93f7413 100644 --- a/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb +++ b/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 diff --git a/spec/services/csv/sales_log_csv_service_spec.rb b/spec/services/csv/sales_log_csv_service_spec.rb index a68403378..26b70cc0d 100644 --- a/spec/services/csv/sales_log_csv_service_spec.rb +++ b/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 diff --git a/spec/services/exports/sales_log_export_service_spec.rb b/spec/services/exports/sales_log_export_service_spec.rb index 5b9a41d02..14e138449 100644 --- a/spec/services/exports/sales_log_export_service_spec.rb +++ b/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) } From 70fe970ae59d233ea87ff45bb236cef712e19601 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Mar 2026 14:40:11 +0000 Subject: [PATCH 6/7] CLDC-4243: Use question.suffix_label in range error (#3191) this will correctly do any calculations if a dictionary suffix is provided --- app/models/validations/shared_validations.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index 4558a5a36..9c3a27f87 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -105,8 +105,8 @@ private def add_range_error(record, question) field = question.check_answer_label || question.id - min = [question.prefix, number_with_delimiter(question.min, delimiter: ","), question.suffix].join("") if question.min - max = [question.prefix, number_with_delimiter(question.max, delimiter: ","), question.suffix].join("") if question.max + min = [question.prefix, number_with_delimiter(question.min, delimiter: ","), question.suffix_label(record)].join("") if question.min + max = [question.prefix, number_with_delimiter(question.max, delimiter: ","), question.suffix_label(record)].join("") if question.max if min && max record.errors.add question.id.to_sym, :outside_the_range, message: I18n.t("validations.shared.numeric.within_range", field:, min:, max:) From 97e217069955ef384d6fe885ffaa4fa6cdc1dc4d Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 2 Mar 2026 15:19:29 +0000 Subject: [PATCH 7/7] CLDC-4012: Allow lower case letters for codes in BU (#3181) * CLDC-4012: Add case-sensitive function for 2025 * CLDC-4012: Add case-sensitive function for 2026 * CLDC-4012: Add tests * CLDC-4012: Remove integer attributes * fixup! CLDC-4012: Add tests move R overrides for integers to their own test * fixup! CLDC-4012: Add tests don't check field 58 * fixup! CLDC-4012: Add tests Co-authored-by: Oscar Richardson <116292912+oscar-richardson-softwire@users.noreply.github.com> --------- Co-authored-by: Oscar Richardson <116292912+oscar-richardson-softwire@users.noreply.github.com> --- .../lettings/year2025/row_parser.rb | 29 +++++++++++++++++ .../lettings/year2026/row_parser.rb | 29 +++++++++++++++++ .../bulk_upload/sales/year2025/row_parser.rb | 31 +++++++++++++++++++ .../bulk_upload/sales/year2026/row_parser.rb | 31 +++++++++++++++++++ .../lettings/year2025/row_parser_spec.rb | 14 +++++++++ .../lettings/year2026/row_parser_spec.rb | 14 +++++++++ .../sales/year2025/row_parser_spec.rb | 14 +++++++++ .../sales/year2026/row_parser_spec.rb | 14 +++++++++ 8 files changed, 176 insertions(+) diff --git a/app/services/bulk_upload/lettings/year2025/row_parser.rb b/app/services/bulk_upload/lettings/year2025/row_parser.rb index 9887c42be..5d132d3ef 100644 --- a/app/services/bulk_upload/lettings/year2025/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2025/row_parser.rb @@ -148,6 +148,26 @@ class BulkUpload::Lettings::Year2025::RowParser ERROR_BASE_KEY = "validations.lettings.2025.bulk_upload".freeze + CASE_INSENSITIVE_FIELDS = [ + :field_42, # What is the lead tenant’s age? + :field_48, # What is person 2’s age? + :field_52, # What is person 3’s age? + :field_56, # What is person 4’s age? + :field_60, # What is person 5’s age? + :field_64, # What is person 6’s age? + :field_68, # What is person 7’s age? + :field_72, # What is person 8’s age? + + :field_43, # Which of these best describes the lead tenant’s gender identity? + :field_49, # Which of these best describes person 2’s gender identity? + :field_53, # Which of these best describes person 3’s gender identity? + :field_57, # Which of these best describes person 4’s gender identity? + :field_61, # Which of these best describes person 5’s gender identity? + :field_65, # Which of these best describes person 6’s gender identity? + :field_69, # Which of these best describes person 7’s gender identity? + :field_73, # Which of these best describes person 8’s gender identity? + ].freeze + attribute :bulk_upload attribute :block_log_creation, :boolean, default: -> { false } @@ -459,6 +479,8 @@ class BulkUpload::Lettings::Year2025::RowParser return @valid = true if blank_row? + normalise_case_insensitive_fields + super(:before_log) @before_errors = errors.dup @@ -560,6 +582,13 @@ class BulkUpload::Lettings::Year2025::RowParser private + def normalise_case_insensitive_fields + CASE_INSENSITIVE_FIELDS.each do |field| + value = send(field) + send("#{field}=", value.upcase) if value.present? + end + end + def validate_valid_radio_option log.attributes.each_key do |question_id| question = log.form.get_question(question_id, log) diff --git a/app/services/bulk_upload/lettings/year2026/row_parser.rb b/app/services/bulk_upload/lettings/year2026/row_parser.rb index 87d6bb526..577a48190 100644 --- a/app/services/bulk_upload/lettings/year2026/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2026/row_parser.rb @@ -166,6 +166,26 @@ class BulkUpload::Lettings::Year2026::RowParser ERROR_BASE_KEY = "validations.lettings.2026.bulk_upload".freeze + CASE_INSENSITIVE_FIELDS = [ + :field_41, # What is the lead tenant's age? + :field_48, # What is person 2's age? + :field_54, # What is person 3's age? + :field_60, # What is person 4's age? + :field_66, # What is person 5's age? + :field_72, # What is person 6's age? + :field_78, # What is person 7's age? + :field_84, # What is person 8's age? + + :field_42, # What is the lead tenant's sex? + :field_50, # What is person 2's sex? + :field_56, # What is person 3's sex? + :field_62, # What is person 4's sex? + :field_68, # What is person 5's sex? + :field_74, # What is person 6's sex? + :field_80, # What is person 7's sex? + :field_86, # What is person 8's sex? + ].freeze + attribute :bulk_upload attribute :block_log_creation, :boolean, default: -> { false } @@ -494,6 +514,8 @@ class BulkUpload::Lettings::Year2026::RowParser return @valid = true if blank_row? + normalise_case_insensitive_fields + super(:before_log) @before_errors = errors.dup @@ -600,6 +622,13 @@ class BulkUpload::Lettings::Year2026::RowParser private + def normalise_case_insensitive_fields + CASE_INSENSITIVE_FIELDS.each do |field| + value = send(field) + send("#{field}=", value.upcase) if value.present? + end + end + def validate_valid_radio_option log.attributes.each_key do |question_id| question = log.form.get_question(question_id, log) diff --git a/app/services/bulk_upload/sales/year2025/row_parser.rb b/app/services/bulk_upload/sales/year2025/row_parser.rb index 19094f6c6..19ad2cb1b 100644 --- a/app/services/bulk_upload/sales/year2025/row_parser.rb +++ b/app/services/bulk_upload/sales/year2025/row_parser.rb @@ -139,6 +139,28 @@ class BulkUpload::Sales::Year2025::RowParser ERROR_BASE_KEY = "validations.sales.2025.bulk_upload".freeze + CASE_INSENSITIVE_FIELDS = [ + :field_28, # Age of buyer 1 + :field_35, # Age of person 2 + :field_43, # Age of person 3 + :field_47, # Age of person 4 + :field_51, # Age of person 5 + :field_55, # Age of person 6 + + :field_29, # Gender identity of buyer 1 + :field_36, # Gender identity of person 2 + :field_44, # Gender identity of person 3 + :field_48, # Gender identity of person 4 + :field_52, # Gender identity of person 5 + :field_56, # Gender identity of person 6 + + :field_64, # What was buyer 2’s previous tenure? + + :field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property? + :field_70, # What is buyer 1’s gross annual income? + :field_72, # What is buyer 2’s gross annual income? + ].freeze + attribute :bulk_upload attribute :block_log_creation, :boolean, default: -> { false } @@ -454,6 +476,8 @@ class BulkUpload::Sales::Year2025::RowParser return true if blank_row? + normalise_case_insensitive_fields + super(:before_log) @before_errors = errors.dup @@ -525,6 +549,13 @@ class BulkUpload::Sales::Year2025::RowParser private + def normalise_case_insensitive_fields + CASE_INSENSITIVE_FIELDS.each do |field| + value = send(field) + send("#{field}=", value.upcase) if value.present? + end + end + def prevtenbuy2 case field_64 when "R" diff --git a/app/services/bulk_upload/sales/year2026/row_parser.rb b/app/services/bulk_upload/sales/year2026/row_parser.rb index efba9967e..5fd6caada 100644 --- a/app/services/bulk_upload/sales/year2026/row_parser.rb +++ b/app/services/bulk_upload/sales/year2026/row_parser.rb @@ -147,6 +147,28 @@ class BulkUpload::Sales::Year2026::RowParser ERROR_BASE_KEY = "validations.sales.2026.bulk_upload".freeze + CASE_INSENSITIVE_FIELDS = [ + :field_28, # Age of buyer 1 + :field_35, # Age of person 2 + :field_43, # Age of person 3 + :field_47, # Age of person 4 + :field_51, # Age of person 5 + :field_55, # Age of person 6 + + :field_122, # Buyer 1's sex, as registered at birth + :field_123, # Buyer/Person 2's sex, as registered at birth + :field_124, # Person 3's sex, as registered at birth + :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_64, # What was buyer 2’s previous tenure? + + :field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property? + :field_70, # What is buyer 1’s gross annual income? + :field_72, # What is buyer 2’s gross annual income? + ].freeze + attribute :bulk_upload attribute :block_log_creation, :boolean, default: -> { false } @@ -470,6 +492,8 @@ class BulkUpload::Sales::Year2026::RowParser return true if blank_row? + normalise_case_insensitive_fields + super(:before_log) @before_errors = errors.dup @@ -542,6 +566,13 @@ class BulkUpload::Sales::Year2026::RowParser private + def normalise_case_insensitive_fields + CASE_INSENSITIVE_FIELDS.each do |field| + value = send(field) + send("#{field}=", value.upcase) if value.present? + end + end + def prevtenbuy2 case field_64 when "R" diff --git a/spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb index e8fe5cf8e..fcc4e9f46 100644 --- a/spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb @@ -541,6 +541,20 @@ RSpec.describe BulkUpload::Lettings::Year2025::RowParser do end end end + + context "and case insensitive fields are set to lowercase" do + let(:case_insensitive_fields) { %w[field_43 field_49 field_53 field_57 field_61 field_65 field_69 field_73] } + let(:case_insensitive_integer_fields_with_r_option) { %w[field_42 field_48 field_52 field_56 field_60 field_64 field_68 field_72] } + let(:attributes) do + valid_attributes + .merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase }) + .merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" }) + end + + it "is still valid" do + expect(parser).to be_valid + end + end end context "when valid row with valid decimal (integer) field_11" do diff --git a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb index d2e7fb797..27c8b7672 100644 --- a/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb @@ -434,6 +434,20 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do end end end + + context "and case insensitive fields are set to lowercase" do + let(:case_insensitive_fields) { %w[field_42 field_50 field_56 field_62 field_68 field_74 field_80 field_86] } + let(:case_insensitive_integer_fields_with_r_option) { %w[field_41 field_48 field_54 field_60 field_66 field_72 field_78 field_84] } + let(:attributes) do + valid_attributes + .merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase }) + .merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" }) + end + + it "is still valid" do + expect(parser).to be_valid + end + end end context "when valid row with valid decimal (integer) field_11" do diff --git a/spec/services/bulk_upload/sales/year2025/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2025/row_parser_spec.rb index 6306249eb..ebebc38b8 100644 --- a/spec/services/bulk_upload/sales/year2025/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2025/row_parser_spec.rb @@ -292,6 +292,20 @@ RSpec.describe BulkUpload::Sales::Year2025::RowParser do expect(questions.map(&:id).size).to eq(0) expect(questions.map(&:id)).to eql([]) end + + context "and case insensitive fields are set to lowercase" do + let(:case_insensitive_fields) { %w[field_29 field_36 field_44 field_48 field_52 field_56] } + let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72] } + let(:attributes) do + valid_attributes + .merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase }) + .merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" }) + end + + it "is still valid" do + expect(parser).to be_valid + end + end end describe "#validate_nulls" do diff --git a/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb index 9b93f7413..3b991ac26 100644 --- a/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2026/row_parser_spec.rb @@ -299,6 +299,20 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do expect(questions.map(&:id).size).to eq(0) expect(questions.map(&:id)).to eql([]) end + + context "and case insensitive fields are set to lowercase" do + let(:case_insensitive_fields) { %w[field_122 field_123 field_124 field_125 field_126 field_127] } + let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72] } + let(:attributes) do + valid_attributes + .merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase }) + .merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" }) + end + + it "is still valid" do + expect(parser).to be_valid + end + end end describe "#validate_nulls" do