From ef65d2837d6c4a537d0f9e841f42acb1bdbea068 Mon Sep 17 00:00:00 2001 From: oscric Date: Thu, 13 Aug 2026 11:34:35 +0100 Subject: [PATCH] Hide address data in CSV download for logs in confidential schemes --- app/services/csv/lettings_log_csv_service.rb | 37 ++++++++++++ .../csv/lettings_log_csv_service_spec.rb | 57 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/app/services/csv/lettings_log_csv_service.rb b/app/services/csv/lettings_log_csv_service.rb index dd3be8595..ad2af2527 100644 --- a/app/services/csv/lettings_log_csv_service.rb +++ b/app/services/csv/lettings_log_csv_service.rb @@ -280,6 +280,31 @@ module Csv SCHEME_AND_LOCATION_ATTRIBUTES = %w[scheme_code scheme_service_name scheme_confidential SCHTYPE scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_local_authority location_startdate].freeze + # TODO: (CLDC-4462): delete once address data for logs in confidential schemes is wiped. + # Interim measure: logs in a confidential scheme that were created before the + # confidential address feature may still hold property address/UPRN data that is no + # longer collected. Blank those columns in the download until the data is wiped (the + # local authority, derived from the scheme location, is intentionally retained). + ADDRESS_FIELDS_HIDDEN_FOR_CONFIDENTIAL_SCHEME = %w[ + uprn + uprn_known + uprn_confirmed + uprn_selection + address_line1 + address_line2 + town_or_city + county + postcode_full + postcode_known + address_line1_input + postcode_full_input + address_line1_as_entered + address_line2_as_entered + town_or_city_as_entered + county_as_entered + postcode_full_as_entered + ].freeze + def lettings_log_attributes ordered_questions = FormHandler.instance.ordered_questions_for_year(@year, "lettings") soft_validations_attributes = soft_validations_attributes(ordered_questions) @@ -344,6 +369,8 @@ module Csv def value(attribute, log) attribute = "rent_type" if attribute == "rent_type_detail" # rent_type_detail is the requested column header for rent_type, so as not to confuse with renttype. It can be exported as label or code. + return nil if hide_confidential_scheme_address?(attribute, log) # TODO: (CLDC-4462): delete once address data for logs in confidential schemes is wiped. + if CUSTOM_CALL_CHAINS.key? attribute.to_sym call_chain = CUSTOM_CALL_CHAINS[attribute.to_sym][@export_type.to_sym] call_chain.reduce(log) { |object, next_call| object&.public_send(next_call) } @@ -376,6 +403,16 @@ module Csv end end + # TODO: (CLDC-4462): delete once address data for logs in confidential schemes is wiped. + def hide_confidential_scheme_address?(attribute, log) + ADDRESS_FIELDS_HIDDEN_FOR_CONFIDENTIAL_SCHEME.include?(attribute) && confidential_scheme_ids.include?(log.scheme_id) + end + + # TODO: (CLDC-4462): delete once address data for logs in confidential schemes is wiped. + def confidential_scheme_ids + @confidential_scheme_ids ||= Scheme.where(sensitive: "Yes").pluck(:id).to_set + end + def person_details_not_known?(log, attribute) details_known_field = PERSON_DETAILS.find { |key, _value| key == attribute }[1]["details_known_field"] log[details_known_field] == 1 # 1 for lettings logs, 2 for sales logs diff --git a/spec/services/csv/lettings_log_csv_service_spec.rb b/spec/services/csv/lettings_log_csv_service_spec.rb index f2fd786c6..2ba2cae2d 100644 --- a/spec/services/csv/lettings_log_csv_service_spec.rb +++ b/spec/services/csv/lettings_log_csv_service_spec.rb @@ -193,6 +193,63 @@ RSpec.describe Csv::LettingsLogCsvService do end end + # TODO: (CLDC-4462): delete once address data for logs in confidential schemes is wiped. + context "when a log's scheme is confidential" do + let(:year) { 2026 } + let(:owning_organisation) { create(:organisation) } + let(:scheme) { create(:scheme, sensitive: 1, owning_organisation:) } + let(:location) { create(:location, scheme:) } + let(:log) do + create( + :lettings_log, + :ignore_validation_errors, + needstype: 2, + owning_organisation:, + managing_organisation: owning_organisation, + assigned_to: user, + scheme:, + location:, + startdate: Time.zone.local(2026, 5, 1), + ).tap do |confidential_log| + # Simulate a log created before the confidential-address feature that still holds + # property address data in the database. + confidential_log.update_columns( + uprn: "123456789012", + address_line1: "1 Secret Street", + address_line2: "Hidden", + town_or_city: "Secretville", + county: "Secretshire", + postcode_full: "AB1 2CD", + postcode_known: 1, + la: "E09000003", + ) + end + end + + def csv_value(attribute) + content_line[attribute_line.index(attribute)] + end + + it "blanks the property address and UPRN columns" do + %w[uprn address_line1 address_line2 town_or_city county postcode_full].each do |attribute| + expect(csv_value(attribute)).to be_nil, "expected the #{attribute} column to be blank for a confidential-scheme log" + end + end + + it "still exports the local authority" do + expect(csv_value("la")).to eq("E09000003") + end + + context "when the scheme is not confidential" do + let(:scheme) { create(:scheme, sensitive: 0, owning_organisation:) } + + it "exports the property address as normal" do + expect(csv_value("address_line1")).to eq("1 Secret Street") + expect(csv_value("postcode_full")).to eq("AB1 2CD") + end + end + end + describe "the full CSV output" do context "when the requested log year is 2026" do let(:year) { 2026 }