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.
Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/workflows/ci.yml +107 -0
  4. data/.gitignore +17 -3
  5. data/.rubocop.yml +20 -0
  6. data/Appraisals +24 -0
  7. data/Gemfile +28 -1
  8. data/LICENSE +21 -0
  9. data/README.md +37 -7
  10. data/Rakefile +37 -0
  11. data/app/models/morty/account.rb +37 -0
  12. data/app/models/morty/account_type.rb +7 -0
  13. data/app/models/morty/activity.rb +147 -0
  14. data/app/models/morty/activity_type.rb +7 -0
  15. data/app/models/morty/application_record.rb +6 -0
  16. data/app/models/morty/entry.rb +24 -0
  17. data/app/models/morty/entry_type.rb +23 -0
  18. data/app/models/morty/ledger.rb +8 -0
  19. data/config/routes.rb +2 -0
  20. data/config.ru +7 -0
  21. data/cucumber.yml +2 -0
  22. data/db/migrate/20260224063053_create_morty_schema.rb +17 -0
  23. data/db/seeds.rb +18 -0
  24. data/db/sql/create_morty_schema.sql +479 -0
  25. data/features/accountant.feature +47 -0
  26. data/features/adjustment.feature +79 -0
  27. data/features/cancel.feature +130 -0
  28. data/features/daily.feature +42 -0
  29. data/features/default.feature +33 -0
  30. data/features/ledger.feature +57 -0
  31. data/features/retroactive.feature +92 -0
  32. data/features/return.feature +112 -0
  33. data/features/reversal.feature +57 -0
  34. data/features/simulation.feature +128 -0
  35. data/features/support/accountants/adjusting_accountant.rb +34 -0
  36. data/features/support/accountants/daily_accountant.rb +13 -0
  37. data/features/support/accountants/default_accountant.rb +2 -0
  38. data/features/support/accountants/defaulting_accountant.rb +32 -0
  39. data/features/support/accountants/multiple_ledgers_accountant.rb +51 -0
  40. data/features/support/accountants/simulating_accountant.rb +36 -0
  41. data/features/support/accountants/sourceless_accountant.rb +2 -0
  42. data/features/support/accountants/waterfalling_accountant.rb +15 -0
  43. data/features/support/env.rb +17 -0
  44. data/features/waterfall.feature +34 -0
  45. data/gemfiles/rails_7.0.gemfile +30 -0
  46. data/gemfiles/rails_7.0.gemfile.lock +494 -0
  47. data/gemfiles/rails_7.1.gemfile +30 -0
  48. data/gemfiles/rails_7.1.gemfile.lock +543 -0
  49. data/gemfiles/rails_7.2.gemfile +30 -0
  50. data/gemfiles/rails_7.2.gemfile.lock +539 -0
  51. data/gemfiles/rails_8.0.gemfile +30 -0
  52. data/gemfiles/rails_8.0.gemfile.lock +536 -0
  53. data/gemfiles/rails_8.1.gemfile +30 -0
  54. data/gemfiles/rails_8.1.gemfile.lock +538 -0
  55. data/lib/morty/accountant.rb +332 -0
  56. data/lib/morty/adjustment.rb +64 -0
  57. data/lib/morty/book.rb +54 -0
  58. data/lib/morty/context/activity.rb +52 -0
  59. data/lib/morty/context/daily.rb +23 -0
  60. data/lib/morty/context/simulation.rb +26 -0
  61. data/lib/morty/cucumber/helpers.rb +27 -0
  62. data/lib/morty/cucumber/steps.rb +191 -0
  63. data/lib/morty/diff.rb +71 -0
  64. data/lib/morty/dsl.rb +86 -0
  65. data/lib/morty/engine.rb +21 -0
  66. data/lib/morty/error.rb +3 -0
  67. data/lib/morty/event.rb +27 -0
  68. data/lib/morty/list/activity.rb +57 -0
  69. data/lib/morty/rate.rb +59 -0
  70. data/lib/morty/schedule.rb +36 -0
  71. data/lib/morty/seed.rb +60 -0
  72. data/lib/morty/source.rb +19 -0
  73. data/lib/morty/tasks/morty_tasks.rake +4 -0
  74. data/lib/morty/version.rb +1 -1
  75. data/lib/morty.rb +27 -1
  76. data/morty.gemspec +22 -19
  77. data/spec/dummy/Rakefile +6 -0
  78. data/spec/dummy/app/assets/images/.keep +0 -0
  79. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  80. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  81. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  82. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  83. data/spec/dummy/app/jobs/application_job.rb +7 -0
  84. data/spec/dummy/app/models/application_record.rb +3 -0
  85. data/spec/dummy/app/models/concerns/.keep +0 -0
  86. data/spec/dummy/app/views/layouts/application.html.erb +28 -0
  87. data/spec/dummy/app/views/pwa/manifest.json.erb +22 -0
  88. data/spec/dummy/app/views/pwa/service-worker.js +26 -0
  89. data/spec/dummy/bin/ci +6 -0
  90. data/spec/dummy/bin/dev +2 -0
  91. data/spec/dummy/bin/rails +4 -0
  92. data/spec/dummy/bin/rake +4 -0
  93. data/spec/dummy/bin/setup +35 -0
  94. data/spec/dummy/config/application.rb +48 -0
  95. data/spec/dummy/config/boot.rb +5 -0
  96. data/spec/dummy/config/cable.yml +10 -0
  97. data/spec/dummy/config/ci.rb +15 -0
  98. data/spec/dummy/config/database.yml +15 -0
  99. data/spec/dummy/config/environment.rb +5 -0
  100. data/spec/dummy/config/environments/development.rb +47 -0
  101. data/spec/dummy/config/environments/test.rb +53 -0
  102. data/spec/dummy/config/initializers/content_security_policy.rb +29 -0
  103. data/spec/dummy/config/initializers/filter_parameter_logging.rb +8 -0
  104. data/spec/dummy/config/initializers/inflections.rb +16 -0
  105. data/spec/dummy/config/locales/en.yml +31 -0
  106. data/spec/dummy/config/puma.rb +39 -0
  107. data/spec/dummy/config/routes.rb +3 -0
  108. data/spec/dummy/config/storage.yml +27 -0
  109. data/spec/dummy/config.ru +6 -0
  110. data/spec/dummy/db/seeds.rb +52 -0
  111. data/spec/dummy/log/.keep +0 -0
  112. data/spec/dummy/public/400.html +135 -0
  113. data/spec/dummy/public/404.html +135 -0
  114. data/spec/dummy/public/406-unsupported-browser.html +135 -0
  115. data/spec/dummy/public/422.html +135 -0
  116. data/spec/dummy/public/500.html +135 -0
  117. data/spec/dummy/public/icon.png +0 -0
  118. data/spec/dummy/public/icon.svg +3 -0
  119. data/spec/lib/accountant_spec.rb +236 -0
  120. data/spec/lib/book_spec.rb +91 -0
  121. data/spec/lib/diff_spec.rb +102 -0
  122. data/spec/lib/event_spec.rb +53 -0
  123. data/spec/lib/list/activity_spec.rb +117 -0
  124. data/spec/lib/schedule_spec.rb +106 -0
  125. data/spec/lib/source_spec.rb +31 -0
  126. data/spec/models/account_spec.rb +48 -0
  127. data/spec/models/activity_spec.rb +139 -0
  128. data/spec/models/entry_spec.rb +41 -0
  129. data/spec/models/entry_type_spec.rb +43 -0
  130. data/spec/rate_spec.rb +83 -0
  131. data/spec/spec_helper.rb +36 -0
  132. data/spec/support/test_helpers.rb +25 -0
  133. metadata +193 -16
  134. data/LICENSE.txt +0 -22
