diff --git a/app/jobs/create_addresses_csv_job.rb b/app/jobs/create_addresses_csv_job.rb new file mode 100644 index 000000000..ef83ee746 --- /dev/null +++ b/app/jobs/create_addresses_csv_job.rb @@ -0,0 +1,22 @@ +class CreateAddressesCsvJob < ApplicationJob + queue_as :default + + BYTE_ORDER_MARK = "\uFEFF".freeze # Required to ensure Excel always reads CSV as UTF-8 + + def perform(organisation, log_type) + csv_service = Csv::MissingAddressesCsvService.new(organisation, []) + case log_type + when "lettings" + csv_string = csv_service.create_lettings_addresses_csv + filename = "#{['lettings-logs-addresses', organisation.name, Time.zone.now].compact.join('-')}.csv" + when "sales" + csv_string = csv_service.create_sales_addresses_csv + filename = "#{['sales-logs-addresses', organisation.name, Time.zone.now].compact.join('-')}.csv" + end + + storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"]) + storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string) + + Rails.logger.info("Created addresses file: #{filename}") + end +end diff --git a/app/services/csv/missing_addresses_csv_service.rb b/app/services/csv/missing_addresses_csv_service.rb index 228e3884d..0b92a852c 100644 --- a/app/services/csv/missing_addresses_csv_service.rb +++ b/app/services/csv/missing_addresses_csv_service.rb @@ -31,15 +31,15 @@ module Csv csv << ["Issue type", "Log ID", "Tenancy start date", "Tenant code", "Property reference", "Log owner", "Owning organisation", "Managing organisation", "UPRN", "Address Line 1", "Address Line 2 (optional)", "Town or City", "County (optional)", "Property’s postcode"] logs_with_missing_addresses.each do |log| - csv << lettings_log_to_csv_row(log, "Full address required") + csv << ["Full address required"] + lettings_log_to_csv_row(log) end logs_with_missing_town_or_city.each do |log| - csv << lettings_log_to_csv_row(log, "Missing town or city") + csv << ["Missing town or city"] + lettings_log_to_csv_row(log) end logs_with_wrong_uprn.each do |log| - csv << lettings_log_to_csv_row(log, "UPRN issues") + csv << ["UPRN issues"] + lettings_log_to_csv_row(log) end end end @@ -68,24 +68,47 @@ module Csv csv << ["Issue type", "Log ID", "Sale completion date", "Purchaser code", "Log owner", "Owning organisation", "UPRN", "Address Line 1", "Address Line 2 (optional)", "Town or City", "County (optional)", "Property’s postcode"] logs_with_missing_addresses.each do |log| - csv << sales_log_to_csv_row(log, "Full address required") + csv << ["Full address required"] + sales_log_to_csv_row(log) end logs_with_missing_town_or_city.each do |log| - csv << sales_log_to_csv_row(log, "Missing town or city") + csv << ["Missing town or city"] + sales_log_to_csv_row(log) end logs_with_wrong_uprn.each do |log| - csv << sales_log_to_csv_row(log, "UPRN issues") + csv << ["UPRN issues"] + sales_log_to_csv_row(log) + end + end + end + + def create_lettings_addresses_csv + logs = @organisation.managed_lettings_logs.filter_by_year(2023) + + CSV.generate(headers: true) do |csv| + csv << ["Log ID", "Tenancy start date", "Tenant code", "Property reference", "Log owner", "Owning organisation", "Managing organisation", "UPRN", "Address Line 1", "Address Line 2 (optional)", "Town or City", "County (optional)", "Property’s postcode"] + + logs.each do |log| + csv << lettings_log_to_csv_row(log) + end + end + end + + def create_sales_addresses_csv + logs = @organisation.sales_logs.filter_by_year(2023) + + CSV.generate(headers: true) do |csv| + csv << ["Log ID", "Sale completion date", "Purchaser code", "Log owner", "Owning organisation", "UPRN", "Address Line 1", "Address Line 2 (optional)", "Town or City", "County (optional)", "Property’s postcode"] + + logs.each do |log| + csv << sales_log_to_csv_row(log) end end end private - def sales_log_to_csv_row(log, issue_type) - [issue_type, - log.id, + def sales_log_to_csv_row(log) + [log.id, log.saledate&.to_date, log.purchid, log.created_by&.email, @@ -98,9 +121,8 @@ module Csv log.postcode_full] end - def lettings_log_to_csv_row(log, issue_type) - [issue_type, - log.id, + def lettings_log_to_csv_row(log) + [log.id, log.startdate&.to_date, log.tenancycode, log.propcode, diff --git a/lib/tasks/send_missing_addresses_csv.rake b/lib/tasks/send_missing_addresses_csv.rake index 98d402db5..9c8e60b59 100644 --- a/lib/tasks/send_missing_addresses_csv.rake +++ b/lib/tasks/send_missing_addresses_csv.rake @@ -87,4 +87,32 @@ namespace :correct_addresses do end end end + + desc "Send all 2023 lettings addresses csv" + task :create_lettings_addresses_csv, %i[organisation_id] => :environment do |_task, args| + organisation_id = args[:organisation_id] + raise "Usage: rake correct_addresses:create_lettings_addresses_csv['organisation_id']" if organisation_id.blank? + + organisation = Organisation.find_by(id: organisation_id) + if organisation.present? + CreateAddressesCsvJob.perform_later(organisation, "lettings") + Rails.logger.info("Creating lettings addresses CSV for #{organisation.name}") + else + Rails.logger.error("Organisation with ID #{organisation_id} not found") + end + end + + desc "Send all 2023 sales addresses csv" + task :create_sales_addresses_csv, %i[organisation_id] => :environment do |_task, args| + organisation_id = args[:organisation_id] + raise "Usage: rake correct_addresses:create_sales_addresses_csv['organisation_id']" if organisation_id.blank? + + organisation = Organisation.find_by(id: organisation_id) + if organisation.present? + CreateAddressesCsvJob.perform_later(organisation, "sales") + Rails.logger.info("Creating sales addresses CSV for #{organisation.name}") + else + Rails.logger.error("Organisation with ID #{organisation_id} not found") + end + end end diff --git a/spec/jobs/create_addresses_csv_job_spec.rb b/spec/jobs/create_addresses_csv_job_spec.rb new file mode 100644 index 000000000..3e35feec4 --- /dev/null +++ b/spec/jobs/create_addresses_csv_job_spec.rb @@ -0,0 +1,49 @@ +require "rails_helper" + +describe CreateAddressesCsvJob do + include Helpers + + let(:job) { described_class.new } + let(:storage_service) { instance_double(Storage::S3Service) } + let(:mailer) { instance_double(CsvDownloadMailer) } + let(:missing_addresses_csv_service) { instance_double(Csv::MissingAddressesCsvService) } + let(:organisation) { build(:organisation) } + let(:users) { create_list(:user, 2) } + + before do + allow(Storage::S3Service).to receive(:new).and_return(storage_service) + allow(storage_service).to receive(:write_file) + + allow(Csv::MissingAddressesCsvService).to receive(:new).and_return(missing_addresses_csv_service) + allow(missing_addresses_csv_service).to receive(:create_lettings_addresses_csv).and_return("") + allow(missing_addresses_csv_service).to receive(:create_sales_addresses_csv).and_return("") + end + + context "when sending all lettings logs csv" do + it "uses an appropriate filename in S3" do + expect(storage_service).to receive(:write_file).with(/lettings-logs-addresses-#{organisation.name}-.*\.csv/, anything) + expect(Rails.logger).to receive(:info).with(/Created addresses file: lettings-logs-addresses-#{organisation.name}-.*\.csv/) + job.perform(organisation, "lettings") + end + + it "creates a MissingAddressesCsvService with the correct organisation and calls create all lettings logs adresses csv" do + expect(Csv::MissingAddressesCsvService).to receive(:new).with(organisation, []) + expect(missing_addresses_csv_service).to receive(:create_lettings_addresses_csv) + job.perform(organisation, "lettings") + end + end + + context "when sending all sales logs csv" do + it "uses an appropriate filename in S3" do + expect(storage_service).to receive(:write_file).with(/sales-logs-addresses-#{organisation.name}-.*\.csv/, anything) + expect(Rails.logger).to receive(:info).with(/Created addresses file: sales-logs-addresses-#{organisation.name}-.*\.csv/) + job.perform(organisation, "sales") + end + + it "creates a MissingAddressesCsvService with the correct organisation and calls create all sales logs adresses csv" do + expect(Csv::MissingAddressesCsvService).to receive(:new).with(organisation, []) + expect(missing_addresses_csv_service).to receive(:create_sales_addresses_csv) + job.perform(organisation, "sales") + end + end +end diff --git a/spec/lib/tasks/send_missing_addresses_csv_spec.rb b/spec/lib/tasks/send_missing_addresses_csv_spec.rb index 5754fe690..dab36f1ad 100644 --- a/spec/lib/tasks/send_missing_addresses_csv_spec.rb +++ b/spec/lib/tasks/send_missing_addresses_csv_spec.rb @@ -477,4 +477,84 @@ RSpec.describe "correct_addresses" do end end end + + describe ":create_lettings_addresses_csv", type: :task do + subject(:task) { Rake::Task["correct_addresses:create_lettings_addresses_csv"] } + + before do + organisation.users.destroy_all + Rake.application.rake_require("tasks/send_missing_addresses_csv") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + let(:organisation) { create(:organisation, name: "test organisation") } + + context "and organisation ID is provided" do + it "enqueues the job with correct organisation" do + expect { task.invoke(organisation.id) }.to enqueue_job(CreateAddressesCsvJob).with(organisation, "lettings") + end + + it "prints out the jobs enqueued" do + expect(Rails.logger).to receive(:info).with(nil) + expect(Rails.logger).to receive(:info).with("Creating lettings addresses CSV for test organisation") + task.invoke(organisation.id) + end + end + + context "when organisation with given ID cannot be found" do + it "prints out error" do + expect(Rails.logger).to receive(:error).with("Organisation with ID fake not found") + task.invoke("fake") + end + end + + context "when organisation ID is not given" do + it "raises an error" do + expect { task.invoke }.to raise_error(RuntimeError, "Usage: rake correct_addresses:create_lettings_addresses_csv['organisation_id']") + end + end + end + end + + describe ":create_sales_addresses_csv", type: :task do + subject(:task) { Rake::Task["correct_addresses:create_sales_addresses_csv"] } + + before do + organisation.users.destroy_all + Rake.application.rake_require("tasks/send_missing_addresses_csv") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + let(:organisation) { create(:organisation, name: "test organisation") } + + context "and organisation ID is provided" do + it "enqueues the job with correct organisation" do + expect { task.invoke(organisation.id) }.to enqueue_job(CreateAddressesCsvJob).with(organisation, "sales") + end + + it "prints out the jobs enqueued" do + expect(Rails.logger).to receive(:info).with(nil) + expect(Rails.logger).to receive(:info).with("Creating sales addresses CSV for test organisation") + task.invoke(organisation.id) + end + end + + context "when organisation with given ID cannot be found" do + it "prints out error" do + expect(Rails.logger).to receive(:error).with("Organisation with ID fake not found") + task.invoke("fake") + end + end + + context "when organisation ID is not given" do + it "raises an error" do + expect { task.invoke }.to raise_error(RuntimeError, "Usage: rake correct_addresses:create_sales_addresses_csv['organisation_id']") + end + end + end + end end diff --git a/spec/services/csv/missing_addresses_csv_service_spec.rb b/spec/services/csv/missing_addresses_csv_service_spec.rb index 44e4bab85..b3e9b41db 100644 --- a/spec/services/csv/missing_addresses_csv_service_spec.rb +++ b/spec/services/csv/missing_addresses_csv_service_spec.rb @@ -308,4 +308,205 @@ RSpec.describe Csv::MissingAddressesCsvService do end end end + + describe "#create_lettings_addresses_csv" do + context "when the organisation has lettings logs" do + let!(:lettings_log) do + create(:lettings_log, + tenancycode: "tenancycode1", + propcode: "propcode1", + startdate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + managing_organisation: organisation, + address_line1: "address", + town_or_city: "town", + old_id: "old_id_1", + old_form_id: "old_form_id_1", + needstype: 1, + uprn_known: 0) + end + + let!(:lettings_log_missing_address) do + create(:lettings_log, + tenancycode: "tenancycode", + propcode: "propcode", + startdate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + managing_organisation: organisation, + address_line1: nil, + town_or_city: nil, + old_id: "old_id", + old_form_id: "old_form_id", + needstype: 1, + uprn_known: 0) + end + + let!(:lettings_log_missing_town) do + create(:lettings_log, + tenancycode: "tenancycode", + propcode: "propcode", + startdate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + managing_organisation: organisation, + address_line1: "existing address", + town_or_city: nil, + old_id: "older_id", + old_form_id: "old_form_id", + needstype: 1, + uprn_known: 0) + end + + let!(:lettings_log_wrong_uprn) do + create(:lettings_log, + tenancycode: "tenancycode", + propcode: "propcode", + startdate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + managing_organisation: organisation, + uprn: "123", + uprn_known: 1, + old_id: "oldest_id", + needstype: 1) + end + + let!(:lettings_log_not_imported) do + create(:lettings_log, + tenancycode: "tenancycode", + propcode: "propcode", + startdate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + managing_organisation: organisation, + uprn: "123", + uprn_known: 1, + needstype: 1) + end + + before do + create(:lettings_log, managing_organisation: organisation, old_id: "exists", startdate: Time.zone.local(2022, 4, 5)) + end + + it "returns a csv with relevant logs" do + csv = CSV.parse(service.create_lettings_addresses_csv) + expect(csv.count).to eq(6) + expect(csv).to include([lettings_log.id.to_s, "2023-04-05", "tenancycode1", "propcode1", "testy@example.com", "Address org", "Address org", nil, "address", nil, "town", nil, nil]) + expect(csv).to include([lettings_log_missing_address.id.to_s, "2023-04-05", "tenancycode", "propcode", "testy@example.com", "Address org", "Address org", nil, nil, nil, nil, nil, nil]) + expect(csv).to include([lettings_log_missing_town.id.to_s, "2023-04-05", "tenancycode", "propcode", "testy@example.com", "Address org", "Address org", nil, "existing address", nil, nil, nil, nil]) + expect(csv).to include([lettings_log_wrong_uprn.id.to_s, "2023-04-05", "tenancycode", "propcode", "testy@example.com", "Address org", "Address org", "123", "Some Place", nil, "Bristol", nil, "BS1 1AD"]) + expect(csv).to include([lettings_log_not_imported.id.to_s, "2023-04-05", "tenancycode", "propcode", "testy@example.com", "Address org", "Address org", "123", "Some Place", nil, "Bristol", nil, "BS1 1AD"]) + end + end + + context "when the organisation does not have relevant lettings logs" do + before do + create(:lettings_log, managing_organisation: organisation, startdate: Time.zone.local(2022, 4, 5)) + end + + it "returns only headers" do + csv = service.create_lettings_addresses_csv + expect(csv).to eq "Log ID,Tenancy start date,Tenant code,Property reference,Log owner,Owning organisation,Managing organisation,UPRN,Address Line 1,Address Line 2 (optional),Town or City,County (optional),Property’s postcode\n" + end + end + end + + describe "#create_sales_addresses_csv" do + context "when the organisation has sales" do + let!(:sales_log) do + create(:sales_log, + purchid: "purchaser code", + saledate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + address_line1: "address", + town_or_city: "city", + old_id: "old_id_1", + old_form_id: "old_form_id_1", + uprn_known: 0) + end + + let!(:sales_log_missing_address) do + create(:sales_log, + purchid: "purchaser code", + saledate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + address_line1: nil, + town_or_city: nil, + old_id: "old_id", + old_form_id: "old_form_id", + uprn_known: 0) + end + + let!(:sales_log_missing_town) do + create(:sales_log, + purchid: "purchaser code", + saledate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + address_line1: "existing address line 1", + town_or_city: nil, + old_id: "older_id", + old_form_id: "old_form_id", + uprn_known: 0) + end + + let!(:sales_log_wrong_uprn) do + create(:sales_log, + :completed, + purchid: "purchaser code", + saledate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + uprn: "123", + town_or_city: "Bristol", + old_id: "oldest_id", + uprn_known: 1, + uprn_confirmed: 1, + la: nil) + end + + let!(:sales_log_not_imported) do + create(:sales_log, + :completed, + purchid: "purchaser code", + saledate: Time.zone.local(2023, 4, 5), + created_by: user, + owning_organisation: organisation, + uprn: "123", + town_or_city: "Bristol", + uprn_known: 1, + uprn_confirmed: 1, + la: nil) + end + + before do + create(:sales_log, :completed, saledate: Time.zone.local(2022, 4, 5)) + end + + it "returns a csv with relevant logs" do + csv = CSV.parse(service.create_sales_addresses_csv) + expect(csv.count).to eq(6) + expect(csv).to include([sales_log.id.to_s, "2023-04-05", "purchaser code", "testy@example.com", "Address org", nil, "address", nil, "city", nil, nil]) + expect(csv).to include([sales_log_missing_address.id.to_s, "2023-04-05", "purchaser code", "testy@example.com", "Address org", nil, nil, nil, nil, nil, nil]) + expect(csv).to include([sales_log_missing_town.id.to_s, "2023-04-05", "purchaser code", "testy@example.com", "Address org", nil, "existing address line 1", nil, nil, nil, nil]) + expect(csv).to include([sales_log_wrong_uprn.id.to_s, "2023-04-05", "purchaser code", "testy@example.com", "Address org", "123", "Some Place", nil, "Bristol", nil, "BS1 1AD"]) + expect(csv).to include([sales_log_not_imported.id.to_s, "2023-04-05", "purchaser code", "testy@example.com", "Address org", "123", "Some Place", nil, "Bristol", nil, "BS1 1AD"]) + end + end + + context "when the organisation does not have relevant sales logs" do + before do + create(:sales_log, :completed, saledate: Time.zone.local(2022, 4, 5)) + end + + it "returns only headers" do + csv = service.create_sales_addresses_csv + expect(csv).to eq("Log ID,Sale completion date,Purchaser code,Log owner,Owning organisation,UPRN,Address Line 1,Address Line 2 (optional),Town or City,County (optional),Property’s postcode\n") + end + end + end end