# frozen_string_literal: true class <%= class_name %>Account::Transactions::Processor include <%= class_name %>Account::DataHelpers attr_reader :<%= file_name %>_account def initialize(<%= file_name %>_account) @<%= file_name %>_account = <%= file_name %>_account end def process unless <%= file_name %>_account.raw_transactions_payload.present? Rails.logger.info "<%= class_name %>Account::Transactions::Processor - No transactions in raw_transactions_payload for <%= file_name %>_account #{<%= file_name %>_account.id}" return { success: true, total: 0, imported: 0, failed: 0, errors: [] } end total_count = <%= file_name %>_account.raw_transactions_payload.count Rails.logger.info "<%= class_name %>Account::Transactions::Processor - Processing #{total_count} transactions for <%= file_name %>_account #{<%= file_name %>_account.id}" imported_count = 0 failed_count = 0 errors = [] # Each entry is processed inside a transaction, but to avoid locking up the DB when # there are hundreds or thousands of transactions, we process them individually. <%= file_name %>_account.raw_transactions_payload.each_with_index do |transaction_data, index| begin result = process_transaction(transaction_data) if result.nil? # Transaction was skipped (e.g., no linked account or blank external_id) failed_count += 1 transaction_id = transaction_data.try(:[], :id) || transaction_data.try(:[], "id") || "unknown" errors << { index: index, transaction_id: transaction_id, error: "Skipped" } else imported_count += 1 end rescue ArgumentError => e # Validation error - log and continue failed_count += 1 transaction_id = transaction_data.try(:[], :id) || transaction_data.try(:[], "id") || "unknown" error_message = "Validation error: #{e.message}" Rails.logger.error "<%= class_name %>Account::Transactions::Processor - #{error_message} (transaction #{transaction_id})" errors << { index: index, transaction_id: transaction_id, error: error_message } rescue => e # Unexpected error - log with full context and continue failed_count += 1 transaction_id = transaction_data.try(:[], :id) || transaction_data.try(:[], "id") || "unknown" error_message = "#{e.class}: #{e.message}" Rails.logger.error "<%= class_name %>Account::Transactions::Processor - Error processing transaction #{transaction_id}: #{error_message}" Rails.logger.error e.backtrace.join("\n") errors << { index: index, transaction_id: transaction_id, error: error_message } end end result = { success: failed_count == 0, total: total_count, imported: imported_count, failed: failed_count, errors: errors } if failed_count > 0 Rails.logger.warn "<%= class_name %>Account::Transactions::Processor - Completed with #{failed_count} failures out of #{total_count} transactions" else Rails.logger.info "<%= class_name %>Account::Transactions::Processor - Successfully processed #{imported_count} transactions" end result end private def account @<%= file_name %>_account.current_account end def import_adapter @import_adapter ||= Account::ProviderImportAdapter.new(account) end def process_transaction(transaction_data) return nil unless account.present? data = transaction_data.with_indifferent_access # TODO: Customize based on your provider's transaction format # Extract transaction fields from the provider's API response external_id = (data[:id] || data[:transaction_id]).to_s return nil if external_id.blank? # Parse transaction attributes amount = parse_transaction_amount(data) return nil if amount.nil? # TODO: Customize date field names based on your provider date = parse_date(data[:date] || data[:transaction_date] || data[:posted_at]) return nil if date.nil? name = data[:name] || data[:description] || data[:merchant_name] || "Transaction" currency = extract_currency(data, fallback: account.currency) # Build provider-specific metadata for transaction.extra extra = build_extra_metadata(data) Rails.logger.info "<%= class_name %>Account::Transactions::Processor - Importing transaction: id=#{external_id} amount=#{amount} date=#{date}" # Use ProviderImportAdapter for proper deduplication via external_id + source import_adapter.import_transaction( external_id: external_id, amount: amount, currency: currency, date: date, name: name[0..254], # Limit to 255 chars source: "<%= file_name %>", extra: extra ) end def parse_transaction_amount(data) amount = parse_decimal(data[:amount]) return nil if amount.nil? # TODO: Adjust sign convention based on your provider # Most banking APIs use positive amounts for debits (money out) # and negative amounts for credits (money in) # Sure convention: positive = money out, negative = money in # # If your provider uses the opposite convention, negate the amount: # amount = -amount amount end def build_extra_metadata(data) # TODO: Customize which fields to store based on your provider { "<%= file_name %>" => { "id" => data[:id] || data[:transaction_id], "pending" => data[:pending] || data[:is_pending], "merchant" => data[:merchant] || data[:merchant_name], "category" => data[:category] }.compact } end end