Module: MercuryBanking::CLI::Transactions

Included in:
Main
Defined in:
lib/mercury_banking/cli/transactions.rb

Overview

Module for transaction-related commands

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Add transaction-related commands to the CLI class



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/mercury_banking/cli/transactions.rb', line 8

def self.included(base)
  base.class_eval do
    include MercuryBanking::Formatters::ExportFormatter

    desc 'transactions_download', 'Download all Mercury transactions as CSV files'
    method_option :output_dir, type: :string, default: 'transactions', desc: 'Directory to save transaction files'
    method_option :start_date, type: :string, default: '2020-01-01',
                               desc: 'Start date for transactions (YYYY-MM-DD)'
    method_option :end_date, type: :string, desc: 'End date for transactions (YYYY-MM-DD)'
    method_option :format, type: :string, default: 'csv', enum: %w[csv beancount ledger all],
                           desc: 'Output format (csv, beancount, ledger, or all)'
    method_option :verbose, type: :boolean, default: false, desc: 'Show detailed debug information'
    def transactions_download
      with_api_client do |client|
        # Create output directory if it doesn't exist
        output_dir = options[:output_dir]
        FileUtils.mkdir_p(output_dir)

        # Get all accounts
        accounts = client.accounts

        # Get start and end dates
        start_date = options[:start_date]
        end_date = options[:end_date]

        # Get format
        format = options[:format] || 'all'

        # Get verbose option
        verbose = options[:verbose]

        # For each account, get transactions and save to file
        accounts.each do ||
           = ["id"]
           = ["name"]
           = ["accountNumber"]

          puts "Fetching transactions for #{} (#{})..."

          # Get all transactions for this account
          transactions = client.get_transactions(, start_date)

          # Add account information to each transaction
          transactions.each do |transaction|
            transaction["accountName"] = 
            transaction["accountId"] = 
            transaction["accountNumber"] = 
          end

          # Filter by end date if specified
          if end_date
            end_date_obj = Date.parse(end_date)
            transactions = transactions.select do |t|
              transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
              transaction_date <= end_date_obj
            end
          end

          if transactions.empty?
            puts "No transactions found for #{}."
            next
          end

          # Group transactions by month
          transactions_by_month = {}

          transactions.each do |t|
            # Get the month from the transaction date
            date = Date.parse(t["postedAt"] || t["createdAt"])
            month_key = "#{date.year}-#{date.month.to_s.rjust(2, '0')}"

            # Initialize the month array if it doesn't exist
            transactions_by_month[month_key] ||= []

            # Add the transaction to the month array
            transactions_by_month[month_key] << t
          end

          # For each month, save transactions to file
          transactions_by_month.each do |month, month_transactions|
            # Use the full account number for the filename
             = ["accountNumber"]

            # Create filenames for different formats
            csv_filename = File.join(output_dir, "#{month}-Mercury-#{}.csv")
            beancount_filename = File.join(output_dir, "#{month}-Mercury-#{}.beancount")
            ledger_filename = File.join(output_dir, "#{month}-Mercury-#{}.ledger")

            # Export transactions in the requested format
            case format
            when 'csv'
              export_to_csv(month_transactions, csv_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{csv_filename}"
            when 'beancount'
              export_to_beancount(month_transactions, beancount_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{beancount_filename}"
            when 'ledger'
              export_to_ledger(month_transactions, ledger_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{ledger_filename}"
            when 'all'
              export_to_csv(month_transactions, csv_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{csv_filename}"

              export_to_beancount(month_transactions, beancount_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{beancount_filename}"

              export_to_ledger(month_transactions, ledger_filename, [], verbose)
              puts "Exported #{month_transactions.size} transactions to #{ledger_filename}"
            end
          end
        end

        puts "\nTransaction export complete. Files saved to #{output_dir}/ directory."
      end
    end

    desc "transactions ACCOUNT_ID_OR_NUMBER", "List transactions for an account with their status"
    method_option :limit, type: :numeric, default: 10, desc: "Number of transactions to show"
    method_option :start, type: :string, desc: "Start date (YYYY-MM-DD)"
    method_option :end, type: :string, desc: "End date (YYYY-MM-DD)"
    method_option :status, type: :string, desc: "Filter by status (pending, sent, complete, failed)"
    method_option :json, type: :boolean, default: false, desc: "Output in JSON format"
    method_option :csv, type: :string, desc: "Export to CSV file"
    def transactions()
      with_api_client do |client|
        # Find the account by ID or account number
         = (client, )

        if .nil?
          puts "Account not found: #{}"
          return
        end

         = ["id"]

        # Get transactions with optional date filters
        start_date = options[:start]
        end_date = options[:end]

        puts "Fetching transactions for #{['name']}..."
        transactions = client.get_transactions(, start_date)

        # Add account information to each transaction
        transactions.each do |transaction|
          transaction["accountName"] = ["name"]
          transaction["accountId"] = ["id"]
          transaction["accountNumber"] = ["accountNumber"]
        end

        # Filter by end date if specified
        if end_date
          end_date_obj = Date.parse(end_date)
          transactions = transactions.select do |t|
            transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
            transaction_date <= end_date_obj
          end
        end

        # Filter by status if specified
        transactions = transactions.select { |t| t["status"] == options[:status] } if options[:status]

        # Limit the number of transactions
        limit = options[:limit]
        transactions = transactions.take(limit) if limit.positive?

        # Export to CSV if requested
        if options[:csv]
          export_to_csv(transactions, options[:csv])
          puts "Exported #{transactions.size} transactions to #{options[:csv]}"
          return
        end

        # Output in JSON format if requested
        if options[:json]
          puts JSON.pretty_generate(transactions)
          return
        end

        # Create a table for display
        table = Terminal::Table.new
        table.title = "Transactions for #{['name']}"
        table.headings = %w[Date Description Amount Status]

        transactions.each do |t|
          date = Date.parse(t["postedAt"] || t["createdAt"])
          description = t["bankDescription"] || t["externalMemo"] || "Unknown"
          amount = t["amount"]
          status = t["status"]

          table.add_row [date, description, "$#{amount}", status]
        end

        puts table
      end
    end

    # Helper methods that should not be exposed as commands
    no_commands do
      # Helper method to sort transactions chronologically
      def sort_transactions_chronologically(transactions)
        transactions.sort_by do |t|
          # Use postedAt if available, otherwise fall back to createdAt
          timestamp = t["postedAt"] || t["createdAt"]
          Time.parse(timestamp)
        end
      end

      # Helper method to find an account by ID or account number
      def (client, )
        accounts = client.accounts

        # Try to find by ID first
         = accounts.find { |a| a["id"] ==  }

        # If not found by ID, try by account number
         = accounts.find { |a| a["accountNumber"] ==  } if .nil?

        # If still not found, try by the last 4 digits of the account number
         = accounts.find { |a| a["accountNumber"]&.end_with?() } if .nil?

        
      end
    end
  end
end