mercury_banking 0.5.38 → 0.7.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,89 +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
34
  format = options[:format] || 'all'
31
-
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"]
40
-
44
+
41
45
  puts "Fetching transactions for #{account_name} (#{account_id})..."
42
-
46
+
43
47
  # Get all transactions for this account
44
48
  transactions = client.get_transactions(account_id, start_date)
45
-
49
+
46
50
  # Add account information to each transaction
47
51
  transactions.each do |transaction|
48
52
  transaction["accountName"] = account_name
49
53
  transaction["accountId"] = account_id
50
54
  transaction["accountNumber"] = account_number
51
55
  end
52
-
56
+
53
57
  # Filter by end date if specified
54
58
  if end_date
55
59
  end_date_obj = Date.parse(end_date)
56
60
  transactions = transactions.select do |t|
57
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
61
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
58
62
  transaction_date <= end_date_obj
59
63
  end
60
64
  end
61
-
65
+
62
66
  if transactions.empty?
63
67
  puts "No transactions found for #{account_name}."
64
68
  next
65
69
  end
66
-
70
+
67
71
  # Group transactions by month
68
72
  transactions_by_month = {}
69
-
73
+
70
74
  transactions.each do |t|
71
75
  # Get the month from the transaction date
72
- date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
76
+ date = Date.parse(t["postedAt"] || t["createdAt"])
73
77
  month_key = "#{date.year}-#{date.month.to_s.rjust(2, '0')}"
74
-
78
+
75
79
  # Initialize the month array if it doesn't exist
76
80
  transactions_by_month[month_key] ||= []
77
-
81
+
78
82
  # Add the transaction to the month array
79
83
  transactions_by_month[month_key] << t
80
84
  end
81
-
85
+
82
86
  # For each month, save transactions to file
83
87
  transactions_by_month.each do |month, month_transactions|
84
88
  # Use the full account number for the filename
85
89
  account_number = account["accountNumber"]
86
-
90
+
87
91
  # Create filenames for different formats
88
92
  csv_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.csv")
89
93
  beancount_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.beancount")
90
94
  ledger_filename = File.join(output_dir, "#{month}-Mercury-#{account_number}.ledger")
91
-
95
+
92
96
  # Export transactions in the requested format
93
97
  case format
94
98
  when 'csv'
@@ -103,20 +107,20 @@ module MercuryBanking
103
107
  when 'all'
104
108
  export_to_csv(month_transactions, csv_filename, [], verbose)
105
109
  puts "Exported #{month_transactions.size} transactions to #{csv_filename}"
106
-
110
+
107
111
  export_to_beancount(month_transactions, beancount_filename, [], verbose)
108
112
  puts "Exported #{month_transactions.size} transactions to #{beancount_filename}"
109
-
113
+
110
114
  export_to_ledger(month_transactions, ledger_filename, [], verbose)
111
115
  puts "Exported #{month_transactions.size} transactions to #{ledger_filename}"
112
116
  end
113
117
  end
114
118
  end
115
-
119
+
116
120
  puts "\nTransaction export complete. Files saved to #{output_dir}/ directory."
117
121
  end
118
122
  end
119
-
123
+
120
124
  desc "transactions ACCOUNT_ID_OR_NUMBER", "List transactions for an account with their status"
121
125
  method_option :limit, type: :numeric, default: 10, desc: "Number of transactions to show"
122
126
  method_option :start, type: :string, desc: "Start date (YYYY-MM-DD)"
@@ -128,77 +132,75 @@ module MercuryBanking
128
132
  with_api_client do |client|
129
133
  # Find the account by ID or account number
130
134
  account = find_account(client, account_id_or_number)
131
-
135
+
132
136
  if account.nil?
133
137
  puts "Account not found: #{account_id_or_number}"
134
138
  return
135
139
  end
136
-
140
+
137
141
  account_id = account["id"]
138
-
142
+
139
143
  # Get transactions with optional date filters
140
144
  start_date = options[:start]
141
145
  end_date = options[:end]
