lunchmoney 0.10.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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.github/dependabot.yml +18 -0
  4. data/.github/workflows/build_and_publish_yard_docs.yml +47 -0
  5. data/.github/workflows/ci.yml +58 -0
  6. data/.github/workflows/dependabot-rbi-updater.yml +43 -0
  7. data/.github/workflows/publish_gem.yml +31 -0
  8. data/.gitignore +62 -0
  9. data/.rubocop.yml +45 -0
  10. data/.ruby-version +1 -0
  11. data/.toys/.toys.rb +10 -0
  12. data/.toys/ci.rb +22 -0
  13. data/.toys/rbi.rb +60 -0
  14. data/.toys/rubocop.rb +10 -0
  15. data/.toys/spoom.rb +15 -0
  16. data/.toys/typecheck.rb +5 -0
  17. data/.yardopts +2 -0
  18. data/Appraisals +22 -0
  19. data/Gemfile +25 -0
  20. data/Gemfile.lock +174 -0
  21. data/LICENSE +21 -0
  22. data/README.md +57 -0
  23. data/bin/console +16 -0
  24. data/bin/rubocop +27 -0
  25. data/bin/setup +8 -0
  26. data/bin/spoom +27 -0
  27. data/bin/srb +27 -0
  28. data/bin/tapioca +27 -0
  29. data/bin/toys +27 -0
  30. data/bin/yard +27 -0
  31. data/lib/lunchmoney/api.rb +147 -0
  32. data/lib/lunchmoney/api_call.rb +109 -0
  33. data/lib/lunchmoney/assets/asset.rb +89 -0
  34. data/lib/lunchmoney/assets/asset_calls.rb +96 -0
  35. data/lib/lunchmoney/budget/budget.rb +74 -0
  36. data/lib/lunchmoney/budget/budget_calls.rb +82 -0
  37. data/lib/lunchmoney/budget/config.rb +38 -0
  38. data/lib/lunchmoney/budget/data.rb +42 -0
  39. data/lib/lunchmoney/categories/category/category.rb +52 -0
  40. data/lib/lunchmoney/categories/category/child_category.rb +42 -0
  41. data/lib/lunchmoney/categories/category_calls.rb +195 -0
  42. data/lib/lunchmoney/configuration.rb +26 -0
  43. data/lib/lunchmoney/crypto/crypto/crypto.rb +43 -0
  44. data/lib/lunchmoney/crypto/crypto/crypto_base.rb +65 -0
  45. data/lib/lunchmoney/crypto/crypto_calls.rb +49 -0
  46. data/lib/lunchmoney/data_object.rb +25 -0
  47. data/lib/lunchmoney/errors.rb +19 -0
  48. data/lib/lunchmoney/exceptions.rb +19 -0
  49. data/lib/lunchmoney/plaid_accounts/plaid_account.rb +73 -0
  50. data/lib/lunchmoney/plaid_accounts/plaid_account_calls.rb +38 -0
  51. data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense.rb +65 -0
  52. data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense_base.rb +29 -0
  53. data/lib/lunchmoney/recurring_expenses/recurring_expense_calls.rb +28 -0
  54. data/lib/lunchmoney/tags/tag/tag.rb +20 -0
  55. data/lib/lunchmoney/tags/tag/tag_base.rb +21 -0
  56. data/lib/lunchmoney/tags/tag_calls.rb +20 -0
  57. data/lib/lunchmoney/transactions/transaction/child_transaction.rb +31 -0
  58. data/lib/lunchmoney/transactions/transaction/split.rb +24 -0
  59. data/lib/lunchmoney/transactions/transaction/transaction.rb +156 -0
  60. data/lib/lunchmoney/transactions/transaction/transaction_base.rb +52 -0
  61. data/lib/lunchmoney/transactions/transaction/transaction_modification_base.rb +30 -0
  62. data/lib/lunchmoney/transactions/transaction/update_transaction.rb +43 -0
  63. data/lib/lunchmoney/transactions/transaction_calls.rb +218 -0
  64. data/lib/lunchmoney/user/user.rb +36 -0
  65. data/lib/lunchmoney/user/user_calls.rb +19 -0
  66. data/lib/lunchmoney/validators.rb +43 -0
  67. data/lib/lunchmoney/version.rb +7 -0
  68. data/lib/lunchmoney.rb +54 -0
  69. data/lunchmoney.gemspec +34 -0
  70. data/sorbet/config +5 -0
  71. data/sorbet/rbi/annotations/.gitattributes +1 -0
  72. data/sorbet/rbi/annotations/activesupport.rbi +410 -0
  73. data/sorbet/rbi/annotations/faraday.rbi +17 -0
  74. data/sorbet/rbi/annotations/mocha.rbi +34 -0
  75. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  76. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  77. data/sorbet/rbi/dsl/.gitattributes +1 -0
  78. data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
  79. data/sorbet/rbi/gems/.gitattributes +1 -0
  80. data/sorbet/rbi/gems/activesupport@7.1.3.rbi +18004 -0
  81. data/sorbet/rbi/gems/addressable@2.8.6.rbi +1993 -0
  82. data/sorbet/rbi/gems/appraisal@2.5.0.rbi +621 -0
  83. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  84. data/sorbet/rbi/gems/base64@0.2.0.rbi +508 -0
  85. data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +77 -0
  86. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  87. data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +11590 -0
  88. data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
  89. data/sorbet/rbi/gems/crack@0.4.5.rbi +144 -0
  90. data/sorbet/rbi/gems/dotenv@2.8.1.rbi +234 -0
  91. data/sorbet/rbi/gems/drb@2.2.0.rbi +1346 -0
  92. data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
  93. data/sorbet/rbi/gems/faraday-net_http@3.1.0.rbi +146 -0
  94. data/sorbet/rbi/gems/faraday@2.9.0.rbi +2911 -0
  95. data/sorbet/rbi/gems/hashdiff@1.1.0.rbi +352 -0
  96. data/sorbet/rbi/gems/i18n@1.14.1.rbi +2325 -0
  97. data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
  98. data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
  99. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  100. data/sorbet/rbi/gems/minitest@5.21.2.rbi +2197 -0
  101. data/sorbet/rbi/gems/mocha@2.1.0.rbi +3934 -0
  102. data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +93 -0
  103. data/sorbet/rbi/gems/net-http@0.4.1.rbi +4068 -0
  104. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  105. data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
  106. data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
  107. data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
  108. data/sorbet/rbi/gems/prism@0.19.0.rbi +29883 -0
  109. data/sorbet/rbi/gems/pry-sorbet@0.2.1.rbi +966 -0
  110. data/sorbet/rbi/gems/pry@0.14.2.rbi +10077 -0
  111. data/sorbet/rbi/gems/public_suffix@5.0.4.rbi +935 -0
  112. data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
  113. data/sorbet/rbi/gems/rack@3.0.8.rbi +5183 -0
  114. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  115. data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
  116. data/sorbet/rbi/gems/rbi@0.1.6.rbi +2922 -0
  117. data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
  118. data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
  119. data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7117 -0
  120. data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
  121. data/sorbet/rbi/gems/rubocop-rails@2.23.1.rbi +9175 -0
  122. data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
  123. data/sorbet/rbi/gems/rubocop-sorbet@0.7.6.rbi +1510 -0
  124. data/sorbet/rbi/gems/rubocop@1.60.1.rbi +57356 -0
  125. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
  126. data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
  127. data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
  128. data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
  129. data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3506 -0
  130. data/sorbet/rbi/gems/thor@1.3.0.rbi +4312 -0
  131. data/sorbet/rbi/gems/toys-core@0.15.4.rbi +9462 -0
  132. data/sorbet/rbi/gems/toys@0.15.4.rbi +243 -0
  133. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5917 -0
  134. data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
  135. data/sorbet/rbi/gems/uri@0.13.0.rbi +2327 -0
  136. data/sorbet/rbi/gems/vcr@6.2.0.rbi +3036 -0
  137. data/sorbet/rbi/gems/webmock@3.19.1.rbi +1768 -0
  138. data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
  139. data/sorbet/rbi/gems/yard@0.9.34.rbi +18084 -0
  140. data/sorbet/shims/module.rbi +6 -0
  141. data/sorbet/tapioca/require.rb +10 -0
  142. metadata +228 -0
