mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
* Add full import/export support for rules with versioned JSON schema This commit implements comprehensive import/export functionality for rules, allowing users to back up and restore their rule definitions. Key features: - Export rules to both CSV and NDJSON formats with versioned schema (v1) - Import rules from CSV with full support for nested conditions and actions - UUID to name mapping for categories and merchants for portability - Support for compound conditions with sub-conditions - Comprehensive test coverage for export and import functionality - UI integration for rules import in the imports interface Technical details: - Extended Family::DataExporter to generate rules.csv and include rules in all.ndjson - Created RuleImport model following the existing Import STI pattern - Added migration for rule-specific columns in import_rows table - Implemented serialization helpers to map UUIDs to human-readable names - Added i18n support for the new import option - Included versioning in NDJSON export to support future schema evolution The implementation ensures rules can be safely exported from one family and imported into another, even when category/merchant IDs differ, by mapping between names and IDs during export/import. * Fix AR migration version * Mention support for rules export * Rabbit suggestion * Fix tests * Missed schema.rb * Fix sample CSV download for rule import * Fix parsing in Rules import * Fix tests * Rule import message i18n * Export tag names, not UUIDs * Make sure tags are created if needed at import * Avoid test errors when running in parallel --------- Co-authored-by: Claude <noreply@anthropic.com>
75 lines
2.4 KiB
Ruby
75 lines
2.4 KiB
Ruby
module ImportsHelper
|
|
def mapping_label(mapping_class)
|
|
{
|
|
"Import::AccountTypeMapping" => "Account Type",
|
|
"Import::AccountMapping" => "Account",
|
|
"Import::CategoryMapping" => "Category",
|
|
"Import::TagMapping" => "Tag"
|
|
}.fetch(mapping_class.name)
|
|
end
|
|
|
|
def import_col_label(key)
|
|
{
|
|
date: "Date",
|
|
amount: "Amount",
|
|
name: "Name",
|
|
currency: "Currency",
|
|
category: "Category",
|
|
tags: "Tags",
|
|
account: "Account",
|
|
notes: "Notes",
|
|
qty: "Quantity",
|
|
ticker: "Ticker",
|
|
exchange: "Exchange",
|
|
price: "Price",
|
|
entity_type: "Type",
|
|
category_parent: "Parent category",
|
|
category_color: "Color",
|
|
category_classification: "Classification",
|
|
category_icon: "Lucide icon"
|
|
}[key]
|
|
end
|
|
|
|
def dry_run_resource(key)
|
|
map = {
|
|
transactions: DryRunResource.new(label: "Transactions", icon: "credit-card", text_class: "text-cyan-500", bg_class: "bg-cyan-500/5"),
|
|
accounts: DryRunResource.new(label: "Accounts", icon: "layers", text_class: "text-orange-500", bg_class: "bg-orange-500/5"),
|
|
categories: DryRunResource.new(label: "Categories", icon: "shapes", text_class: "text-blue-500", bg_class: "bg-blue-500/5"),
|
|
tags: DryRunResource.new(label: "Tags", icon: "tags", text_class: "text-violet-500", bg_class: "bg-violet-500/5"),
|
|
rules: DryRunResource.new(label: "Rules", icon: "workflow", text_class: "text-green-500", bg_class: "bg-green-500/5")
|
|
}
|
|
|
|
map[key]
|
|
end
|
|
|
|
def permitted_import_configuration_path(import)
|
|
if permitted_import_types.include?(import.type.underscore)
|
|
"import/configurations/#{import.type.underscore}"
|
|
else
|
|
raise "Unknown import type: #{import.type}"
|
|
end
|
|
end
|
|
|
|
def cell_class(row, field)
|
|
base = "bg-container text-sm focus:ring-gray-900 theme-dark:focus:ring-gray-100 focus:border-solid w-full max-w-full disabled:text-subdued"
|
|
|
|
row.valid? # populate errors
|
|
|
|
border = row.errors.key?(field) ? "border-destructive" : "border-transparent"
|
|
|
|
[ base, border ].join(" ")
|
|
end
|
|
|
|
def cell_is_valid?(row, field)
|
|
row.valid? # populate errors
|
|
!row.errors.key?(field)
|
|
end
|
|
|
|
private
|
|
def permitted_import_types
|
|
%w[transaction_import trade_import account_import mint_import category_import rule_import]
|
|
end
|
|
|
|
DryRunResource = Struct.new(:label, :icon, :text_class, :bg_class, keyword_init: true)
|
|
end
|