rock_books 0.4.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/RELEASE_NOTES.md +33 -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 -136
  8. data/lib/rock_books/documents/chart_of_accounts.rb +29 -12
  9. data/lib/rock_books/documents/journal.rb +2 -6
  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 +207 -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/html_report_helper.rb +35 -0
  24. data/lib/rock_books/reports/helpers/text_report_helper.rb +134 -0
  25. data/lib/rock_books/reports/income_statement.rb +9 -47
  26. data/lib/rock_books/reports/journal_report.rb +72 -0
  27. data/lib/rock_books/reports/multidoc_txn_by_account_report.rb +32 -0
  28. data/lib/rock_books/reports/multidoc_txn_report.rb +25 -0
  29. data/lib/rock_books/reports/receipts_report.rb +6 -55
  30. data/lib/rock_books/reports/templates/html/index.html.erb +141 -0
  31. data/lib/rock_books/reports/templates/html/report_page.html.erb +12 -0
  32. data/lib/rock_books/reports/templates/text/_receipt_section.txt.erb +17 -0
  33. data/lib/rock_books/reports/templates/text/_totals.txt.erb +8 -0
  34. data/lib/rock_books/reports/templates/text/balance_sheet.txt.erb +21 -0
  35. data/lib/rock_books/reports/templates/text/income_statement.txt.erb +21 -0
  36. data/lib/rock_books/reports/templates/text/journal.txt.erb +20 -0
  37. data/lib/rock_books/reports/templates/text/multidoc_txn_by_account_report.txt.erb +28 -0
  38. data/lib/rock_books/reports/templates/text/multidoc_txn_report.txt.erb +22 -0
  39. data/lib/rock_books/reports/templates/text/receipts_report.txt.erb +13 -0
  40. data/lib/rock_books/reports/templates/text/tx_one_account.txt.erb +18 -0
  41. data/lib/rock_books/reports/tx_one_account.rb +10 -45
  42. data/lib/rock_books/types/account.rb +13 -1
  43. data/lib/rock_books/types/account_type.rb +18 -7
  44. data/lib/rock_books/version.rb +1 -1
  45. data/rock_books.gemspec +5 -3
  46. metadata +64 -16
  47. data/lib/rock_books/documents/index.html.erb +0 -156
  48. data/lib/rock_books/documents/receipts.html.erb +0 -54
  49. data/lib/rock_books/reports/multidoc_transaction_report.rb +0 -66
  50. data/lib/rock_books/reports/reporter.rb +0 -118
  51. data/lib/rock_books/reports/transaction_report.rb +0 -105
  52. data/lib/rock_books/reports/tx_by_account.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce2a320fd7ced495ee7c8ab111413b0b368928c5da80dcb0885d6d1b4bbb22a3
4
- data.tar.gz: 607db6692e994e1a4f3a3400cb56a6827e176c707f917e81cb065cf6655cfc8f
3
+ metadata.gz: d37feeb3b20b730f06340d5e026dca81b564dc7cd6cb174a2d8bd0ffcbcb6441
4
+ data.tar.gz: 41a1e166136c536fde00512d8e624ce49c894766cd5f18ed673f8f67fdaf8908
5
5
  SHA512:
6
- metadata.gz: d29c0671a29843c0dbaeb4521e0d4b1fd61427476cd96ae726a651693975bedd129e82ddd51c1486318878b22fac199733cd2d7fc984d602a9abd682dc652c04
7
- data.tar.gz: 6cb2feaf8126241c2211f8188a500cadc6e88302b0671c495060a408075dadf34a3820370348417a5bc2c9d72537c9a0301a77b05f89c392af51b35ad1661bea
6
+ metadata.gz: 41e4511367107b2be92ff53eb28c42635884413c3fa91c010eee41a53da6ac94b3b9a8f7eccf3522610eee287008944d318f3250dcdc349544491dcb13dd515c
7
+ data.tar.gz: 45239916850fd6102b50c8ca719a2d336b0704a116c0c4f2af5014c5a061c8b6a6147f95811a88fe13f51e23f345baeb3eda16b0e789b9682596945b92ddabb4
data/README.md CHANGED
@@ -31,7 +31,10 @@ To simplify its implementation, RockBooks assumes some conventions:
31
31
  * `statements` - statements from banks, etc.
32
32
  * `worksheets` - spreadsheets, etc., e.g. mileage and per diem calculations