@@ -0,0 +1,32 @@
1
+ class DefaultingAccountant < Morty::Accountant
2
+ balance :accruing, %w[principal principal_late]
3
+
4
+ activity :issue do
5
+ entry :principal, :cash, amount
6
+ end
7
+
8
+ activity :interest do
9
+ entry :interest, :revenue, amount
10
+ end
11
+
12
+ daily do
13
+ activity :interest, today, rate.daily * balances[:accruing]
14
+ end
15
+
16
+ daily_guard do
17
+ accountant.activities.with_type(:interest).none? { |a| a.effective_date == accountant.date }
18
+ end
19
+
20
+ waterfall :default, limit: :cr, entries: <<~END
21
+ interest_late interest
22
+ principal_late principal
23
+ END
24
+
25
+ waterfall :payment, limit: :cr, complete: true, entries: <<~END
26
+ cash interest_late
27
+ cash principal_late
28
+ cash interest
29
+ cash principal
30
+ cash payable
31
+ END
32
+ end
@@ -0,0 +1,51 @@
1
+ class MultipleLedgersAccountant < Morty::Accountant
2
+ balance :accruing, %w[principal]
3
+ balance :varying, %w[principal]
4
+
5
+ daily do
6
+ activity :interest, today
7
+ end
8
+
9
+ daily_guard do
10
+ true
11
+ end
12
+
13
+ ledger :aggressive do
14
+ balance :accruing, %w[principal]
15
+ balance :varying, %w[principal cash]
16
+
17
+ activity :issue do
18
+ entry :principal, :cash, amount
19
+ end
20
+
21
+ activity :payment do
22
+ waterfall amount, limit: :cr, complete: true, entries: <<~END
23
+ cash interest
24
+ cash principal
25
+ cash payable
26
+ END
27
+ end
28
+
29
+ activity :interest do
30
+ entry :interest, :revenue, accountant.rate_for(activity.effective_date).daily * balances[:accruing] * 10
31
+ end
32
+ end
33
+
34
+ ledger :default do
35
+ activity :issue do
36
+ entry :principal, :cash, amount
37
+ end
38
+
39
+ activity :payment do
40
+ waterfall amount, limit: :cr, complete: true, entries: <<~END
41
+ cash interest
42
+ cash principal
43
+ cash payable
44
+ END
45
+ end
46
+
47
+ activity :interest do
48
+ entry :interest, :revenue, accountant.rate_for(activity.effective_date).daily * balances[:accruing]
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ class SimulatingAccountant < Morty::Accountant
2
+ balance :accruing, %w[principal]
3
+
4
+ activity :issue do
5
+ entry :principal, :cash, amount
6
+ end
7
+
8
+ waterfall :payment, limit: :cr, complete: true, entries: <<~END
9
+ cash late_fee
10
+ cash interest
11
+ cash principal
12
+ cash payable
13
+ END
14
+
15
+ activity :interest do
16
+ if rate = accountant.rate_for(accountant.date.yesterday)
17
+ amount ||= (rate.daily_for(accountant.date.yesterday) * balances[:accruing]).floor(2)
18
+
19
+ entry :interest, :revenue, amount
20
+ end
21
+ end
22
+
23
+ activity :late_fee do
24
+ entry :late_fee, :late_fee_revenue, amount
25
+ end
26
+
27
+ daily do
28
+ activity :interest, today
29
+ end
30
+
31
+ daily_guard do
32
+ accountant.activities.none? do |activity|
33
+ activity.effective_date == accountant.date && activity.type?(:interest)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2 @@
1
+ class SourcelessAccountant < Morty::Accountant
2
+ end
@@ -0,0 +1,15 @@
1
+ class WaterfallingAccountant < Morty::Accountant
2
+ activity :issue do
3
+ entry :receivable, :cash, amount
4
+ end
5
+
6
+ waterfall :payment, limit: :cr, complete: true, entries: <<~END
7
+ cash receivable
8
+ cash payable
9
+ END
10
+
11
+ waterfall :refund, limit: :dr, complete: true, entries: <<~END
12
+ payable cash
13
+ receivable cash
14
+ END
15
+ end
@@ -0,0 +1,17 @@
1
+ puts "loading features/support/env"
2
+
3
+ require "timecop"
4
+ require_relative "../../spec/spec_helper"
5
+
6
+ Morty::Engine.load_seed
7
+
8
+ Before do
9
+ ActiveRecord::Base.connection.begin_transaction(joinable: false)
10
+ end
11
+
12
+ After do
13
+ ActiveRecord::Base.connection.rollback_transaction
14
+ Timecop.return
15
+ end
16
+
17
+ require "morty/cucumber/steps"
@@ -0,0 +1,34 @@
1
+ Feature: Payment waterfalls
2
+
3
+ Background:
4
+ Given a waterfalling accountant
5
+ And a start date of today
6
+
7
+ Scenario: No limit
8
+ When I apply an issue activity for $1.00
9
+
10
+ Then I have these balances:
11
+ | cash | -1.00 |
12
+ | receivable | 1.00 |
13
+
14
+ Scenario: DR limit
15
+ When I apply these activities:
16
+ | issue | 1.00 |
17
+ | payment | 1.00 |
18
+ | payment | 2.00 |
19
+
20
+ Then I have these balances:
21
+ | cash | 2.00 |
22
+ | receivable | 0.00 |
23
+ | payable | -2.00 |
24
+
25
+ Scenario: CR limit
26
+ When I apply these activities:
27
+ | payment | 1.00 |
28
+ | refund | 1.00 |
29
+ | refund | 2.00 |
30
+
31
+ Then I have these balances:
32
+ | cash | -2.00 |
33
+ | receivable | 2.00 |
34
+ | payable | 0.00 |
@@ -0,0 +1,30 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails"
6
+ gem "lookup_by", github: "companygardener/lookup_by"
7
+ gem "rubocop-rails-omakase", require: false
8
+ gem "appraisal", "~> 2.5.0"
9
+ gem "activerecord", "~> 7.0.0"
10
+ gem "railties", "~> 7.0.0"
11
+
12
+ group :development, :test do
13
+ gem "rspec-rails"
14
+ gem "pg"
15
+ gem "puma"
16
+ gem "propshaft"
17
+ end
18
+
19
+ group :test do
20
+ gem "rspec-its"
21
+ gem "factory_bot_rails"
22
+ gem "shoulda-matchers"
23
+ gem "cucumber"
24
+ gem "simplecov", require: false
25
+ gem "rspec_junit_formatter"
26
+ gem "timecop"
27
+ gem "chronic"
28
+ end
29
+
30
+ gemspec path: "../"