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.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/build_and_publish_yard_docs.yml +47 -0
- data/.github/workflows/ci.yml +58 -0
- data/.github/workflows/dependabot-rbi-updater.yml +43 -0
- data/.github/workflows/publish_gem.yml +31 -0
- data/.gitignore +62 -0
- data/.rubocop.yml +45 -0
- data/.ruby-version +1 -0
- data/.toys/.toys.rb +10 -0
- data/.toys/ci.rb +22 -0
- data/.toys/rbi.rb +60 -0
- data/.toys/rubocop.rb +10 -0
- data/.toys/spoom.rb +15 -0
- data/.toys/typecheck.rb +5 -0
- data/.yardopts +2 -0
- data/Appraisals +22 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +174 -0
- data/LICENSE +21 -0
- data/README.md +57 -0
- data/bin/console +16 -0
- data/bin/rubocop +27 -0
- data/bin/setup +8 -0
- data/bin/spoom +27 -0
- data/bin/srb +27 -0
- data/bin/tapioca +27 -0
- data/bin/toys +27 -0
- data/bin/yard +27 -0
- data/lib/lunchmoney/api.rb +147 -0
- data/lib/lunchmoney/api_call.rb +109 -0
- data/lib/lunchmoney/assets/asset.rb +89 -0
- data/lib/lunchmoney/assets/asset_calls.rb +96 -0
- data/lib/lunchmoney/budget/budget.rb +74 -0
- data/lib/lunchmoney/budget/budget_calls.rb +82 -0
- data/lib/lunchmoney/budget/config.rb +38 -0
- data/lib/lunchmoney/budget/data.rb +42 -0
- data/lib/lunchmoney/categories/category/category.rb +52 -0
- data/lib/lunchmoney/categories/category/child_category.rb +42 -0
- data/lib/lunchmoney/categories/category_calls.rb +195 -0
- data/lib/lunchmoney/configuration.rb +26 -0
- data/lib/lunchmoney/crypto/crypto/crypto.rb +43 -0
- data/lib/lunchmoney/crypto/crypto/crypto_base.rb +65 -0
- data/lib/lunchmoney/crypto/crypto_calls.rb +49 -0
- data/lib/lunchmoney/data_object.rb +25 -0
- data/lib/lunchmoney/errors.rb +19 -0
- data/lib/lunchmoney/exceptions.rb +19 -0
- data/lib/lunchmoney/plaid_accounts/plaid_account.rb +73 -0
- data/lib/lunchmoney/plaid_accounts/plaid_account_calls.rb +38 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense.rb +65 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense/recurring_expense_base.rb +29 -0
- data/lib/lunchmoney/recurring_expenses/recurring_expense_calls.rb +28 -0
- data/lib/lunchmoney/tags/tag/tag.rb +20 -0
- data/lib/lunchmoney/tags/tag/tag_base.rb +21 -0
- data/lib/lunchmoney/tags/tag_calls.rb +20 -0
- data/lib/lunchmoney/transactions/transaction/child_transaction.rb +31 -0
- data/lib/lunchmoney/transactions/transaction/split.rb +24 -0
- data/lib/lunchmoney/transactions/transaction/transaction.rb +156 -0
- data/lib/lunchmoney/transactions/transaction/transaction_base.rb +52 -0
- data/lib/lunchmoney/transactions/transaction/transaction_modification_base.rb +30 -0
- data/lib/lunchmoney/transactions/transaction/update_transaction.rb +43 -0
- data/lib/lunchmoney/transactions/transaction_calls.rb +218 -0
- data/lib/lunchmoney/user/user.rb +36 -0
- data/lib/lunchmoney/user/user_calls.rb +19 -0
- data/lib/lunchmoney/validators.rb +43 -0
- data/lib/lunchmoney/version.rb +7 -0
- data/lib/lunchmoney.rb +54 -0
- data/lunchmoney.gemspec +34 -0
- data/sorbet/config +5 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activesupport.rbi +410 -0
- data/sorbet/rbi/annotations/faraday.rbi +17 -0
- data/sorbet/rbi/annotations/mocha.rbi +34 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/annotations/webmock.rbi +9 -0
- data/sorbet/rbi/dsl/.gitattributes +1 -0
- data/sorbet/rbi/dsl/active_support/callbacks.rbi +22 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activesupport@7.1.3.rbi +18004 -0
- data/sorbet/rbi/gems/addressable@2.8.6.rbi +1993 -0
- data/sorbet/rbi/gems/appraisal@2.5.0.rbi +621 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +508 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +77 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +11590 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
- data/sorbet/rbi/gems/crack@0.4.5.rbi +144 -0
- data/sorbet/rbi/gems/dotenv@2.8.1.rbi +234 -0
- data/sorbet/rbi/gems/drb@2.2.0.rbi +1346 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/faraday-net_http@3.1.0.rbi +146 -0
- data/sorbet/rbi/gems/faraday@2.9.0.rbi +2911 -0
- data/sorbet/rbi/gems/hashdiff@1.1.0.rbi +352 -0
- data/sorbet/rbi/gems/i18n@1.14.1.rbi +2325 -0
- data/sorbet/rbi/gems/json@2.7.1.rbi +1561 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/minitest@5.21.2.rbi +2197 -0
- data/sorbet/rbi/gems/mocha@2.1.0.rbi +3934 -0
- data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +93 -0
- data/sorbet/rbi/gems/net-http@0.4.1.rbi +4068 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.19.0.rbi +29883 -0
- data/sorbet/rbi/gems/pry-sorbet@0.2.1.rbi +966 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10077 -0
- data/sorbet/rbi/gems/public_suffix@5.0.4.rbi +935 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +161 -0
- data/sorbet/rbi/gems/rack@3.0.8.rbi +5183 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
- data/sorbet/rbi/gems/rbi@0.1.6.rbi +2922 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7117 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.34.5.rbi +2576 -0
- data/sorbet/rbi/gems/rubocop-rails@2.23.1.rbi +9175 -0
- data/sorbet/rbi/gems/rubocop-shopify@2.14.0.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.7.6.rbi +1510 -0
- data/sorbet/rbi/gems/rubocop@1.60.1.rbi +57356 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23136 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3506 -0
- data/sorbet/rbi/gems/thor@1.3.0.rbi +4312 -0
- data/sorbet/rbi/gems/toys-core@0.15.4.rbi +9462 -0
- data/sorbet/rbi/gems/toys@0.15.4.rbi +243 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5917 -0
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
- data/sorbet/rbi/gems/uri@0.13.0.rbi +2327 -0
- data/sorbet/rbi/gems/vcr@6.2.0.rbi +3036 -0
- data/sorbet/rbi/gems/webmock@3.19.1.rbi +1768 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18084 -0
- data/sorbet/shims/module.rbi +6 -0
- data/sorbet/tapioca/require.rb +10 -0
- 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
|