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.
- checksums.yaml +7 -0
- data/.env_test +1 -0
- data/.gitignore +24 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +90 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +26 -0
- data/Gemfile.lock +140 -0
- data/LICENSE +21 -0
- data/LINTING_REPORT.md +118 -0
- data/README.md +244 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/mercury +17 -0
- data/bin/setup +8 -0
- data/lib/mercury_banking/api.rb +184 -0
- data/lib/mercury_banking/cli/accounts.rb +61 -0
- data/lib/mercury_banking/cli/base.rb +68 -0
- data/lib/mercury_banking/cli/financials.rb +302 -0
- data/lib/mercury_banking/cli/reconciliation.rb +406 -0
- data/lib/mercury_banking/cli/reports.rb +265 -0
- data/lib/mercury_banking/cli/transactions.rb +222 -0
- data/lib/mercury_banking/cli.rb +209 -0
- data/lib/mercury_banking/formatters/export_formatter.rb +306 -0
- data/lib/mercury_banking/formatters/table_formatter.rb +133 -0
- data/lib/mercury_banking/multi.rb +135 -0
- data/lib/mercury_banking/recipient.rb +29 -0
- data/lib/mercury_banking/reconciliation.rb +139 -0
- data/lib/mercury_banking/reports/balance_sheet.rb +586 -0
- data/lib/mercury_banking/reports/reconciliation.rb +307 -0
- data/lib/mercury_banking/utils/command_utils.rb +18 -0
- data/lib/mercury_banking/version.rb +3 -0
- data/lib/mercury_banking.rb +19 -0
- data/mercury_banking.gemspec +37 -0
- metadata +183 -0
@@ -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
|