mudrat_projector 0.9.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +49 -0
  7. data/README.md +46 -0
  8. data/Rakefile +26 -0
  9. data/bin/testrb +3 -0
  10. data/finance_fu.txt +41 -0
  11. data/lib/mudrat_projector/account.rb +75 -0
  12. data/lib/mudrat_projector/amortizer.rb +103 -0
  13. data/lib/mudrat_projector/banker_rounding.rb +11 -0
  14. data/lib/mudrat_projector/chart_of_accounts.rb +119 -0
  15. data/lib/mudrat_projector/date_diff.rb +169 -0
  16. data/lib/mudrat_projector/projection.rb +45 -0
  17. data/lib/mudrat_projector/projector.rb +71 -0
  18. data/lib/mudrat_projector/schedule.rb +45 -0
  19. data/lib/mudrat_projector/scheduled_transaction.rb +45 -0
  20. data/lib/mudrat_projector/tax_calculation.rb +154 -0
  21. data/lib/mudrat_projector/tax_calculator.rb +144 -0
  22. data/lib/mudrat_projector/tax_values_by_year.yml +102 -0
  23. data/lib/mudrat_projector/transaction.rb +71 -0
  24. data/lib/mudrat_projector/transaction_entry.rb +126 -0
  25. data/lib/mudrat_projector/transaction_handler.rb +19 -0
  26. data/lib/mudrat_projector/validator.rb +49 -0
  27. data/lib/mudrat_projector/version.rb +3 -0
  28. data/lib/mudrat_projector.rb +27 -0
  29. data/mudrat_projector.gemspec +28 -0
  30. data/test/integrations/long_term_projection_test.rb +42 -0
  31. data/test/integrations/mortgage_test.rb +85 -0
  32. data/test/integrations/self_employed_tax_calculation_test.rb +44 -0
  33. data/test/models/accounts_test.rb +39 -0
  34. data/test/models/chart_of_accounts_test.rb +170 -0
  35. data/test/models/date_diff_test.rb +117 -0
  36. data/test/models/projection_test.rb +62 -0
  37. data/test/models/projector_test.rb +168 -0
  38. data/test/models/schedule_test.rb +68 -0
  39. data/test/models/scheduled_transaction_test.rb +47 -0
  40. data/test/models/tax_calculator_test.rb +145 -0
  41. data/test/models/transaction_entry_test.rb +37 -0
  42. data/test/models/transaction_handler_test.rb +67 -0
  43. data/test/models/transaction_test.rb +66 -0
  44. data/test/models/validator_test.rb +45 -0
  45. data/test/test_helper.rb +69 -0
  46. metadata +204 -0
@@ -0,0 +1,117 @@
1
+ require 'test_helper'
2
+
3
+ class DateDiffTest < Minitest::Test
4
+ def test_date_diff_via_keyword_args
5
+ via_keyword_args = date_diff(
6
+ unit: :year,
7
+ from: jan_1_2000,
8
+ to: jan_1_2000,
9
+ )
10
+ via_positional_params = date_diff(
11
+ :year,
12
+ jan_1_2000,
13
+ jan_1_2000,
14
+ )
15
+ assert_equal via_positional_params, via_keyword_args
16
+ end
17
+
18
+ def test_years_between_a_few_days
19
+ assert_equal (1.0 / 366.0), date_diff(:year, jan_1_2000, jan_1_2000)
20
+ assert_equal (31.0 / 366.0), date_diff(:year, jan_1_2000, jan_31_2000)
21
+ assert_equal (32.0 / 366.0), date_diff(:year, jan_1_2000, feb_1_2000)
22
+ assert_equal (2/366.0 + 2/365.0), date_diff(:year, dec_30_2000, jan_2_2001)
23
+ end
24
+
25
+ def test_quarters_between_a_few_days
26
+ q1_2000 = (31 + 29 + 31).to_f
27
+ q4_2000 = (31 + 30 + 31).to_f
28
+ q1_2001 = (31 + 28 + 31).to_f
29
+ assert_equal (1.0 / q1_2000), date_diff(:quarter, jan_1_2000, jan_1_2000)
30
+ assert_equal (31.0 / q1_2000), date_diff(:quarter, jan_1_2000, jan_31_2000)
31
+ assert_equal (32.0 / q1_2000), date_diff(:quarter, jan_1_2000, feb_1_2000)
32
+ assert_equal (2/q4_2000 + 2/q1_2001), date_diff(:quarter, dec_30_2000, jan_2_2001)
33
+ end
34
+
35
+ def test_months_between_a_few_days
36
+ assert_equal (1.0 / 31.0), date_diff(:month, jan_1_2000, jan_1_2000)
37
+ assert_equal (31.0 / 31.0), date_diff(:month, jan_1_2000, jan_31_2000)
38
+ assert_equal (1.0 + 1/29.0), date_diff(:month, jan_1_2000, feb_1_2000)
39
+ assert_equal (2.0/31.0 + 2.0/31.0), date_diff(:month, dec_30_2000, jan_2_2001)
40
+ end
41
+
42
+ def test_weeks_between_a_few_days
43
+ assert_equal (1.0 / 7.0), date_diff(:week, jan_1_2000, jan_1_2000)
44
+ assert_equal (31.0 / 7.0), date_diff(:week, jan_1_2000, jan_31_2000)
45
+ assert_equal (32.0 / 7.0), date_diff(:week, jan_1_2000, feb_1_2000)
46
+ assert_equal (2/7.0 + 2/7.0), date_diff(:week, dec_30_2000, jan_2_2001)
47
+ end
48
+
49
+ def test_days_between_a_few_days
50
+ assert_equal 1.0, date_diff(:day, jan_1_2000, jan_1_2000)
51
+ assert_equal 31.0, date_diff(:day, jan_1_2000, jan_31_2000)
52
+ assert_equal 32.0, date_diff(:day, jan_1_2000, feb_1_2000)
53
+ assert_equal 4.0, date_diff(:day, dec_30_2000, jan_2_2001)
54
+ end
55
+
56
+ def test_six_months
57
+ assert_equal ((1 + 29 + 31 + 30 + 31 + 2) / 366.0), date_diff(:year, jan_31_2000, jun_2_2000)
58
+ assert_equal (1.0/31.0 + 4.0 + 2.0/30.0), date_diff(:month, jan_31_2000, jun_2_2000)
59
+ assert_equal ((1 + 29 + 31 + 30 + 31 + 2) / 7.0), date_diff(:week, jan_31_2000, jun_2_2000)
60
+ assert_equal (1 + 29 + 31 + 30 + 31 + 2), date_diff(:day, jan_31_2000, jun_2_2000)
61
+ end
62
+
63
+ def test_a_single_year
64
+ assert_equal 1.0, date_diff(:year, jan_1_2000, dec_31_2000)
65
+ assert_equal 4.0, date_diff(:quarter, jan_1_2000, dec_31_2000)
66
+ assert_equal 12.0, date_diff(:month, jan_1_2000, dec_31_2000)
67
+ end
68
+
69
+ def test_full_years
70
+ dec_12_2004 = Date.new 2004, 12, 31
71
+ assert_equal 5.0, date_diff(:year, jan_1_2000, dec_12_2004)
72
+ assert_equal 20.0, date_diff(:quarter, jan_1_2000, dec_12_2004)
73
+ assert_equal 60.0, date_diff(:month, jan_1_2000, dec_12_2004)
74
+ end
75
+
76
+ def test_a_year_and_four_days_across_a_leap_year
77
+ assert_equal (2/366.0 + 1.0 + 2/365.0), date_diff(:year, dec_30_2000, Date.new(2002, 1, 2))
78
+ assert_equal (2/31.0 + 12.0 + 2/31.0), date_diff(:month, dec_30_2000, Date.new(2002, 1, 2))
79
+ end
80
+
81
+ def test_advancing_days
82
+ assert_equal Date.new(2000, 1, 2), advance(from: jan_1_2000, unit: :day, intervals: 1)
83
+ assert_equal Date.new(2000, 1, 3), advance(from: jan_1_2000, unit: :day, intervals: 2)
84
+ assert_equal Date.new(2000, 1, 3), advance(from: jan_1_2000, unit: :day, intervals: 1.5)
85
+ end
86
+
87
+ def test_advancing_weeks
88
+ assert_equal Date.new(2000, 1, 8), advance(from: jan_1_2000, unit: :week, intervals: 1)
89
+ assert_equal Date.new(2000, 1, 15), advance(from: jan_1_2000, unit: :week, intervals: 2)
90
+ end
91
+
92
+ def test_advancing_months
93
+ assert_equal Date.new(2000, 2, 1), advance(from: jan_1_2000, unit: :month, intervals: 1)
94
+ assert_equal Date.new(2000, 3, 1), advance(from: jan_1_2000, unit: :month, intervals: 2)
95
+ assert_equal Date.new(2000, 4, 16), advance(from: apr_1_2000, unit: :month, intervals: 0.5)
96
+ end
97
+
98
+ def test_advancing_quarters
99
+ assert_equal Date.new(2000, 4, 1), advance(from: jan_1_2000, unit: :quarter, intervals: 1)
100
+ assert_equal Date.new(2000, 7, 1), advance(from: jan_1_2000, unit: :quarter, intervals: 2)
101
+ end
102
+
103
+ def test_advancing_years
104
+ assert_equal Date.new(2001, 1, 1), advance(from: jan_1_2000, unit: :year, intervals: 1)
105
+ assert_equal Date.new(2002, 1, 1), advance(from: jan_1_2000, unit: :year, intervals: 2)
106
+ end
107
+
108
+ private
109
+
110
+ def date_diff *args
111
+ DateDiff.date_diff *args
112
+ end
113
+
114
+ def advance *args
115
+ DateDiff.advance *args
116
+ end
117
+ end
@@ -0,0 +1,62 @@
1
+ require 'test_helper'
2
+
3
+ class ProjectionTest < Minitest::Test
4
+ def setup
5
+ @chart = ChartOfAccounts.new.tap do |c|
6
+ c.add_account :checking, type: :asset
7
+ c.add_account :job, type: :revenue
8
+ end
9
+ @projection = Projection.new range: (jan_1_2000..dec_31_2000), chart: @chart
10
+ end
11
+
12
+ def test_adding_transaction
13
+ @projection << valid_transaction
14
+ assert_equal 1, @projection.transaction_sequence.size
15
+ end
16
+
17
+ def test_adding_transactions_out_of_order
18
+ @projection << (first = valid_transaction(date: jan_3_2000))
19
+ @projection << (second = valid_transaction(date: jan_2_2000))
20
+ @projection << (third = valid_transaction(date: jan_2_2000))
21
+ @projection << (fourth = valid_transaction(date: jan_1_2000))
22
+
23
+ assert_equal [fourth, second, third, first].map(&:date),
24
+ @projection.transaction_sequence.map(&:date)
25
+ end
26
+
27
+ def test_project_freezes_projection_and_plays_transactions_through_chart
28
+ add_valid_transactions
29
+ assert_equal 0, @chart.net_worth
30
+
31
+ @projection.project!
32
+ assert @projection.frozen?
33
+
34
+ assert_equal 3032, @chart.net_worth
35
+ end
36
+
37
+ def test_project_will_yield_to_block
38
+ add_valid_transactions
39
+
40
+ dates = []
41
+ @projection.project! { |t| dates << t.date }
42
+
43
+ assert_equal [jan_1_2000, feb_2_2000, mar_3_2000, apr_4_2000], dates
44
+ end
45
+
46
+ private
47
+
48
+ def add_valid_transactions
49
+ @projection << valid_transaction(date: jan_1_2000)
50
+ @projection << valid_transaction(date: feb_2_2000, amount: 32)
51
+ @projection << valid_transaction(date: mar_3_2000)
52
+ @projection << valid_transaction(date: apr_4_2000)
53
+ end
54
+
55
+ def valid_transaction date: jan_1_2000, amount: 1000
56
+ Transaction.new(
57
+ date: date,
58
+ debit: { amount: amount, account_id: :checking },
59
+ credit: { amount: amount, account_id: :job },
60
+ )
61
+ end
62
+ end
@@ -0,0 +1,168 @@
1
+ require 'test_helper'
2
+
3
+ class ProjectorTest < Minitest::Test
4
+ def setup
5
+ @projector = Projector.new from: jan_1_2000
6
+ @projector.add_account :checking, type: :asset
7
+ @projector.add_account :nustartup_inc, type: :revenue
8
+ end
9
+
10
+ def test_next_projector_includes_deferred_transactions_from_projector
11
+ @projector.add_transaction(
12
+ date: jan_1_2000,
13
+ debit: { amount: 1000, account_id: :checking },
14
+ credit: { amount: 1000, account_id: :nustartup_inc },
15
+ schedule: every_month(until: jun_30_2001),
16
+ )
17
+ next_projector = @projector.project to: dec_31_2000, build_next: true
18
+ assert_equal jan_1_2001, next_projector.from
19
+ next_projector.project to: dec_31_2001
20
+
21
+ assert_equal 18000, next_projector.net_worth
22
+ end
23
+
24
+ def test_project_yields_to_block
25
+ @projector.add_transaction(
26
+ date: jan_1_2000,
27
+ debit: { amount: 1000, account_id: :checking },
28
+ credit: { amount: 1000, account_id: :nustartup_inc },
29
+ schedule: every_month(until: apr_30_2000),
30
+ )
31
+
32
+ expected_dates = [jan_1_2000, feb_1_2000, mar_1_2000, apr_1_2000]
33
+ actual_dates = []
34
+
35
+ @projector.project to: dec_31_2000 do |transaction|
36
+ actual_dates.push transaction.date
37
+ end
38
+
39
+ assert_equal expected_dates, actual_dates
40
+ end
41
+ end
42
+
43
+ class ProjectorAccountsTest < Minitest::Test
44
+ def setup
45
+ @projector = Projector.new from: jan_1_2000
46
+ end
47
+
48
+ def test_add_account_refuses_to_overwrite_account
49
+ @projector.add_account :checking, type: :asset
50
+ assert_raises Projector::AccountExists do
51
+ @projector.add_account :checking, type: :asset
52
+ end
53
+ end
54
+
55
+ def test_add_account_refuses_to_add_account_without_valid_type
56
+ assert_raises Projector::InvalidAccount do
57
+ @projector.add_account :checking, type: nil
58
+ end
59
+ assert_raises Projector::InvalidAccount do
60
+ @projector.add_account :checking, type: :foozle
61
+ end
62
+ end
63
+
64
+ def test_supplying_an_opening_balance
65
+ assert_equal 0, @projector.net_worth
66
+
67
+ @projector.add_account(
68
+ :checking,
69
+ open_date: jan_1_2000,
70
+ opening_balance: 500,
71
+ type: :asset,
72
+ )
73
+ refute_equal 0, @projector.balance
74
+
75
+ assert_raises Projector::InvalidAccount do
76
+ @projector.add_account(
77
+ :savings,
78
+ open_date: jan_2_2000,
79
+ opening_balance: 500,
80
+ type: :asset,
81
+ )
82
+ end
83
+
84
+ @projector.add_account(
85
+ :estate,
86
+ open_date: jan_1_2000,
87
+ opening_balance: 500,
88
+ type: :equity,
89
+ )
90
+ assert_equal 0, @projector.balance
91
+ assert_equal 500, @projector.net_worth
92
+ end
93
+
94
+ def test_accounts_must_be_balanced_to_run_projection
95
+ @projector.add_account(
96
+ :checking,
97
+ open_date: jan_1_2000,
98
+ opening_balance: 500,
99
+ type: :asset,
100
+ )
101
+ refute_equal 0, @projector.balance
102
+
103
+ assert_raises Projector::BalanceError do
104
+ @projector.project to: dec_31_2000
105
+ end
106
+ end
107
+
108
+ def test_add_accounts_passes_account_hashes_to_add_account
109
+ assert_equal [], @projector.accounts
110
+ @projector.accounts = { checking: { type: :asset } }
111
+ assert_equal [:checking], @projector.accounts
112
+ end
113
+ end
114
+
115
+ class ProjectorTransactionTest < Minitest::Test
116
+ def setup
117
+ @projector = Projector.new from: jan_1_2000
118
+ @projector.add_account :checking, type: :asset
119
+ @projector.add_account :nustartup_inc, type: :revenue
120
+ end
121
+
122
+ def test_single_transaction
123
+ add_simple_transaction
124
+
125
+ @projector.project to: dec_31_2000
126
+
127
+ assert_equal 1000, @projector.net_worth
128
+ assert_equal 1000, @projector.account_balance(:checking)
129
+ assert_equal 1000, @projector.account_balance(:nustartup_inc)
130
+ end
131
+
132
+ def test_scheduled_transaction
133
+ add_scheduled_transaction
134
+
135
+ @projector.project to: dec_31_2000
136
+
137
+ assert_equal 12000, @projector.net_worth
138
+ assert_equal 12000, @projector.account_balance(:checking)
139
+ assert_equal 12000, @projector.account_balance(:nustartup_inc)
140
+ end
141
+
142
+ def test_future_transaction
143
+ add_simple_transaction jan_1_2010
144
+
145
+ @projector.project to: dec_31_2000
146
+
147
+ assert_equal 0, @projector.net_worth
148
+ end
149
+
150
+ private
151
+
152
+ def add_scheduled_transaction date = jan_1_2000
153
+ @projector.add_transaction(
154
+ date: date,
155
+ credit: { amount: 1000, account_id: :nustartup_inc },
156
+ debit: { amount: 1000, account_id: :checking },
157
+ schedule: every_month,
158
+ )
159
+ end
160
+
161
+ def add_simple_transaction date = jan_1_2000
162
+ @projector.add_transaction(
163
+ date: date,
164
+ credit: { amount: 1000, account_id: :nustartup_inc },
165
+ debit: { amount: 1000, account_id: :checking },
166
+ )
167
+ end
168
+ end
@@ -0,0 +1,68 @@
1
+ require 'test_helper'
2
+
3
+ class ScheduledTransactionTest < Minitest::Test
4
+ def test_slice_does_not_return_leftover_if_ending_within_slice_date
5
+ @schedule = Schedule.new every_month until: dec_31_2000
6
+ _, leftover = @schedule.slice(jan_1_2000..dec_31_2000)
7
+ assert_nil leftover
8
+ end
9
+
10
+ def test_slice_returns_leftover_if_schedule_has_no_end
11
+ @schedule = Schedule.new every_month
12
+ _, leftover = @schedule.slice(jan_1_2000..dec_31_2000)
13
+
14
+ expected_schedule = { scalar: 1, unit: :month }
15
+ assert_equal expected_schedule, leftover
16
+ end
17
+
18
+ def test_slice_returns_leftover_if_ending_after_slice_date
19
+ @schedule = Schedule.new every_month until: jun_30_2001
20
+ _, leftover = @schedule.slice(jan_1_2000..dec_31_2000)
21
+
22
+ expected_schedule = { scalar: 1, unit: :month, count: 6 }
23
+ assert_equal expected_schedule, leftover
24
+ end
25
+
26
+ def test_slice_yields_advancing_dates
27
+ @schedule = Schedule.new every_month until: apr_15_2000
28
+
29
+ expected_dates = [jan_1_2000, feb_1_2000, mar_1_2000, apr_1_2000]
30
+ actual_dates, _ = @schedule.slice(jan_1_2000..apr_15_2000)
31
+ actual_dates.map! &:first
32
+
33
+ assert_equal expected_dates, actual_dates
34
+ end
35
+
36
+ def test_slice_prorates_final_period
37
+ @schedule = Schedule.new every_month until: apr_15_2000
38
+
39
+ expected_prorates = [1, 1, 1, 0.5]
40
+ actual_prorates, _ = @schedule.slice(jan_1_2000..dec_31_2000)
41
+ actual_prorates.map! &:last
42
+
43
+ assert_equal expected_prorates, actual_prorates
44
+ end
45
+
46
+ def test_handles_start_after_projection
47
+ @schedule = Schedule.new every_month(from: apr_1_2000, until: jun_15_2000)
48
+
49
+ expected_prorates = [1, 1, 0.5]
50
+ actual_prorates, _ = @schedule.slice(apr_1_2000..jun_15_2000)
51
+ actual_prorates.map! &:last
52
+
53
+ assert_equal expected_prorates, actual_prorates
54
+ end
55
+
56
+ def test_handles_start_during_range_and_end_after_range
57
+ @schedule = Schedule.new every_month(from: jun_16_2000, until: apr_15_2001)
58
+
59
+ expected_prorates = [1, 1, 1, 1, 1, 0.5 ]
60
+ expected_leftover = { scalar: 1, unit: :month, count: 4.5 }
61
+
62
+ actual_prorates, actual_leftover = @schedule.slice(jun_16_2000..nov_30_2000)
63
+ actual_prorates.map! &:last
64
+
65
+ assert_equal expected_prorates, actual_prorates
66
+ assert_equal expected_leftover, actual_leftover
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class ScheduledTransactionTest < Minitest::Test
4
+ def setup
5
+ @chart = ChartOfAccounts.new
6
+ @chart.add_account :checking, type: :asset
7
+ @chart.add_account :job, type: :revenue
8
+ end
9
+
10
+ def test_prorating
11
+ in_range, _ = scheduled_transaction.slice apr_15_2000
12
+ assert_equal [1000, 1000, 1000, 500], in_range.map { |t| t.credits.first.scalar }
13
+
14
+ in_range, _ = scheduled_transaction_with_percentage.slice apr_15_2000
15
+ assert_equal [0.25, 0.25, 0.25, 0.125], in_range.map { |t| t.credits.first.scalar }
16
+ end
17
+
18
+ def test_leftover
19
+ _, leftover = scheduled_transaction(apr_15_2000).slice apr_15_2000
20
+ assert_nil leftover
21
+
22
+ _, leftover = scheduled_transaction(apr_16_2000).slice apr_15_2000
23
+ refute_nil leftover
24
+
25
+ assert_equal apr_16_2000, leftover.date
26
+ end
27
+
28
+ private
29
+
30
+ def scheduled_transaction end_date = nil
31
+ ScheduledTransaction.new(
32
+ date: jan_1_2000,
33
+ debit: { account_id: :checking, amount: 1000 },
34
+ credit: { account_id: :job, amount: 1000 },
35
+ schedule: every_month(until: end_date),
36
+ )
37
+ end
38
+
39
+ def scheduled_transaction_with_percentage
40
+ ScheduledTransaction.new(
41
+ date: jan_1_2000,
42
+ debit: { account_id: :checking, percent: 0.25, of: :job },
43
+ credit: { account_id: :job, percent: 0.25, of: :job },
44
+ schedule: every_month,
45
+ )
46
+ end
47
+ end
@@ -0,0 +1,145 @@
1
+ require 'test_helper'
2
+
3
+ class TaxCalculatorTest < Minitest::Test
4
+ def setup
5
+ @projector = Projector.new from: jan_1_2012
6
+ @projector.add_account :checking, type: :asset
7
+ @projector.add_account :job, type: :revenue, tags: %i(salary)
8
+
9
+ @projector.add_transaction(
10
+ date: jan_1_2012,
11
+ credit: { amount: 6000, account_id: :job },
12
+ debit: { amount: 6000, account_id: :checking },
13
+ schedule: every_month,
14
+ )
15
+ @projector.add_transaction(
16
+ date: jun_30_2013,
17
+ credit: { amount: 3000, account_id: :job },
18
+ debit: { amount: 3000, account_id: :checking },
19
+ )
20
+ end
21
+
22
+ def test_basic_1040
23
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
24
+ calculation = @tax_calculator.calculate!
25
+
26
+ assert_equal 2012, calculation.year
27
+ assert_equal 72000, calculation.gross
28
+ assert_equal 72000, calculation.total_income
29
+ assert_equal 0, calculation.adjustments
30
+ assert_equal 72000, calculation.agi
31
+ assert_equal 5950, calculation.deduction
32
+ assert_equal 3800, calculation.exemption
33
+ assert_equal 4068, calculation.withholding_tax
34
+ assert_equal 62250, calculation.taxable_income
35
+ assert_equal 11592.5, calculation.income_tax
36
+ assert_equal 15660.5, calculation.taxes
37
+ assert_equal 15660.5, calculation.taxes_owed
38
+ assert_equal 56339.5, calculation.net
39
+ assert_equal 21.75, calculation.effective_rate
40
+ end
41
+
42
+ def test_basic_1040_with_withholding
43
+ @projector.add_transaction(
44
+ date: jan_1_2012,
45
+ credit: { amount: 1000, account_id: :checking },
46
+ debit: { amount: 1000, account_id: TaxCalculator::EXPENSE_ACCOUNT_ID },
47
+ schedule: every_month,
48
+ )
49
+
50
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
51
+ calculation = @tax_calculator.calculate!
52
+ assert_equal 3660.5, calculation.taxes_owed
53
+ end
54
+
55
+ def test_basic_1040_married
56
+ @tax_calculator = TaxCalculator.new projector: @projector, household: married
57
+ calculation = @tax_calculator.calculate!
58
+
59
+ assert_equal 11900, calculation.deduction
60
+ assert_equal 3800 * 4, calculation.exemption
61
+ end
62
+
63
+ def test_charity_contribution
64
+ @projector.add_account :public_radio, type: :expense, tags: %i(501c)
65
+ @projector.add_transaction(
66
+ date: jun_30_2012,
67
+ debit: { amount: 15000, account_id: :public_radio },
68
+ credit: { amount: 15000, account_id: :checking },
69
+ )
70
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
71
+ calculation = @tax_calculator.calculate!
72
+
73
+ assert_equal 15000, calculation.deduction
74
+ end
75
+
76
+ def test_basic_1040_2013
77
+ @projector = @projector.project to: dec_31_2012, build_next: true
78
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
79
+ calculation = @tax_calculator.calculate!
80
+
81
+ assert_equal 75000, calculation.total_income
82
+ assert_equal 5737.5, calculation.withholding_tax
83
+ end
84
+
85
+ def test_hsa_deduction
86
+ @projector.add_account :hsa, type: :asset, tags: %i(hsa individual)
87
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
88
+ @projector.add_transaction(
89
+ date: jun_30_2012,
90
+ debit: { amount: 4000, account_id: :hsa },
91
+ credit: { amount: 4000, account_id: :checking },
92
+ )
93
+ calculation = @tax_calculator.calculate!
94
+
95
+ assert_equal 72000, calculation.gross
96
+ assert_equal 72000, calculation.total_income
97
+ assert_equal 3100, calculation.adjustments
98
+ assert_equal 68900, calculation.agi
99
+ end
100
+
101
+ def test_pretax_hsa_deduction
102
+ @projector.add_account :hsa, type: :asset, tags: %i(hsa family)
103
+ @projector.add_transaction(
104
+ date: jun_30_2012,
105
+ debit: { amount: 7000, account_id: :hsa },
106
+ credit: { amount: 7000, account_id: :job },
107
+ )
108
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
109
+ calculation = @tax_calculator.calculate!
110
+
111
+ assert_equal 79000, calculation.gross
112
+ assert_equal 72000, calculation.total_income
113
+ assert_equal 0, calculation.adjustments
114
+ assert_equal 72000, calculation.agi
115
+ end
116
+
117
+ def test_mortgage_interest_and_property_taxes_paid
118
+ @projector.add_account :interest, type: :expense, tags: %i(mortgage_interest)
119
+ @projector.add_account :property_taxes, type: :expense, tags: %i(property tax)
120
+ @projector.add_transaction(
121
+ date: jun_30_2012,
122
+ debit: { amount: 7500, account_id: :interest },
123
+ credit: { amount: 7500, account_id: :job },
124
+ )
125
+ @projector.add_transaction(
126
+ date: jun_30_2012,
127
+ debit: { amount: 5000, account_id: :property_taxes },
128
+ credit: { amount: 5000, account_id: :job },
129
+ )
130
+ @tax_calculator = TaxCalculator.new projector: @projector, household: single
131
+ calculation = @tax_calculator.calculate!
132
+
133
+ assert_equal 12500, calculation.deduction
134
+ end
135
+
136
+ private
137
+
138
+ def single
139
+ { filing_status: :single, exemptions: 1 }
140
+ end
141
+
142
+ def married
143
+ { filing_status: :married_filing_jointly, exemptions: 4 }
144
+ end
145
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class TransactionEntryTest < Minitest::Test
4
+ def test_must_supply_type_of_debit_or_credit
5
+ assert_raises ArgumentError do
6
+ TransactionEntry.new credit_or_debit: :foo, amount: 1, account_id: :checking
7
+ end
8
+ end
9
+
10
+ def test_must_supply_a_nonzero_amount
11
+ assert_raises ArgumentError do
12
+ TransactionEntry.new credit_or_debit: :credit, amount: 0, account_id: :checking
13
+ end
14
+ end
15
+
16
+ def test_must_supply_an_account
17
+ assert_raises KeyError do
18
+ TransactionEntry.new credit_or_debit: :credit, amount: 1
19
+ end
20
+ end
21
+
22
+ def test_calculate_amount_sets_amount_and_delta
23
+ chart = ChartOfAccounts.new
24
+ chart.add_account :checking, type: :asset, opening_balance: 1000
25
+ chart.add_account :job, type: :revenue, opening_balance: 1000
26
+
27
+ t1 = TransactionEntry.new credit_or_debit: :credit, amount: 1, account_id: :checking
28
+ t1.calculate chart
29
+ assert_equal 1, t1.amount
30
+ assert_equal -1, t1.delta
31
+
32
+ t2 = TransactionEntry.new credit_or_debit: :debit, percent: 0.5, of: :checking, account_id: :checking
33
+ t2.calculate chart
34
+ assert_equal 500, t2.amount
35
+ assert_equal 500, t2.delta
36
+ end
37
+ end