mercury_banking 0.5.34

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.
@@ -0,0 +1,302 @@
1
+ require 'mercury_banking/cli/base'
2
+ require 'mercury_banking/formatters/export_formatter'
3
+ require 'mercury_banking/reports/balance_sheet'
4
+
5
+ module MercuryBanking
6
+ module CLI
7
+ # Module for financial report commands
8
+ module Financials
9
+ # Define the financials command class
10
+ class FinancialsCommand < Thor
11
+ include MercuryBanking::CLI::Base
12
+ include MercuryBanking::Formatters::ExportFormatter
13
+ include MercuryBanking::Reports::BalanceSheet
14
+
15
+ desc "balancesheet", "Generate a balance sheet report and cross-check with Mercury account balances"
16
+ method_option :format, type: :string, default: 'ledger', desc: 'Accounting format to use (ledger or beancount)'
17
+ method_option :start, type: :string, default: '2020-01-01', desc: 'Start date for transactions (YYYY-MM-DD)'
18
+ method_option :end, type: :string, desc: 'End date for transactions (YYYY-MM-DD)'
19
+ method_option :save_report, type: :string, desc: 'Save report output to specified file'
20
+ method_option :verbose, type: :boolean, default: false, desc: 'Show detailed debug information'
21
+ method_option :ledger_file, type: :string, desc: 'Use an existing ledger file instead of fetching transactions from Mercury'
22
+ def balancesheet
23
+ # Call the balance_sheet method directly
24
+ balance_sheet
25
+ end
26
+
27
+ # Methods that should not be exposed as commands
28
+ no_commands do
29
+ # Implementation of the balance sheet functionality
30
+ def balance_sheet
31
+ # If a ledger file is provided, use it directly
32
+ if options[:ledger_file]
33
+ ledger_file = options[:ledger_file]
34
+ format = options[:format]
35
+ end_date = options[:end]
36
+ verbose = options[:verbose]
37
+
38
+ puts "Using existing ledger file: #{ledger_file}"
39
+
40
+ if !File.exist?(ledger_file)
41
+ puts "Error: Ledger file not found at #{ledger_file}"
42
+ return
43
+ end
44
+
45
+ # Generate balance sheet from the ledger file
46
+ case format
47
+ when 'ledger'
48
+ balance_sheet_output = generate_ledger_balance_sheet(ledger_file, end_date)
49
+ if balance_sheet_output
50
+ puts "\n=== Balance Sheet ===\n"
51
+ puts balance_sheet_output
52
+
53
+ # Save report to file if requested
54
+ if options[:save_report]
55
+ File.write(options[:save_report], balance_sheet_output)
56
+ puts "\nReport saved to #{options[:save_report]}"
57
+ end
58
+ else
59
+ puts "Failed to generate balance sheet from ledger file."
60
+ end
61
+ when 'beancount'
62
+ balance_sheet_output = generate_beancount_balance_sheet(ledger_file, end_date)
63
+ if balance_sheet_output
64
+ puts "\n=== Balance Sheet ===\n"
65
+ puts balance_sheet_output
66
+
67
+ # Save report to file if requested
68
+ if options[:save_report]
69
+ File.write(options[:save_report], balance_sheet_output)
70
+ puts "\nReport saved to #{options[:save_report]}"
71
+ end
72
+ else
73
+ puts "Failed to generate balance sheet from beancount file."
74
+ end
75
+ else
76
+ puts "Unsupported format: #{format}. Please use 'ledger' or 'beancount'."
77
+ end
78
+
79
+ return
80
+ end
81
+
82
+ # Otherwise, fetch data from Mercury API
83
+ with_api_client do |client|
84
+ # Get current account balances from Mercury
85
+ accounts = client.accounts
86
+
87
+ # Create a mapping of account names to balances
88
+ mercury_balances = {}
89
+ accounts.each do |account|
90
+ mercury_balances[account['name']] = account['currentBalance']
91
+ end
92
+
93
+ # Get all transactions for the balance sheet
94
+ start_date = options[:start]
95
+ end_date = options[:end]
96
+ format = options[:format]
97
+ verbose = options[:verbose]
98
+
99
+ date_range = "since #{start_date}"
100
+ date_range += " until #{end_date}" if end_date
101
+
102
+ puts "Fetching transactions for all accounts #{date_range}..."
103
+
104
+ transactions = client.get_all_transactions(start_date)
105
+
106
+ # Filter by end date if specified
107
+ if end_date
108
+ end_date_obj = Date.parse(end_date)
109
+ transactions = transactions.select do |t|
110
+ transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
111
+ transaction_date <= end_date_obj
112
+ end
113
+ end
114
+
115
+ if transactions.empty?
116
+ puts "No transactions found to generate balance sheet."
117
+ return
118
+ end
119
+
120
+ # Create a temporary file
121
+ require 'tempfile'
122
+ temp_file = Tempfile.new(['mercury_transactions', ".#{format}"])
123
+ output_file = temp_file.path
124
+
125
+ # Export transactions in the specified format
126
+ case format
127
+ when 'ledger'
128
+ export_to_ledger(transactions, output_file, [], verbose)
129
+ balance_sheet_output = generate_ledger_balance_sheet(output_file, end_date)
130
+ when 'beancount'
131
+ export_to_beancount(transactions, output_file, [], verbose)
132
+ balance_sheet_output = generate_beancount_balance_sheet(output_file, end_date)
133
+ else
134
+ puts "Unsupported format: #{format}. Please use 'ledger' or 'beancount'."
135
+ temp_file.unlink
136
+ return
137
+ end
138
+
139
+ # Parse the balance sheet output to extract account balances
140
+ ledger_balances = parse_balance_sheet_output(balance_sheet_output, format, verbose)
141
+
142
+ # Cross-check Mercury balances with ledger balances
143
+ puts "\n=== Balance Sheet Cross-Check ==="
144
+ puts "Mercury Account".ljust(30) + "Mercury Balance".ljust(15) + "Ledger Balance".ljust(15) + "Difference"
145
+ puts "-" * 75
146
+
147
+ total_diff = 0
148
+ mercury_balances.each do |account_name, mercury_balance|
149
+ # Find the corresponding ledger account (might be prefixed with Assets:)
150
+ ledger_account_key = ledger_balances.keys.find { |k| k.include?(account_name) }
151
+
152
+ # Debug information
153
+ if verbose
154
+ puts "Looking for Mercury account '#{account_name}' in ledger accounts:"
155
+ ledger_balances.keys.each do |k|
156
+ puts " - #{k} (match: #{k.include?(account_name) ? 'Yes' : 'No'})"
157
+ end
158
+ end
159
+
160
+ ledger_balance = ledger_account_key ? ledger_balances[ledger_account_key] : 0
161
+
162
+ # Calculate difference
163
+ diff = mercury_balance - ledger_balance
164
+ total_diff += diff.abs
165
+
166
+ # Format for display
167
+ mercury_balance_str = format("$%.2f", mercury_balance)
168
+ ledger_balance_str = format("$%.2f", ledger_balance)
169
+ diff_str = format("$%.2f", diff)
170
+
171
+ # Add warning marker for differences
172
+ diff_marker = diff.abs > 0.01 ? " ⚠️" : ""
173
+
174
+ puts account_name.ljust(30) + mercury_balance_str.ljust(15) + ledger_balance_str.ljust(15) + diff_str + diff_marker
175
+ end
176
+
177
+ puts "-" * 75
178
+ puts "Total Discrepancy: #{format("$%.2f", total_diff)}"
179
+
180
+ if total_diff > 0.01
181
+ puts "\n⚠️ Warning: There are discrepancies between Mercury account balances and the ledger balance sheet."
182
+ puts "This could be due to:"
183
+ puts " - Transactions not yet recorded in the ledger"
184
+ puts " - Incorrect categorization of transactions"
185
+ puts " - Timing differences between when transactions were recorded"
186
+ else
187
+ puts "\n✓ Balance sheet matches Mercury account balances."
188
+ end
189
+
190
+ # Save report to file if requested
191
+ if options[:save_report] && balance_sheet_output
192
+ full_report = balance_sheet_output + "\n\n" + "=== Balance Sheet Cross-Check ===\n"
193
+ mercury_balances.each do |account_name, mercury_balance|
194
+ ledger_account_key = ledger_balances.keys.find { |k| k.include?(account_name) }
195
+ ledger_balance = ledger_account_key ? ledger_balances[ledger_account_key] : 0
196
+ diff = mercury_balance - ledger_balance
197
+ full_report += "#{account_name}: Mercury $#{format("%.2f", mercury_balance)} vs Ledger $#{format("%.2f", ledger_balance)} (Diff: $#{format("%.2f", diff)})\n"
198
+ end
199
+
200
+ File.write(options[:save_report], full_report)
201
+ puts "\nReport saved to #{options[:save_report]}"
202
+ end
203
+
204
+ # Clean up temporary file
205
+ temp_file.unlink
206
+ end
207
+ end
208
+ end
209
+
210
+ desc "incomestatement", "Generate an income statement report"
211
+ method_option :format, type: :string, default: 'ledger', desc: 'Accounting format to use (ledger or beancount)'
212
+ method_option :start, type: :string, default: '2020-01-01', desc: 'Start date for transactions (YYYY-MM-DD)'
213
+ method_option :end, type: :string, desc: 'End date for transactions (YYYY-MM-DD)'
214
+ method_option :save_report, type: :string, desc: 'Save report output to specified file'
215
+ method_option :verbose, type: :boolean, default: false, desc: 'Show detailed debug information'
216
+ def incomestatement
217
+ # Access the parent class to call with_api_client
218
+ parent_class = self.class.parent_class
219
+ parent_instance = parent_class.new
220
+
221
+ parent_instance.with_api_client do |client|
222
+ # Get all transactions for the income statement
223
+ start_date = options[:start]
224
+ end_date = options[:end]
225
+ format = options[:format]
226
+ verbose = options[:verbose]
227
+
228
+ date_range = "since #{start_date}"
229
+ date_range += " until #{end_date}" if end_date
230
+
231
+ puts "Fetching transactions for all accounts #{date_range}..."
232
+
233
+ transactions = client.get_all_transactions(start_date)
234
+
235
+ # Filter by end date if specified
236
+ if end_date
237
+ end_date_obj = Date.parse(end_date)
238
+ transactions = transactions.select do |t|
239
+ transaction_date = t["postedAt"] ? Date.parse(t["postedAt"]) : Date.parse(t["createdAt"])
240
+ transaction_date <= end_date_obj
241
+ end
242
+ end
243
+
244
+ # Create a temporary file
245
+ require 'tempfile'
246
+ temp_file = Tempfile.new(['mercury_transactions', ".#{format}"])
247
+ output_file = temp_file.path
248
+
249
+ # Export transactions in the specified format
250
+ case format
251
+ when 'ledger'
252
+ parent_instance.export_to_ledger(transactions, output_file, [], verbose)
253
+ income_statement_output = parent_instance.generate_ledger_reports(output_file, 'income', nil, end_date)
254
+ when 'beancount'
255
+ parent_instance.export_to_beancount(transactions, output_file, [], verbose)
256
+ income_statement_output = parent_instance.generate_beancount_reports(output_file, 'income', nil, end_date)
257
+ else
258
+ puts "Unsupported format: #{format}. Please use 'ledger' or 'beancount'."
259
+ temp_file.unlink
260
+ return
261
+ end
262
+
263
+ # Display the income statement
264
+ puts income_statement_output
265
+
266
+ # Save the report to a file if requested
267
+ if options[:save_report]
268
+ File.open(options[:save_report], 'w') do |file|
269
+ file.puts income_statement_output
270
+ end
271
+ puts "\nReport saved to #{options[:save_report]}"
272
+ end
273
+
274
+ # Clean up the temporary file
275
+ temp_file.unlink
276
+ end
277
+ end
278
+
279
+ # Store reference to parent class
280
+ class << self
281
+ attr_accessor :parent_class
282
+ end
283
+ end
284
+
285
+ # Add financial report commands to the CLI class
286
+ def self.included(base)
287
+ base.class_eval do
288
+ # Register the financials command
289
+ desc "financials SUBCOMMAND", "Financial reporting commands"
290
+ subcommand "financials", FinancialsCommand
291
+
292
+ # Set the parent class for the subcommand
293
+ FinancialsCommand.parent_class = base
294
+
295
+ # Remove the old methods with underscores if they exist
296
+ remove_method :financials_balancesheet if method_defined?(:financials_balancesheet)
297
+ remove_method :financials_incomestatement if method_defined?(:financials_incomestatement)
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end