mirror of
https://github.com/we-promise/sure.git
synced 2026-04-20 12:34:12 +00:00
Feat: Add QIF (Quicken Interchange Format) import functionality (#1074)
* Feat: Add QIF (Quicken Interchange Format) import functionality - Add the ability to import QIF files for users coming from Quicken - Includes categories and tags - Comprehensive tests for QifImport, including parsing, row generation, and import functionality. - Ensure handling of hierarchical categories (ex "Home:Home Improvement" is imported as Parent:Child) * Fix QIF import issues raised in code review - Fix two-digit year windowing in QIF date parser (e.g. '99 → 1999, not 2099) - Fix ArgumentError from invalid `undef: :raise` encoding option - Nil-safe `leaf_category_name` with blank guard and `.to_s` coercion - Memoize `qif_account_type` to avoid re-parsing the full QIF file - Add strong parameters (`selection_params`) to QifCategorySelectionsController - Wrap all mutations in DB transactions in uploads and category-selections controllers - Skip unchanged tag rows (only write rows where tags actually differ) - Replace hardcoded strings with i18n keys across QIF views and nav - Fix potentially colliding checkbox/label IDs in category selection view - Improve keyboard accessibility: use semantic `<label>` for file picker area Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix QIF import test count and Brakeman mass assignment warning - Update ImportsControllerTest to expect 4 disabled import options (was 3), accounting for the new QIF import type added in this branch - Remove :account_id from upload_params permit list; it was never accessed through strong params (always via params.dig with Current.family scope), so this resolves the Brakeman high-confidence mass assignment warning Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix: QIF import security, safety, and i18n issues raised in code review - Added french, spanish and german translations for newly added i18n keys - Replace params.dig(:import, :account_id) with a proper strong-params accessor (import_account_id) in UploadsController to satisfy Rails parameter filtering requirements - Guard ImportsController#show against QIF imports reaching the publish screen before a file has been uploaded, preventing an unrescued error on publish - Gate the QIF "Clean" nav step link on import.uploaded? to prevent routing to CleansController with an unconfigured import (which would raise "Unknown import type: QifImport" via ImportsHelper) - Replace hard-coded "txn" pluralize calls in the category/tag selection view with t(".txn_count") and add pluralization keys to the locale file - Localize all hard-coded strings in the QIF upload section of uploads/show.html.erb and add corresponding en.yml keys - Convert the CSV upload drop zone from a clickable <div> (JS-only) to a semantic <label> element, making it keyboard-accessible without JavaScript * Fix: missing translations keys * Add icon mapping and random color assignment to new categories * fix a lint issue * Add a warning about splits and some plumbing for future support. Updated locales. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,26 @@ class Import::CategoryMapping < Import::Mapping
|
||||
class << self
|
||||
def mappables_by_key(import)
|
||||
unique_values = import.rows.map(&:category).uniq
|
||||
categories = import.family.categories.where(name: unique_values).index_by(&:name)
|
||||
|
||||
unique_values.index_with { |value| categories[value] }
|
||||
# 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
|
||||
@@ -33,7 +49,30 @@ class Import::CategoryMapping < Import::Mapping
|
||||
def create_mappable!
|
||||
return unless creatable?
|
||||
|
||||
self.mappable = import.family.categories.find_or_create_by!(name: key)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user