mirror of
https://github.com/we-promise/sure.git
synced 2026-06-01 00:39:01 +00:00
* fix(merchants): preserve manual merchant edits across provider sync Fixes #1977. Merging merchants, converting a synced (provider) merchant to a family merchant, and unlinking a merchant all reassign transactions.merchant_id via update_all without flagging the entries as user_modified. The next provider sync sees the entries as unmodified and reverts the change. Add Entry.mark_user_modified_for_transactions! and call it (before the merchant_id update, so the scope still matches) in Merchant::Merger#merge!, ProviderMerchant#convert_to_family_merchant_for, and #unlink_from_family. The sync skip-guard already honours user_modified, so flagged entries are left untouched on subsequent syncs. * fix(merchants): pass transaction relation to bulk user_modified helper Addresses PR #1981 review (CodeRabbit): mark_user_modified_for_transactions! now accepts an ActiveRecord::Relation and selects ids via subquery, so large merges/unlinks don't materialize ids or hit SQL parameter limits. Array of ids still supported. Callers pass the scope relation directly.
68 lines
2.8 KiB
Ruby
68 lines
2.8 KiB
Ruby
class ProviderMerchant < Merchant
|
|
enum :source, { plaid: "plaid", simplefin: "simplefin", lunchflow: "lunchflow", synth: "synth", ai: "ai", enable_banking: "enable_banking", coinstats: "coinstats", mercury: "mercury", brex: "brex", indexa_capital: "indexa_capital", sophtron: "sophtron" }
|
|
|
|
validates :name, uniqueness: { scope: [ :source ] }
|
|
validates :source, presence: true
|
|
|
|
# Convert this ProviderMerchant to a FamilyMerchant for a specific family.
|
|
# Only affects transactions belonging to that family.
|
|
# Returns the newly created FamilyMerchant.
|
|
def convert_to_family_merchant_for(family, attributes = {})
|
|
transaction do
|
|
family_merchant = family.merchants.create!(
|
|
name: attributes[:name].presence || name,
|
|
color: attributes[:color].presence || FamilyMerchant::COLORS.sample,
|
|
website_url: attributes[:website_url].presence || website_url
|
|
)
|
|
|
|
scope = family.transactions.where(merchant_id: id)
|
|
|
|
# Protect the manual reassignment from being reverted on the next
|
|
# provider sync (issue #1977). Must run before the merchant_id update.
|
|
Entry.mark_user_modified_for_transactions!(scope)
|
|
|
|
# Update only this family's transactions to point to new merchant
|
|
scope.update_all(merchant_id: family_merchant.id)
|
|
|
|
family_merchant
|
|
end
|
|
end
|
|
|
|
# Generate logo URL from website_url using BrandFetch, if configured.
|
|
def generate_logo_url_from_website!
|
|
if website_url.present? && Setting.brand_fetch_client_id.present?
|
|
domain = extract_domain(website_url)
|
|
size = Setting.brand_fetch_logo_size
|
|
update!(logo_url: "https://cdn.brandfetch.io/#{domain}/icon/fallback/lettermark/w/#{size}/h/#{size}?c=#{Setting.brand_fetch_client_id}")
|
|
elsif website_url.blank?
|
|
update!(logo_url: nil)
|
|
end
|
|
end
|
|
|
|
# Unlink from family's transactions (set merchant_id to null).
|
|
# Does NOT delete the ProviderMerchant since it may be used by other families.
|
|
# Tracks the unlink in FamilyMerchantAssociation so it shows as "recently unlinked".
|
|
def unlink_from_family(family)
|
|
scope = family.transactions.where(merchant_id: id)
|
|
|
|
# Protect the manual unlink from being reverted on the next provider sync
|
|
# (issue #1977). Must run before the merchant_id is nulled.
|
|
Entry.mark_user_modified_for_transactions!(scope)
|
|
|
|
scope.update_all(merchant_id: nil)
|
|
|
|
# Track that this merchant was unlinked from this family
|
|
association = FamilyMerchantAssociation.find_or_initialize_by(family: family, merchant: self)
|
|
association.update!(unlinked_at: Time.current)
|
|
end
|
|
|
|
private
|
|
|
|
def extract_domain(url)
|
|
normalized_url = url.start_with?("http://", "https://") ? url : "https://#{url}"
|
|
URI.parse(normalized_url).host&.sub(/\Awww\./, "")
|
|
rescue URI::InvalidURIError
|
|
url.sub(/\Awww\./, "")
|
|
end
|
|
end
|