From 44862af259f67f6e40c6159db99a7e229dea21c9 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire Date: Thu, 29 Feb 2024 12:43:03 +0000 Subject: [PATCH] feat: mvp commit with address selector from address line 1 and postcode using OS places find endpoint --- .../form/lettings/pages/address_matcher.rb | 18 ++++++ .../form/lettings/pages/address_selection.rb | 17 ++++++ .../address_line1_for_address_matcher.rb | 14 +++++ .../lettings/questions/address_selection.rb | 60 +++++++++++++++++++ .../questions/postcode_for_address_matcher.rb | 25 ++++++++ .../subsections/property_information.rb | 10 +++- app/models/lettings_log.rb | 5 ++ app/models/log.rb | 44 +++++++++++++- app/services/address_client.rb | 52 ++++++++++++++++ app/services/address_data_presenter.rb | 41 +++++++++++++ app/views/form/page.html.erb | 1 + ..._add_address_selection_to_lettings_logs.rb | 5 ++ db/schema.rb | 3 +- 13 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 app/models/form/lettings/pages/address_matcher.rb create mode 100644 app/models/form/lettings/pages/address_selection.rb create mode 100644 app/models/form/lettings/questions/address_line1_for_address_matcher.rb create mode 100644 app/models/form/lettings/questions/address_selection.rb create mode 100644 app/models/form/lettings/questions/postcode_for_address_matcher.rb create mode 100644 app/services/address_client.rb create mode 100644 app/services/address_data_presenter.rb create mode 100644 db/migrate/20240227163853_add_address_selection_to_lettings_logs.rb diff --git a/app/models/form/lettings/pages/address_matcher.rb b/app/models/form/lettings/pages/address_matcher.rb new file mode 100644 index 000000000..cfd24a4d1 --- /dev/null +++ b/app/models/form/lettings/pages/address_matcher.rb @@ -0,0 +1,18 @@ +class Form::Lettings::Pages::AddressMatcher < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "address_matcher" + @header = "Enter address details" + @depends_on = [ + { "is_supported_housing?" => false, "uprn_known" => 0 }, + { "is_supported_housing?" => false, "uprn_confirmed" => 0 }, + ] + end + + def questions + @questions ||= [ + Form::Lettings::Questions::AddressLine1ForAddressMatcher.new(nil, nil, self), + Form::Lettings::Questions::PostcodeForAddressMatcher.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/lettings/pages/address_selection.rb b/app/models/form/lettings/pages/address_selection.rb new file mode 100644 index 000000000..72c93cd1d --- /dev/null +++ b/app/models/form/lettings/pages/address_selection.rb @@ -0,0 +1,17 @@ +class Form::Lettings::Pages::AddressSelection < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "address_selection" + @header = "We found some addresses that might be this property" + end + + def questions + @questions ||= [ + Form::Lettings::Questions::AddressSelection.new(nil, nil, self), + ] + end + + def routed_to?(log, _current_user = nil) + log.uprn_known == 0 && log.address_line1.present? && log.postcode_full.present? + end +end diff --git a/app/models/form/lettings/questions/address_line1_for_address_matcher.rb b/app/models/form/lettings/questions/address_line1_for_address_matcher.rb new file mode 100644 index 000000000..601b3f5eb --- /dev/null +++ b/app/models/form/lettings/questions/address_line1_for_address_matcher.rb @@ -0,0 +1,14 @@ +class Form::Lettings::Questions::AddressLine1ForAddressMatcher < ::Form::Question + def initialize(id, hsh, page) + super + @id = "address_line1" + @header = "Address line 1" + @error_label = "Address line 1" + @type = "text" + @plain_label = true + @check_answer_label = "Address line 1" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true + @question_number = 12 + @hide_question_number_on_page = true + end +end diff --git a/app/models/form/lettings/questions/address_selection.rb b/app/models/form/lettings/questions/address_selection.rb new file mode 100644 index 000000000..5c2a80ae1 --- /dev/null +++ b/app/models/form/lettings/questions/address_selection.rb @@ -0,0 +1,60 @@ +class Form::Lettings::Questions::AddressSelection < ::Form::Question + def initialize(id, hsh, page) + super + @id = "address_selection" + @header = "Select the correct address" + @type = "radio" + @check_answer_label = "Select the correct address" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true # have just added this, check if it works! + end + + def answer_options(log = nil, user = nil) + answer_opts = { + # "0" => { "value" => "address 0" }, + # "1" => { "value" => "address 1" }, + # "2" => { "value" => "address 2" }, + # "3" => { "value" => "address 3" }, + # "4" => { "value" => "address 4" }, + # "5" => { "value" => "address 5" }, + # "6" => { "value" => "address 6" }, + # "7" => { "value" => "address 7" }, + # "8" => { "value" => "address 8" }, + # "9" => { "value" => "address 9" }, + } + return answer_opts unless ActiveRecord::Base.connected? + return answer_opts unless log + return answer_opts unless log.address_options + + values = [] + log.address_options.each do |option| + values.append(option) + end + + { + "0" => { "value" => values[0] }, + "1" => { "value" => values[1] }, + "2" => { "value" => values[2] }, + "3" => { "value" => values[3] }, + "4" => { "value" => values[4] }, + "5" => { "value" => values[5] }, + "6" => { "value" => values[6] }, + "7" => { "value" => values[7] }, + "8" => { "value" => values[8] }, + "9" => { "value" => values[9] }, + }.freeze + end + + def displayed_answer_options(log, user = nil) + answer_options(log, user) + end + + def hidden_in_check_answers?(log, _current_user = nil) + log.uprn_known == 1 || log.uprn_confirmed == 1 + end + +private + + def selected_answer_option_is_derived?(_log) + true + end +end diff --git a/app/models/form/lettings/questions/postcode_for_address_matcher.rb b/app/models/form/lettings/questions/postcode_for_address_matcher.rb new file mode 100644 index 000000000..0d8c8ff7c --- /dev/null +++ b/app/models/form/lettings/questions/postcode_for_address_matcher.rb @@ -0,0 +1,25 @@ +class Form::Lettings::Questions::PostcodeForAddressMatcher < ::Form::Question + def initialize(id, hsh, page) + super + @id = "postcode_full" + @header = "Postcode" + @type = "text" + @width = 5 + @inferred_check_answers_value = [{ + "condition" => { + "pcodenk" => 1, + }, + "value" => "Not known", + }] + @inferred_answers = { + "la" => { + "is_la_inferred" => true, + }, + } + @plain_label = true + @check_answer_label = "Postcode" + @disable_clearing_if_not_routed_or_dynamic_answer_options = true + @question_number = 12 + @hide_question_number_on_page = true + end +end diff --git a/app/models/form/lettings/subsections/property_information.rb b/app/models/form/lettings/subsections/property_information.rb index 4600d5e95..edf4f6a58 100644 --- a/app/models/form/lettings/subsections/property_information.rb +++ b/app/models/form/lettings/subsections/property_information.rb @@ -31,7 +31,15 @@ class Form::Lettings::Subsections::PropertyInformation < ::Form::Subsection end def uprn_questions - if form.start_date.year >= 2023 + if form.start_year_after_2024? + [ + Form::Lettings::Pages::Uprn.new(nil, nil, self), + Form::Lettings::Pages::UprnConfirmation.new(nil, nil, self), + Form::Lettings::Pages::AddressMatcher.new(nil, nil, self), + Form::Lettings::Pages::AddressSelection.new(nil, nil, self), + Form::Lettings::Pages::Address.new(nil, nil, self), + ] + elsif form.start_date.year == 2023 [ Form::Lettings::Pages::Uprn.new(nil, nil, self), Form::Lettings::Pages::UprnConfirmation.new(nil, nil, self), diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 5dc998bbb..500416ace 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -34,6 +34,7 @@ class LettingsLog < Log before_validation :reset_previous_location_fields!, unless: :previous_postcode_known? before_validation :set_derived_fields! before_validation :process_uprn_change!, if: :should_process_uprn_change? + before_validation :process_address_change!, if: :should_process_address_change? belongs_to :scheme, optional: true belongs_to :location, optional: true @@ -847,4 +848,8 @@ private def should_process_uprn_change? uprn && startdate && (uprn_changed? || startdate_changed?) && collection_start_year_for_date(startdate) >= 2023 end + + def should_process_address_change? + address_selection && startdate && (address_selection_changed? || startdate_changed?) && form.start_year_after_2024? + end end diff --git a/app/models/log.rb b/app/models/log.rb index e15869634..95a72a981 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -53,7 +53,7 @@ class Log < ApplicationRecord scope :filter_by_owning_organisation, ->(owning_organisation, _user = nil) { where(owning_organisation:) } scope :filter_by_managing_organisation, ->(managing_organisation, _user = nil) { where(managing_organisation:) } - attr_accessor :skip_update_status, :skip_update_uprn_confirmed, :skip_dpo_validation + attr_accessor :skip_update_status, :skip_update_uprn_confirmed, :skip_update_address_selection, :skip_dpo_validation def process_uprn_change! if uprn.present? @@ -66,6 +66,7 @@ class Log < ApplicationRecord self.uprn_known = 1 self.uprn_confirmed = nil unless skip_update_uprn_confirmed + self.address_selection = nil self.address_line1 = presenter.address_line1 self.address_line2 = presenter.address_line2 self.town_or_city = presenter.town_or_city @@ -75,6 +76,47 @@ class Log < ApplicationRecord end end + def process_address_change! + if [address_selection, address_line1, postcode_full].all?(&:present?) + address_string = "#{address_line1}, , , #{postcode_full}" + service = AddressClient.new(address_string) + service.call + + return errors.add(:address_line1, :address_error, message: service.error) if service.error.present? + + presenter = AddressDataPresenter.new(service.result[address_selection]) + + self.uprn_known = 1 + self.uprn_confirmed = 1 + self.address_selection = nil # unless skip_update_address_confirmed + self.uprn = presenter.uprn #skip process uprn change? + self.address_line1 = presenter.address_line1 + self.address_line2 = presenter.address_line2 + self.town_or_city = presenter.town_or_city + self.postcode_full = presenter.postcode + self.county = nil + process_postcode_changes! + end + end + + def address_options + if [address_line1, postcode_full].all?(&:present?) + address_string = "#{address_line1}, , , #{postcode_full}" + service = AddressClient.new(address_string) + service.call + + return errors.add(:address_line1, :address_error, message: service.error) if service.error.present? + + address_options = [] + service.result.first(10).each do |result| + presenter = AddressDataPresenter.new(result) + address_options.append(presenter.address) + end + + @address_options = address_options + end + end + def collection_start_year return @start_year if @start_year diff --git a/app/services/address_client.rb b/app/services/address_client.rb new file mode 100644 index 000000000..7f270bfd1 --- /dev/null +++ b/app/services/address_client.rb @@ -0,0 +1,52 @@ +require "net/http" + +class AddressClient + attr_reader :address + attr_accessor :error + + ADDRESS = "api.os.uk".freeze + PATH = "/search/places/v1/find".freeze + + def initialize(address) + @address = address + end + + def call + unless response.is_a?(Net::HTTPSuccess) && result.present? + @error = "Address is not recognised. Check the address, or enter the UPRN" + end + rescue JSON::ParserError + @error = "Address is not recognised. Check the address, or enter the UPRN" + end + + def result + @result ||= JSON.parse(response.body)["results"]&.map { |address| address["DPA"] } + end + + private + + def http_client + client = Net::HTTP.new(ADDRESS, 443) + client.use_ssl = true + client.verify_mode = OpenSSL::SSL::VERIFY_PEER + client.max_retries = 3 + client.read_timeout = 10 # seconds + client + end + + def endpoint_uri + uri = URI(PATH) + params = { + query: address, + key: ENV["OS_DATA_KEY"], + matchprecision: 3, + maxresults: 10, + } + uri.query = URI.encode_www_form(params) + uri.to_s + end + + def response + @response ||= http_client.request_get(endpoint_uri) + end +end diff --git a/app/services/address_data_presenter.rb b/app/services/address_data_presenter.rb new file mode 100644 index 000000000..3dcced33e --- /dev/null +++ b/app/services/address_data_presenter.rb @@ -0,0 +1,41 @@ +require "net/http" + +class AddressDataPresenter + attr_reader :data + + def initialize(data) + @data = data + end + + def uprn + data["UPRN"] + end + + def address_line1 + [data["BUILDING_NUMBER"], data["BUILDING_NAME"], data["THOROUGHFARE_NAME"]].compact.join(", ") + end + + def address_line2 + data["DEPENDENT_LOCALITY"] + end + + def town_or_city + data["POST_TOWN"] + end + + def postcode + data["POSTCODE"] + end + + def address + data["ADDRESS"] + end + + # def match + # data["MATCH"] + # end + # + # def match_description + # data["MATCH_DESCRIPTION"] + # end +end diff --git a/app/views/form/page.html.erb b/app/views/form/page.html.erb index a16c01c89..9506983f4 100644 --- a/app/views/form/page.html.erb +++ b/app/views/form/page.html.erb @@ -61,6 +61,7 @@ <%= f.hidden_field :page, value: @page.id %> <%= f.hidden_field :interruption_page_id, value: @interruption_page_id %> <%= f.hidden_field :interruption_page_referrer_type, value: @interruption_page_referrer_type %> + <%#= f.hidden_field :address_answer_options, value: @log.address_options %>
<% if !@page.interruption_screen? %> diff --git a/db/migrate/20240227163853_add_address_selection_to_lettings_logs.rb b/db/migrate/20240227163853_add_address_selection_to_lettings_logs.rb new file mode 100644 index 000000000..cea9e3db0 --- /dev/null +++ b/db/migrate/20240227163853_add_address_selection_to_lettings_logs.rb @@ -0,0 +1,5 @@ +class AddAddressSelectionToLettingsLogs < ActiveRecord::Migration[7.0] + def change + add_column :lettings_logs, :address_selection, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b5b9c9fc0..b80b2fe56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_02_16_163519) do +ActiveRecord::Schema[7.0].define(version: 2024_02_27_163853) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -308,6 +308,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_02_16_163519) do t.integer "nationality_all_group" t.integer "reasonother_value_check" t.integer "accessible_register" + t.integer "address_selection" t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id" t.index ["location_id"], name: "index_lettings_logs_on_location_id"