module Csv class SalesLogCsvService def initialize(user:, export_type:, year:) @user = user @export_type = export_type @year = year @attributes = sales_log_attributes @definitions = sales_log_definitions end def prepare_csv(logs) CSV.generate(headers: true) do |csv| formatted_attributes = formatted_attribute_headers if @year >= 2023 csv << formatted_attributes.map do |attribute| record = @definitions.find { |r| r.variable == attribute.downcase } record&.tap { |r| r.update!(last_accessed: Time.zone.now) }&.definition end end csv << formatted_attributes logs.find_each do |log| csv << @attributes.map { |attribute| value(attribute, log) } end end end private CUSTOM_CALL_CHAINS = { day: { labels: %i[saledate day], codes: %i[saledate day], }, month: { labels: %i[saledate month], codes: %i[saledate month], }, year: { labels: %i[saledate year], codes: %i[saledate year], }, is_dpo: { labels: %i[assigned_to is_dpo], codes: %i[assigned_to is_dpo], }, assigned_to: { labels: %i[assigned_to email], codes: %i[assigned_to email], }, created_by: { labels: %i[created_by email], codes: %i[created_by email], }, owning_organisation_name: { labels: %i[owning_organisation name], codes: %i[owning_organisation name], }, managing_organisation_name: { labels: %i[managing_organisation name], codes: %i[managing_organisation name], }, creation_method: { labels: %i[creation_method], codes: %i[creation_method_before_type_cast], }, mscharge_value_check: { labels: %i[monthly_charges_value_check], codes: %i[monthly_charges_value_check], }, }.freeze PERSON_DETAILS = {}.tap { |hash| hash["age1"] = { "refused_code" => "-9", "refused_label" => "Not known", "age_known_field" => "age1_known" } (2..6).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 ORDERED_ADDRESS_FIELDS = %w[uprn address_line1 address_line2 town_or_city county postcode_full is_la_inferred la_label la 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[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 value_value_check mscharge_value_check].freeze SUPPORT_ATTRIBUTE_NAME_MAPPINGS = { "duplicate_set_id" => "DUPLICATESET", "bulk_upload_id" => "BULKUPLOADID", "created_at" => "CREATEDDATE", "updated_at" => "UPLOADDATE", "old_form_id" => "FORM", "collection_start_year" => "COLLECTIONYEAR", "creation_method" => "CREATIONMETHOD", "is_dpo" => "DATAPROTECT", "created_by" => "CREATEDBY", "owning_organisation_name" => "OWNINGORGNAME", "managing_organisation_name" => "MANINGORGNAME", "assigned_to" => "USERNAME", "ownershipsch" => "OWNERSHIP", "companybuy" => "COMPANY", "buylivein" => "LIVEINBUYER", "jointpur" => "JOINT", "address_line1" => "ADDRESS1", "address_line2" => "ADDRESS2", "town_or_city" => "TOWNCITY", "postcode_full" => "POSTCODE", "is_la_inferred" => "ISLAINFERRED", "la_label" => "LANAME", "uprn_selection" => "UPRNSELECTED", "address_line1_input" => "ADDRESS1INPUT", "postcode_full_input" => "POSTCODEINPUT", "address_line1_as_entered" => "BULKADDRESS1", "address_line2_as_entered" => "BULKADDRESS2", "town_or_city_as_entered" => "BULKTOWNCITY", "county_as_entered" => "BULKCOUNTY", "postcode_full_as_entered" => "BULKPOSTCODE", "la_as_entered" => "BULKLA", "ethnic_group" => "ETHNICGROUP1", "nationality_all" => "NATIONALITYALL1", "buy1livein" => "LIVEINBUYER1", "ethnic_group2" => "ETHNICGROUP2", "ethnicbuy2" => "ETHNIC2", "nationality_all_buyer2" => "NATIONALITYALL2", "buy2livein" => "LIVEINBUYER2", "hholdcount" => "HHTYPE", "previous_la_known" => "PREVIOUSLAKNOWN", "prevloc_label" => "PREVLOCNAME", "prevtenbuy2" => "PREVTEN2", "income1nk" => "INC1NK", "income2nk" => "INC2NK", "staircasesale" => "STAIRCASETOSALE", "soctenant" => "SOCTEN", "mortlen" => "MORTLEN1", "has_mscharge" => "HASMSCHARGE", "nationalbuy2" => "NATIONAL2", "uprn_confirmed" => "UPRNCONFIRMED", }.freeze def formatted_attribute_headers return @attributes unless @user.support? @attributes.map do |attribute| SUPPORT_ATTRIBUTE_NAME_MAPPINGS[attribute] || attribute.upcase end end def sales_log_attributes ordered_questions = FormHandler.instance.ordered_questions_for_year(@year, "sales") ordered_questions.reject! { |q| q.id.match?(/((? %w[day month year], "exdate" => %w[exday exmonth exyear], "hodate" => %w[hoday homonth hoyear], "ppostcode_full" => %w[ppostc1 ppostc2], "la" => %w[la la_label], "prevloc" => %w[prevloc prevloc_label], "assigned_to_id" => %w[created_by assigned_to], "owning_organisation_id" => %w[owning_organisation_name], "managing_organisation_id" => %w[managing_organisation_name], "value" => %w[value value_value_check], "mscharge" => %w[mscharge mscharge_value_check], } unless @user.support? && @year >= 2024 mappings["postcode_full"] = %w[pcode1 pcode2] end mappings 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 non_question_fields case @year when 2022 %w[id status created_at updated_at old_form_id collection_start_year creation_method is_dpo] when 2023 %w[id status duplicate_set_id created_at updated_at old_form_id collection_start_year creation_method is_dpo] when 2024 %w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo] else %w[id status duplicate_set_id created_at updated_at collection_start_year creation_method bulk_upload_id is_dpo] end end def all_address_fields ORDERED_ADDRESS_FIELDS + %w[uprn_confirmed] end def value(attribute, log) 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.send(attribute) elsif FIELDS_ALWAYS_EXPORTED_AS_LABELS.key? attribute attribute = FIELDS_ALWAYS_EXPORTED_AS_LABELS[attribute] value = log.send(attribute) get_label(value, attribute, log) elsif SYSTEM_DATE_FIELDS.include? attribute log.public_send(attribute)&.iso8601 elsif PERSON_DETAILS.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) || 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] == 2 # 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) 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 end end