mercury_banking 0.5.37 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MercuryBanking
2
4
  module CLI
3
5
  # Module for report-related commands
@@ -9,11 +11,11 @@ module MercuryBanking
9
11
  no_commands do
10
12
  # The balance_sheet method has been moved to the Financials module
11
13
  end
12
-
14
+
13
15
  desc 'statement ACCOUNT_NAME', 'Generate a statement for a specific account and period'
14
16
  method_option :start, type: :string, default: '2020-01-01', desc: 'Start date for statement (YYYY-MM-DD)'
15
17
  method_option :end, type: :string, desc: 'End date for statement (YYYY-MM-DD)'
16
- method_option :format, type: :string, default: 'text', enum: ['text', 'json', 'csv'], desc: 'Output format'
18
+ method_option :format, type: :string, default: 'text', enum: %w[text json csv], desc: 'Output format'
17
19
  method_option :save, type: :string, desc: 'Save statement to specified file'
18
20
  method_option :debug, type: :boolean, default: false, desc: 'Show debug information about balance calculation'
19
21
  def statement(account_name)
@@ -22,117 +24,119 @@ module MercuryBanking
22
24
  end_date = options[:end]
23
25
  format = options[:format]
24
26
  debug = options[:debug]
25
-
27
+
26
28
  # Find the account by name
27
29
  accounts = client.accounts
28
30
  account = accounts.find { |a| a["name"] == account_name }
29
-
31
+
30
32
  unless account
31
33
  puts "Error: Account '#{account_name}' not found. Available accounts:"
32
34
  accounts.each { |a| puts "- #{a['name']}" }
33
35
  return
34
36
  end
35
-
37
+
36
38
  # Get transactions for the account
37
39
  transactions = client.get_transactions(account["id"], start_date)
38
-
40
+
39
41
  # Filter by end date if specified
40
42
  if end_date
41
43
  end_date_obj = Date.parse(end_date)
42
44
  transactions = transactions.select do |t|
43
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
45
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
44
46
  transaction_date <= end_date_obj
45
47
  end
46
48
  end
47
-
49
+
48
50
  if transactions.empty?
49
51
  puts "No transactions found for the specified period."
50
52
  return
51
53
  end
52
-
54
+
53
55
  # Sort transactions by date
54
56
  transactions = transactions.sort_by do |t|
55
57
  date = t["postedAt"] || t["createdAt"]
56
58
  Time.parse(date)
57
59
  end
58
-
60
+
59
61
  # Calculate opening and closing balances
60
62
  current_balance = account["currentBalance"]
61
63
  closing_balance = current_balance
62
-
63
- if debug
64
- puts "Debug: Current balance from Mercury API: $#{format("%.2f", current_balance)}"
65
- end
66
-
64
+
65
+ puts "Debug: Current balance from Mercury API: $#{format('%.2f', current_balance)}" if debug
66
+
67
67
  # Subtract all transactions that occurred after the end date
68
68
  if end_date
69
69
  end_date_obj = Date.parse(end_date)
70
70
  after_end_date_transactions = transactions.select do |t|
71
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
71
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
72
72
  transaction_date > end_date_obj
73
73
  end
74
-
74
+
75
75
  if debug && after_end_date_transactions.any?
76
76
  puts "Debug: Transactions after end date (#{end_date}):"
77
77
  after_end_date_transactions.each do |t|
78
78
  date = t["postedAt"] ? Time.parse(t["postedAt"]).strftime("%Y-%m-%d") : Time.parse(t["createdAt"]).strftime("%Y-%m-%d")
79
- puts " #{date}: $#{format("%.2f", t["amount"])}"
79
+ puts " #{date}: $#{format('%.2f', t['amount'])}"
80
80
  end
81
81
  end
82
-
82
+
83
83
  after_end_date_sum = after_end_date_transactions.sum { |t| t["amount"] }
84
84
  closing_balance = current_balance - after_end_date_sum
85
-
86
- if debug
87
- puts "Debug: Sum of transactions after end date: $#{format("%.2f", after_end_date_sum)}"
88
- puts "Debug: Closing balance at end date: $#{format("%.2f", closing_balance)}"
89
- end
90
- else
85
+
91
86
  if debug
92
- puts "Debug: No end date specified, closing balance equals current balance"
87
+ puts "Debug: Sum of transactions after end date: $#{format('%.2f', after_end_date_sum)}"
88
+ puts "Debug: Closing balance at end date: $#{format('%.2f', closing_balance)}"
93
89
  end
