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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7462246cd8ae62e943c75905486e1732586bd2d9c746c5ea145bd3c751cf637
4
- data.tar.gz: 78fa22e6f1186a552d453340d2ac052e342f959ea1fb58c9a8ed6fc0ea4ed37b
3
+ metadata.gz: ebc94a9d98f612b4c8c13ed48739004619b4bc4d82d484314f50001d6ee784b6
4
+ data.tar.gz: c565f0945fbecc80f4af87afc67f57a9d73e293e95f096a02c1bdbcc2e7a1afb
5
5
  SHA512:
6
- metadata.gz: b24be35a05935f8ae333f656d6400523a43a8f118b54cb0d42ecdc64bc405ac1ee5543d4292deef13676f898597ff6548142beb5089dc041fddd3e06bdceb6a0
7
- data.tar.gz: 458d376cbad3a5d420c3908c6eb8a33037539b6d43d28f50ca5f55327d76edcd85b4d243a975fe72efdda40e0ea96b64570d3e3e551ef8678e354be0d05e4232
6
+ metadata.gz: 514102c4e924ee1890a3f0233b7584c63e9a988595e562b7387e2ed696e41ffffd3b48e72fd939442edeef5a146fd473bb9fe0ea2e657d3b3d57bedc748d8472
7
+ data.tar.gz: 67b889a280ed577daa98358b4a927d83bf9339a35d8e9106aa3127776a6028bda7108a55cd7ee3c4164217e8213728c4d742be44b55317e583d7d604151f1a48
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,38 @@
1
+ ### v0.8.0
2
+
3
+ * Add metadata to PDF and HTML reports
4
+ * Add report generation timestamp and accounting period to all reports.
5
+ * Refactoring and cleanup.
6
+
7
+
8
+ ### v0.7.1
9
+
10
+ * Refactor report helpers.
11
+ * Improve/reduce text output during reporting.
12
+ * Add title to HTML reports.
13
+ * Minor fixes.
14
+
15
+
16
+ ### v0.7.0
17
+
18
+ * Dependencies on external commands in Linux and Mac OS for generating PDF and HTML files has been eliminated,
19
+ using the prawn gem for PDF and simple ERB templating for HTML.
20
+ * Massive refactoring of reports to separate data generation from presentation.
21
+ * Reports are now computed and then written one at a time. Previously they were all computed, then all written.
22
+ * ERB is now used for generating text reports.
23
+ * Fix receipt hyperlinks in HTML output.
24
+ * Improve some error output.
25
+ * Various minor improvements and bug fixes.
26
+
27
+
28
+ ### v0.6.1
29
+
30
+ * Linux PDF generation fixed by using wkhtmltopdf instead of cupsfilter.
31
+
32
+ ### v0.6.0
33
+
34
+ * Linux support added!
35
+
1
36
  ### v0.5.0
2
37
 
3
38
  * Add receipt hyperlinks to HTML output.
@@ -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,18 +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
- require_relative '../helpers/html_helper'
7
7
  require_relative '../helpers/parse_helper'
8
- require_relative '../reports/balance_sheet'
9
- require_relative '../reports/income_statement'
10
- require_relative '../reports/multidoc_transaction_report'
11
- require_relative '../reports/receipts_report'
12
- require_relative '../reports/report_context'
13
- require_relative '../reports/transaction_report'
14
- require_relative '../reports/tx_by_account'
15
- require_relative '../reports/tx_one_account'
8
+ require_relative '../reports/book_set_reporter'
16
9
 
17
10
  require 'erb'
18
11
  require 'open3'
@@ -29,107 +22,6 @@ module RockBooks
29
22
  end
30
23
 
31
24
 
