module Csv class LettingsLogCsvService def initialize(user:, export_type:, year:) @user = user @export_type = export_type @year = year @attributes = lettings_log_attributes end def prepare_csv(logs) CSV.generate(headers: true) do |csv| csv << @attributes logs.find_each do |log| csv << @attributes.map { |attribute| value(attribute, log) } end end end private CUSTOM_CALL_CHAINS = { assigned_to: { labels: %i[assigned_to email], codes: %i[assigned_to email], }, created_by: { labels: %i[created_by email], codes: %i[created_by email], }, updated_by: { labels: %i[updated_by email], codes: %i[updated_by email], }, location_code: { labels: %i[location id], codes: %i[location id], }, location_postcode: { labels: %i[location postcode], codes: %i[location postcode], }, location_name: { labels: %i[location name], codes: %i[location name], }, location_units: { labels: %i[location units], codes: %i[location units], }, location_type_of_unit: { labels: %i[location type_of_unit], codes: %i[location type_of_unit_before_type_cast], }, location_mobility_type: { labels: %i[location mobility_type], codes: %i[location mobility_type_before_type_cast], }, location_local_authority: { labels: %i[location location_admin_district], codes: %i[location location_admin_district], }, location_startdate: { labels: %i[location startdate], codes: %i[location startdate], }, scheme_service_name: { labels: %i[scheme service_name], codes: %i[scheme service_name], }, scheme_confidential: { labels: %i[scheme sensitive], codes: %i[scheme sensitive_before_type_cast], }, SCHTYPE: { labels: %i[scheme scheme_type], codes: %i[scheme scheme_type_before_type_cast], }, scheme_registered_under_care_act: { labels: %i[scheme registered_under_care_act], codes: %i[scheme registered_under_care_act_before_type_cast], }, scheme_owning_organisation_name: { labels: %i[scheme owning_organisation name], codes: %i[scheme owning_organisation name], }, scheme_primary_client_group: { labels: %i[scheme primary_client_group], codes: %i[scheme primary_client_group_before_type_cast], }, scheme_has_other_client_group: { labels: %i[scheme has_other_client_group], codes: %i[scheme has_other_client_group_before_type_cast], }, scheme_secondary_client_group: { labels: %i[scheme secondary_client_group], codes: %i[scheme secondary_client_group_before_type_cast], }, scheme_support_type: { labels: %i[scheme support_type], codes: %i[scheme support_type_before_type_cast], }, scheme_intended_stay: { labels: %i[scheme intended_stay], codes: %i[scheme intended_stay_before_type_cast], }, scheme_created_at: { labels: %i[scheme created_at], codes: %i[scheme created_at], }, scheme_code: { labels: %i[scheme id_to_display], codes: %i[scheme id_to_display], }, creation_method: { labels: %i[creation_method], codes: %i[creation_method_before_type_cast], }, is_dpo: { labels: %i[assigned_to is_dpo?], codes: %i[assigned_to is_dpo?], }, renttype_detail: { labels: %i[renttype_detail], codes: %i[renttype_detail_code], }, }.freeze PERSON_DETAILS = {}.tap { |hash| hash["age1"] = { "refused_code" => "-9", "refused_label" => "Not known", "age_known_field" => "age1_known" } (2..8).each do |i| hash["age#{i}"] = { "refused_code" => "-9", "refused_label" => "Not known", "details_known_field" => "details_known_#{i}", "age_known_field" => "age#{i}_known" } hash["sex#{i}"] = { "refused_code" => "R", "refused_label" => "Prefers not to say", "details_known_field" => "details_known_#{i}" } hash["relat#{i}"] = { "refused_code" => "R", "refused_label" => "Prefers not to say", "details_known_field" => "details_known_#{i}" } hash["ecstat#{i}"] = { "refused_code" => "10", "refused_label" => "Prefers not to say", "details_known_field" => "details_known_#{i}" } end }.freeze FIELDS_ALWAYS_EXPORTED_AS_CODES = %w[ la prevloc ].freeze FIELDS_ALWAYS_EXPORTED_AS_LABELS = { "la_label" => "la", "prevloc_label" => "prevloc", }.freeze SYSTEM_DATE_FIELDS = %w[ created_at updated_at ].freeze USER_DATE_FIELDS = %w[ mrcdate startdate voiddate ].freeze LETTYPE_LABELS = { 1 => "Social rent general needs private registered provider", 2 => "Social rent supported housing private registered provider", 3 => "Social rent general needs local authority", 4 => "Social rent supported housing local authority", 5 => "Affordable rent general needs private registered provider", 6 => "Affordable rent supported housing private registered provider", 7 => "Affordable rent general needs local authority", 8 => "Affordable rent supported housing local authority", 9 => "Intermediate rent general needs private registered provider", 10 => "Intermediate rent supported housing private registered provider", 11 => "Intermediate rent general needs local authority", 12 => "Intermediate rent supported housing local authority", }.freeze IRPRODUCT_LABELS = { 1 => "Rent to Buy", 2 => "London Living Rent", 3 => "Other intermediate rent product", }.freeze LAR_LABELS = { 1 => "Yes", 2 => "No", 3 => "Don't know", }.freeze NEWPROP_LABELS = { 1 => "Yes", 2 => "No", }.freeze INCREF_LABELS = { 0 => "No", 2 => "Yes", 1 => "Prefers not to say", }.freeze RENTTYPE_LABELS = { 1 => "Social Rent", 2 => "Affordable Rent", 3 => "Intermediate Rent", }.freeze LABELS = { "lettype" => LETTYPE_LABELS, "irproduct" => IRPRODUCT_LABELS, "lar" => LAR_LABELS, "newprop" => NEWPROP_LABELS, "incref" => INCREF_LABELS, "renttype" => RENTTYPE_LABELS, }.freeze CONVENTIONAL_YES_NO_ATTRIBUTES = %w[illness_type_1 illness_type_2 illness_type_3 illness_type_4 illness_type_5 illness_type_6 illness_type_7 illness_type_8 illness_type_9 illness_type_10 refused cbl cap chr accessible_register letting_allocation_none housingneeds_a housingneeds_b housingneeds_c housingneeds_d housingneeds_e housingneeds_f housingneeds_g housingneeds_h has_benefits nocharge postcode_known].freeze YES_OR_BLANK_ATTRIBUTES = %w[declaration rp_homeless rp_insan_unsat rp_medwel rp_hardship rp_dontknow].freeze ATTRIBUTE_MAPPINGS = { "owning_organisation_id" => %w[owning_organisation_name], "managing_organisation_id" => %w[managing_organisation_name], "assigned_to_id" => [], "scheme_id" => [], "location_id" => [], "rent_type" => %w[renttype renttype_detail], "hb" => %w[hb has_benefits], "age1" => %w[refused hhtype totchild totelder totadult age1], "housingneeds_type" => %w[housingneeds_type housingneeds_a housingneeds_b housingneeds_c housingneeds_f housingneeds_g housingneeds_h], "net_income_known" => %w[net_income_known incref], "irproduct_other" => %w[irproduct irproduct_other lar], "la" => %w[is_la_inferred la_label la], "prevloc" => %w[is_previous_la_inferred prevloc_label prevloc], "needstype" => %w[needstype lettype], "voiddate" => %w[voiddate vacdays], "rsnvac" => %w[rsnvac newprop], "household_charge" => %w[household_charge nocharge], "brent" => %w[brent wrent rent_value_check], "scharge" => %w[scharge wscharge], "pscharge" => %w[pscharge wpschrge], "supcharg" => %w[supcharg wsupchrg], "tcharge" => %w[tcharge wtcharge], "chcharge" => %w[chcharge wchchrg], "tshortfall" => %w[tshortfall wtshortfall], "letting_allocation_unknown" => %w[letting_allocation_none], }.freeze ORDERED_ADDRESS_FIELDS = %w[uprn address_line1 address_line2 town_or_city county postcode_full is_la_inferred la_label la uprn_known uprn_selection address_search_value_check 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 la_as_entered].freeze SUPPORT_ONLY_ATTRIBUTES = %w[postcode_known is_la_inferred totchild totelder totadult net_income_known previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 wrent wscharge wpschrge wsupchrg wtcharge wtshortfall old_form_id old_id tshortfall_known hhtype la prevloc updated_by_id uprn_confirmed address_line1_input postcode_full_input uprn_selection address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered created_by].freeze 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 def lettings_log_attributes ordered_questions = FormHandler.instance.ordered_questions_for_year(@year, "lettings") soft_validations_attributes = soft_validations_attributes(ordered_questions) ordered_questions.reject! { |q| q.id.match?(/age\d_known|nationality_all_group|rent_value_check/) } attributes = insert_derived_and_related_attributes(ordered_questions) order_address_fields_for_support(attributes) final_attributes = non_question_fields + attributes + SCHEME_AND_LOCATION_ATTRIBUTES @user.support? ? final_attributes : final_attributes - SUPPORT_ONLY_ATTRIBUTES - soft_validations_attributes end def insert_derived_and_related_attributes(ordered_questions) ordered_questions.flat_map do |question| if question.type == "checkbox" question.answer_options.keys.reject { |key| key == "divider" }.map { |key| ATTRIBUTE_MAPPINGS.fetch(key, key) }.flatten else ATTRIBUTE_MAPPINGS.fetch(question.id, question.id) end end end def order_address_fields_for_support(attributes) if @user.support? && @year >= 2024 first_address_field_index = attributes.find_index { |q| all_address_fields.include?(q) } if first_address_field_index attributes.reject! { |q| all_address_fields.include?(q) } attributes.insert(first_address_field_index, *ORDERED_ADDRESS_FIELDS) end end end def all_address_fields ORDERED_ADDRESS_FIELDS + %w[uprn_confirmed] end def non_question_fields case @year when 2022 %w[id status created_by assigned_to is_dpo created_at updated_by updated_at creation_method old_id old_form_id collection_start_year] when 2023 %w[id status duplicate_set_id created_by assigned_to is_dpo created_at updated_by updated_at creation_method old_id old_form_id collection_start_year] when 2024 %w[id status duplicate_set_id created_by assigned_to is_dpo created_at updated_by updated_at creation_method collection_start_year bulk_upload_id] else %w[id status duplicate_set_id created_by assigned_to is_dpo created_at updated_by updated_at creation_method collection_start_year bulk_upload_id] end end def soft_validations_attributes(ordered_questions) ordered_questions.select { |q| q.type == "interruption_screen" }.map(&:id) end 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. 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) } elsif FIELDS_ALWAYS_EXPORTED_AS_CODES.include? attribute log.public_send(attribute) elsif FIELDS_ALWAYS_EXPORTED_AS_LABELS.key? attribute attribute = FIELDS_ALWAYS_EXPORTED_AS_LABELS[attribute] value = log.public_send(attribute) get_label(value, attribute, log) elsif SYSTEM_DATE_FIELDS.include? attribute log.public_send(attribute)&.iso8601 elsif USER_DATE_FIELDS.include? attribute log.public_send(attribute)&.strftime("%F") elsif PERSON_DETAILS.any? { |key, _value| key == attribute } && (person_details_not_known?(log, attribute) || age_not_known?(log, attribute)) case @export_type when "codes" PERSON_DETAILS.find { |key, _value| key == attribute }[1]["refused_code"] when "labels" PERSON_DETAILS.find { |key, _value| key == attribute }[1]["refused_label"] end else value = log.public_send(attribute) case @export_type when "codes" value when "labels" answer_label = get_label(value, attribute, log) answer_label || label_if_boolean_value(value) || (YES_OR_BLANK_ATTRIBUTES.include?(attribute) && value != 1 ? nil : value) end end 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 end def age_not_known?(log, attribute) age_known_field = PERSON_DETAILS.find { |key, _value| key == attribute }[1]["age_known_field"] log[age_known_field] == 1 end def get_label(value, attribute, log) return LABELS[attribute][value] if LABELS.key?(attribute) return conventional_yes_no_label(value) if CONVENTIONAL_YES_NO_ATTRIBUTES.include?(attribute) return "Yes" if YES_OR_BLANK_ATTRIBUTES.include?(attribute) && value == 1 log.form .get_question(attribute, log) &.label_from_value(value) end def label_if_boolean_value(value) return "Yes" if value == true return "No" if value == false end def conventional_yes_no_label(value) return "Yes" if value == 1 return "No" if value&.zero? end end end