90
+ elsif debug
91
+ puts "Debug: No end date specified, closing balance equals current balance"
94
92
  end
95
-
93
+
96
94
  # Calculate opening balance by subtracting all transactions in the period
97
95
  opening_balance = closing_balance
98
-
99
- if debug
100
- puts "Debug: Transactions in the statement period:"
101
- end
102
-
96
+
97
+ puts "Debug: Transactions in the statement period:" if debug
98
+
103
99
  transactions_sum = 0
104
100
  transactions.each do |t|
105
101
  amount = t["amount"]
106
102
  transactions_sum += amount
107
-
103
+
108
104
  if debug
109
105
  date = t["postedAt"] ? Time.parse(t["postedAt"]).strftime("%Y-%m-%d") : Time.parse(t["createdAt"]).strftime("%Y-%m-%d")
110
- puts " #{date}: $#{format("%.2f", amount)}"
106
+ puts " #{date}: $#{format('%.2f', amount)}"
111
107
  end
112
-
108
+
113
109
  opening_balance -= amount
114
110
  end
115
-
111
+
116
112
  if debug
117
- puts "Debug: Sum of transactions in period: $#{format("%.2f", transactions_sum)}"
118
- puts "Debug: Opening balance calculation: $#{format("%.2f", closing_balance)} - $#{format("%.2f", transactions_sum)} = $#{format("%.2f", opening_balance)}"
113
+ puts "Debug: Sum of transactions in period: $#{format('%.2f', transactions_sum)}"
114
+ puts "Debug: Opening balance calculation: $#{format('%.2f',
115
+ closing_balance)} - $#{format('%.2f',
116
+ transactions_sum)} = $#{format(
117
+ '%.2f', opening_balance
118
+ )}"
119
119
  end
120
-
120
+
121
121
  # Generate statement based on format
122
122
  case format
123
123
  when 'text'
124
- generate_text_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, options[:save], debug)
124
+ generate_text_statement(account, transactions, opening_balance, closing_balance, start_date, end_date,
125
+ options[:save], debug)
125
126
  when 'json'
126
- generate_json_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, options[:save])
127
+ generate_json_statement(account, transactions, opening_balance, closing_balance, start_date, end_date,
128
+ options[:save])
127
129
  when 'csv'
128
- generate_csv_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, options[:save])
130
+ generate_csv_statement(account, transactions, opening_balance, closing_balance, start_date, end_date,
131
+ options[:save])
129
132
  end
130
133
  end
131
134
  end
132
-
135
+
133
136
  private
134
-
135
- def generate_text_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, save_path, debug = false)
137
+
138
+ def generate_text_statement(account, transactions, opening_balance, closing_balance, start_date, end_date,
139
+ save_path, debug = false)
136
140
  # Create statement header
137
141
  statement = []
138
142
  statement << "Mercury Banking Statement"
@@ -140,13 +144,13 @@ module MercuryBanking
140
144
  statement << "Period: #{start_date} to #{end_date || 'present'}"
141
145
  statement << "Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
142
146
  statement << ""
143
- statement << "Opening Balance: $#{format("%.2f", opening_balance)}"
147
+ statement << "Opening Balance: $#{format('%.2f', opening_balance)}"
144
148
  statement << ""
145
149
  statement << "Transactions:"
146
- statement << "-" * 80
147
- statement << "Date".ljust(12) + "Description".ljust(40) + "Amount".ljust(15) + "Balance"
148
- statement << "-" * 80
149
-
150
+ statement << ("-" * 80)
151
+ statement << ("#{'Date'.ljust(12)}#{'Description'.ljust(40)}#{'Amount'.ljust(15)}Balance")
152
+ statement << ("-" * 80)
153
+
150
154
  # Add transactions
151
155
  running_balance = opening_balance
152
156
  transactions.each do |t|
@@ -155,13 +159,15 @@ module MercuryBanking
155
159
  description = description.length > 37 ? "#{description[0..34]}..." : description
156
160
  amount = t["amount"]
157
161
  running_balance += amount
158
-
159
- statement << date.ljust(12) + description.ljust(40) + format("$%.2f", amount).rjust(15) + format("$%.2f", running_balance).rjust(15)
162
+
163
+ statement << (date.ljust(12) + description.ljust(40) + format("$%.2f",
164
+ amount).rjust(15) + format("$%.2f",
165
+ running_balance).rjust(15))
160
166
  end
161
-
162
- statement << "-" * 80
163
- statement << "Closing Balance: $#{format("%.2f", closing_balance)}"
164
-
167
+
168
+ statement << ("-" * 80)
169
+ statement << "Closing Balance: $#{format('%.2f', closing_balance)}"
170
+
165
171
  if debug
