mirror of
https://github.com/we-promise/sure.git
synced 2026-04-18 11:34:13 +00:00
* Add Recent Runs visibility for rule executions Adds a comprehensive tracking system for rule execution history with the following features: - Creates RuleRun model to track execution metadata: * Date/time of execution * Execution type (manual/scheduled) * Success/failure status * Rule reference * Transaction counts (processed and modified) * Error messages for failed runs - Updates RuleJob to automatically record execution results: * Captures transaction processing statistics * Handles success/failure states * Stores error details for debugging - Adds "Recent Runs" section to rules index page: * Paginated display (20 runs per page) * Columnar layout similar to LLM usage page * Visual status indicators (success/failed badges) * Error tooltips for failed runs * Responsive design with design system tokens - Includes i18n translations for all user-facing strings This provides users with visibility into rule execution history, making it easier to debug issues and monitor rule performance. * Update schema.rb with rule_runs table definition * Linter noise * Separate transaction counts into Queued, Processed, and Modified Previously, the code eagerly reported transactions as "processed" when they were only queued for processing. This commit separates the counts into three distinct metrics: - Transactions Queued: Count of transactions matching the rule's filter conditions before any processing begins - Transactions Processed: Count of transactions that were actually processed and modified by the rule actions - Transactions Modified: Count of transactions that had their values changed (currently same as Processed, but allows for future differentiation) Changes: - Add transactions_queued column to rule_runs table - Update RuleJob to track all three counts separately - Update action executors to return count of modified transactions - Update Rule#apply to aggregate modification counts from actions - Add transactions_queued label to locales - Update Recent Runs view to display new column - Add validation for transactions_queued in RuleRun model The tracking now correctly reports: 1. How many transactions matched the filter (queued) 2. How many were actually modified (processed/modified) 3. Distinguishes between matching and modifying transactions * Add Pending status to track async rule execution progress Introduced a new "pending" status for rule runs to properly track async AI operations. The system now: - Tracks pending async jobs with a counter that decrements as jobs complete - Updates transactions_modified incrementally as each job finishes - Only counts transactions that were actually modified (not just queued) - Displays pending status with yellow badge in the UI - Automatically transitions from pending to success when all jobs complete This provides better visibility into long-running AI categorization and merchant detection operations, showing real-time progress as Sidekiq processes the batches. * Fix migration version to 7.2 as per project standards * Consolidate rule_runs migrations into single migration file Merged three separate migrations (create, add_transactions_queued, add_pending_jobs_count) into a single CreateRuleRuns migration. This provides better clarity and maintains a clean migration history. Changes: - Updated CreateRuleRuns migration to include all columns upfront - Removed redundant add_column migrations - Updated schema version to 2025_11_24_000000 * Linter and test fixes * Space optimization * LLM l10n is better than no l10n * Fix implementation for tags/AI rules * Fix tests * Use batch_size * Consider jobs "unknown" status sometimes * Rabbit suggestion * Rescue block for RuleRun.create! --------- Co-authored-by: Claude <noreply@anthropic.com>
128 lines
4.2 KiB
Ruby
128 lines
4.2 KiB
Ruby
class Family < ApplicationRecord
|
|
include PlaidConnectable, SimplefinConnectable, LunchflowConnectable, EnableBankingConnectable, Syncable, AutoTransferMatchable, Subscribeable
|
|
|
|
DATE_FORMATS = [
|
|
[ "MM-DD-YYYY", "%m-%d-%Y" ],
|
|
[ "DD.MM.YYYY", "%d.%m.%Y" ],
|
|
[ "DD-MM-YYYY", "%d-%m-%Y" ],
|
|
[ "YYYY-MM-DD", "%Y-%m-%d" ],
|
|
[ "DD/MM/YYYY", "%d/%m/%Y" ],
|
|
[ "YYYY/MM/DD", "%Y/%m/%d" ],
|
|
[ "MM/DD/YYYY", "%m/%d/%Y" ],
|
|
[ "D/MM/YYYY", "%e/%m/%Y" ],
|
|
[ "YYYY.MM.DD", "%Y.%m.%d" ],
|
|
[ "YYYYMMDD", "%Y%m%d" ]
|
|
].freeze
|
|
|
|
has_many :users, dependent: :destroy
|
|
has_many :accounts, dependent: :destroy
|
|
has_many :invitations, dependent: :destroy
|
|
|
|
has_many :imports, dependent: :destroy
|
|
has_many :family_exports, dependent: :destroy
|
|
|
|
has_many :entries, through: :accounts
|
|
has_many :transactions, through: :accounts
|
|
has_many :rules, dependent: :destroy
|
|
has_many :trades, through: :accounts
|
|
has_many :holdings, through: :accounts
|
|
|
|
has_many :tags, dependent: :destroy
|
|
has_many :categories, dependent: :destroy
|
|
has_many :merchants, dependent: :destroy, class_name: "FamilyMerchant"
|
|
|
|
has_many :budgets, dependent: :destroy
|
|
has_many :budget_categories, through: :budgets
|
|
|
|
has_many :llm_usages, dependent: :destroy
|
|
has_many :recurring_transactions, dependent: :destroy
|
|
|
|
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }
|
|
validates :date_format, inclusion: { in: DATE_FORMATS.map(&:last) }
|
|
|
|
def assigned_merchants
|
|
merchant_ids = transactions.where.not(merchant_id: nil).pluck(:merchant_id).uniq
|
|
Merchant.where(id: merchant_ids)
|
|
end
|
|
|
|
def auto_categorize_transactions_later(transactions, rule_run_id: nil)
|
|
AutoCategorizeJob.perform_later(self, transaction_ids: transactions.pluck(:id), rule_run_id: rule_run_id)
|
|
end
|
|
|
|
def auto_categorize_transactions(transaction_ids)
|
|
AutoCategorizer.new(self, transaction_ids: transaction_ids).auto_categorize
|
|
end
|
|
|
|
def auto_detect_transaction_merchants_later(transactions, rule_run_id: nil)
|
|
AutoDetectMerchantsJob.perform_later(self, transaction_ids: transactions.pluck(:id), rule_run_id: rule_run_id)
|
|
end
|
|
|
|
def auto_detect_transaction_merchants(transaction_ids)
|
|
AutoMerchantDetector.new(self, transaction_ids: transaction_ids).auto_detect
|
|
end
|
|
|
|
def balance_sheet
|
|
@balance_sheet ||= BalanceSheet.new(self)
|
|
end
|
|
|
|
def income_statement
|
|
@income_statement ||= IncomeStatement.new(self)
|
|
end
|
|
|
|
def eu?
|
|
country != "US" && country != "CA"
|
|
end
|
|
|
|
def requires_securities_data_provider?
|
|
# If family has any trades, they need a provider for historical prices
|
|
trades.any?
|
|
end
|
|
|
|
def requires_exchange_rates_data_provider?
|
|
# If family has any accounts not denominated in the family's currency, they need a provider for historical exchange rates
|
|
return true if accounts.where.not(currency: self.currency).any?
|
|
|
|
# If family has any entries in different currencies, they need a provider for historical exchange rates
|
|
uniq_currencies = entries.pluck(:currency).uniq
|
|
return true if uniq_currencies.count > 1
|
|
return true if uniq_currencies.count > 0 && uniq_currencies.first != self.currency
|
|
|
|
false
|
|
end
|
|
|
|
def missing_data_provider?
|
|
(requires_securities_data_provider? && Security.provider.nil?) ||
|
|
(requires_exchange_rates_data_provider? && ExchangeRate.provider.nil?)
|
|
end
|
|
|
|
def oldest_entry_date
|
|
entries.order(:date).first&.date || Date.current
|
|
end
|
|
|
|
# Used for invalidating family / balance sheet related aggregation queries
|
|
def build_cache_key(key, invalidate_on_data_updates: false)
|
|
# Our data sync process updates this timestamp whenever any family account successfully completes a data update.
|
|
# By including it in the cache key, we can expire caches every time family account data changes.
|
|
data_invalidation_key = invalidate_on_data_updates ? latest_sync_completed_at : nil
|
|
|
|
[
|
|
id,
|
|
key,
|
|
data_invalidation_key,
|
|
accounts.maximum(:updated_at)
|
|
].compact.join("_")
|
|
end
|
|
|
|
# Used for invalidating entry related aggregation queries
|
|
def entries_cache_version
|
|
@entries_cache_version ||= begin
|
|
ts = entries.maximum(:updated_at)
|
|
ts.present? ? ts.to_i : 0
|
|
end
|
|
end
|
|
|
|
def self_hoster?
|
|
Rails.application.config.app_mode.self_hosted?
|
|
end
|
|
end
|