142
-
143
- puts "Fetching transactions for #{account["name"]}..."
146
+
147
+ puts "Fetching transactions for #{account['name']}..."
144
148
  transactions = client.get_transactions(account_id, start_date)
145
-
149
+
146
150
  # Add account information to each transaction
147
151
  transactions.each do |transaction|
148
152
  transaction["accountName"] = account["name"]
149
153
  transaction["accountId"] = account["id"]
150
154
  transaction["accountNumber"] = account["accountNumber"]
151
155
  end
152
-
156
+
153
157
  # Filter by end date if specified
154
158
  if end_date
155
159
  end_date_obj = Date.parse(end_date)
156
160
  transactions = transactions.select do |t|
157
- transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
161
+ transaction_date = Date.parse(t["postedAt"] || t["createdAt"])
158
162
  transaction_date <= end_date_obj
159
163
  end
160
164
  end
161
-
165
+
162
166
  # Filter by status if specified
163
- if options[:status]
164
- transactions = transactions.select { |t| t["status"] == options[:status] }
165
- end
166
-
167
+ transactions = transactions.select { |t| t["status"] == options[:status] } if options[:status]
168
+
167
169
  # Limit the number of transactions
168
170
  limit = options[:limit]
169
- transactions = transactions.take(limit) if limit > 0
170
-
171
+ transactions = transactions.take(limit) if limit.positive?
172
+
171
173
  # Export to CSV if requested
172
174
  if options[:csv]
173
175
  export_to_csv(transactions, options[:csv])
174
176
  puts "Exported #{transactions.size} transactions to #{options[:csv]}"
175
177
  return
176
178
  end
177
-
179
+
178
180
  # Output in JSON format if requested
179
181
  if options[:json]
180
182
  puts JSON.pretty_generate(transactions)
181
183
  return
182
184
  end
183
-
185
+
184
186
  # Create a table for display
185
187
  table = Terminal::Table.new
186
- table.title = "Transactions for #{account["name"]}"
187
- table.headings = ['Date', 'Description', 'Amount', 'Status']
188
-
188
+ table.title = "Transactions for #{account['name']}"
189
+ table.headings = %w[Date Description Amount Status]
190
+
189
191
  transactions.each do |t|
190
- date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
192
+ date = Date.parse(t["postedAt"] || t["createdAt"])
191
193
  description = t["bankDescription"] || t["externalMemo"] || "Unknown"
192
194
  amount = t["amount"]
193
195
  status = t["status"]
194
-
196
+
195
197
  table.add_row [date, description, "$#{amount}", status]
196
198
  end
197
-
199
+
198
200
  puts table
199
201
  end
200
202
  end
201
-
203
+
202
204
  # Helper methods that should not be exposed as commands
203
205
  no_commands do
204
206
  # Helper method to sort transactions chronologically
@@ -209,24 +211,20 @@ module MercuryBanking
209
211
  Time.parse(timestamp)
210
212
  end
211
213
  end
212
-
214
+
213
215
  # Helper method to find an account by ID or account number
214
216
  def find_account(client, account_id_or_number)
215
217
  accounts = client.accounts
216
-
218
+
217
219
  # Try to find by ID first
218
220
  account = accounts.find { |a| a["id"] == account_id_or_number }
219
-
221
+
220
222
  # If not found by ID, try by account number
221
- if account.nil?
222
- account = accounts.find { |a| a["accountNumber"] == account_id_or_number }
223
- end
224
-
223
+ account = accounts.find { |a| a["accountNumber"] == account_id_or_number } if account.nil?
224
+
225
225
  # If still not found, try by the last 4 digits of the account number
226
- if account.nil?
227
- account = accounts.find { |a| a["accountNumber"]&.end_with?(account_id_or_number) }
228
- end
229
-
226
+ account = accounts.find { |a| a["accountNumber"]&.end_with?(account_id_or_number) } if account.nil?
227
+
230
228
  account
231
229
  end
232
230
  end
@@ -234,4 +232,4 @@ module MercuryBanking
234
232
  end
235
233
  end
236
234
  end
237
- end
235
+ end