mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-08 06:55:21 +02:00
fixes: Merge request fixes
- Changing to the new syncable structure - Fixing missing account error display - Shuffling some code around
This commit is contained in:
parent
3f11e357c3
commit
7e05681144
8 changed files with 122 additions and 62 deletions
|
@ -70,7 +70,7 @@ class Account < ApplicationRecord
|
|||
# Since Plaid Items sync as a "group", if the item is syncing, even if the account
|
||||
# sync hasn't yet started (i.e. we're still fetching the Plaid data), show it as syncing in UI.
|
||||
if linked?
|
||||
plaid_account&.plaid_item&.syncing? || self_syncing
|
||||
plaid_account&.plaid_item&.syncing? || simple_fin_account&.simple_fin_item&.syncing? || self_syncing
|
||||
else
|
||||
self_syncing
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Family < ApplicationRecord
|
||||
include PlaidConnectable, Syncable, AutoTransferMatchable, Subscribeable
|
||||
include PlaidConnectable, SimpleFinConnectable, Syncable, AutoTransferMatchable, Subscribeable
|
||||
|
||||
DATE_FORMATS = [
|
||||
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
||||
|
@ -38,6 +38,7 @@ class Family < ApplicationRecord
|
|||
# If any accounts or plaid items are syncing, the family is also syncing, even if a formal "Family Sync" is not running.
|
||||
def syncing?
|
||||
Sync.joins("LEFT JOIN plaid_items ON plaid_items.id = syncs.syncable_id AND syncs.syncable_type = 'PlaidItem'")
|
||||
.joins("LEFT JOIN simple_fin_items ON simple_fin_items.id = syncs.syncable_id AND syncs.syncable_type = 'SimpleFinItem'")
|
||||
.joins("LEFT JOIN accounts ON accounts.id = syncs.syncable_id AND syncs.syncable_type = 'Account'")
|
||||
.where("syncs.syncable_id = ? OR accounts.family_id = ? OR plaid_items.family_id = ?", id, id, id)
|
||||
.visible
|
||||
|
|
15
app/models/family/simple_fin_connectable.rb
Normal file
15
app/models/family/simple_fin_connectable.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
module Family::SimpleFinConnectable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :simple_fin_items, dependent: :destroy
|
||||
end
|
||||
|
||||
def get_simple_fin_available(accountable_type: nil)
|
||||
provider = Provider::Registry.get_provider(:simple_fin)
|
||||
|
||||
provider.is_available(id, accountable_type)
|
||||
end
|
||||
|
||||
private
|
||||
end
|
|
@ -16,45 +16,6 @@ class SimpleFinItem < ApplicationRecord
|
|||
scope :ordered, -> { order(created_at: :desc) }
|
||||
scope :needs_update, -> { where(status: :requires_update) }
|
||||
|
||||
##
|
||||
# Syncs the simple_fin_item given and all other simple_fin accounts available (reduces calls to the API)
|
||||
def sync_data(sync, start_date: nil)
|
||||
Rails.logger.info("SimpleFINItem: Starting sync for all SimpleFIN accounts")
|
||||
|
||||
begin
|
||||
# Fetch all accounts for this specific connection from SimpleFIN.
|
||||
sf_accounts_data = provider.get_available_accounts(nil)
|
||||
# Iterate over every account and attempt to apply transactions where possible
|
||||
sf_accounts_data.each do |sf_account_data|
|
||||
begin
|
||||
# Find or create the SimpleFinAccount record.
|
||||
sfa = SimpleFinAccount.find_by(external_id: sf_account_data["id"])
|
||||
rescue StandardError
|
||||
# Ignore because it could be non existent accounts from the central sync
|
||||
end
|
||||
|
||||
if sfa != nil
|
||||
begin
|
||||
# Sync the detailed data for this account
|
||||
sfa.sync_account_data!(sf_account_data)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("SimpleFINItem: Sync failed for account #{sf_account_data["id"]}: #{e.message}")
|
||||
sfa.simple_fin_item.update(id: sf_account_data["id"], status: :requires_update) # We had problems so make sure this account knows
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Rails.logger.info("SimpleFINItem: Sync completed for all accounts")
|
||||
|
||||
rescue Provider::SimpleFin::RateLimitExceededError =>e
|
||||
Rails.logger.error("SimpleFINItem: Sync failed: #{e.message}")
|
||||
raise StandardError, "SimpleFIN Rate Limit: #{e.message}" # Re-raise as a generic StandardError
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("SimpleFINItem: Sync failed: #{e.message}")
|
||||
raise e # Re-raise so Sync#perform can record the failure.
|
||||
end
|
||||
end
|
||||
|
||||
def provider
|
||||
@provider ||= Provider::Registry.get_provider(:simple_fin)
|
||||
end
|
||||
|
@ -63,4 +24,42 @@ class SimpleFinItem < ApplicationRecord
|
|||
update!(scheduled_for_deletion: true)
|
||||
DestroyJob.perform_later(self)
|
||||
end
|
||||
|
||||
def syncing?
|
||||
Sync.joins("LEFT JOIN accounts a ON a.id = syncs.syncable_id AND syncs.syncable_type = 'Account'")
|
||||
.joins("LEFT JOIN simple_fin_accounts sfa ON sfa.id = a.simple_fin_account_id")
|
||||
.where("syncs.syncable_id = ? OR sfa.simple_fin_item_id = ?", id, id)
|
||||
.visible
|
||||
.exists?
|
||||
end
|
||||
|
||||
def auto_match_categories!
|
||||
if family.categories.none?
|
||||
family.categories.bootstrap!
|
||||
end
|
||||
|
||||
alias_matcher = build_category_alias_matcher(family.categories)
|
||||
|
||||
accounts.each do |account|
|
||||
matchable_transactions = account.transactions
|
||||
.where(category_id: nil)
|
||||
.where.not(simple_fin_category: nil)
|
||||
.enrichable(:category_id)
|
||||
|
||||
matchable_transactions.each do |transaction|
|
||||
category = alias_matcher.match(transaction.simple_fin_category)
|
||||
|
||||
if category.present?
|
||||
SimpleFinItem.transaction do
|
||||
transaction.log_enrichment!(
|
||||
attribute_name: "category_id",
|
||||
attribute_value: category.id,
|
||||
source: "simple_fin"
|
||||
)
|
||||
transaction.set_category!(category)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
51
app/models/simple_fin_item/syncer.rb
Normal file
51
app/models/simple_fin_item/syncer.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
class SimpleFinItem::Syncer
|
||||
attr_reader :simple_fin_item
|
||||
|
||||
def initialize(simple_fin_item)
|
||||
@simple_fin_item = simple_fin_item
|
||||
@provider ||= Provider::Registry.get_provider(:simple_fin)
|
||||
end
|
||||
|
||||
def perform_sync(sync)
|
||||
Rails.logger.info("Starting sync for all SimpleFIN accounts")
|
||||
|
||||
begin
|
||||
# Fetch all accounts for this specific connection from SimpleFIN.
|
||||
sf_accounts_data = @provider.get_available_accounts(nil)
|
||||
# Iterate over every account and attempt to apply transactions where possible
|
||||
sf_accounts_data.each do |sf_account_data|
|
||||
begin
|
||||
# Find or create the SimpleFinAccount record.
|
||||
sfa = SimpleFinAccount.find_by(external_id: sf_account_data["id"])
|
||||
rescue StandardError
|
||||
# Ignore because it could be non existent accounts from the central sync
|
||||
end
|
||||
|
||||
if sfa != nil
|
||||
begin
|
||||
# Sync the detailed data for this account
|
||||
sfa.sync_account_data!(sf_account_data)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Sync failed for account #{sf_account_data["id"]}: #{e.message}")
|
||||
sfa.simple_fin_item.update(id: sf_account_data["id"], status: :requires_update) # We had problems so make sure this account knows
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Rails.logger.info("Sync completed for all accounts")
|
||||
|
||||
rescue Provider::SimpleFin::RateLimitExceededError =>e
|
||||
Rails.logger.error("Sync failed: #{e.message}")
|
||||
raise StandardError, "SimpleFIN Rate Limit: #{e.message}" # Re-raise as a generic StandardError
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("Sync failed: #{e.message}")
|
||||
raise e # Re-raise so Sync#perform can record the failure.
|
||||
end
|
||||
end
|
||||
|
||||
def perform_post_sync
|
||||
simple_fin_item.auto_match_categories!
|
||||
end
|
||||
|
||||
private
|
||||
end
|
|
@ -2,6 +2,17 @@
|
|||
|
||||
<%# Flag indicators of account issues %>
|
||||
<% if account.simple_fin_account&.simple_fin_item&.status == "requires_update" %>
|
||||
<% if link_to_path == nil %>
|
||||
<%= icon(
|
||||
"alert-triangle",
|
||||
id: "account-issue-icon",
|
||||
as_button: true,
|
||||
size: "sm",
|
||||
rel: "noopener noreferrer",
|
||||
disabled: account.syncing?,
|
||||
title: given_title
|
||||
) %>
|
||||
<% elsif %>
|
||||
<%= link_to link_to_path, target: target do %>
|
||||
<%= icon(
|
||||
"alert-triangle",
|
||||
|
@ -13,6 +24,7 @@
|
|||
title: given_title
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Force some styling that keeps getting overwritten %>
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<%= tag.p account.name, class: "text-sm text-primary font-medium mb-0.5 truncate" %>
|
||||
<%= tag.p account.short_subtype_label, class: "text-sm text-secondary truncate" %>
|
||||
</div>
|
||||
|
||||
<%# Render account error warnings %>
|
||||
<%= render "accounts/account_error", account: account, given_title: "Account has an error" %>
|
||||
|
||||
<% if account.syncing? %>
|
||||
<div class="ml-auto text-right grow h-10">
|
||||
|
|
21
db/schema.rb
generated
21
db/schema.rb
generated
|
@ -592,27 +592,6 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_17_134646) do
|
|||
t.index ["date"], name: "index_sfrl_on_date", unique: true
|
||||
end
|
||||
|
||||
create_table "stock_exchanges", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "acronym"
|
||||
t.string "mic", null: false
|
||||
t.string "country", null: false
|
||||
t.string "country_code", null: false
|
||||
t.string "city"
|
||||
t.string "website"
|
||||
t.string "timezone_name"
|
||||
t.string "timezone_abbr"
|
||||
t.string "timezone_abbr_dst"
|
||||
t.string "currency_code"
|
||||
t.string "currency_symbol"
|
||||
t.string "currency_name"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["country"], name: "index_stock_exchanges_on_country"
|
||||
t.index ["country_code"], name: "index_stock_exchanges_on_country_code"
|
||||
t.index ["currency_code"], name: "index_stock_exchanges_on_currency_code"
|
||||
end
|
||||
|
||||
create_table "subscriptions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "family_id", null: false
|
||||
t.string "status", null: false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue