Handle missing category import headers and accept name* (#487)

* Handle missing headers in category import

* Hoist category import header lookups
This commit is contained in:
Nelluk
2025-12-22 14:41:37 -05:00
committed by GitHub
parent 204315b70b
commit 8c528c1b24
3 changed files with 66 additions and 5 deletions

View File

@@ -12,6 +12,9 @@ class Import::ConfigurationsController < ApplicationController
@import.reload.sync_mappings
redirect_to import_clean_path(@import), notice: "Import configured successfully."
rescue ActiveRecord::RecordInvalid => e
message = e.record.errors.full_messages.to_sentence.presence || e.message
redirect_back_or_to import_configuration_path(@import), alert: message
end
private

View File

@@ -59,19 +59,52 @@ class CategoryImport < Import
def generate_rows_from_csv
rows.destroy_all
validate_required_headers!
name_header = header_for("name")
color_header = header_for("color")
parent_header = header_for("parent_category", "parent category")
classification_header = header_for("classification")
icon_header = header_for("lucide_icon", "lucide icon", "icon")
csv_rows.each do |row|
rows.create!(
name: row["name"].to_s.strip,
category_color: row["color"].to_s.strip,
category_parent: row["parent_category"].to_s.strip,
category_classification: row["classification"].to_s.strip,
category_icon: (row["lucide_icon"].presence || row["icon"]).to_s.strip,
name: row[name_header].to_s.strip,
category_color: row[color_header].to_s.strip,
category_parent: row[parent_header].to_s.strip,
category_classification: row[classification_header].to_s.strip,
category_icon: row[icon_header].to_s.strip,
currency: default_currency
)
end
end
private
def validate_required_headers!
missing_headers = required_column_keys.map(&:to_s).reject { |key| header_for(key).present? }
return if missing_headers.empty?
errors.add(:base, "Missing required columns: #{missing_headers.join(', ')}")
raise ActiveRecord::RecordInvalid.new(self)
end
def header_for(*candidates)
candidates.each do |candidate|
normalized = normalize_header(candidate)
header = normalized_headers[normalized]
return header if header.present?
end
nil
end
def normalized_headers
@normalized_headers ||= csv_headers.to_h { |header| [ normalize_header(header), header ] }
end
def normalize_header(header)
header.to_s.strip.downcase.gsub(/\*/, "").gsub(/[\s-]+/, "_")
end
def ensure_placeholder_category(name)
trimmed_name = name.to_s.strip

View File

@@ -69,4 +69,29 @@ class CategoryImportTest < ActiveSupport::TestCase
assert_equal "#bbbbbb", snacks.color
assert_equal "pizza", snacks.lucide_icon
end
test "accepts required headers with an asterisk suffix" do
csv = <<~CSV
name*,color,parent_category,classification,icon
Food & Drink,#f97316,,expense,carrot
CSV
import = @family.imports.create!(type: "CategoryImport", raw_file_str: csv, col_sep: ",")
import.generate_rows_from_csv
assert_equal 1, import.rows.count
assert_equal "Food & Drink", import.rows.first.name
end
test "fails fast when required headers are missing" do
csv = <<~CSV
title,color,parent_category,classification,icon
Food & Drink,#f97316,,expense,carrot
CSV
import = @family.imports.create!(type: "CategoryImport", raw_file_str: csv, col_sep: ",")
error = assert_raises(ActiveRecord::RecordInvalid) { import.generate_rows_from_csv }
assert_includes error.message, "Missing required columns: name"
end
end