166
172
  statement << ""
167
173
  statement << "Debug Information:"
@@ -169,7 +175,7 @@ module MercuryBanking
169
175
  statement << "- Closing balance is the current balance adjusted for any transactions after the end date"
170
176
  statement << "- Running balance starts with the opening balance and adds each transaction amount"
171
177
  end
172
-
178
+
173
179
  # Output or save
174
180
  if save_path
175
181
  File.write(save_path, statement.join("\n"))
@@ -178,8 +184,9 @@ module MercuryBanking
178
184
  puts statement.join("\n")
179
185
  end
180
186
  end
181
-
182
- def generate_json_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, save_path)
187
+
188
+ def generate_json_statement(account, transactions, opening_balance, closing_balance, start_date, end_date,
189
+ save_path)
183
190
  # Format transactions
184
191
  formatted_transactions = transactions.map do |t|
185
192
  {
@@ -189,7 +196,7 @@ module MercuryBanking
189
196
  status: t["status"]
190
197
  }
191
198
  end
192
-
199
+
193
200
  # Create statement object
194
201
  statement = {
195
202
  account: {
@@ -208,10 +215,10 @@ module MercuryBanking
208
215
  transactions: formatted_transactions,
209
216
  generated_at: Time.now.iso8601
210
217
  }
211
-
218
+
212
219
  # Output or save
213
220
  json_output = JSON.pretty_generate(statement)
214
-
221
+
215
222
  if save_path
216
223
  File.write(save_path, json_output)
217
224
  puts "Statement saved to #{save_path}"
@@ -219,19 +226,20 @@ module MercuryBanking
219
226
  puts json_output
220
227
  end
221
228
  end
222
-
223
- def generate_csv_statement(account, transactions, opening_balance, closing_balance, start_date, end_date, save_path)
229
+
230
+ def generate_csv_statement(_account, transactions, opening_balance, closing_balance, start_date, end_date,
231
+ save_path)
224
232
  require 'csv'
225
-
233
+
226
234
  # Prepare CSV data
227
235
  csv_data = []
228
-
236
+
229
237
  # Add header row
230
- csv_data << ["Date", "Description", "Amount", "Balance", "Status"]
231
-
238
+ csv_data << %w[Date Description Amount Balance Status]
239
+
232
240
  # Add opening balance row
233
241
  csv_data << [start_date, "Opening Balance", "", opening_balance, ""]
234
-
242
+
235
243
  # Add transactions
236
244
  running_balance = opening_balance
237
245
  transactions.each do |t|
@@ -239,18 +247,18 @@ module MercuryBanking
239
247
  description = t["bankDescription"] || t["externalMemo"] || "Unknown transaction"
240
248
  amount = t["amount"]
241
249
  running_balance += amount
242
-
250
+
243
251
  csv_data << [date, description, amount, running_balance, t["status"]]
244
252
  end
245
-
253
+
246
254
  # Add closing balance row
247
255
  csv_data << [end_date || Time.now.strftime("%Y-%m-%d"), "Closing Balance", "", closing_balance, ""]
248
-
256
+
249
257
  # Output or save
250
258
  csv_output = CSV.generate do |csv|
251
259
  csv_data.each { |row| csv << row }
252
260
  end
253
-
261
+
254
262
  if save_path
255
263
  File.write(save_path, csv_output)
256
264
  puts "Statement saved to #{save_path}"
@@ -262,4 +270,4 @@ module MercuryBanking
262
270
  end
263
271
  end
264
272
  end
265
- end
273
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MercuryBanking
2
4
  module CLI
3
5
  # Module for transaction-related commands
@@ -6,81 +8,91 @@ module MercuryBanking
6
8
  def self.included(base)
7
9
  base.class_eval do
8
10
  include MercuryBanking::Formatters::ExportFormatter
9
-
11
+
10
12
  desc 'transactions_download', 'Download all Mercury transactions as CSV files'
11
13
  method_option :output_dir, type: :string, default: 'transactions', desc: 'Directory to save transaction files'
12
- method_option :start_date, type: :string, default: '2020-01-01', desc: 'Start date for transactions (YYYY-MM-DD)'
14
+ method_option :start_date, type: :string, default: '2020-01-01',
15
+ desc: 'Start date for transactions (YYYY-MM-DD)'
13
16
  method_option :end_date, type: :string, desc: 'End date for transactions (YYYY-MM-DD)'
14
- method_option :format, type: :string, default: 'csv', enum: ['csv', 'beancount', 'ledger', 'all'], desc: 'Output format (csv, beancount, ledger, or all)'
17
+ method_option :format, type: :string, default: 'csv', enum: %w[csv beancount ledger all],
18
+ desc: 'Output format (csv, beancount, ledger, or all)'
15
19
  method_option :verbose, type: :boolean, default: false, desc: 'Show detailed debug information'
16
20
  def transactions_download
17
21
  with_api_client do |client|
18
22
  # Create output directory if it doesn't exist
19
23
  output_dir = options[:output_dir]
20
24
  FileUtils.mkdir_p(output_dir)
21
-
25
+
22
26
  # Get all accounts
23
27
  accounts = client.accounts
24
-
28
+
25
29
  # Get start and end dates
26
30
  start_date = options[:start_date]
27
31
  end_date = options[:end_date]
28
-
32
+
29
33
  # Get format
30
- format = options[:format]
31
-
34
+ format = options[:format] || 'all'
35
+
32
36
  # Get verbose option
33
37
  verbose = options[:verbose]
34
-
38
+
35
39
  # For each account, get transactions and save to file
36
40
  accounts.each do |account|
37
41
  account_id = account["id"]
38
42
  account_name = account["name"]
39
-
43
+ account_number = account["accountNumber"]
44
+
40
45
  puts "Fetching transactions for #{account_name} (#{account_id})..."
41
-
46
+
42
47
  # Get all transactions for this account
43
48
  transactions = client.get_transactions(account_id, start_date)
44
-
49
+
50
+ # Add account information to each transaction
51
+ transactions.each do |transaction|
52
+ transaction["accountName"] = account_name
53
+ transaction["accountId"] = account_id
54
+ transaction["accountNumber"] = account_number
55
+ end
56
+
45
57
  # Filter by end date if specified
46
58
  if end_date
47
59
  end_date_obj = Date.parse(end_date)
48
60
  transactions = transactions.select do |t|
49
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
61
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
50
62
  transaction_date <= end_date_obj
51
63
  end
52
64
  end
53
-
65
+
54
66
  if transactions.empty?
55
67
  puts "No transactions found for #{account_name}."
56
68
  next
57
69
  end
58
-
70
+
59
71
  # Group transactions by month
60
72
  transactions_by_month = {}
61
-
73
+
62
74
  transactions.each do |t|
63
75
  # Get the month from the transaction date
64
- date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
76
+ date = Date.parse(t["postedAt"] || t["createdAt"])
65
77
  month_key = "#{date.year}-#{date.month.to_s.rjust(2, '0')}"
66
-
78
+
67
79
  # Initialize the month array if it doesn't exist
68
80
  transactions_by_month[month_key] ||= []
69
-
81
+
70
82
  # Add the transaction to the month array
71
83
  transactions_by_month[month_key] << t
72
84
  end
73
-
85
+
74
86
  # For each month, save transactions to file
75
87
  transactions_by_month.each do |month, month_transactions|
76
88
  # Use the full account number for the filename
77
89
  account_number = account["accountNumber"]
78
-
90
+
79
91
  # Create filenames for different formats
80
92
  csv_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.csv")
81
93
  beancount_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.beancount")
82
94
  ledger_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.ledger")
83
-
95
+
84
96
  # Export transactions in the requested format
85
97
  case format
86
98
  when 'csv'
@@ -95,20 +107,20 @@ module MercuryBanking
95
107
  when 'all'
96
108
  export_to_csv(month_transactions, csv_filename, [], verbose)
97
109
  puts "Exported #{month_transactions.size} transactions to #{csv_filename}"
98
-
110
+
99
111
  export_to_beancount(month_transactions, beancount_filename, [], verbose)
100
112
  puts "Exported #{month_transactions.size} transactions to #{beancount_filename}"
101
-
113
+
102
114
  export_to_ledger(month_transactions, ledger_filename, [], verbose)
103
115
  puts "Exported #{month_transactions.size} transactions to #{ledger_filename}"
104
116
  end
105
117
  end
106
118
  end
107
-
119
+
108
120
  puts "\nTransaction export complete. Files saved to #{output_dir}/ directory."
109
121
  end
110
122
  end
111
-
123
+
112
124
  desc "transactions ACCOUNT_ID_OR_NUMBER", "List transactions for an account with their status"
113
125
  method_option :limit, type: :numeric, default: 10, desc: "Number of transactions to show"
114
126
  method_option :start, type: :string, desc: "Start date (YYYY-MM-DD)"
@@ -120,70 +132,75 @@ module MercuryBanking
120
132
  with_api_client do |client|
121
133
  # Find the account by ID or account number
122
134
  account = find_account(client, account_id_or_number)
123
-
135
+
124
136
  if account.nil?
125
137
  puts "Account not found: #{account_id_or_number}"
126
138
  return
127
139
  end
128
-
140
+
129
141
  account_id = account["id"]
130
-
142
+
131
143
  # Get transactions with optional date filters
132
144
  start_date = options[:start]
133
145
  end_date = options[:end]
134
-
135
- puts "Fetching transactions for #{account["name"]}..."
146
+
147
+ puts "Fetching transactions for #{account['name']}..."
136
148
  transactions = client.get_transactions(account_id, start_date)
137
-
149
+
150
+ # Add account information to each transaction
151
+ transactions.each do |transaction|
152
+ transaction["accountName"] = account["name"]
153
+ transaction["accountId"] = account["id"]
154
+ transaction["accountNumber"] = account["accountNumber"]
155
+ end
156
+
138
157
  # Filter by end date if specified
139
158
  if end_date
140
159
  end_date_obj = Date.parse(end_date)
141
160
  transactions = transactions.select do |t|
142
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
161
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
143
162
  transaction_date <= end_date_obj
144
163
  end
145
164
  end
146
-
165
+
147
166
  # Filter by status if specified
148
- if options[:status]
149
- transactions = transactions.select { |t| t["status"] == options[:status] }
150
- end
151
-
167
+ transactions = transactions.select { |t| t["status"] == options[:status] } if options[:status]
168
+
152
169
  # Limit the number of transactions
153
170
  limit = options[:limit]
154
- transactions = transactions.take(limit) if limit > 0
155
-
171
+ transactions = transactions.take(limit) if limit.positive?
172
+
156
173
  # Export to CSV if requested
157
174
  if options[:csv]
158
175
  export_to_csv(transactions, options[:csv])
159
176
  puts "Exported #{transactions.size} transactions to #{options[:csv]}"
160
177
  return
161
178
  end
162
-
179
+
163
180
  # Output in JSON format if requested
164
181
  if options[:json]
165
182
  puts JSON.pretty_generate(transactions)
166
183
  return
167
184
  end
168
-
185
+
169
186
  # Create a table for display
170
187
  table = Terminal::Table.new
171
- table.title = "Transactions for #{account["name"]}"
172
- table.headings = ['Date', 'Description', 'Amount', 'Status']
173
-
188
+ table.title = "Transactions for #{account['name']}"
189
+ table.headings = %w[Date Description Amount Status]
190
+
174
191
  transactions.each do |t|
175
- date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
192
+ date = Date.parse(t["postedAt"] || t["createdAt"])
176
193
  description = t["bankDescription"] || t["externalMemo"] || "Unknown"
177
194
  amount = t["amount"]
178
195
  status = t["status"]
179
-
196
+
180
197
  table.add_row [date, description, "$#{amount}", status]
181
198
  end
182
-
199
+
183
200
  puts table
184
201
  end
185
202
  end
186
-
203
+
187
204
  # Helper methods that should not be exposed as commands
188
205
  no_commands do
189
206
  # Helper method to sort transactions chronologically
@@ -194,24 +211,20 @@ module MercuryBanking
194
211
  Time.parse(timestamp)
195
212
  end
196
213
  end
197
-
214
+
198
215
  # Helper method to find an account by ID or account number
199
216
  def find_account(client, account_id_or_number)
200
217
  accounts = client.accounts
201
-
218
+
202
219
  # Try to find by ID first
203
220
  account = accounts.find { |a| a["id"] == account_id_or_number }
204
-
221
+
205
222
  # If not found by ID, try by account number
206
- if account.nil?
207
- account = accounts.find { |a| a["accountNumber"] == account_id_or_number }
208
- end
209
-
223
+ account = accounts.find { |a| a["accountNumber"] == account_id_or_number } if account.nil?
224
+
210
225
  # If still not found, try by the last 4 digits of the account number
211
- if account.nil?
212
- account = accounts.find { |a| a["accountNumber"]&.end_with?(account_id_or_number) }
213
- end
214
-
226
+ account = accounts.find { |a| a["accountNumber"]&.end_with?(account_id_or_number) } if account.nil?
227
+
215
228
  account
216
229
  end
217
230
  end
@@ -219,4 +232,4 @@ module MercuryBanking
219
232
  end
220
233
  end
221
234
  end
222
- end
235
+ end