morty 0.0.1 → 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.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/ci.yml +107 -0
- data/.gitignore +17 -3
- data/.rubocop.yml +20 -0
- data/Appraisals +24 -0
- data/Gemfile +28 -1
- data/LICENSE +21 -0
- data/README.md +37 -7
- data/Rakefile +37 -0
- data/app/models/morty/account.rb +37 -0
- data/app/models/morty/account_type.rb +7 -0
- data/app/models/morty/activity.rb +147 -0
- data/app/models/morty/activity_type.rb +7 -0
- data/app/models/morty/application_record.rb +6 -0
- data/app/models/morty/entry.rb +24 -0
- data/app/models/morty/entry_type.rb +23 -0
- data/app/models/morty/ledger.rb +8 -0
- data/config/routes.rb +2 -0
- data/config.ru +7 -0
- data/cucumber.yml +2 -0
- data/db/migrate/20260224063053_create_morty_schema.rb +17 -0
- data/db/seeds.rb +18 -0
- data/db/sql/create_morty_schema.sql +479 -0
- data/features/accountant.feature +47 -0
- data/features/adjustment.feature +79 -0
- data/features/cancel.feature +130 -0
- data/features/daily.feature +42 -0
- data/features/default.feature +33 -0
- data/features/ledger.feature +57 -0
- data/features/retroactive.feature +92 -0
- data/features/return.feature +112 -0
- data/features/reversal.feature +57 -0
- data/features/simulation.feature +128 -0
- data/features/support/accountants/adjusting_accountant.rb +34 -0
- data/features/support/accountants/daily_accountant.rb +13 -0
- data/features/support/accountants/default_accountant.rb +2 -0
- data/features/support/accountants/defaulting_accountant.rb +32 -0
- data/features/support/accountants/multiple_ledgers_accountant.rb +51 -0
- data/features/support/accountants/simulating_accountant.rb +36 -0
- data/features/support/accountants/sourceless_accountant.rb +2 -0
- data/features/support/accountants/waterfalling_accountant.rb +15 -0
- data/features/support/env.rb +17 -0
- data/features/waterfall.feature +34 -0
- data/gemfiles/rails_7.0.gemfile +30 -0
- data/gemfiles/rails_7.0.gemfile.lock +494 -0
- data/gemfiles/rails_7.1.gemfile +30 -0
- data/gemfiles/rails_7.1.gemfile.lock +543 -0
- data/gemfiles/rails_7.2.gemfile +30 -0
- data/gemfiles/rails_7.2.gemfile.lock +539 -0
- data/gemfiles/rails_8.0.gemfile +30 -0
- data/gemfiles/rails_8.0.gemfile.lock +536 -0
- data/gemfiles/rails_8.1.gemfile +30 -0
- data/gemfiles/rails_8.1.gemfile.lock +538 -0
- data/lib/morty/accountant.rb +332 -0
- data/lib/morty/adjustment.rb +64 -0
- data/lib/morty/book.rb +54 -0
- data/lib/morty/context/activity.rb +52 -0
- data/lib/morty/context/daily.rb +23 -0
- data/lib/morty/context/simulation.rb +26 -0
- data/lib/morty/cucumber/helpers.rb +27 -0
- data/lib/morty/cucumber/steps.rb +191 -0
- data/lib/morty/diff.rb +71 -0
- data/lib/morty/dsl.rb +86 -0
- data/lib/morty/engine.rb +21 -0
- data/lib/morty/error.rb +3 -0
- data/lib/morty/event.rb +27 -0
- data/lib/morty/list/activity.rb +57 -0
- data/lib/morty/rate.rb +59 -0
- data/lib/morty/schedule.rb +36 -0
- data/lib/morty/seed.rb +60 -0
- data/lib/morty/source.rb +19 -0
- data/lib/morty/tasks/morty_tasks.rake +4 -0
- data/lib/morty/version.rb +1 -1
- data/lib/morty.rb +27 -1
- data/morty.gemspec +22 -19
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +28 -0
- data/spec/dummy/app/views/pwa/manifest.json.erb +22 -0
- data/spec/dummy/app/views/pwa/service-worker.js +26 -0
- data/spec/dummy/bin/ci +6 -0
- data/spec/dummy/bin/dev +2 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +35 -0
- data/spec/dummy/config/application.rb +48 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/ci.rb +15 -0
- data/spec/dummy/config/database.yml +15 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +47 -0
- data/spec/dummy/config/environments/test.rb +53 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +29 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/locales/en.yml +31 -0
- data/spec/dummy/config/puma.rb +39 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/storage.yml +27 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/db/seeds.rb +52 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/400.html +135 -0
- data/spec/dummy/public/404.html +135 -0
- data/spec/dummy/public/406-unsupported-browser.html +135 -0
- data/spec/dummy/public/422.html +135 -0
- data/spec/dummy/public/500.html +135 -0
- data/spec/dummy/public/icon.png +0 -0
- data/spec/dummy/public/icon.svg +3 -0
- data/spec/lib/accountant_spec.rb +236 -0
- data/spec/lib/book_spec.rb +91 -0
- data/spec/lib/diff_spec.rb +102 -0
- data/spec/lib/event_spec.rb +53 -0
- data/spec/lib/list/activity_spec.rb +117 -0
- data/spec/lib/schedule_spec.rb +106 -0
- data/spec/lib/source_spec.rb +31 -0
- data/spec/models/account_spec.rb +48 -0
- data/spec/models/activity_spec.rb +139 -0
- data/spec/models/entry_spec.rb +41 -0
- data/spec/models/entry_type_spec.rb +43 -0
- data/spec/rate_spec.rb +83 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/test_helpers.rb +25 -0
- metadata +193 -16
- data/LICENSE.txt +0 -22
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Feature: Cancellations
|
|
2
|
+
|
|
3
|
+
Create an adjustment when an activity is cancelled.
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given a simulating accountant
|
|
7
|
+
|
|
8
|
+
And a start date of 2026-01-01
|
|
9
|
+
And an interest rate of 36.5%
|
|
10
|
+
And the schedule:
|
|
11
|
+
| payment | 2026-01-06 | 1005.00 |
|
|
12
|
+
|
|
13
|
+
And I simulate these activities:
|
|
14
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
15
|
+
|
|
16
|
+
Then I have these balances:
|
|
17
|
+
| cash | -1000.00 |
|
|
18
|
+
| principal | 1000.00 |
|
|
19
|
+
|
|
20
|
+
And I have 1 issue activity
|
|
21
|
+
|
|
22
|
+
Scenario: Cancel an activity without adjustment
|
|
23
|
+
When I save and reload the accountant
|
|
24
|
+
And I cancel the 2026-01-01 issue on 2026-01-01
|
|
25
|
+
|
|
26
|
+
Then I have these balances:
|
|
27
|
+
| cash | 0.00 |
|
|
28
|
+
| principal | 0.00 |
|
|
29
|
+
|
|
30
|
+
And these activity counts:
|
|
31
|
+
| issue | 1 |
|
|
32
|
+
| cancel | 1 |
|
|
33
|
+
|
|
34
|
+
Scenario: Cancel an activity with adjustment
|
|
35
|
+
Given I simulate until 2026-01-02
|
|
36
|
+
|
|
37
|
+
When I save and reload the accountant
|
|
38
|
+
And I cancel the 2026-01-01 issue on 2026-01-02
|
|
39
|
+
|
|
40
|
+
Then I have these balances:
|
|
41
|
+
| cash | 0.00 |
|
|
42
|
+
| principal | 0.00 |
|
|
43
|
+
| interest | 0.00 |
|
|
44
|
+
| revenue | 0.00 |
|
|
45
|
+
|
|
46
|
+
And these activity counts:
|
|
47
|
+
| issue | 1 |
|
|
48
|
+
| cancel | 1 |
|
|
49
|
+
| interest | 1 |
|
|
50
|
+
| adjustment | 1 |
|
|
51
|
+
|
|
52
|
+
Scenario: Cancel an activity in the past
|
|
53
|
+
When I simulate until 2026-01-08
|
|
54
|
+
And I save and reload the accountant
|
|
55
|
+
And I cancel the 2026-01-06 payment on 2026-01-08
|
|
56
|
+
|
|
57
|
+
Then I have these balances:
|
|
58
|
+
| cash | -1000.00 |
|
|
59
|
+
| principal | 1000.00 |
|
|
60
|
+
| interest | 7.00 |
|
|
61
|
+
| revenue | -7.00 |
|
|
62
|
+
|
|
63
|
+
And these activity counts:
|
|
64
|
+
| issue | 1 |
|
|
65
|
+
| payment | 1 |
|
|
66
|
+
| interest | 5 |
|
|
67
|
+
| cancel | 1 |
|
|
68
|
+
| adjustment | 1 |
|
|
69
|
+
|
|
70
|
+
Scenario: Cancel an activity entered today
|
|
71
|
+
When I simulate until 2026-01-06
|
|
72
|
+
|
|
73
|
+
Then I have these balances:
|
|
74
|
+
| cash | 5.00 |
|
|
75
|
+
| principal | 0.00 |
|
|
76
|
+
| interest | 0.00 |
|
|
77
|
+
| revenue | -5.00 |
|
|
78
|
+
|
|
79
|
+
And these activity counts:
|
|
80
|
+
| issue | 1 |
|
|
81
|
+
| payment | 1 |
|
|
82
|
+
| interest | 5 |
|
|
83
|
+
|
|
84
|
+
When I save and reload the accountant
|
|
85
|
+
And I cancel the 2026-01-06 payment on 2026-01-06
|
|
86
|
+
|
|
87
|
+
Then I have these balances:
|
|
88
|
+
| cash | -1000.00 |
|
|
89
|
+
| principal | 1000.00 |
|
|
90
|
+
| interest | 5.00 |
|
|
91
|
+
| revenue | -5.00 |
|
|
92
|
+
|
|
93
|
+
And these activity counts:
|
|
94
|
+
| issue | 1 |
|
|
95
|
+
| payment | 1 |
|
|
96
|
+
| interest | 5 |
|
|
97
|
+
| cancel | 1 |
|
|
98
|
+
|
|
99
|
+
Scenario: Cancel an interleaved activity
|
|
100
|
+
When I simulate these activities:
|
|
101
|
+
| payment | 2026-01-01 | 500.00 |
|
|
102
|
+
| finish | 2026-01-06 | |
|
|
103
|
+
|
|
104
|
+
Then I have these balances:
|
|
105
|
+
| cash | 505.00 |
|
|
106
|
+
| principal | 0.00 |
|
|
107
|
+
| interest | 0.00 |
|
|
108
|
+
| revenue | -2.50 |
|
|
109
|
+
| payable | -502.50 |
|
|
110
|
+
|
|
111
|
+
And I have these activity counts:
|
|
112
|
+
| issue | 1 |
|
|
113
|
+
| payment | 2 |
|
|
114
|
+
| interest | 5 |
|
|
115
|
+
|
|
116
|
+
When I cancel the 2026-01-01 payment
|
|
117
|
+
|
|
118
|
+
Then I have these balances:
|
|
119
|
+
| cash | 5.00 |
|
|
120
|
+
| principal | 0.00 |
|
|
121
|
+
| interest | 0.00 |
|
|
122
|
+
| revenue | -5.00 |
|
|
123
|
+
| payable | 0.00 |
|
|
124
|
+
|
|
125
|
+
And these activity counts:
|
|
126
|
+
| issue | 1 |
|
|
127
|
+
| payment | 2 |
|
|
128
|
+
| interest | 5 |
|
|
129
|
+
| cancel | 1 |
|
|
130
|
+
| adjustment | 1 |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Feature: Daily
|
|
2
|
+
|
|
3
|
+
An accountant can define a daily process to run during a simulation.
|
|
4
|
+
It may accrue daily interest, assess late fees, default principal balances, etc.
|
|
5
|
+
|
|
6
|
+
In addition, it could be the actual process used to perform these activities on a daily basis.
|
|
7
|
+
|
|
8
|
+
Background:
|
|
9
|
+
Given a daily accountant
|
|
10
|
+
And a start date of today
|
|
11
|
+
And an interest rate of 36.5%
|
|
12
|
+
|
|
13
|
+
Scenario: Daily guard passes
|
|
14
|
+
When I run the daily for today
|
|
15
|
+
Then I have an interest activity
|
|
16
|
+
|
|
17
|
+
Scenario: Daily guard fails
|
|
18
|
+
When I run the daily for today
|
|
19
|
+
Then I have 1 activity
|
|
20
|
+
|
|
21
|
+
When I run the daily for tomorrow
|
|
22
|
+
Then I still have 1 activity
|
|
23
|
+
|
|
24
|
+
When I run the daily for 2 days from now
|
|
25
|
+
Then I have 2 activities
|
|
26
|
+
|
|
27
|
+
And I have these balances:
|
|
28
|
+
| interest | 2.00 |
|
|
29
|
+
| revenue | -2.00 |
|
|
30
|
+
|
|
31
|
+
Scenario: Daily runs only once
|
|
32
|
+
Given a simulating accountant
|
|
33
|
+
And a start date of today
|
|
34
|
+
And an interest rate of 36.5%
|
|
35
|
+
And I simulate these activities:
|
|
36
|
+
| issue | 1000.00 |
|
|
37
|
+
|
|
38
|
+
When I run the daily for tomorrow
|
|
39
|
+
Then I have 1 interest activity
|
|
40
|
+
|
|
41
|
+
When I run the daily for tomorrow
|
|
42
|
+
Then I have 1 interest activity
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Feature: Default
|
|
2
|
+
|
|
3
|
+
Scenario: Late principal and interest
|
|
4
|
+
Given a defaulting accountant
|
|
5
|
+
And a start date of 2026-01-01
|
|
6
|
+
And an interest rate of 36.5%
|
|
7
|
+
|
|
8
|
+
And the schedule:
|
|
9
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
10
|
+
| default | 2026-02-01 | 100.00 |
|
|
11
|
+
| default | 2026-03-01 | 100.00 |
|
|
12
|
+
|
|
13
|
+
When I simulate until 2026-02-01
|
|
14
|
+
|
|
15
|
+
Then I save and reload the accountant
|
|
16
|
+
And I have these balances:
|
|
17
|
+
| cash | -1000.00 |
|
|
18
|
+
| principal | 931.00 |
|
|
19
|
+
| principal_late | 69.00 |
|
|
20
|
+
| interest | 0.00 |
|
|
21
|
+
| interest_late | 31.00 |
|
|
22
|
+
| revenue | -31.00 |
|
|
23
|
+
|
|
24
|
+
When I simulate until 2026-03-01
|
|
25
|
+
|
|
26
|
+
Then I save and reload the accountant
|
|
27
|
+
And I have these balances:
|
|
28
|
+
| cash | -1000.00 |
|
|
29
|
+
| principal | 859.00 |
|
|
30
|
+
| principal_late | 141.00 |
|
|
31
|
+
| interest | 0.00 |
|
|
32
|
+
| interest_late | 59.00 |
|
|
33
|
+
| revenue | -59.00 |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Feature: Ledgers
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given a multiple ledgers accountant
|
|
5
|
+
And a start date of 2026-01-01
|
|
6
|
+
And an interest rate of 36.5%
|
|
7
|
+
And the schedule:
|
|
8
|
+
| payment | 2026-01-06 | 1005.00 |
|
|
9
|
+
|
|
10
|
+
Scenario: Different dailies
|
|
11
|
+
When I simulate these activities:
|
|
12
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
13
|
+
| finish | 2026-01-06 | |
|
|
14
|
+
|
|
15
|
+
Then the default ledger has these balances:
|
|
16
|
+
| cash | 5.00 |
|
|
17
|
+
| principal | 0.00 |
|
|
18
|
+
| interest | 0.00 |
|
|
19
|
+
| revenue | -5.00 |
|
|
20
|
+
|
|
21
|
+
And the aggressive ledger has these balances:
|
|
22
|
+
| cash | 5.00 |
|
|
23
|
+
| principal | 45.00 |
|
|
24
|
+
| interest | 0.00 |
|
|
25
|
+
| revenue | -50.00 |
|
|
26
|
+
|
|
27
|
+
And I have these activity counts:
|
|
28
|
+
| issue | 1 |
|
|
29
|
+
| payment | 1 |
|
|
30
|
+
| interest | 5 |
|
|
31
|
+
|
|
32
|
+
When I save and reload the accountant
|
|
33
|
+
|
|
34
|
+
Then I have these activity counts:
|
|
35
|
+
| issue | 1 |
|
|
36
|
+
| payment | 1 |
|
|
37
|
+
| interest | 5 |
|
|
38
|
+
|
|
39
|
+
And the default ledger has these balances:
|
|
40
|
+
| cash | 5.00 |
|
|
41
|
+
| principal | 0.00 |
|
|
42
|
+
| interest | 0.00 |
|
|
43
|
+
| revenue | -5.00 |
|
|
44
|
+
|
|
45
|
+
And the default ledger has these aggregated balances:
|
|
46
|
+
| accruing | 0.00 |
|
|
47
|
+
| varying | 0.00 |
|
|
48
|
+
|
|
49
|
+
And the aggressive ledger has these balances:
|
|
50
|
+
| cash | 5.00 |
|
|
51
|
+
| principal | 45.00 |
|
|
52
|
+
| interest | 0.00 |
|
|
53
|
+
| revenue | -50.00 |
|
|
54
|
+
|
|
55
|
+
And the aggressive ledger has these aggregated balances:
|
|
56
|
+
| accruing | 45.00 |
|
|
57
|
+
| varying | 50.00 |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Feature: Retroactive Activities
|
|
2
|
+
|
|
3
|
+
Create activities that are accounted for in present, but effective in the past.
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given a simulating accountant
|
|
7
|
+
And a start date of 2026-01-01
|
|
8
|
+
And an interest rate of 36.5%
|
|
9
|
+
|
|
10
|
+
Scenario: Retroactive activity
|
|
11
|
+
|
|
12
|
+
When I simulate to 2026-01-03
|
|
13
|
+
|
|
14
|
+
Then I have all zero balances
|
|
15
|
+
|
|
16
|
+
When I apply an issue activity effective 2026-01-01 for $1000.00
|
|
17
|
+
|
|
18
|
+
Then I have these balances:
|
|
19
|
+
| cash | -1000.00 |
|
|
20
|
+
| principal | 1000.00 |
|
|
21
|
+
| interest | 2.00 |
|
|
22
|
+
| revenue | -2.00 |
|
|
23
|
+
|
|
24
|
+
And I have these activity counts:
|
|
25
|
+
| issue | 1 |
|
|
26
|
+
| adjustment | 1 |
|
|
27
|
+
|
|
28
|
+
Scenario: Retroactive activity with adjustment
|
|
29
|
+
|
|
30
|
+
When I simulate these activities:
|
|
31
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
32
|
+
| finish | 2026-01-02 | |
|
|
33
|
+
|
|
34
|
+
Then I have these balances:
|
|
35
|
+
| cash | -1000.00 |
|
|
36
|
+
| principal | 1000.00 |
|
|
37
|
+
| interest | 1.00 |
|
|
38
|
+
| revenue | -1.00 |
|
|
39
|
+
|
|
40
|
+
When I apply a payment activity effective 2026-01-01 for $1000.00
|
|
41
|
+
|
|
42
|
+
Then I have all zero balances
|
|
43
|
+
And I have these activity counts:
|
|
44
|
+
| issue | 1 |
|
|
45
|
+
| payment | 1 |
|
|
46
|
+
| interest | 1 |
|
|
47
|
+
| adjustment | 1 |
|
|
48
|
+
|
|
49
|
+
Scenario: Cancelling a retroactive activity
|
|
50
|
+
|
|
51
|
+
When I simulate these activities:
|
|
52
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
53
|
+
| finish | 2026-01-02 | |
|
|
54
|
+
|
|
55
|
+
And I apply a payment activity effective 2026-01-01 for $500.00
|
|
56
|
+
|
|
57
|
+
Then I have these balances:
|
|
58
|
+
| cash | -500.00 |
|
|
59
|
+
| principal | 500.00 |
|
|
60
|
+
| interest | 0.50 |
|
|
61
|
+
| revenue | -0.50 |
|
|
62
|
+
|
|
63
|
+
When I simulate to 2026-01-03
|
|
64
|
+
|
|
65
|
+
Then I have these balances:
|
|
66
|
+
| cash | -500.00 |
|
|
67
|
+
| principal | 500.00 |
|
|
68
|
+
| interest | 1.00 |
|
|
69
|
+
| revenue | -1.00 |
|
|
70
|
+
|
|
71
|
+
When I cancel the 2026-01-01 payment
|
|
72
|
+
|
|
73
|
+
Then I have these balances:
|
|
74
|
+
| cash | -1000.00 |
|
|
75
|
+
| principal | 1000.00 |
|
|
76
|
+
| interest | 2.00 |
|
|
77
|
+
| revenue | -2.00 |
|
|
78
|
+
|
|
79
|
+
When I save and reload the accountant
|
|
80
|
+
|
|
81
|
+
Then I have these balances:
|
|
82
|
+
| cash | -1000.00 |
|
|
83
|
+
| principal | 1000.00 |
|
|
84
|
+
| interest | 2.00 |
|
|
85
|
+
| revenue | -2.00 |
|
|
86
|
+
|
|
87
|
+
And I have these activity counts:
|
|
88
|
+
| issue | 1 |
|
|
89
|
+
| interest | 2 |
|
|
90
|
+
| payment | 1 |
|
|
91
|
+
| cancel | 1 |
|
|
92
|
+
| adjustment | 2 |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Feature: Returns
|
|
2
|
+
|
|
3
|
+
Create an adjustment activity when an activity is returned.
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given a simulating accountant
|
|
7
|
+
|
|
8
|
+
And a start date of 2016-01-01
|
|
9
|
+
And an interest rate of 36.5%
|
|
10
|
+
And the schedule:
|
|
11
|
+
| payment | 2026-01-06 | 1005.00 |
|
|
12
|
+
|
|
13
|
+
And I simulate these activities:
|
|
14
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
15
|
+
|
|
16
|
+
Then I have these balances:
|
|
17
|
+
| cash | -1000.00 |
|
|
18
|
+
| principal | 1000.00 |
|
|
19
|
+
|
|
20
|
+
And I have 1 issue activity
|
|
21
|
+
|
|
22
|
+
Scenario: Return an activity without adjustment
|
|
23
|
+
When I save and reload the accountant
|
|
24
|
+
And I return the 2026-01-01 issue
|
|
25
|
+
|
|
26
|
+
Then I have these balances:
|
|
27
|
+
| cash | 0.00 |
|
|
28
|
+
| principal | 0.00 |
|
|
29
|
+
|
|
30
|
+
And these activity counts:
|
|
31
|
+
| issue | 1 |
|
|
32
|
+
| return | 1 |
|
|
33
|
+
|
|
34
|
+
Scenario: Return an activity with adjustment
|
|
35
|
+
When I simulate until 2026-01-02
|
|
36
|
+
|
|
37
|
+
And I save and reload the accountant
|
|
38
|
+
And I return the 2026-01-01 issue
|
|
39
|
+
|
|
40
|
+
Then I have these balances:
|
|
41
|
+
| cash | 0.00 |
|
|
42
|
+
| principal | 0.00 |
|
|
43
|
+
| interest | 0.00 |
|
|
44
|
+
| revenue | 0.00 |
|
|
45
|
+
|
|
46
|
+
And these activity counts:
|
|
47
|
+
| issue | 1 |
|
|
48
|
+
| return | 1 |
|
|
49
|
+
| interest | 1 |
|
|
50
|
+
| adjustment | 1 |
|
|
51
|
+
|
|
52
|
+
Scenario: Return an activity entered today
|
|
53
|
+
When I simulate until 2026-01-06
|
|
54
|
+
|
|
55
|
+
Then I have these balances:
|
|
56
|
+
| cash | 5.00 |
|
|
57
|
+
| principal | 0.00 |
|
|
58
|
+
| interest | 0.00 |
|
|
59
|
+
| revenue | -5.00 |
|
|
60
|
+
|
|
61
|
+
And these activity counts:
|
|
62
|
+
| issue | 1 |
|
|
63
|
+
| payment | 1 |
|
|
64
|
+
| interest | 5 |
|
|
65
|
+
|
|
66
|
+
When I save and reload the accountant
|
|
67
|
+
And I return the 2026-01-06 payment
|
|
68
|
+
|
|
69
|
+
Then I have these balances:
|
|
70
|
+
| cash | -1000.00 |
|
|
71
|
+
| principal | 1000.00 |
|
|
72
|
+
| interest | 5.00 |
|
|
73
|
+
| revenue | -5.00 |
|
|
74
|
+
|
|
75
|
+
And these activity counts:
|
|
76
|
+
| issue | 1 |
|
|
77
|
+
| payment | 1 |
|
|
78
|
+
| interest | 5 |
|
|
79
|
+
| return | 1 |
|
|
80
|
+
|
|
81
|
+
Scenario: Return an interleaved activity
|
|
82
|
+
When I simulate these activities:
|
|
83
|
+
| payment | 2026-01-01 | 500.00 |
|
|
84
|
+
| finish | 2026-01-06 | |
|
|
85
|
+
|
|
86
|
+
Then I have these balances:
|
|
87
|
+
| cash | 505.00 |
|
|
88
|
+
| principal | 0.00 |
|
|
89
|
+
| interest | 0.00 |
|
|
90
|
+
| revenue | -2.50 |
|
|
91
|
+
| payable | -502.50 |
|
|
92
|
+
|
|
93
|
+
And these activity counts:
|
|
94
|
+
| issue | 1 |
|
|
95
|
+
| payment | 2 |
|
|
96
|
+
| interest | 5 |
|
|
97
|
+
|
|
98
|
+
When I return the 2026-01-01 payment
|
|
99
|
+
|
|
100
|
+
Then I have these balances:
|
|
101
|
+
| cash | 5.00 |
|
|
102
|
+
| principal | 0.00 |
|
|
103
|
+
| interest | 0.00 |
|
|
104
|
+
| revenue | -5.00 |
|
|
105
|
+
| payable | 0.00 |
|
|
106
|
+
|
|
107
|
+
And these activity counts:
|
|
108
|
+
| issue | 1 |
|
|
109
|
+
| payment | 2 |
|
|
110
|
+
| interest | 5 |
|
|
111
|
+
| return | 1 |
|
|
112
|
+
| adjustment | 1 |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Feature: Reversal
|
|
2
|
+
|
|
3
|
+
Reverse an activity.
|
|
4
|
+
|
|
5
|
+
Background:
|
|
6
|
+
Given a simulating accountant
|
|
7
|
+
|
|
8
|
+
And a start date of 2026-01-01
|
|
9
|
+
And an interest rate of 0.00%
|
|
10
|
+
|
|
11
|
+
And I simulate these activities:
|
|
12
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
13
|
+
| late_fee | 2026-01-02 | 10.00 |
|
|
14
|
+
|
|
15
|
+
Then I have these balances:
|
|
16
|
+
| principal | 1000.00 |
|
|
17
|
+
| cash | -1000.00 |
|
|
18
|
+
| late_fee | 10.00 |
|
|
19
|
+
| late_fee_revenue | -10.00 |
|
|
20
|
+
|
|
21
|
+
And I have a late_fee activity
|
|
22
|
+
And I save and reload the accountant
|
|
23
|
+
|
|
24
|
+
Scenario: Reverse an activity
|
|
25
|
+
When I reverse the 2026-01-02 late_fee
|
|
26
|
+
|
|
27
|
+
Then I have these balances:
|
|
28
|
+
| principal | 1000.00 |
|
|
29
|
+
| cash | -1000.00 |
|
|
30
|
+
| late_fee | 0.00 |
|
|
31
|
+
| late_fee_revenue | 0.00 |
|
|
32
|
+
|
|
33
|
+
And these activity counts:
|
|
34
|
+
| issue | 1 |
|
|
35
|
+
| late_fee | 1 |
|
|
36
|
+
| reversal | 1 |
|
|
37
|
+
|
|
38
|
+
# Cancelling the late fee would make a lot more sense,
|
|
39
|
+
# as it will create the adjustment and zero out the late_fee
|
|
40
|
+
# receivable
|
|
41
|
+
Scenario: Reverse an activity, retroactively
|
|
42
|
+
When I apply a payment for $10
|
|
43
|
+
And I simulate until 2026-01-03
|
|
44
|
+
|
|
45
|
+
And I reverse the 2026-01-02 late_fee
|
|
46
|
+
|
|
47
|
+
Then I have these balances:
|
|
48
|
+
| principal | 1000.00 |
|
|
49
|
+
| cash | -990.00 |
|
|
50
|
+
| late_fee | -10.00 |
|
|
51
|
+
| late_fee_revenue | 0.00 |
|
|
52
|
+
|
|
53
|
+
And these activity counts:
|
|
54
|
+
| issue | 1 |
|
|
55
|
+
| late_fee | 1 |
|
|
56
|
+
| payment | 1 |
|
|
57
|
+
| reversal | 1 |
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Feature: Simulation
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given a simulating accountant
|
|
5
|
+
And a start date of 2026-01-01
|
|
6
|
+
And an interest rate of 36.5%
|
|
7
|
+
And the schedule:
|
|
8
|
+
| payment | 2026-01-11 | 1010.00 |
|
|
9
|
+
|
|
10
|
+
Scenario: No start date
|
|
11
|
+
Given the accountant:
|
|
12
|
+
"""
|
|
13
|
+
@accountant = SimulatingAccountant.new
|
|
14
|
+
"""
|
|
15
|
+
Then the accountant is valid
|
|
16
|
+
But I cannot simulate
|
|
17
|
+
|
|
18
|
+
Scenario: Same day payoff
|
|
19
|
+
When I simulate these activities:
|
|
20
|
+
| issue | 2026-01-01 | 100.00 |
|
|
21
|
+
| payment | 2026-01-01 | 100.00 |
|
|
22
|
+
|
|
23
|
+
Then I have these balances:
|
|
24
|
+
| cash | 0.00 |
|
|
25
|
+
| principal | 0.00 |
|
|
26
|
+
|
|
27
|
+
Scenario: Next-Day Payment
|
|
28
|
+
When I simulate these activities:
|
|
29
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
30
|
+
| payment | 2026-01-02 | 1000.00 |
|
|
31
|
+
|
|
32
|
+
Then I have these balances:
|
|
33
|
+
| cash | 0.00 |
|
|
34
|
+
| principal | 1.00 |
|
|
35
|
+
| interest | 0.00 |
|
|
36
|
+
| revenue | -1.00 |
|
|
37
|
+
|
|
38
|
+
And I have these activity counts:
|
|
39
|
+
| issue | 1 |
|
|
40
|
+
| payment | 1 |
|
|
41
|
+
| interest | 1 |
|
|
42
|
+
|
|
43
|
+
Scenario: Simulation with scheduled activity
|
|
44
|
+
When I simulate these activities:
|
|
45
|
+
| issue | 2026-01-01 | 1000.00 |
|
|
46
|
+
| finish | 2026-01-11 | |
|
|
47
|
+
|
|
48
|
+
Then I have these balances:
|
|
49
|
+
| cash | 10.00 |
|
|
50
|
+
| principal | 0.00 |
|
|
51
|
+
| interest | 0.00 |
|
|
52
|
+
| revenue | -10.00 |
|
|
53
|
+
|
|
54
|
+
And I have these activity counts:
|
|
55
|
+
| issue | 1 |
|
|
56
|
+
| payment | 1 |
|
|
57
|
+
| interest | 10 |
|
|
58
|
+
|
|
59
|
+
And I save the accountant
|
|
60
|
+
|
|
61
|
+
When I reload the accountant with a start date of 2026-01-02
|
|
62
|
+
Then I have these balances:
|
|
63
|
+
| cash | -1000.00 |
|
|
64
|
+
| principal | 1000.00 |
|
|
65
|
+
| interest | 1.00 |
|
|
66
|
+
| revenue | -1.00 |
|
|
67
|
+
|
|
68
|
+
And I have these activity counts:
|
|
69
|
+
| issue | 1 |
|
|
70
|
+
| interest | 1 |
|
|
71
|
+
|
|
72
|
+
When I reload the accountant with a start_date of 2026-01-11
|
|
73
|
+
Then I have these balances:
|
|
74
|
+
| cash | 10.00 |
|
|
75
|
+
| principal | 0.00 |
|
|
76
|
+
| interest | 0.00 |
|
|
77
|
+
| revenue | -10.00 |
|
|
78
|
+
|
|
79
|
+
And I have these activity counts:
|
|
80
|
+
| issue | 1 |
|
|
81
|
+
| payment | 1 |
|
|
82
|
+
| interest | 10 |
|
|
83
|
+
|
|
84
|
+
Scenario: Multiple interest rates
|
|
85
|
+
Given a simulating accountant
|
|
86
|
+
And a start date of 2026-01-01
|
|
87
|
+
And the interest rates:
|
|
88
|
+
| effective_date | rate |
|
|
89
|
+
| 2026-01-01 | 36.5 |
|
|
90
|
+
| 2026-01-02 | 0 |
|
|
91
|
+
| 2026-01-03 | 36.5 |
|
|
92
|
+
|
|
93
|
+
When I simulate these activities:
|
|
94
|
+
| issue | 2026-01-01 | 1000.0 |
|
|
95
|
+
| finish | 2026-01-04 | |
|
|
96
|
+
|
|
97
|
+
Then I have these balances:
|
|
98
|
+
| cash | -1000.00 |
|
|
99
|
+
| principal | 1000.00 |
|
|
100
|
+
| interest | 2.00 |
|
|
101
|
+
| revenue | -2.00 |
|
|
102
|
+
|
|
103
|
+
Scenario: Daily interest rate, payment on start date
|
|
104
|
+
Given a simulating accountant
|
|
105
|
+
And a start date of 2026-01-01
|
|
106
|
+
And a daily interest rate of 1.00%
|
|
107
|
+
And the schedule:
|
|
108
|
+
| issue | 2026-01-01 | 1000.00|
|
|
109
|
+
| payment | 2026-01-01 | 500.00 |
|
|
110
|
+
|
|
111
|
+
When I simulate until 2026-01-01
|
|
112
|
+
Then I have these balances:
|
|
113
|
+
| cash | -500.00 |
|
|
114
|
+
| principal | 500.00 |
|
|
115
|
+
|
|
116
|
+
When I simulate until 2026-01-01
|
|
117
|
+
Then I have these balances:
|
|
118
|
+
| cash | -500.00 |
|
|
119
|
+
| principal | 500.00 |
|
|
120
|
+
|
|
121
|
+
When I save the accountant
|
|
122
|
+
And I reload the accountant with a start date of 2026-01-03
|
|
123
|
+
And I simulate until 2026-01-03
|
|
124
|
+
Then I have these balances:
|
|
125
|
+
| cash | -500.00 |
|
|
126
|
+
| principal | 500.00 |
|
|
127
|
+
| interest | 5.00 |
|
|
128
|
+
| revenue | -5.00 |
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class AdjustingAccountant < Morty::Accountant
|
|
2
|
+
balance :accruing, %w[principal]
|
|
3
|
+
|
|
4
|
+
activity :issue do
|
|
5
|
+
entry :principal, :cash, amount
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
activity :interest do
|
|
9
|
+
if rate = accountant.rate_for(accountant.date.yesterday)
|
|
10
|
+
amount ||= (rate.daily_for(accountant.date.yesterday) * balances[:accruing]).floor(2)
|
|
11
|
+
|
|
12
|
+
entry :interest, :revenue, amount
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
activity :late_fee do
|
|
17
|
+
entry :late_fee, :late_fee_revenue, amount
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
waterfall :payment, limit: :cr, complete: true, entries: <<~END
|
|
21
|
+
cash late_fee
|
|
22
|
+
cash interest
|
|
23
|
+
cash principal
|
|
24
|
+
cash payable
|
|
25
|
+
END
|
|
26
|
+
|
|
27
|
+
daily do
|
|
28
|
+
activity :interest, accountant.date.yesterday
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
daily_guard do
|
|
32
|
+
accountant.activities.with_type(:interest).none? { |a| a.effective_date == accountant.date.yesterday }
|
|
33
|
+
end
|
|
34
|
+
end
|