33
33
 
34
-
34
+ #### Supported Operating Systems
35
+
36
+ At this time, RockBooks is tested only on Mac OS and Linux. However, it will probably work fine on Windows.
37
+
35
38
  #### Text Files as Input
36
39
 
37
40
  Instead of a web interface, data input is done in plain text files. This isn't as fancy but has the following advantages:
@@ -91,7 +94,6 @@ As a product written by a single developer in his spare time, RockBooks lacks so
91
94
  * On the fly data validation
92
95
  * Data entry conveniences such as drop down selection lists for data such as accounts
93
96
  * Fancy reporting and graphing -- however, RockBooks' bringing links to all the entity's documentation and output into a single web page may well be more useful
94
- * At this time, RockBooks is only tested on Macs. The input files are plain text files and could be created on any OS, but the validation and report generation might not work. Get in touch with me if you are using a different OS and want to use RockBooks, and are willing and available to test my changes. Linux in particular should be an easy port.
95
97
 
96
98
 
97
99
  ## Installation
@@ -1,3 +1,36 @@
1
+ ### v0.7.1
2
+
3
+ * Refactor report helpers.
4
+ * Improve/reduce text output during reporting.
5
+ * Add title to HTML reports.
6
+ * Minor fixes.
7
+
8
+
9
+ ### v0.7.0
10
+
11
+ * Dependencies on external commands in Linux and Mac OS for generating PDF and HTML files has been eliminated,
12
+ using the prawn gem for PDF and simple ERB templating for HTML.
13
+ * Massive refactoring of reports to separate data generation from presentation.
14
+ * Reports are now computed and then written one at a time. Previously they were all computed, then all written.
15
+ * ERB is now used for generating text reports.
16
+ * Fix receipt hyperlinks in HTML output.
17
+ * Improve some error output.
18
+ * Various minor improvements and bug fixes.
19
+
20
+
21
+ ### v0.6.1
22
+
23
+ * Linux PDF generation fixed by using wkhtmltopdf instead of cupsfilter.
24
+
25
+ ### v0.6.0
26
+
27
+ * Linux support added!
28
+
29
+ ### v0.5.0
30
+
31
+ * Add receipt hyperlinks to HTML output.
32
+
33
+
1
34
  ## v0.4.0
2
35
 
3
36
  * Sort unused receipts alphanumerically.
@@ -4,7 +4,7 @@ require 'ostruct'
4
4
 
5
5
  require_relative '../../rock_books'
6
6
  require_relative '../version'
7
- require_relative '../reports/reporter'
7
+ require_relative '../reports/helpers/text_report_helper'
8
8
  require_relative '../helpers/book_set_loader'
9
9
 
10
10
  module RockBooks
@@ -105,7 +105,7 @@ When in interactive shell mode:
105
105
  end
106
106
  end
107
107
 
108
- validate_receipts_dir = -> do
108
+ validate_receipt_dir = -> do
109
109
  File.directory?(options.receipt_dir) ? nil : \
110
110
  "Receipts directory '#{options.receipt_dir}' does not exist. "
111
111
  end
@@ -114,7 +114,7 @@ When in interactive shell mode:
114
114
  output << validate_input_dir.()
115
115
  output << validate_output_dir.()
116
116
  if run_options.do_receipts
117
- output << validate_receipts_dir.()
117
+ output << validate_receipt_dir.()
118
118
  end
119
119
 
120
120
  output.compact!
@@ -298,9 +298,9 @@ When in interactive shell mode:
298
298
 
299
299
 
300
300
  def cmd_d
301
- book_set.all_reports($filter).each do |short_name, report_text|
301
+ book_set.all_reports($filter).each do |short_name, text_report|
302
302
  puts "#{short_name}:\n\n"
303
- puts report_text
303
+ puts text_report
304
304
  puts "\n\n\n"
305
305
  end
306
306
  nil
@@ -358,7 +358,7 @@ When in interactive shell mode:
358
358
  end
359
359
 
360
360
  def cmd_w
361
- book_set.all_reports_to_files(run_options.output_dir, $filter)
361
+ BookSetReporter.new(book_set, run_options.output_dir, $filter).generate
362
362
  nil
363
363
  end
364
364
 
@@ -1,4 +1,4 @@
1
- require 'awesome_print'
1
+ require 'amazing_print'
2
2
  require 'optparse'
