mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 03:54:08 +00:00
148 lines
5.6 KiB
Plaintext
148 lines
5.6 KiB
Plaintext
# 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
|