Files
sure/app/models/import/category_mapping.rb
Claude b39a08be6a Add parent/child category hierarchy to all category selects; add search to account select
Category selection consistency:
- DS::Select (shared component powering the main transaction form,
  transaction edit, bulk-update, and transfer category pickers) now
  indents subcategories with a corner-down-right icon, matching the
  existing transaction-row category dropdown.
- Feed DS::Select-based category pickers with Category.alphabetically_by_hierarchy
  (parent name, then parent-before-children, then own name) so children
  render directly under their parent.
- Added Category::Group.select_options, a shared helper producing
  parent-then-child ordered options (with an indent marker) for plain
  HTML <select> elements. Used by:
  - Rule builder category condition/action selects
  - Bulk 'categorize transactions' select
  - CSV/QIF import category mapping select
- Grouped the splits category combobox and the transaction search
  category filter checklist the same way, both with the
  corner-down-right indent icon used elsewhere.

Account selection:
- Added searchable: true to the account select in the new/edit
  transaction form, matching the category and merchant selects next
  to it.
2026-07-28 16:44:20 +00:00

79 lines
2.2 KiB
Ruby

class Import::CategoryMapping < Import::Mapping
class << self
def mappables_by_key(import)
unique_values = import.rows.map(&:category).uniq
# For hierarchical QIF keys like "Home:Home Improvement", look up the child
# name ("Home Improvement") since category names are unique per family.
lookup_names = unique_values.map { |v| leaf_category_name(v) }
categories = import.family.categories.where(name: lookup_names).index_by(&:name)
unique_values.index_with { |value| categories[leaf_category_name(value)] }
end
private
# Returns the leaf (child) name for a potentially hierarchical key.
# "Home:Home Improvement" → "Home Improvement"
# "Fees & Charges" → "Fees & Charges"
def leaf_category_name(key)
return "" if key.blank?
parts = key.to_s.split(":", 2)
parts.length == 2 ? parts[1].strip : key
end
end
def selectable_values
family_categories = Category::Group.select_options(import.family.categories)
unless key.blank?
family_categories.unshift [ "Add as new category", CREATE_NEW_KEY ]
end
family_categories
end
def requires_selection?
false
end
def values_count
import.rows.where(category: key).count
end
def mappable_class
Category
end
def create_mappable!
return unless creatable?
parts = key.split(":", 2)
if parts.length == 2
parent_name = parts[0].strip
child_name = parts[1].strip
# Ensure the parent category exists before creating the child.
parent = import.family.categories.find_or_create_by!(name: parent_name) do |cat|
cat.color = Category::COLORS.sample
cat.lucide_icon = Category.suggested_icon(parent_name)
end
self.mappable = import.family.categories.find_or_create_by!(name: child_name) do |cat|
cat.parent = parent
cat.color = parent.color
cat.lucide_icon = Category.suggested_icon(child_name)
end
else
self.mappable = import.family.categories.find_or_create_by!(name: key) do |cat|
cat.color = Category::COLORS.sample
cat.lucide_icon = Category.suggested_icon(key)
end
end
save!
end
end