rock_books 0.1.0

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +200 -0
  8. data/RELEASE_NOTES.md +4 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/exe/rock_books +5 -0
  13. data/lib/rock_books/cmd_line/command_line_interface.rb +391 -0
  14. data/lib/rock_books/cmd_line/main.rb +108 -0
  15. data/lib/rock_books/documents/book_set.rb +113 -0
  16. data/lib/rock_books/documents/chart_of_accounts.rb +113 -0
  17. data/lib/rock_books/documents/journal.rb +161 -0
  18. data/lib/rock_books/documents/journal_entry.rb +73 -0
  19. data/lib/rock_books/documents/journal_entry_builder.rb +148 -0
  20. data/lib/rock_books/errors/account_not_found_error.rb +20 -0
  21. data/lib/rock_books/errors/error.rb +10 -0
  22. data/lib/rock_books/filters/acct_amount_filters.rb +12 -0
  23. data/lib/rock_books/filters/journal_entry_filters.rb +84 -0
  24. data/lib/rock_books/helpers/book_set_loader.rb +62 -0
  25. data/lib/rock_books/helpers/parse_helper.rb +22 -0
  26. data/lib/rock_books/reports/balance_sheet.rb +60 -0
  27. data/lib/rock_books/reports/income_statement.rb +63 -0
  28. data/lib/rock_books/reports/multidoc_transaction_report.rb +66 -0
  29. data/lib/rock_books/reports/receipts_report.rb +57 -0
  30. data/lib/rock_books/reports/report_context.rb +15 -0
  31. data/lib/rock_books/reports/reporter.rb +118 -0
  32. data/lib/rock_books/reports/transaction_report.rb +103 -0
  33. data/lib/rock_books/reports/tx_by_account.rb +82 -0
  34. data/lib/rock_books/reports/tx_one_account.rb +63 -0
  35. data/lib/rock_books/types/account.rb +7 -0
  36. data/lib/rock_books/types/account_type.rb +33 -0
  37. data/lib/rock_books/types/acct_amount.rb +52 -0
  38. data/lib/rock_books/version.rb +3 -0
  39. data/lib/rock_books.rb +7 -0
  40. data/rock_books.gemspec +39 -0
  41. data/sample_data/minimal/rockbooks-inputs/2017-xyz-chart-of-accounts.rbt +62 -0
  42. data/sample_data/minimal/rockbooks-inputs/2017-xyz-checking-journal.rbt +17 -0
  43. data/sample_data/minimal/rockbooks-inputs/2017-xyz-general-journal.rbt +14 -0
  44. data/sample_data/minimal/rockbooks-inputs/2017-xyz-visa-journal.rbt +23 -0
  45. metadata +158 -0
