rock_books 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/RELEASE_NOTES.md +35 -0
  4. data/assets/fonts/JetBrainsMono-Medium.ttf +0 -0
  5. data/lib/rock_books/cmd_line/command_line_interface.rb +6 -6
  6. data/lib/rock_books/cmd_line/main.rb +1 -9
  7. data/lib/rock_books/documents/book_set.rb +4 -143
  8. data/lib/rock_books/documents/chart_of_accounts.rb +29 -12
  9. data/lib/rock_books/documents/journal.rb +3 -8
  10. data/lib/rock_books/documents/journal_entry.rb +7 -2
  11. data/lib/rock_books/documents/journal_entry_builder.rb +4 -0
  12. data/lib/rock_books/helpers/book_set_loader.rb +3 -3
  13. data/lib/rock_books/reports/balance_sheet.rb +9 -43
  14. data/lib/rock_books/reports/book_set_reporter.rb +218 -0
  15. data/lib/rock_books/reports/data/bs_is_data.rb +61 -0
  16. data/lib/rock_books/reports/data/bs_is_section_data.rb +28 -0
  17. data/lib/rock_books/reports/data/journal_data.rb +37 -0
  18. data/lib/rock_books/reports/data/multidoc_txn_by_account_data.rb +40 -0
  19. data/lib/rock_books/reports/data/multidoc_txn_report_data.rb +39 -0
  20. data/lib/rock_books/reports/data/receipts_report_data.rb +47 -0
  21. data/lib/rock_books/reports/data/tx_one_account_data.rb +37 -0
  22. data/lib/rock_books/reports/helpers/erb_helper.rb +21 -0
  23. data/lib/rock_books/reports/helpers/receipts_hyperlink_converter.rb +47 -0
  24. data/lib/rock_books/reports/helpers/text_report_helper.rb +142 -0
  25. data/lib/rock_books/reports/income_statement.rb +9 -47
  26. data/lib/rock_books/reports/index_html_page.rb +25 -0
  27. data/lib/rock_books/reports/journal_report.rb +72 -0
  28. data/lib/rock_books/reports/multidoc_txn_by_account_report.rb +32 -0
  29. data/lib/rock_books/reports/multidoc_txn_report.rb +25 -0
  30. data/lib/rock_books/reports/receipts_report.rb +6 -55
  31. data/lib/rock_books/reports/templates/html/index.html.erb +144 -0
  32. data/lib/rock_books/reports/templates/html/report_page.html.erb +13 -0
  33. data/lib/rock_books/reports/templates/text/_receipt_section.txt.erb +17 -0
  34. data/lib/rock_books/reports/templates/text/_totals.txt.erb +8 -0
  35. data/lib/rock_books/reports/templates/text/balance_sheet.txt.erb +22 -0
  36. data/lib/rock_books/reports/templates/text/income_statement.txt.erb +22 -0
  37. data/lib/rock_books/reports/templates/text/journal.txt.erb +20 -0
  38. data/lib/rock_books/reports/templates/text/multidoc_txn_by_account_report.txt.erb +30 -0
  39. data/lib/rock_books/reports/templates/text/multidoc_txn_report.txt.erb +24 -0
  40. data/lib/rock_books/reports/templates/text/receipts_report.txt.erb +15 -0
  41. data/lib/rock_books/reports/templates/text/tx_one_account.txt.erb +20 -0
  42. data/lib/rock_books/reports/tx_one_account.rb +10 -45
  43. data/lib/rock_books/types/account.rb +13 -1
  44. data/lib/rock_books/types/account_type.rb +18 -7
  45. data/lib/rock_books/version.rb +2 -1
  46. data/rock_books.gemspec +5 -3
  47. metadata +65 -17
  48. data/lib/rock_books/documents/index.html.erb +0 -156
  49. data/lib/rock_books/documents/receipts.html.erb +0 -54
  50. data/lib/rock_books/helpers/html_helper.rb +0 -29
  51. data/lib/rock_books/reports/multidoc_transaction_report.rb +0 -66
  52. data/lib/rock_books/reports/reporter.rb +0 -118
  53. data/lib/rock_books/reports/transaction_report.rb +0 -105
  54. data/lib/rock_books/reports/tx_by_account.rb +0 -82