32
- def report_context
33
- @report_context ||= ReportContext.new(chart_of_accounts, journals, 80)
34
- end
35
-
36
-
37
- def all_reports(filter = nil)
38
-
39
- context = report_context
40
- report_hash = context.journals.each_with_object({}) do |journal, report_hash|
41
- key = journal.short_name.to_sym
42
- report_hash[key] = TransactionReport.new(journal, context).call(filter)
43
- end
44
- report_hash[:all_txns_by_date] = MultidocTransactionReport.new(context).call(filter)
45
- report_hash[:all_txns_by_amount] = MultidocTransactionReport.new(context).call(filter, :amount)
46
- report_hash[:all_txns_by_acct] = TxByAccount.new(context).call
47
- report_hash[:balance_sheet] = BalanceSheet.new(context).call
48
- report_hash[:income_statement] = IncomeStatement.new(context).call
49
-
50
- if run_options.do_receipts
51
- report_hash[:receipts] = ReceiptsReport.new(context, *missing_existing_unused_receipts).call
52
- end
53
-
54
- chart_of_accounts.accounts.each do |account|
55
- key = ('acct_' + account.code).to_sym
56
- report = TxOneAccount.new(context, account.code).call
57
- report_hash[key] = report
58
- end
59
-
60
- report_hash
61
- end
62
-
63
-
64
- def run_command(command)
65
- puts "\n----\nRunning command: #{command}"
66
- stdout, stderr, status = Open3.capture3(command)
67
- puts "Status was #{status}."
68
- unless stdout.size == 0
69
- puts "\nStdout was:\n\n#{stdout}"
70
- end
71
- unless stderr.size == 0
72
- puts "\nStderr was:\n\n#{stderr}"
73
- end
74
- puts
75
- stdout
76
- end
77
-
78
-
79
- def all_reports_to_files(directory = '.', filter = nil)
80
- reports = all_reports(filter)
81
-
82
- create_directories = -> do
83
- %w(txt pdf html).each do |format|
84
- dir = File.join(directory, format, SINGLE_ACCT_SUBDIR)
85
- FileUtils.mkdir_p(dir)
86
- end
87
- end
88
-
89
- # "./pdf/short_name.pdf" or "./pdf/single_account/short_name.pdf"
90
- build_filespec = ->(directory, short_name, file_format) do
91
- fragments = [directory, file_format, "#{short_name}.#{file_format}"]
92
- is_acct_report = /^acct_/.match(short_name)
93
- if is_acct_report
94
- fragments.insert(2, SINGLE_ACCT_SUBDIR)
95
- end
96
- File.join(*fragments)
97
- end
98
-
99
- create_index_html = -> do
100
- filespec = build_filespec.(directory, 'index', 'html')
101
- File.write(filespec, index_html_content)
102
- puts "Created index.html"
103
- end
104
-
105
- write_reports = ->do
106
-
107
- reports.each do |short_name, report_text|
108
- txt_filespec = build_filespec.(directory, short_name, 'txt')
109
- html_filespec = build_filespec.(directory, short_name, 'html')
110
- pdf_filespec = build_filespec.(directory, short_name, 'pdf')
111
-
112
- File.write(txt_filespec, report_text)
113
- # Use smaller size for the PDF but larger size for the web pages:
114
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 11 #{txt_filespec} -output #{html_filespec}")
115
- run_command("cupsfilter #{html_filespec} > #{pdf_filespec}")
116
- run_command("textutil -convert html -font 'Courier New Bold' -fontsize 14 #{txt_filespec} -output #{html_filespec}")
117
-
118
- hyperlinkized_text, replacements_made = HtmlHelper.convert_receipts_to_hyperlinks(File.read(html_filespec))
119
- if replacements_made
120
- File.write(html_filespec, hyperlinkized_text)
121
- end
122
-
123
- puts "Created reports in txt, html, and pdf for #{"%-20s" % short_name} at #{File.dirname(txt_filespec)}.\n\n\n"
124
- end
125
- end
126
-
127
- create_directories.()
128
- create_index_html.()
129
- write_reports.()
130
- end
131
-
132
-
133
25
  def journal_names
134
26
  journals.map(&:short_name)
135
27
  end
@@ -146,37 +38,6 @@ module RockBooks
146
38
  @all_entries ||= Journal.entries_in_documents(journals)
147
39
  end
148
40
 
149
-
150
- def receipt_full_filespec(receipt_filespec)
151
- File.join(run_options.receipt_dir, receipt_filespec)
152
- end
153
-
154
-
155
- def missing_existing_unused_receipts
156
- missing = []
157
- existing = []
158
- unused = Dir['receipts/**/*'].select { |s| File.file?(s) }.sort # Remove files as they are found
159
- unused.map! { |s| "./" + s } # Prepend './' to match the data
160
-
161
- all_entries.each do |entry|
162
- entry.receipts.each do |receipt|
163
- filespec = receipt_full_filespec(receipt)
164
- unused.delete(filespec)
165
- file_exists = File.file?(filespec)
166
- list = (file_exists ? existing : missing)
167
- list << { receipt: receipt, journal: entry.doc_short_name }
168
- end
169
- end
170
- [missing, existing, unused]
171
- end
172
-
173
- def index_html_content
174
- erb_filespec = File.join(File.dirname(__FILE__), 'index.html.erb')
175
- erb = ERB.new(File.read(erb_filespec))
176
- erb.result_with_hash(
177
- journals: journals,
178
- chart_of_accounts: chart_of_accounts,
179
- run_options: run_options)
180
- end
181
41
  end
182
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|
@@ -67,12 +65,10 @@ class Journal
67
65
  attr_reader :short_name, :account_code, :chart_of_accounts, :date_prefix, :debit_or_credit, :doc_type, :title, :entries
68
66
 
69
67
  # short_name is a name that will appear on reports identifying the journal from which a transaction comes
70
- def initialize(chart_of_accounts, input_lines, short_name = nil)
68
+ def initialize(chart_of_accounts, input_lines)
71
69
  @chart_of_accounts = chart_of_accounts
72
- @short_name = short_name
73
70
  @entries = []
74
71
  @date_prefix = ''
75
- @title = ''
76
72
  input_lines.each_with_index do |line, linenum|
77
73
  context = JournalEntryContext.new(self, linenum + 1, line)
78
74
  parse_line(context)
@@ -131,7 +127,6 @@ class Journal
131
127
  end
132
128
 
133
129
 
134
-
135
130
  def acct_amounts
136
131
  entries.each_with_object([]) { |entry, acct_amounts| acct_amounts << entry.acct_amounts }.flatten
137
132
  end