3
3
  require 'pry'
4
4
  require 'shellwords'
@@ -67,10 +67,6 @@ class Main
67
67
  options.verbose_mode = v
68
68
  end
69
69
 
70
- parser.on('-y', '--[no-]say', 'Say error messages.') do |v|
71
- options.say = v
72
- end
73
-
74
70
  parser.on('', '--[no-]receipts', 'Include report on existing and missing receipts.') do |v|
75
71
  options.do_receipts = v
76
72
  end
@@ -101,10 +97,6 @@ class Main
101
97
 
102
98
  HEREDOC
103
99
 
104
- if run_options.say
105
- `say #{error}`
106
- end
107
-
108
100
  exit(-1)
109
101
  binding.pry
110
102
  raise error
@@ -1,17 +1,11 @@
1
- require 'awesome_print'
1
+ require 'amazing_print'
2
+ require 'os'
2
3
 
3
4
  require_relative 'chart_of_accounts'
4
5
  require_relative 'journal'
5
6
  require_relative '../filters/journal_entry_filters' # for shell mode
6
7
  require_relative '../helpers/parse_helper'
7
- require_relative '../reports/balance_sheet'
8
- require_relative '../reports/income_statement'
9
- require_relative '../reports/multidoc_transaction_report'
10
- require_relative '../reports/receipts_report'
11
- require_relative '../reports/report_context'
12
- require_relative '../reports/transaction_report'
13
- require_relative '../reports/tx_by_account'
14
- require_relative '../reports/tx_one_account'
8
+ require_relative '../reports/book_set_reporter'
15
9
 
16
10
  require 'erb'
17
11
  require 'open3'
@@ -28,101 +22,6 @@ module RockBooks
28
22
  end
29
23
 
30
24
 
31
- def report_context
32
- @report_context ||= ReportContext.new(chart_of_accounts, journals, 80)
33
- end
34
-
35
-
36
- def all_reports(filter = nil)
37
-
38
- context = report_context
39
- report_hash = context.journals.each_with_object({}) do |journal, report_hash|
40
- key = journal.short_name.to_sym
41
- report_hash[key] = TransactionReport.new(journal, context).call(filter)
42
- end
43
- report_hash[:all_txns_by_date] = MultidocTransactionReport.new(context).call(filter)
44
- report_hash[:all_txns_by_amount] = MultidocTransactionReport.new(context).call(filter, :amount)
45
- report_hash[:all_txns_by_acct] = TxByAccount.new(context).call
46
- report_hash[:balance_sheet] = BalanceSheet.new(context).call
47
- report_hash[:income_statement] = IncomeStatement.new(context).call
48
-
49
- if run_options.do_receipts
50
- report_hash[:receipts] = ReceiptsReport.new(context, *missing_existing_unused_receipts).call
51
- end
52
-
53
- chart_of_accounts.accounts.each do |account|
54
- key = ('acct_' + account.code).to_sym
55
- report = TxOneAccount.new(context, account.code).call
56
- report_hash[key] = report
57
- end
58
-
59
- report_hash
60
- end
61
-
62
-
63
- def run_command(command)
64
- puts "\n----\nRunning command: #{command}"
65
- stdout, stderr, status = Open3.capture3(command)
66
- puts "Status was #{status}."
67
- unless stdout.size == 0
68
- puts "\nStdout was:\n\n#{stdout}"
69
- end
70
- unless stderr.size == 0
71
- puts "\nStderr was:\n\n#{stderr}"
72
- end
73
- puts
74
- stdout
75
- end
76
-
77
-
78
- def all_reports_to_files(directory = '.', filter = nil)
79
- reports = all_reports(filter)
80
-
81
- create_directories = -> do
82
- %w(txt pdf html).each do |format|
83
- dir = File.join(directory, format, SINGLE_ACCT_SUBDIR)
84
- FileUtils.mkdir_p(dir)
85
- end
86
- end
87
-
88
- # "./pdf/short_name.pdf" or "./pdf/single_account/short_name.pdf"
89
- build_filespec = ->(directory, short_name, file_format) do
90
- fragments = [directory, file_format, "#{short_name}.#{file_format}"]
91
- is_acct_report = /^acct_/.match(short_name)
92
- if is_acct_report
93
- fragments.insert(2, SINGLE_ACCT_SUBDIR)
94
- end
95
- File.join(*fragments)
96
- end
97
-
98
- create_index_html = -> do
99
- filespec = build_filespec.(directory, 'index', 'html')
100
- File.write(filespec, index_html_content)
101
- puts "Created index.html"
102
- end
103
-
104
- write_reports = ->do
105
-
106
- reports.each do |short_name, report_text|
107
- txt_filespec = build_filespec.(directory, short_name, 'txt')
108
- html_filespec = build_filespec.(directory, short_name, 'html')
109
- pdf_filespec = build_filespec.(directory, short_name, 'pdf')
110
-
111
- File.write(txt_filespec, report_text)
112
- # Use smaller size for the PDF but larger size for the web pages:
113
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 11 #{txt_filespec} -output #{html_filespec}")
114
- run_command("cupsfilter #{html_filespec} > #{pdf_filespec}")
115
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 14 #{txt_filespec} -output #{html_filespec}")
116
- puts "Created reports in txt, html, and pdf for #{"%-20s" % short_name} at #{File.dirname(txt_filespec)}.\n\n\n"
117
- end
118
- end
119
-
120
- create_directories.()
121
- create_index_html.()
122
- write_reports.()
123
- end
124
-
125
-
126
25
  def journal_names