@@ -1,54 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <body>
4
-
5
- <h1><%= chart_of_accounts.entity %> -- Receipts</h1>
6
- <p>Reports Generated at <%= DateTime.now.strftime('%Y-%m-%d_%H-%M-%S') %> by RockBooks version <%=RockBooks::VERSION %></p>
7
- <br />
8
-
9
- <% receipts.each %>
10
- <h2>Financial Statements</h2>
11
- <ul>
12
- <li><a href='balance_sheet.html'>Balance Sheet</a></li>
13
- <li><a href='income_statement.html'>Income Statement</a></li>
14
- </ul>
15
-
16
- <h2>All Transactions</h2>
17
- <ul>
18
- <li><a href="all_txns_by_acct.html">By Account</a></li>
19
- <li><a href="all_txns_by_amount.html">By Amount</a></li>
20
- <li><a href="all_txns_by_date.html">By Date</a></li>
21
- </ul>
22
-
23
- <h2>Journals</h2>
24
- <ul>
25
- <% journals.each do |journal|
26
- filespec = journal.short_name + '.html'
27
- caption = "#{journal.title} -- #{journal.short_name} -- #{journal.account_code}"
28
- %>
29
- <li><a href="<%= filespec %>"><%= caption %></a></li>
30
- <% end %>
31
- </ul>
32
-
33
- <h2>Individual Accounts</h2>
34
- <ul>
35
-
36
- <%
37
- chart_of_accounts.accounts.each do |account|
38
- filespec = File.join('single-account', "acct_#{account.code}.html")
39
- caption = "#{account.name} (#{account.code})"
40
- %>
41
- <li><a href="<%= filespec %>"><%= caption %></a></li>
42
- <% end %>
43
- </ul>
44
-
45
-
46
- <h2>Receipts</h2>
47
- <ul>
48
- <% if run_options.do_receipts %>
49
- <li><a href="receipts.html">Missing and Existing Receipts</a></li>
50
- <% end %>
51
- </ul>
52
-
53
- </body>
54
- </html>
@@ -1,29 +0,0 @@
1
- module RockBooks
2
- module HtmlHelper
3
-
4
- module_function
5
-
6
- def self.convert_receipts_to_hyperlinks(html_text)
7
- html_lines = html_text.split("\n")
8
- replacements_made = false
9
-
10
- html_lines.each_with_index do |line, index|
11
- matches = /Receipt:\s*(.*?)</.match(line)
12
- if matches
13
- receipt_filespec = matches[1]
14
- line_with_hyperlink = line.gsub( \
15
- /Receipt:\s*#{receipt_filespec}/, \
16
- %Q{Receipt: <a href="../../../receipts/#{receipt_filespec}">#{receipt_filespec}</a>})
17
- html_lines[index] = line_with_hyperlink
18
- replacements_made = true
19
- end
20
- end
21
-
22
- if replacements_made
23
- html_text = html_lines.join("\n")
24
- end
25
-
26
- [html_text, replacements_made]
27
- end
28
- end
29
- end
@@ -1,66 +0,0 @@
1
- require_relative '../documents/journal'
2
- require_relative 'reporter'
3
- require_relative 'report_context'
4
-
5
- module RockBooks
6
-
7
- class MultidocTransactionReport
8
-
9
- include Reporter
10
-
11
- attr_accessor :context
12
-
13
- SORT_BY_VALID_OPTIONS = %i(date_and_account amount)
14
-
15
- def initialize(report_context)
16
- @context = report_context
17
- end
18
-
19
-
20
- def generate_header(sort_by)
21
- lines = [banner_line]
22
- lines << center(context.entity || 'Unspecified Entity')
23
- lines << center('Multi Document Transaction Report')
24
- lines << center('Sorted by Amount Descending') if sort_by == :amount
25
- lines << ''
26
- lines << center('Source Documents:')
27
- lines << ''
28
- context.journals.each do |document|
29
- short_name = SHORT_NAME_FORMAT_STRING % document.short_name
30
- lines << center("#{short_name} -- #{document.title}")
31
- end
32
- lines << banner_line
33
- lines << ''
34
- lines << ' Date Document Amount Account'
35
- lines << ' ---- -------- ------ -------'
36
- lines.join("\n") << "\n\n"
37
- end
38
-
39
-
40
- def generate_report(filter = nil, sort_by = :date_and_account)
41
- unless SORT_BY_VALID_OPTIONS.include?(sort_by)
42
- raise Error.new("sort_by option '#{sort_by}' not in valid choices of #{SORT_BY_VALID_OPTIONS}.")
43
- end
44
-
45
- entries = Journal.entries_in_documents(context.journals, filter)
46
-
47
- if sort_by == :amount
48
- JournalEntry.sort_entries_by_amount_descending!(entries)
49
- end
50
-
51
- sio = StringIO.new
52
- sio << generate_header(sort_by)
53
- entries.each { |entry| sio << format_multidoc_entry(entry) << "\n" }
54
-
55
- totals = AcctAmount.aggregate_amounts_by_account(JournalEntry.entries_acct_amounts(entries))
56
- sio << generate_and_format_totals('Totals', totals)
57
-
58
- sio << "\n"
59
- sio.string
60
- end
61
-
62
-
63
- alias_method :to_s, :generate_report
64
- alias_method :call, :generate_report
65
- end
66
- end
@@ -1,118 +0,0 @@
1
- require_relative '../documents/journal_entry'
2
-
3
- module RockBooks
4
- module Reporter
5
-
6
- module_function
7
-
8
- SHORT_NAME_MAX_LENGTH = 16
9
-
10
- SHORT_NAME_FORMAT_STRING = "%#{SHORT_NAME_MAX_LENGTH}.#{SHORT_NAME_MAX_LENGTH}s"
11
-
12
-
13
- def page_width
14
- context.page_width || 80
15
- end
16
-
17
-
18
- def format_account_code(code)
19
- "%*.*s" % [max_account_code_length, max_account_code_length, code]
20
- end
21
-
22
-
23
- def account_code_name_type_string(account)
24
- "#{account.code} -- #{account.name} (#{account.type.to_s.capitalize})"
25
- end
26
-
27
-
28
- def format_amount(amount)
29
- "%9.2f" % amount
30
- end
31
-
32
-
33
- # e.g. " 117.70 tr.mileage Travel - Mileage Allowance"
34
- def format_acct_amount(acct_amount)
35
- "%s %s %s" % [
36
- format_amount(acct_amount.amount),
37
- format_account_code(acct_amount.code),
38
- context.chart_of_accounts.name_for_code(acct_amount.code)
39
- ]
40
- end
41
-
42
-
43
- def banner_line
44
- @banner_line ||= '-' * page_width
45
- end
46
-
47
-
48
- def center(string)
49
- indent = (page_width - string.length) / 2
50
- indent = 0 if indent < 0
51
- (' ' * indent) + string
52
- end
53
-
54
-
55
- def max_account_code_length
56
- @max_account_code_length ||= context.chart_of_accounts.max_account_code_length
57
- end
58
-
59
-
60
- def generate_and_format_totals(section_caption, totals)
61
- output = section_caption
62
- output << "\n#{'-' * section_caption.length}\n\n"
63
- format_string = "%12.2f %-#{context.chart_of_accounts.max_account_code_length}s %s\n"
64
- totals.keys.sort.each do |account_code|
65
- account_name = context.chart_of_accounts.name_for_code(account_code)
66
- account_total = totals[account_code]
67
- output << format_string % [account_total, account_code, account_name]
68
- end
69
-
70
- output << "------------\n"
71
- output << "%12.2f\n" % totals.values.sum.round(2)
72
- output
73
- end
74
-
75
-
76
- def generate_account_type_section(section_caption, totals, section_type, need_to_reverse_sign)
77
- account_codes_this_section = context.chart_of_accounts.account_codes_of_type(section_type)
78
-
79
- totals_this_section = totals.select do |account_code, _amount|
80
- account_codes_this_section.include?(account_code)
81
- end
82
-
83
- if need_to_reverse_sign
84
- totals_this_section.each { |code, amount| totals_this_section[code] = -amount }
85
- end
86
-
87
- section_total_amount = totals_this_section.map { |aa| aa.last }.sum
88
-
89
- output = generate_and_format_totals(section_caption, totals_this_section)
90
- [ output, section_total_amount ]
91
- end
92
-
93
-
94
- def format_multidoc_entry(entry)
95
- acct_amounts = entry.acct_amounts
96
-
97
- # "2017-10-29 hsbc_visa":
98
- output = entry.date.to_s << ' ' << (SHORT_NAME_FORMAT_STRING % entry.doc_short_name) << ' '
99
-
100
- indent = ' ' * output.length
101
-
102
- output << format_acct_amount(acct_amounts.first) << "\n"
103
-
104
- acct_amounts[1..-1].each do |acct_amount|
105
- output << indent << format_acct_amount(acct_amount) << "\n"
106
- end
107
-
108
- if entry.description && entry.description.length > 0
109
- output << entry.description
110
- end
111
-
112
- output
113
- end
114
-
115
- end
116
- end
117
-
118
-
@@ -1,105 +0,0 @@
1
- require_relative 'reporter'
2
- require_relative 'report_context'
3
-
4
- module RockBooks
5
-
6
- class TransactionReport
7
-
8
- include Reporter
9
-
10
- attr_accessor :journal, :context
11
-
12
-
13
- def initialize(journal, report_context)
14
- @journal = journal
15
- @context = report_context
16
- end
17
-
18
-
19
- def generate_header
20
-
21
- code = journal.account_code
22
- name = journal.chart_of_accounts.name_for_code(code)
23
- title = "Transactions for Account ##{code} -- #{name}"
24
-
25
- lines = [banner_line]
26
- lines << center(context.entity || 'Unspecified Entity')
27
- lines << center(journal.title) if journal.title && journal.title.length > 0
28
- lines << center(title)
29
- lines << banner_line
30
- lines << ''
31
- lines << ''
32
- lines << ''
33
- lines.join("\n")
34
- end
35
-
36
-
37
- def format_entry_first_acct_amount(entry)
38
- entry.date.to_s \
39
- << ' ' \
40
- << format_acct_amount(entry.acct_amounts.last) \
41
- << "\n"
42
- end
43
-
44
-
45
- # Formats an entry like this, with entry description added on additional line(s) if it exists:
46
- # 2018-05-21 $120.00 701 Office Supplies
47
- def format_entry_no_split(entry)
48
- output = format_entry_first_acct_amount(entry)
49
-
50
- if entry.description && entry.description.length > 0
51
- output << entry.description
52
- end
53
- output
54
- end
55
-
56
-
57
- # Formats an entry like this, with entry description added on additional line(s) if it exists::
58
- # 2018-05-21 $120.00 95.00 701 Office Supplies
59
- # 25.00 751 Gift to Customer
60
- def format_entry_with_split(entry)
61
- output = format_entry_first_acct_amount(entry)
62
- indent = ' ' * 12
63
-
64
- entry.acct_amounts[1..-1].each do |acct_amount|
65
- output << indent << format_acct_amount(acct_amount) << "\n"
66
- end
67
-
68
- if entry.description && entry.description.length > 0
69
- output << entry.description
70
- end
71
- end
72
-
73
-
74
- def format_entry(entry)
75
- if entry.acct_amounts.size > 2
76
- format_entry_with_split(entry)
77
- else
78
- format_entry_no_split(entry)
79
- end
80
- end
81
-
82
-
83
- def generate_report(filter = nil)
84
- sio = StringIO.new
85
- sio << generate_header
86
-
87
- entries = journal.entries
88
- if filter
89
- entries = entries.select { |entry| filter.(entry) }
90
- end
91
-
92
- entries.each do |entry|
93
- sio << format_entry(entry) << "\n"
94
- end
95
- totals = AcctAmount.aggregate_amounts_by_account(JournalEntry.entries_acct_amounts(entries))
96
- sio << generate_and_format_totals('Totals', totals)
97
- sio.string
98
- end
99
-
100
-
101
- alias_method :to_s, :generate_report
102
- alias_method :call, :generate_report
103
- end
104
-
105
- end
@@ -1,82 +0,0 @@
1
- require_relative '../documents/chart_of_accounts'
2
- require_relative '../documents/journal'
3
- require_relative 'reporter'
4
- require_relative 'report_context'
5
-
6
- module RockBooks
7
-
8
- class TxByAccount
9
-
10
- include Reporter
11
-
12
- attr_accessor :context
13
-
14
-
15
- def initialize(report_context)
16
- @context = report_context
17
- end
18
-
19
-
20
- def generate_header
21
- lines = [banner_line]
22
- lines << center(context.entity || 'Unspecified Entity')
23
- lines << center("Transactions by Account")
24
- lines << banner_line
25
- lines << ''
26
- lines << ''
27
- lines << ''
28
- lines.join("\n")
29
- end
30
-
31
-
32
- def account_header(account, account_total)
33
- total_string = "%.2f" % account_total
34
- title = "Total: #{total_string} -- #{account_code_name_type_string(account)}"
35
-
36
- <<~HEREDOC
37
- #{banner_line}
38
- #{center(title)}
39
- #{banner_line}
40
-
41
- HEREDOC
42
- end
43
-
44
-
45
- def account_total_line(account_code, account_total)
46
- account_name = context.chart_of_accounts.name_for_code(account_code)
47
- "%.2f Total for account: %s - %s" % [account_total, account_code, account_name]
48
- end
49
-
50
-
51
- def generate_report
52
- output = generate_header
53
-
54
- all_entries = Journal.entries_in_documents(context.journals)
55
-
56
- context.chart_of_accounts.accounts.each do |account|
57
- code = account.code
58
- account_entries = JournalEntry.entries_containing_account_code(all_entries, code)
59
- account_total = JournalEntry.total_for_code(account_entries, code)
60
- output << account_header(account, account_total)
61
-
62
- account_entries.each do |entry|
63
- output << format_multidoc_entry(entry) << "\n"
64
- output << "\n" if entry.description && entry.description.length > 0
65
- end
66
- output << account_total_line(code, account_total) << "\n"
67
- output << "\n\n\n"
68
- end
69
-
70
- totals = AcctAmount.aggregate_amounts_by_account(JournalEntry.entries_acct_amounts(all_entries))
71
- output << generate_and_format_totals('Totals', totals)
72
-
73
- output
74
- end
75
-
76
- alias_method :to_s, :generate_report
77
- alias_method :call, :generate_report
78
-
79
-
80
- end
81
-
82
- end