Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to set a budget for a category #54

Merged
merged 3 commits into from
Jan 4, 2024

Conversation

grablair
Copy link
Collaborator

@grablair grablair commented Dec 6, 2023

Resolves #27
Resolves #28 (as a budget is "deleted" when its amount is set to 0)

Tested locally, and behavior is as expected.

Test Results
In [1]: from monarchmoney import MonarchMoney
   ...:
   ...: import asyncio
   ...:
   ...: import os
   ...:
   ...: import json
   ...: import logging
   ...:
   ...: #logging.basicConfig(level=logging.DEBUG)
   ...:
   ...: mm = MonarchMoney(session_file = ".mm/mm_session.pickle")
   ...: asyncio.run(mm.login())
Using saved session found at .mm/mm_session.pickle

In [2]: result = asyncio.run(mm.update_budget_amount(
   ...:         "160539862435436991",
   ...:         0))

In [3]: result
Out[3]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163102124240083688',
   'budgetAmount': 0.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [4]: result = asyncio.run(mm.update_budget_amount(
   ...:         "160539862435436991",
   ...:         10
   ...:         ))

In [5]: result
Out[5]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163102124240083688',
   'budgetAmount': 10.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [6]: result = asyncio.run(mm.update_budget_amount(
   ...:         "160539862435436991",
   ...:         -10
   ...:         ))

In [7]: result
Out[7]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163102124240083688',
   'budgetAmount': -10.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [8]: result = asyncio.run(mm.update_budget_amount(
   ...:         "160539862435436991",
   ...:         10, apply_to_future = True
   ...:         ))

In [9]: result = asyncio.run(mm.update_budget_amount(
   ...:         "160539862435436991",
   ...:         0, apply_to_future = True
   ...:         ))

In [10]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         0, start_date = "2024-02-01"
    ...:         ))

In [11]: result
Out[11]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163103789401840600',
   'budgetAmount': 0.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [12]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         10, start_date = "2024-02-01"
    ...:         ))

In [13]: result
Out[13]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163103789401840600',
   'budgetAmount': 10.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [14]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         0, start_date = "2024-02-01"
    ...:         ))

In [15]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         10, start_date = "2024-02-05"
    ...:         ))

In [16]: result
Out[16]:
{'updateOrCreateBudgetItem': {'budgetItem': {'id': '163103789401840600',
   'budgetAmount': 10.0,
   '__typename': 'BudgetItem'},
  '__typename': 'UpdateOrCreateBudgetItemMutation'}}

In [17]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         100, start_date = "2024-02-01", timeframe="year"
    ...:         ))

TransportQueryError: {'message': 'Something went wrong while processing: None on request_id: None.', 'locations': [{'line': 1, 'column': 34}]}

In [18]: result = asyncio.run(mm.update_budget_amount(
    ...:         "160539862435436991",
    ...:         100, start_date = "2024-02-01", timeframe="day"
    ...:         ))

TransportQueryError: {'message': 'Something went wrong while processing: None on request_id: None.', 'locations': [{'line': 1, 'column': 34}]}

In [19]:

monarchmoney/monarchmoney.py Outdated Show resolved Hide resolved
@Sean280ZX
Copy link
Contributor

Sean280ZX commented Dec 10, 2023

@grablair in your observations have you noticed that the id for setting the budget is a different id from when you get a budget? I wasn't sure if I'm misunderstanding where the id comes from.

I think get budget returns category ID. And setting a budget requires a budget item ID

@grablair
Copy link
Collaborator Author

grablair commented Dec 10, 2023

Oh, you may be right there. Good catch! I'll take a look at that sometime over the next day or two and make changes accordingly.

@grablair
Copy link
Collaborator Author

grablair commented Dec 10, 2023

@grablair in your observations have you noticed that the id for setting the budget is a different id from when you get a budget? I wasn't sure if I'm misunderstanding where the id comes from.

I think get budget returns category ID. And setting a budget requires a budget item ID

Just did some investigating:

In my tests for this PR, I used the ID 160539862435436991. This ID is definitely the ID for my "Personal" category.

The parameters for the set_budget method take a category ID, not a budget item ID, and it's named as such in the variables:

{
    "operationName": "Common_UpdateBudgetItem",
    "variables": {
        "input": {
            "startDate": "2023-11-01",
            "timeframe": "month",
            "categoryId": "160708626927917806",
            "amount": 84,
            "applyToFuture": false
        }
    },
    "query": "mutation Common_UpdateBudgetItem($input: UpdateOrCreateBudgetItemMutationInput!) {\n  updateOrCreateBudgetItem(input: $input) {\n    budgetItem {\n      id\n      budgetAmount\n      __typename\n    }\n    __typename\n  }\n}"
}

So I think the set_budget call only needs the category ID, as implemented here.


However, you are right that the budget item ID is different. The result I get for an "update_budget" is:

{
    "data": {
        "updateOrCreateBudgetItem": {
            "budgetItem": {
                "id": "162741527472314884",
                "budgetAmount": 84,
                "__typename": "BudgetItem"
            },
            "__typename": "UpdateOrCreateBudgetItemMutation"
        }
    }
}

That budget item ID is definitely not a category ID. I checked on my categories setting page.

I don't see the budget item ID actually used anywhere on first cursory glance. 🤷🏻‍♂️

cc: @Sean280ZX

@Sean280ZX
Copy link
Contributor

@grablair i agree in that budget item is is not being used. I was mixing up the query section and the variables.

It appears that there are two possible variables to consider in the set budget mutation: categoryId and categoryGroupId. You need to use the categoryGroupId variable if you are doing group level budgeting (which I do).

Can the set budget function be updated to handle either categoryId or categoryGroupId?

@grablair
Copy link
Collaborator Author

grablair commented Dec 15, 2023

Added category group support.

cc: @Sean280ZX

Test Results
❯ ipython
Python 3.11.6 (main, Nov  2 2023, 04:39:43) [Clang 14.0.3 (clang-1403.0.22.14.1)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.16.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from monarchmoney import MonarchMoney
   ...:
   ...: import asyncio
   ...:
   ...: import os
   ...:
   ...: import json
   ...: import logging
   ...:
   ...: #logging.basicConfig(level=logging.DEBUG)
   ...:
   ...: mm = MonarchMoney(session_file = ".mm/mm_session.pickle")
   ...: asyncio.run(mm.login())
Using saved session found at .mm/mm_session.pickle

In [2]: result = asyncio.run(mm.set_budget_amount(
   ...:         10
   ...:         ))

Exception: You must specify either a category_id OR category_group_id; not both

In [3]: result = asyncio.run(mm.set_budget_amount(
   ...:         10, category_group_id = '163958387947674191', category_id = '163958387947674191'
   ...:         ))

Exception: You must specify either a category_id OR category_group_id; not both

In [4]: result = asyncio.run(mm.set_budget_amount(
   ...:         10, category_group_id = '163958387947674191'
   ...:         ))

In [5]: result = asyncio.run(mm.set_budget_amount(
   ...:         -10, category_id = "160539862435436991"
   ...:         ))

@Sean280ZX
Copy link
Contributor

@grablair changes looks/work well.

Copy link
Owner

@hammem hammem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @grablair!

@hammem hammem merged commit 9aba5b4 into hammem:main Jan 4, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature] Delete a budget [Feature] Create/set a budget
3 participants