127
26
  journals.map(&:short_name)
128
27
  end
@@ -139,37 +38,6 @@ module RockBooks
139
38
  @all_entries ||= Journal.entries_in_documents(journals)
140
39
  end
141
40
 
142
-
143
- def receipt_full_filespec(receipt_filespec)
144
- File.join(run_options.receipt_dir, receipt_filespec)
145
- end
146
-
147
-
148
- def missing_existing_unused_receipts
149
- missing = []
150
- existing = []
151
- unused = Dir['receipts/**/*'].select { |s| File.file?(s) }.sort # Remove files as they are found
152
- unused.map! { |s| "./" + s } # Prepend './' to match the data
153
-
154
- all_entries.each do |entry|
155
- entry.receipts.each do |receipt|
156
- filespec = receipt_full_filespec(receipt)
157
- unused.delete(filespec)
158
- file_exists = File.file?(filespec)
159
- list = (file_exists ? existing : missing)
160
- list << { receipt: receipt, journal: entry.doc_short_name }
161
- end
162
- end
163
- [missing, existing, unused]
164
- end
165
-
166
- def index_html_content
167
- erb_filespec = File.join(File.dirname(__FILE__), 'index.html.erb')
168
- erb = ERB.new(File.read(erb_filespec))
169
- erb.result_with_hash(
170
- journals: journals,
171
- chart_of_accounts: chart_of_accounts,
172
- run_options: run_options)
173
- end
174
41
  end
175
42
  end
43
+
@@ -1,3 +1,4 @@
1
+ require 'stringio'
1
2
  require_relative '../types/account'
2
3
  require_relative '../types/account_type'
3
4
  require_relative '../errors/error'
@@ -10,8 +11,8 @@ class ChartOfAccounts
10
11
  REQUIRED_FIELDS.each { |field| attr_reader(field) }
11
12
 
12
13
 
13
- def self.from_file(file)
14
- self.new(File.readlines(file).map(&:chomp))
14
+ def self.from_file(filespec)
15
+ self.new(File.readlines(filespec).map(&:chomp), filespec)
15
16
  end
16
17
 
17
18
 
@@ -20,11 +21,16 @@ class ChartOfAccounts
20
21
  end
21
22
 
22
23
 
23
- def initialize(input_lines)
24
+ def initialize(input_lines, filespec = nil)
25
+ @filespec = filespec
24
26
  @accounts = []
25
- input_lines.each { |line| parse_line(line) }
27
+ parse_lines(input_lines)
26
28
  # TODO: Add validation for required fields.
29
+ check_for_missing_fields
30
+ end
31
+
27
32
 
33
+ def check_for_missing_fields
28
34
  missing_fields = REQUIRED_FIELDS.select do |field|
29
35
  instance_variable_get("@#{field}").nil?
30
36
  end
@@ -35,6 +41,19 @@ class ChartOfAccounts
35
41
  end
36
42
 
37
43
 
