mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-22 22:59:39 +02:00
Plaid sync domain improvements (#2267)
Breaks our Plaid sync process out into more manageable classes. Notably, this moves the sync process to a distinct, 2-step flow: 1. Import stage - we first make API calls and import Plaid data to "mirror" tables 2. Processing stage - read the raw data, apply business rules, build internal domain models and sync balances This provides several benefits: - Plaid syncs can now be "replayed" without fetching API data again - Mirror tables provide better audit and debugging capabilities - Eliminates the "all or nothing" sync behavior that is currently in place, which is brittle
This commit is contained in:
parent
5c82af0e8c
commit
03a146222d
72 changed files with 3763 additions and 706 deletions
95
app/models/plaid_entry/processor.rb
Normal file
95
app/models/plaid_entry/processor.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
class PlaidEntry::Processor
|
||||
# plaid_transaction is the raw hash fetched from Plaid API and converted to JSONB
|
||||
def initialize(plaid_transaction, plaid_account:, category_matcher:)
|
||||
@plaid_transaction = plaid_transaction
|
||||
@plaid_account = plaid_account
|
||||
@category_matcher = category_matcher
|
||||
end
|
||||
|
||||
def process
|
||||
PlaidAccount.transaction do
|
||||
entry = account.entries.find_or_initialize_by(plaid_id: plaid_id) do |e|
|
||||
e.entryable = Transaction.new
|
||||
end
|
||||
|
||||
entry.assign_attributes(
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
date: date
|
||||
)
|
||||
|
||||
entry.enrich_attribute(
|
||||
:name,
|
||||
name,
|
||||
source: "plaid"
|
||||
)
|
||||
|
||||
if detailed_category
|
||||
matched_category = category_matcher.match(detailed_category)
|
||||
|
||||
if matched_category
|
||||
entry.transaction.enrich_attribute(
|
||||
:category_id,
|
||||
matched_category.id,
|
||||
source: "plaid"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if merchant
|
||||
entry.transaction.enrich_attribute(
|
||||
:merchant_id,
|
||||
merchant.id,
|
||||
source: "plaid"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :plaid_transaction, :plaid_account, :category_matcher
|
||||
|
||||
def account
|
||||
plaid_account.account
|
||||
end
|
||||
|
||||
def plaid_id
|
||||
plaid_transaction["transaction_id"]
|
||||
end
|
||||
|
||||
def name
|
||||
plaid_transaction["merchant_name"] || plaid_transaction["original_description"]
|
||||
end
|
||||
|
||||
def amount
|
||||
plaid_transaction["amount"]
|
||||
end
|
||||
|
||||
def currency
|
||||
plaid_transaction["iso_currency_code"]
|
||||
end
|
||||
|
||||
def date
|
||||
plaid_transaction["date"]
|
||||
end
|
||||
|
||||
def detailed_category
|
||||
plaid_transaction.dig("personal_finance_category", "detailed")
|
||||
end
|
||||
|
||||
def merchant
|
||||
merchant_id = plaid_transaction["merchant_entity_id"]
|
||||
merchant_name = plaid_transaction["merchant_name"]
|
||||
|
||||
return nil unless merchant_id.present? && merchant_name.present?
|
||||
|
||||
ProviderMerchant.find_or_create_by!(
|
||||
source: "plaid",
|
||||
name: merchant_name,
|
||||
) do |m|
|
||||
m.provider_merchant_id = merchant_id
|
||||
m.website_url = plaid_transaction["website"]
|
||||
m.logo_url = plaid_transaction["logo_url"]
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue