Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion app/models/family/data_exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ def serialize_condition(condition)
operator: condition.operator,
value: resolve_condition_value(condition)
}
value_ref = resolve_condition_value_ref(condition)
data[:value_ref] = value_ref if value_ref.present?

if condition.compound? && condition.sub_conditions.any?
data[:sub_conditions] = condition.sub_conditions.map { |sub| serialize_condition(sub) }
Expand All @@ -440,10 +442,14 @@ def serialize_condition(condition)
end

def serialize_action(action)
{
data = {
action_type: action.action_type,
value: resolve_action_value(action)
}
value_ref = resolve_action_value_ref(action)
data[:value_ref] = value_ref if value_ref.present?

data
end

def resolve_condition_value(condition)
Expand All @@ -464,6 +470,20 @@ def resolve_condition_value(condition)
condition.value
end

def resolve_condition_value_ref(condition)
return unless condition.value.present?

if condition.condition_type == "transaction_category"
category = @family.categories.find_by(id: condition.value)
return rule_value_ref("Category", category) if category
end

if condition.condition_type == "transaction_merchant"
merchant = @family.merchants.find_by(id: condition.value)
rule_value_ref("Merchant", merchant) if merchant
end
end

def resolve_action_value(action)
return action.value unless action.value.present?

Expand All @@ -488,6 +508,33 @@ def resolve_action_value(action)
action.value
end

def resolve_action_value_ref(action)
return unless action.value.present?

if action.action_type == "set_transaction_category"
category = @family.categories.find_by(id: action.value) || @family.categories.find_by(name: action.value)
return rule_value_ref("Category", category) if category
end

if action.action_type == "set_transaction_merchant"
merchant = @family.merchants.find_by(id: action.value) || @family.merchants.find_by(name: action.value)
return rule_value_ref("Merchant", merchant) if merchant
end

if action.action_type == "set_transaction_tags"
tag = @family.tags.find_by(id: action.value) || @family.tags.find_by(name: action.value)
rule_value_ref("Tag", tag) if tag
end
end

def rule_value_ref(type, record)
{
type: type,
id: record.id,
name: record.name
}
end

def serialize_conditions_for_csv(conditions)
conditions.where(parent_id: nil).map { |c| serialize_condition(c) }.to_json
end
Expand Down
8 changes: 6 additions & 2 deletions app/models/family/data_importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ def build_rule_action(rule, action_data)

def resolve_rule_condition_value(condition_data)
condition_type = condition_data["condition_type"]
value = condition_data["value"]
value = rule_operand_value(condition_data)

return value unless value.present?

Expand Down Expand Up @@ -655,7 +655,7 @@ def resolve_rule_condition_value(condition_data)

def resolve_rule_action_value(action_data)
action_type = action_data["action_type"]
value = action_data["value"]
value = rule_operand_value(action_data)

return value unless value.present?

Expand Down Expand Up @@ -688,6 +688,10 @@ def resolve_rule_action_value(action_data)
value
end

def rule_operand_value(data)
data["value"].presence || data.dig("value_ref", "name")
end
Comment thread
coderabbitai[bot] marked this conversation as resolved.

def importable_cost_basis_source(value)
source = value.to_s
Holding::COST_BASIS_SOURCES.include?(source) ? source : nil
Expand Down
30 changes: 30 additions & 0 deletions test/models/family/data_exporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,35 @@ class Family::DataExporterTest < ActiveSupport::TestCase
assert_equal "set_transaction_category", actions[0]["action_type"]
# Should export category name instead of UUID
assert_equal "Test Category", actions[0]["value"]
assert_equal({ "type" => "Category", "id" => @category.id, "name" => "Test Category" }, actions[0]["value_ref"])
end
end

test "exports rule condition value refs for mapped operands" do
category_rule = @family.rules.build(
name: "Category Condition Rule",
resource_type: "transaction",
active: true
)
category_rule.conditions.build(
condition_type: "transaction_category",
operator: "=",
value: @category.id
)
category_rule.actions.build(action_type: "auto_categorize")
category_rule.save!

zip_data = @exporter.generate_export

Zip::File.open_buffer(zip_data) do |zip|
rule_data = zip.read("all.ndjson").split("\n").filter_map do |line|
parsed = JSON.parse(line)
parsed if parsed["type"] == "Rule" && parsed["data"]["name"] == "Category Condition Rule"
end.first

condition = rule_data["data"]["conditions"].first
assert_equal "Test Category", condition["value"]
assert_equal({ "type" => "Category", "id" => @category.id, "name" => "Test Category" }, condition["value_ref"])
end
end

Expand Down Expand Up @@ -256,6 +285,7 @@ class Family::DataExporterTest < ActiveSupport::TestCase
assert_equal "set_transaction_tags", actions[0]["action_type"]
# Should export tag name instead of UUID
assert_equal "Test Tag", actions[0]["value"]
assert_equal({ "type" => "Tag", "id" => @tag.id, "name" => "Test Tag" }, actions[0]["value_ref"])
end
end

Expand Down
45 changes: 45 additions & 0 deletions test/models/family/data_importer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,51 @@ class Family::DataImporterTest < ActiveSupport::TestCase
assert_equal category.id, action.value
end

test "imports rules from normalized operand value refs" do
ndjson = build_ndjson([
{
type: "Rule",
version: 1,
data: {
name: "Map Merchant To Dining",
resource_type: "transaction",
active: true,
conditions: [
{
condition_type: "transaction_merchant",
operator: "=",
value_ref: {
type: "Merchant",
id: "source-merchant-id",
name: "Coffee Bar"
}
}
],
actions: [
{
action_type: "set_transaction_category",
value_ref: {
type: "Category",
id: "source-category-id",
name: "Dining"
}
}
]
}
}
])

importer = Family::DataImporter.new(@family, ndjson)
importer.import!

rule = @family.rules.find_by!(name: "Map Merchant To Dining")
merchant = @family.merchants.find_by!(name: "Coffee Bar")
category = @family.categories.find_by!(name: "Dining")

assert_equal merchant.id, rule.conditions.first.value
assert_equal category.id, rule.actions.first.value
end

test "imports rules with compound conditions" do
ndjson = build_ndjson([
{
Expand Down
Loading