44
+ def parse_lines(input_lines)
45
+ input_lines.each_with_index do |line, line_num|
46
+ begin
47
+ parse_line(line)
48
+ rescue => e
49
+ file_message_fragment = (@filespec ? " in file '#{@filespec}'" : '')
50
+ puts "Error parsing chart of accounts#{file_message_fragment}. Bad line is line ##{line_num}, text is:\n#{line}\n\n"
51
+ raise
52
+ end
53
+ end
54
+ end
55
+
56
+
38
57
  def parse_date(date_string)
39
58
  # TODO: Add better handling for this error.
40
59
  # begin
@@ -69,16 +88,14 @@ class ChartOfAccounts
69
88
  rest = matcher[2]
70
89
 
71
90
  matcher = rest.match(/^(\S+)\s+(.*)$/)
91
+
72
92
  account_type_token = matcher[1]
73
- account_type = AccountType.to_type(account_type_token).symbol
93
+ account_type = AccountType.letter_to_type(account_type_token)
74
94
 
75
95
  name = matcher[2]
76
96
 
77
- accounts << Account.new(code, account_type, name)
97
+ accounts << Account.new(code, account_type.symbol, name)
78
98
  end
79
- rescue => e
80
- puts "Error parsing chart of accounts. Line text is:\n#{line}\n\n"
81
- raise
82
99
  end
83
100
 
84
101
  end
@@ -105,7 +122,7 @@ class ChartOfAccounts
105
122
 
106
123
 
107
124
  def report_string
108
- result = ''
125
+ result = StringIO.new
109
126
 
110
127
  if title
111
128
  result << title << "\n\n"
@@ -113,9 +130,9 @@ class ChartOfAccounts
113
130
 
114
131
  code_width = @accounts.inject(0) { |width, a| width = [width, a.code.length].max }
115
132
  format_string = "%-#{code_width}s %-10.10s %s\n"
116
- accounts.each { |a| result << (format_string % [a.code, a.type.to_s, a.name]) }
133
+ accounts.each { |a| result << sprintf(format_string, a.code, a.type.to_s, a.name) }
117
134
 
118
- result
135
+ result.string
119
136
  end
120
137
 
121
138
 
@@ -7,7 +7,7 @@ require_relative '../types/acct_amount'
7
7
  require_relative '../types/journal_entry_context'
8
8
  require_relative 'journal_entry'
9
9
  require_relative 'journal_entry_builder'
10
- require_relative '../reports/reporter'
10
+ require_relative '../reports/helpers/text_report_helper'
11
11
 
12
12
  module RockBooks
13
13
 
@@ -45,9 +45,7 @@ class Journal
45
45
  end
46
46
 
47
47
 
48
-
49
-
50
- def self.acct_amounts_in_documents(documents, entries_filter = nil, acct_amounts_filter = nil)
48
+ def self.acct_amounts_in_documents(documents, entries_filter = nil, acct_amounts_filter = nil)
51
49
  entries = entries_in_documents(documents, entries_filter)
52
50
 
53
51
  acct_amounts = entries.each_with_object([]) do |entry, acct_amounts|
@@ -72,7 +70,6 @@ class Journal
72
70
  @short_name = short_name
73
71
  @entries = []
74
72
  @date_prefix = ''
75
- @title = ''
76
73
  input_lines.each_with_index do |line, linenum|
77
74
  context = JournalEntryContext.new(self, linenum + 1, line)
78
75
  parse_line(context)
@@ -131,7 +128,6 @@ class Journal
131
128
  end
132
129
 
133
130
 
134
-
135
131
  def acct_amounts
136
132
  entries.each_with_object([]) { |entry, acct_amounts| acct_amounts << entry.acct_amounts }.flatten
137
133
  end
@@ -32,12 +32,17 @@ class JournalEntry < Struct.new(:date, :acct_amounts, :doc_short_name, :descript
32
32
 
33
33
  def self.sort_entries_by_amount_descending!(entries)
34
34
  entries.sort_by! do |entry|
35
- [entry.total_absolute_value, entry.doc_short_name]
35
+ [-entry.total_absolute_value, entry.doc_short_name]
36
36
  end
37
- entries.reverse!
38
37
  end
39
38
 
40
39
 
40
+ def self.sort_entries_by_date!(entries)
41
+ entries.sort_by! { |entry| [entry.date, entry.doc_short_name] }
42
+ end
43
+
44
+
45
+
41
46
  def total_for_code(account_code)
42
47
  acct_amounts_with_code(account_code).map(&:amount).sum
43
48
  end