@@ -0,0 +1,96 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "asset"
5
+
6
+ module LunchMoney
7
+ # https://lunchmoney.dev/#assets
8
+ class AssetCalls < ApiCall
9
+ sig { returns(T.any(T::Array[LunchMoney::Asset], LunchMoney::Errors)) }
10
+ def assets
11
+ response = get("assets")
12
+
13
+ api_errors = errors(response)
14
+ return api_errors if api_errors.present?
15
+
16
+ response.body[:assets].map do |asset|
17
+ LunchMoney::Asset.new(**asset)
18
+ end
19
+ end
20
+
21
+ sig do
22
+ params(
23
+ type_name: String,
24
+ name: String,
25
+ balance: String,
26
+ subtype_name: T.nilable(String),
27
+ display_name: T.nilable(String),
28
+ balance_as_of: T.nilable(String),
29
+ currency: T.nilable(String),
30
+ institution_name: T.nilable(String),
31
+ closed_on: T.nilable(String),
32
+ exclude_transactions: T.nilable(T::Boolean),
33
+ ).returns(T.any(LunchMoney::Asset, LunchMoney::Errors))
34
+ end
35
+ def create_asset(type_name:, name:, balance:, subtype_name: nil, display_name: nil, balance_as_of: nil,
36
+ currency: nil, institution_name: nil, closed_on: nil, exclude_transactions: nil)
37
+ params = {
38
+ type_name:,
39
+ name:,
40
+ balance:,
41
+ subtype_name:,
42
+ display_name:,
43
+ balance_as_of:,
44
+ currency:,
45
+ institution_name:,
46
+ closed_on:,
47
+ exclude_transactions:,
48
+ }
49
+
50
+ response = post("assets", params)
51
+
52
+ api_errors = errors(response)
53
+ return api_errors if api_errors.present?
54
+
55
+ LunchMoney::Asset.new(**response.body)
56
+ end
57
+
58
+ sig do
59
+ params(
60
+ asset_id: Integer,
61
+ type_name: T.nilable(String),
62
+ name: T.nilable(String),
63
+ balance: T.nilable(String),
64
+ subtype_name: T.nilable(String),
65
+ display_name: T.nilable(String),
66
+ balance_as_of: T.nilable(String),
67
+ currency: T.nilable(String),
68
+ institution_name: T.nilable(String),
69
+ closed_on: T.nilable(String),
70
+ exclude_transactions: T.nilable(T::Boolean),
71
+ ).returns(T.any(LunchMoney::Asset, LunchMoney::Errors))
72
+ end
73
+ def update_asset(asset_id, type_name: nil, name: nil, balance: nil, subtype_name: nil, display_name: nil,
74
+ balance_as_of: nil, currency: nil, institution_name: nil, closed_on: nil, exclude_transactions: nil)
75
+ params = {
76
+ type_name:,
77
+ name:,
78
+ balance:,
79
+ subtype_name:,
80
+ display_name:,
81
+ balance_as_of:,
82
+ currency:,
83
+ institution_name:,
84
+ closed_on:,
85
+ exclude_transactions:,
86
+ }
87
+
88
+ response = put("assets/#{asset_id}", params)
89
+
90
+ api_errors = errors(response)
91
+ return api_errors if api_errors.present?
92
+
93
+ LunchMoney::Asset.new(**response.body)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,74 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "data"
5
+ require_relative "config"
6
+
7
+ module LunchMoney
8
+ # https://lunchmoney.dev/#budget-object
9
+ class Budget < LunchMoney::DataObject
10
+ # API object reference documentation: https://lunchmoney.dev/#budget-object
11
+
12
+ sig { returns(String) }
13
+ attr_accessor :category_name
14
+
15
+ sig { returns(Integer) }
16
+ attr_accessor :order
17
+
18
+ sig { returns(T.nilable(String)) }
19
+ attr_accessor :category_group_name
20
+
21
+ sig { returns(T.nilable(Integer)) }
22
+ attr_accessor :category_id, :group_id
23
+
24
+ sig { returns(T.nilable(T::Boolean)) }
25
+ attr_accessor :is_group
26
+
27
+ sig { returns(T::Boolean) }
28
+ attr_accessor :is_income, :exclude_from_budget, :exclude_from_totals, :archived
29
+
30
+ sig { returns(T::Hash[Symbol, LunchMoney::Data]) }
31
+ attr_accessor :data
32
+
33
+ sig { returns(T.nilable(T::Hash[Symbol, LunchMoney::Config])) }
34
+ attr_accessor :config
35
+
36
+ sig { returns(T.nilable({ list: T::Array[LunchMoney::RecurringExpense] })) }
37
+ attr_accessor :recurring
38
+
39
+ sig do
40
+ params(
41
+ is_income: T::Boolean,
42
+ exclude_from_budget: T::Boolean,
43
+ exclude_from_totals: T::Boolean,
44
+ data: T::Hash[Symbol, LunchMoney::Data],
45
+ category_name: String,
46
+ order: Integer,
47
+ archived: T::Boolean,
48
+ category_id: T.nilable(Integer),
49
+ category_group_name: T.nilable(String),
50
+ group_id: T.nilable(Integer),
51
+ is_group: T.nilable(T::Boolean),
52
+ config: T.nilable(T::Hash[Symbol, LunchMoney::Config]),
53
+ recurring: T.nilable({ list: T::Array[LunchMoney::RecurringExpense] }),
54
+ ).void
55
+ end
56
+ def initialize(is_income:, exclude_from_budget:, exclude_from_totals:, data:, category_name:, order:, archived:,
57
+ category_id: nil, category_group_name: nil, group_id: nil, is_group: nil, config: nil, recurring: nil)
58
+ super()
59
+ @category_id = category_id
60
+ @is_income = is_income
61
+ @exclude_from_budget = exclude_from_budget
62
+ @exclude_from_totals = exclude_from_totals
63
+ @data = data
64
+ @category_name = category_name
65
+ @order = order
66
+ @category_group_name = category_group_name
67
+ @group_id = group_id
68
+ @is_group = is_group
69
+ @config = config
70
+ @archived = archived
71
+ @recurring = recurring
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,82 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "budget"
5
+
6
+ module LunchMoney
7
+ # https://lunchmoney.dev/#budget
8
+ class BudgetCalls < ApiCall
9
+ sig do
10
+ params(
11
+ start_date: String,
12
+ end_date: String,
13
+ currency: T.nilable(String),
14
+ ).returns(T.any(T::Array[LunchMoney::Budget], LunchMoney::Errors))
15
+ end
16
+ def budgets(start_date:, end_date:, currency: nil)
17
+ params = clean_params({ start_date:, end_date:, currency: })
18
+ response = get("budgets", query_params: params)
19
+
20
+ api_errors = errors(response)
21
+ return api_errors if api_errors.present?
22
+
23
+ response.body.map do |budget|
24
+ if budget[:data]
25
+ data_keys = budget[:data].keys
26
+ data_keys.each do |data_key|
27
+ budget[:data][data_key] = LunchMoney::Data.new(**budget[:data][data_key])
28
+ end
29
+ end
30
+
31
+ if budget[:config]
32
+ config_keys = budget[:config].keys
33
+ config_keys.each do |config_key|
34
+ budget[:config][config_key] = LunchMoney::Data.new(**budget[:config][config_key])
35
+ end
36
+ end
37
+
38
+ if budget[:recurring]
39
+ budget[:recurring][:list]&.map! { |recurring| LunchMoney::RecurringExpenseBase.new(**recurring) }
40
+ end
41
+
42
+ LunchMoney::Budget.new(**budget)
43
+ end
44
+ end
45
+
46
+ sig do
47
+ params(
48
+ start_date: String,
49
+ category_id: Integer,
50
+ amount: Number,
51
+ currency: T.nilable(String),
52
+ ).returns(T.any(
53
+ T::Hash[Symbol, { category_id: Integer, amount: Number, currency: String, start_date: String }],
54
+ LunchMoney::Errors,
55
+ ))
56
+ end
57
+ def upsert_budget(start_date:, category_id:, amount:, currency: nil)
58
+ params = clean_params({ start_date:, category_id:, amount:, currency: })
59
+ response = put("budgets", params)
60
+
61
+ api_errors = errors(response)
62
+ return api_errors if api_errors.present?
63
+
64
+ response.body
65
+ end
66
+
67
+ sig { params(start_date: String, category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
68
+ def remove_budget(start_date:, category_id:)
69
+ params = {
70
+ start_date:,
71
+ category_id:,
72
+ }
73
+
74
+ response = delete("budgets", query_params: params)
75
+
76
+ api_errors = errors(response)
77
+ return api_errors if api_errors.present?
78
+
79
+ response.body
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # https://lunchmoney.dev/#config-object
6
+ class Config < LunchMoney::DataObject
7
+ # API object reference documentation: https://lunchmoney.dev/#config-object
8
+
9
+ sig { returns(Integer) }
10
+ attr_accessor :config_id
11
+
12
+ sig { returns(Number) }
13
+ attr_accessor :amount, :to_base
14
+
15
+ sig { returns(String) }
16
+ attr_accessor :cadence, :currency, :auto_suggest
17
+
18
+ sig do
19
+ params(
20
+ config_id: Integer,
21
+ cadence: String,
22
+ amount: Number,
23
+ currency: String,
24
+ to_base: Number,
25
+ auto_suggest: String,
26
+ ).void
27
+ end
28
+ def initialize(config_id:, cadence:, amount:, currency:, to_base:, auto_suggest:)
29
+ super()
30
+ @config_id = config_id
31
+ @cadence = cadence
32
+ @amount = amount
33
+ @currency = currency
34
+ @to_base = to_base
35
+ @auto_suggest = auto_suggest
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # https://lunchmoney.dev/#data-object
6
+ class Data < LunchMoney::DataObject
7
+ # API object reference documentation: https://lunchmoney.dev/#data-object
8
+
9
+ sig { returns(T.nilable(Integer)) }
10
+ attr_accessor :num_transactions
11
+
12
+ sig { returns(T.nilable(Number)) }
13
+ attr_accessor :budget_amount, :budget_to_base, :spending_to_base
14
+
15
+ sig { returns(T.nilable(String)) }
16
+ attr_accessor :budget_currency
17
+
18
+ sig { returns(T.nilable(T::Boolean)) }
19
+ attr_accessor :is_automated
20
+
21
+ sig do
22
+ params(
23
+ spending_to_base: T.nilable(Number),
24
+ num_transactions: T.nilable(Integer),
25
+ budget_amount: T.nilable(Number),
26
+ budget_currency: T.nilable(String),
27
+ budget_to_base: T.nilable(Number),
28
+ is_automated: T.nilable(T::Boolean),
29
+ ).void
30
+ end
31
+ def initialize(spending_to_base: nil, num_transactions: nil, budget_amount: nil, budget_currency: nil,
32
+ budget_to_base: nil, is_automated: nil)
33
+ super()
34
+ @budget_amount = budget_amount
35
+ @budget_currency = budget_currency
36
+ @budget_to_base = budget_to_base
37
+ @spending_to_base = spending_to_base
38
+ @num_transactions = num_transactions
39
+ @is_automated = is_automated
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # https://lunchmoney.dev/#categories-object
6
+ class Category < ChildCategory
7
+ sig { returns(T.nilable(String)) }
8
+ attr_accessor :group_category_name
9
+
10
+ sig { returns(T::Boolean) }
11
+ attr_accessor :is_income, :exclude_from_budget, :exclude_from_totals, :is_group
12
+
13
+ sig { returns(T.nilable(Integer)) }
14
+ attr_accessor :group_id, :order
15
+
16
+ sig { returns(T.nilable(T::Array[T.any(LunchMoney::Category, LunchMoney::ChildCategory)])) }
17
+ attr_accessor :children
18
+
19
+ sig do
20
+ params(
21
+ id: Integer,
22
+ name: String,
23
+ is_income: T::Boolean,
24
+ exclude_from_budget: T::Boolean,
25
+ exclude_from_totals: T::Boolean,
26
+ is_group: T::Boolean,
27
+ archived: T.nilable(T::Boolean),
28
+ archived_on: T.nilable(String),
29
+ updated_at: T.nilable(String),
30
+ created_at: T.nilable(String),
31
+ group_id: T.nilable(Integer),
32
+ order: T.nilable(Integer),
33
+ description: T.nilable(String),
34
+ children: T.nilable(T::Array[T.any(LunchMoney::Category, LunchMoney::ChildCategory)]),
35
+ group_category_name: T.nilable(String),
36
+ ).void
37
+ end
38
+ def initialize(id:, name:, is_income:, exclude_from_budget:, exclude_from_totals:, is_group:, archived: nil,
39
+ archived_on: nil, updated_at: nil, created_at: nil, group_id: nil, order: nil, description: nil, children: nil,
40
+ group_category_name: nil)
41
+ super(id:, name:, archived:, archived_on:, updated_at:, created_at:, description:)
42
+ @is_income = is_income
43
+ @exclude_from_budget = exclude_from_budget
44
+ @exclude_from_totals = exclude_from_totals
45
+ @is_group = is_group
46
+ @group_id = group_id
47
+ @order = order
48
+ @children = children
49
+ @group_category_name = group_category_name
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # A slimmed down version of https://lunchmoney.dev/#categories-object used in the
6
+ # `children` field of some category calls
7
+ class ChildCategory < LunchMoney::DataObject
8
+ sig { returns(Integer) }
9
+ attr_accessor :id
10
+
11
+ sig { returns(String) }
12
+ attr_accessor :name
13
+
14
+ sig { returns(T.nilable(String)) }
15
+ attr_accessor :description, :archived_on, :updated_at, :created_at
16
+
17
+ sig { returns(T.nilable(T::Boolean)) }
18
+ attr_accessor :archived
19
+
20
+ sig do
21
+ params(
22
+ id: Integer,
23
+ name: String,
24
+ archived: T.nilable(T::Boolean),
25
+ archived_on: T.nilable(String),
26
+ updated_at: T.nilable(String),
27
+ created_at: T.nilable(String),
28
+ description: T.nilable(String),
29
+ ).void
30
+ end
31
+ def initialize(id:, name:, archived: nil, archived_on: nil, updated_at: nil, created_at: nil, description: nil)
32
+ super()
33
+ @id = id
34
+ @name = name
35
+ @archived = archived
36
+ @archived_on = archived_on
37
+ @updated_at = updated_at
38
+ @created_at = created_at
39
+ @description = description
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,195 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "category/child_category"
5
+ require_relative "category/category"
6
+
7
+ module LunchMoney
8
+ # https://lunchmoney.dev/#categories
9
+ class CategoryCalls < ApiCall
10
+ # Valid query parameter formets for categories
11
+ VALID_FORMATS = T.let(
12
+ [
13
+ "flattened",
14
+ "nested",
15
+ ],
16
+ T::Array[String],
17
+ )
18
+
19
+ sig do
20
+ params(
21
+ format: T.nilable(T.any(String, Symbol)),
22
+ ).returns(T.any(T::Array[LunchMoney::Category], LunchMoney::Errors))
23
+ end
24
+ def categories(format: nil)
25
+ response = get("categories", query_params: categories_params(format:))
26
+
27
+ api_errors = errors(response)
28
+ return api_errors if api_errors.present?
29
+
30
+ response.body[:categories].map do |category|
31
+ category[:children]&.map! { |child_category| LunchMoney::Category.new(**child_category) }
32
+
33
+ LunchMoney::Category.new(**category)
34
+ end
35
+ end
36
+
37
+ sig { params(category_id: Integer).returns(T.any(LunchMoney::Category, LunchMoney::Errors)) }
38
+ def category(category_id)
39
+ response = get("categories/#{category_id}")
40
+
41
+ api_errors = errors(response)
42
+ return api_errors if api_errors.present?
43
+
44
+ response.body[:children]&.map! { |child_category| LunchMoney::ChildCategory.new(**child_category) }
45
+
46
+ LunchMoney::Category.new(**response.body)
47
+ end
48
+
49
+ sig do
50
+ params(
51
+ name: String,
52
+ description: T.nilable(String),
53
+ is_income: T::Boolean,
54
+ exclude_from_budget: T::Boolean,
55
+ exclude_from_totals: T::Boolean,
56
+ archived: T::Boolean,
57
+ group_id: T.nilable(Integer),
58
+ ).returns(T.any(T::Hash[Symbol, Integer], LunchMoney::Errors))
59
+ end
60
+ def create_category(name:, description: nil, is_income: false, exclude_from_budget: false,
61
+ exclude_from_totals: false, archived: false, group_id: nil)
62
+ params = clean_params({
63
+ name:,
64
+ description:,
65
+ is_income:,
66
+ exclude_from_budget:,
67
+ exclude_from_totals:,
68
+ archived:,
69
+ group_id:,
70
+ })
71
+ response = post("categories", params)
72
+
73
+ api_errors = errors(response)
74
+ return api_errors if api_errors.present?
75
+
76
+ response.body
77
+ end
78
+
79
+ sig do
80
+ params(
81
+ name: String,
82
+ description: T.nilable(String),
83
+ is_income: T::Boolean,
84
+ exclude_from_budget: T::Boolean,
85
+ exclude_from_totals: T::Boolean,
86
+ category_ids: T::Array[Integer],
87
+ new_categories: T::Array[String],
88
+ ).returns(T.any(T::Hash[Symbol, Integer], LunchMoney::Errors))
89
+ end
90
+ def create_category_group(name:, description: nil, is_income: false, exclude_from_budget: false,
91
+ exclude_from_totals: false, category_ids: [], new_categories: [])
92
+
93
+ params = {
94
+ name:,
95
+ description:,
96
+ is_income:,
97
+ exclude_from_budget:,
98
+ exclude_from_totals:,
99
+ category_ids:,
100
+ new_categories:,
101
+ }
102
+
103
+ response = post("categories/group", params)
104
+
105
+ api_errors = errors(response)
106
+ return api_errors if api_errors.present?
107
+
108
+ response.body
109
+ end
110
+
111
+ sig do
112
+ params(
113
+ category_id: Integer,
114
+ name: T.nilable(String),
115
+ description: T.nilable(String),
116
+ is_income: T.nilable(T::Boolean),
117
+ exclude_from_budget: T.nilable(T::Boolean),
118
+ exclude_from_totals: T.nilable(T::Boolean),
119
+ archived: T.nilable(T::Boolean),
120
+ group_id: T.nilable(Integer),
121
+ ).returns(T.any(T::Boolean, LunchMoney::Errors))
122
+ end
123
+ def update_category(category_id, name: nil, description: nil, is_income: nil, exclude_from_budget: nil,
124
+ exclude_from_totals: nil, archived: nil, group_id: nil)
125
+
126
+ params = clean_params({
127
+ name:,
128
+ description:,
129
+ is_income:,
130
+ exclude_from_budget:,
131
+ exclude_from_totals:,
132
+ archived:,
133
+ group_id:,
134
+ })
135
+ response = put("categories/#{category_id}", params)
136
+
137
+ api_errors = errors(response)
138
+ return api_errors if api_errors.present?
139
+
140
+ response.body
141
+ end
142
+
143
+ sig do
144
+ params(
145
+ group_id: Integer,
146
+ category_ids: T::Array[Integer],
147
+ new_categories: T::Array[String],
148
+ ).returns(T.any(LunchMoney::Category, LunchMoney::Errors))
149
+ end
150
+ def add_to_category_group(group_id, category_ids: [], new_categories: [])
151
+ params = {
152
+ category_ids:,
153
+ new_categories:,
154
+ }
155
+
156
+ response = post("categories/group/#{group_id}/add", params)
157
+
158
+ api_errors = errors(response)
159
+ return api_errors if api_errors.present?
160
+
161
+ LunchMoney::Category.new(**response.body)
162
+ end
163
+
164
+ sig { params(category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
165
+ def delete_category(category_id)
166
+ response = delete("categories/#{category_id}")
167
+
168
+ api_errors = errors(response)
169
+ return api_errors if api_errors.present?
170
+
171
+ response.body
172
+ end
173
+
174
+ sig { params(category_id: Integer).returns(T.any(T::Boolean, LunchMoney::Errors)) }
175
+ def force_delete_category(category_id)
176
+ response = delete("categories/#{category_id}/force")
177
+
178
+ api_errors = errors(response)
179
+ return api_errors if api_errors.present?
180
+
181
+ response.body
182
+ end
183
+
184
+ private
185
+
186
+ sig { params(format: T.nilable(T.any(String, Symbol))).returns(T.nilable(T::Hash[Symbol, String])) }
187
+ def categories_params(format:)
188
+ return unless format
189
+
190
+ raise(InvalidQueryParameter, "format must be either flattened or nested") if VALID_FORMATS.exclude?(format.to_s)
191
+
192
+ { format: format.to_s }
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # Holds global configuration options for this gem
6
+ # @example api_key
7
+ # LunchMoney::Configuration.api_key
8
+ # => "your_api_key"
9
+ #
10
+ # @example validate_object_attributes
11
+ # LunchMoney::Configuration.validate_object_attributes
12
+ # => true
13
+ class Configuration
14
+ sig { returns(T.nilable(String)) }
15
+ attr_accessor :api_key
16
+
17
+ sig { returns(T::Boolean) }
18
+ attr_accessor :validate_object_attributes
19
+
20
+ sig { void }
21
+ def initialize
22
+ @api_key = ENV.fetch("LUNCHMONEY_TOKEN", nil)
23
+ @validate_object_attributes = T.let(true, T::Boolean)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LunchMoney
5
+ # https://lunchmoney.dev/#crypto-object
6
+ class Crypto < CryptoBase
7
+ include LunchMoney::Validators
8
+
9
+ sig { returns(String) }
10
+ attr_reader :balance_as_of
11
+
12
+ sig { returns(String) }
13
+ attr_accessor :currency, :status
14
+
15
+ sig do
16
+ params(
17
+ created_at: String,
18
+ source: String,
19
+ name: String,
20
+ balance: String,
21
+ balance_as_of: String,
22
+ currency: String,
23
+ status: String,
24
+ institution_name: T.nilable(String),
25
+ id: T.nilable(Integer),
26
+ zabo_account_id: T.nilable(Integer),
27
+ display_name: T.nilable(String),
28
+ ).void
29
+ end
30
+ def initialize(created_at:, source:, name:, balance:, balance_as_of:, currency:,
31
+ status:, institution_name: nil, id: nil, zabo_account_id: nil, display_name: nil)
32
+ super(created_at:, source:, name:, balance:, institution_name:, id:, zabo_account_id:, display_name:)
33
+ @balance_as_of = T.let(validate_iso8601!(balance_as_of), String)
34
+ @currency = currency
35
+ @status = status
36
+ end
37
+
38
+ sig { params(time: String).void }
39
+ def balance_as_of=(time)
40
+ @balance_as_of = validate_iso8601!(time)
41
+ end
42
+ end
43
+ end