@@ -0,0 +1,118 @@
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
+
@@ -0,0 +1,103 @@
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.first) \
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 { |entry| sio << format_entry(entry) << "\n" }
93
+ totals = AcctAmount.aggregate_amounts_by_account(JournalEntry.entries_acct_amounts(entries))
94
+ sio << generate_and_format_totals('Totals', totals)
95
+ sio.string
96
+ end
97
+
98
+
99
+ alias_method :to_s, :generate_report
100
+ alias_method :call, :generate_report
101
+ end
102
+
103
+ end
@@ -0,0 +1,82 @@
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
@@ -0,0 +1,63 @@
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 TxOneAccount
9
+
10
+ include Reporter
11
+
12
+ attr_reader :context, :account_code, :account
13
+
14
+
15
+ def initialize(report_context, account_code)
16
+ @context = report_context
17
+ @account_code = account_code
18
+ @account = context.chart_of_accounts.account_for_code(account_code)
19
+ end
20
+
21
+
22
+ def generate_header(account_total)
23
+ lines = [banner_line]
24
+ lines << center(context.entity || 'Unspecified Entity')
25
+ lines << center("Transactions for Account #{account_code_name_type_string(account)}")
26
+ lines << center("Total: %.2f" % account_total)
27
+ lines << banner_line
28
+ lines << ''
29
+ lines << ''
30
+ lines << ''
31
+ lines.join("\n")
32
+ end
33
+
34
+
35
+ def process_account(entries)
36
+ entries.each_with_object('') do |entry, output|
37
+ output << format_multidoc_entry(entry) << "\n"
38
+ output << "\n" if entry.description && entry.description.length > 0
39
+ end
40
+ end
41
+
42
+
43
+ def generate_report
44
+ entries = Journal.entries_in_documents(context.journals, JournalEntryFilters.account_code_filter(account_code))
45
+ account_total = JournalEntry.total_for_code(entries, account_code)
46
+ output = generate_header(account_total)
47
+
48
+ if entries.empty?
49
+ output << "There were no transactions for this account.\n\n\n\n"
50
+ else
51
+ output << process_account(entries)
52
+ totals = AcctAmount.aggregate_amounts_by_account(JournalEntry.entries_acct_amounts(entries))
53
+ output << generate_and_format_totals('Totals', totals)
54
+ end
55
+
56
+ output
57
+ end
58
+
59
+ alias_method :to_s, :generate_report
60
+ alias_method :call, :generate_report
61
+ end
62
+
63
+ end
@@ -0,0 +1,7 @@
1
+ module RockBooks
2
+
3
+ # The ChartOfAccount holds the set of all these accounts for the entity.
4
+ class Account < Struct.new(:code, :type, :name)
5
+ end
6
+
7
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../errors/error'
2
+
3
+ module RockBooks
4
+
5
+ class AccountType < Struct.new(:symbol, :singular_name, :plural_name)
6
+
7
+ ASSET = self.new(:asset, 'Asset', 'Assets')
8
+ LIABILITY = self.new(:liability, 'Liability', 'Liabilities')
9
+ EQUITY = self.new(:equity, 'Equity', 'Equity')
10
+ INCOME = self.new(:income, 'Income', 'Income')
11
+ EXPENSE = self.new(:expense, 'Expense', 'Expenses')
12
+
13
+ ALL_TYPES = [ASSET, LIABILITY, EQUITY, INCOME, EXPENSE]
14
+
15
+ TYPE_HASH = {
16
+ 'A' => ASSET,
17
+ 'L' => LIABILITY,
18
+ 'O' => EQUITY,
19
+ 'I' => INCOME,
20
+ 'E' => EXPENSE
21
+ }
22
+
23
+ # Converts strings
24
+ def self.to_type(string)
25
+ type = TYPE_HASH[string[0].upcase]
26
+ if type.nil?
27
+ raise Error.new("Account type of #{string} not valid. " +
28
+ "Must be one of #{TYPE_HASH.keys} (#{ALL_TYPES.map(&:singular_name)})")
29
+ end
30
+ type
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ module RockBooks
2
+
3
+ # This class represents an account code and an amount.
4
+ # Journal entries will have multiple instances of these.
5
+ class AcctAmount < Struct.new(:date, :code, :amount)
6
+
7
+
8
+ # Same as constructor except it raises an error if the account code is not in the chart of accounts.
9
+ def self.create_with_chart_validation(date, code, amount, chart_of_accounts)
10
+ unless chart_of_accounts.include?(code)
11
+ raise AccountNotFoundError.new(code)
12
+ end
13
+ self.new(date, code, amount)
14
+ end
15
+
16
+
17
+ def self.total_amount(acct_amounts)
18
+ acct_amounts.inject(0) { |sum, acct_amount| sum += acct_amount.amount }
19
+ end
20
+
21
+
22
+ # Returns a hash whose keys are account codes and values are the totals for those codes.
23
+ # The 'aggregate' in the method name is intended to be a noun, not a verb.
24
+ def self.aggregate_amounts_by_account(acct_amounts)
25
+ totals = acct_amounts.each_with_object(Hash.new(0)) do |acct_amount, by_account|
26
+ by_account[acct_amount.code] += acct_amount.amount
27
+ end
28
+ totals.each do |code, amount |
29
+ totals[code] = amount.round(2)
30
+ end
31
+ end
32
+
33
+
34
+ # Returns the subset of the passed array of acct_amount's that contain the specified account code
35
+ def self.containing_code(acct_amounts, account_code)
36
+ acct_amounts.select { |acct_amount| acct_amount.code == account_code }
37
+ end
38
+
39
+
40
+ # For the passed array of AcctAmount's, calculate the total for a single account.
41
+ def self.total_amount_for_code(acct_amounts, account_code)
42
+ containing_code(acct_amounts, account_code) \
43
+ .map(&:amount) \
44
+ .sum
45
+ end
46
+
47
+
48
+ def self.filter(acct_amounts, filter)
49
+ acct_amounts.select { |acct_amount| filter.(acct_amount)}
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module RockBooks
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rock_books.rb ADDED
@@ -0,0 +1,7 @@
1
+ module RockBooks
2
+
3
+ DEFAULT_INPUT_DIR = './rockbooks-inputs'
4
+ DEFAULT_OUTPUT_DIR = './rockbooks-reports'
5
+ DEFAULT_RECEIPT_DIR = './receipts'
6
+ SINGLE_ACCT_SUBDIR = 'single-account'
7
+ end
@@ -0,0 +1,39 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "rock_books/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rock_books"
8
+ spec.version = RockBooks::VERSION
9
+ spec.authors = ["Keith Bennett"]
10
+ spec.email = ["keithrbennett@gmail.com"]
11
+
12
+ spec.summary = %q{Very basic accounting package.}
13
+ spec.description = %q{Extremely primitive accounting software.}
14
+ spec.homepage = "http://example.com"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata["allowed_push_host"] = ": Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against " \
23
+ # "public gem pushes."
24
+ # end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency 'awesome_print', '> 0'
34
+ spec.add_dependency 'pry', '> 0.0.0'
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.16"
37
+ spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ end
@@ -0,0 +1,62 @@
1
+ @doc_type: chart_of_accounts
2
+ @title: Chart of Accounts - 2017
3
+ @entity: XYZ Consulting, Inc.
4
+
5
+
6
+ # Assets
7
+
8
+ ck.hsbc A HSBC Checking
9
+ paypal A Paypal
10
+ accts.rec A Accounts Receivable
11
+
12
+
13
+ # Liabilities
14
+
15
+ cc.hsbc.visa L Visa Credit Card
16
+ loan.to.sh L Loan Payable to Shareholder
17
+
18
+
19
+ # Equity
20
+
21
+ own.equity O Owner's Equity
22
+ ret.earn O Retained Earnings
23
+
24
+
25
+ # Income
26
+
27
+ sls.cons I Sales - Consulting
28
+
29
+
30
+ # Expenses
31
+
32
+ bank.fees E Bank Charges
33
+ books.refs E Books, Screencasts, References
34
+ conf.fees E Conference Fees
35
+ cowork.fees E Coworking Fees
36
+ govt.fees E Government Fees
37
+ inet.fees E Internet Service, Domain, and Hosting Fees
38
+ insurance E Insurance
39
+ int.exp E Interest Expense
40
+ mktng.exp E Marketing Expenses
41
+ meals.ent E Meals & Entertainment
42
+ misc.exp E Miscellaneous Expenses
43
+ prof.fees E Professional Fees
44
+ repair.maint E Repair & Maintenance
45
+ ship.exp E Shipping and Mailing Expenses
46
+ sw.exp E Software Expense
47
+ supplies E Supplies
48
+ cc.proc E Credit Card Processing Fees
49
+ tr.airfare E Travel - Air Fares
50
+ tr.autorent E Travel - Auto Rental
51
+ tr.gas.etc E Travel - Gas, Oil, Tolls, etc.
52
+ tr.govt E Travel - Government Fees
53
+ tr.lodging E Travel - Lodging
54
+ tr.m.i E Travel - Meals & Incidentals
55
+ tr.mileage E Travel - Mileage Allowance
56
+ tr.misc E Travel - Miscellaneous
57
+ tr.parking E Travel - Parking
58
+ tr.perdiem.mi E Travel - Per Diem (Meals and Incidentals)
59
+ tr.taxi E Travel - Taxi
60
+ tr.trainfare E Travel - Train Fare
61
+ tr.m.e E Meals and Entertainment
62
+ tr.unclass E Expenses Not Yet Classified
@@ -0,0 +1,17 @@
1
+ @doc_type: journal
2
+ @title: HSBC Checking Disbursements Journal - 2017
3
+ @account_code: ck.hsbc
4
+ @debit_or_credit: debit
5
+ @short_name: ck.hsbc
6
+ @date_prefix: 2017-
7
+
8
+ 01-01 -5000.00 own.equity
9
+ Initial Deposit from Shareholder
10
+
11
+ 01-05 2000.00 cc.hsbc.visa
12
+
13
+ 01-07 -10000.00 sls.cons
14
+ Invoice #437, Dec. 2016 work, ABC, Inc.
15
+
16
+
17
+
@@ -0,0 +1,14 @@
1
+ @doc_type: general_journal
2
+ @title: General Journal - 2017
3
+ @date_prefix: 2017-
4
+ @short_name: general
5
+
6
+ 01-08 tr.airfare 300.00 loan.to.sh -300.00
7
+ Phoenix conference air ticket paid on personal credit card
8
+
9
+ 01-20 tr.perdiem.mi 495.00 loan.to.sh -495.00
10
+ Per diem allowance for Phoenix conference (see worksheet)
11
+
12
+ 01-31 tr.mileage 117.70 loan.to.sh -117.70
13
+ Mileage reimbursement for business travel of January 2017 (see worksheet)
14
+
@@ -0,0 +1,23 @@
1
+ @doc_type: journal
2
+ @title: HSBC Visa Journal - 2017
3
+ @account_code: cc.hsbc.visa
4
+ @short_name: hsbc_visa
5
+ @debit_or_credit: debit
6
+ @date_prefix: 2017-
7
+
8
+ 01-02 750 insurance
9
+ Professional insurance for the year 2017, Hartford Insurance
10
+ Receipt: 01/2017-01-02-hartford-insurance.pdf
11
+
12
+ 01-02 100 cowork.fees
13
+ New Work City coworking fee for the month of January
14
+ Receipt: 01/2017-01-02-nwc.pdf
15
+
16
+ 01-06 500 conf.fees
17
+ Phoenix conference registration fee
18
+ Receipt: 01/2017-01-06-phoenix-conference-registration.pdf
19
+
20
+ 01-20 400 tr.lodging
21
+ Hampton Inn Phoenix (conference)
22
+ Receipt: 01/2017-01-20-phoenix-hampton.pdf
23
+