Skip to content

Commit 0326fd3

Browse files
author
Josh Waldrep
committed
move relink helpers to concern; native rake args; extract date parser; docs nits
1 parent 3512199 commit 0326fd3

File tree

1 file changed

+1
-102
lines changed

1 file changed

+1
-102
lines changed

app/controllers/simplefin_items_controller.rb

Lines changed: 1 addition & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
require "set"
22
class SimplefinItemsController < ApplicationController
33
include SimplefinItems::RelinkHelpers
4-
before_action :set_simplefin_item, only: [ :show, :edit, :update, :destroy, :sync, :balances, :setup_accounts, :complete_account_setup, :errors, :relink, :apply_relink ]
4+
before_action :set_simplefin_item, only: [ :show, :edit, :update, :destroy, :sync, :balances, :setup_accounts, :complete_account_setup, :errors, :relink, :manual_relink, :apply_relink ]
55

66
def index
77
@simplefin_items = Current.family.simplefin_items.active.ordered
@@ -355,107 +355,6 @@ def apply_relink
355355

356356

357357

358-
def normalize_name(str)
359-
s = str.to_s.downcase.strip
360-
return s if s.empty?
361-
s.gsub(NAME_NORM_RE, " ")
362-
end
363-
364-
def compute_relink_candidates
365-
# Best-effort dedup before building candidates
366-
@simplefin_item.dedup_simplefin_accounts! rescue nil
367-
368-
family = @simplefin_item.family
369-
manuals = family.accounts.left_joins(:account_providers).where(account_providers: { id: nil }).to_a
370-
371-
# Evaluate only one SimpleFin account per upstream account_id (prefer linked, else newest)
372-
grouped = @simplefin_item.simplefin_accounts.group_by(&:account_id)
373-
sfas = grouped.values.map { |list| list.find { |s| s.current_account.present? } || list.max_by(&:updated_at) }
374-
375-
Rails.logger.info("SimpleFin compute_relink_candidates: manuals=#{manuals.size} sfas=#{sfas.size} (item_id=#{@simplefin_item.id})")
376-
377-
used_manual_ids = Set.new
378-
pairs = []
379-
380-
sfas.each do |sfa|
381-
next if sfa.name.blank?
382-
# Heuristics (with ambiguity guards): last4 > balance ±0.01 > name
383-
raw = (sfa.raw_payload || {}).with_indifferent_access
384-
sfa_last4 = raw[:mask] || raw[:last4] || raw[:"last-4"] || raw[:"account_number_last4"]
385-
sfa_last4 = sfa_last4.to_s.strip.presence
386-
sfa_balance = (sfa.current_balance || sfa.available_balance).to_d rescue 0.to_d
387-
388-
chosen = nil
389-
reason = nil
390-
391-
# 1) last4 match: compute all candidates not yet used
392-
if sfa_last4.present?
393-
last4_matches = manuals.reject { |a| used_manual_ids.include?(a.id) }.select do |a|
394-
a_last4 = nil
395-
%i[mask last4 number_last4 account_number_last4].each do |k|
396-
if a.respond_to?(k)
397-
val = a.public_send(k)
398-
a_last4 = val.to_s.strip.presence if val.present?
399-
break if a_last4
400-
end
401-
end
402-
a_last4.present? && a_last4 == sfa_last4
403-
end
404-
# Ambiguity guard: skip if multiple matches
405-
if last4_matches.size == 1
406-
cand = last4_matches.first
407-
# Conflict guard: if both have balances and differ wildly, skip
408-
begin
409-
ab = (cand.balance || cand.cash_balance || 0).to_d
410-
if sfa_balance.nonzero? && ab.nonzero? && (ab - sfa_balance).abs > BigDecimal("1.00")
411-
cand = nil
412-
end
413-
rescue
414-
# ignore balance parsing errors
415-
end
416-
if cand
417-
chosen = cand
418-
reason = "last4"
419-
end
420-
end
421-
end
422-
423-
# 2) balance proximity
424-
if chosen.nil? && sfa_balance.nonzero?
425-
balance_matches = manuals.reject { |a| used_manual_ids.include?(a.id) }.select do |a|
426-
begin
427-
ab = (a.balance || a.cash_balance || 0).to_d
428-
(ab - sfa_balance).abs <= BigDecimal("0.01")
429-
rescue
430-
false
431-
end
432-
end
433-
if balance_matches.size == 1
434-
chosen = balance_matches.first
435-
reason = "balance"
436-
end
437-
end
438-
439-
# 3) exact normalized name
440-
if chosen.nil?
441-
name_matches = manuals.reject { |a| used_manual_ids.include?(a.id) }.select { |a| normalize_name(a.name) == normalize_name(sfa.name) }
442-
if name_matches.size == 1
443-
chosen = name_matches.first
444-
reason = "name"
445-
end
446-
end
447-
448-
if chosen
449-
used_manual_ids << chosen.id
450-
pairs << { sfa_id: sfa.id, sfa_name: sfa.name, manual_id: chosen.id, manual_name: chosen.name, reason: reason }
451-
end
452-
end
453-
454-
Rails.logger.info("SimpleFin compute_relink_candidates: built #{pairs.size} pairs (item_id=#{@simplefin_item.id})")
455-
456-
# Return without the reason field to the view
457-
pairs.map { |p| p.slice(:sfa_id, :sfa_name, :manual_id, :manual_name) }
458-
end
459358

460359
def set_simplefin_item
461360
if defined?(Current) && Current.respond_to?(:family) && Current.family.present?

0 commit comments

Comments
 (0)