moneywiz-api 0.0.1__tar.gz
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.
- moneywiz-api-0.0.1/LICENSE +21 -0
- moneywiz-api-0.0.1/PKG-INFO +61 -0
- moneywiz-api-0.0.1/README.md +47 -0
- moneywiz-api-0.0.1/pyproject.toml +23 -0
- moneywiz-api-0.0.1/setup.cfg +4 -0
- moneywiz-api-0.0.1/src/__init__.py +0 -0
- moneywiz-api-0.0.1/src/moneywiz_api/__init__.py +3 -0
- moneywiz-api-0.0.1/src/moneywiz_api/database_accessor.py +99 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/__init__.py +0 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/account_manager.py +43 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/category_manager.py +33 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/investment_holding_manager.py +19 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/payee_manager.py +15 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/record_manager.py +40 -0
- moneywiz-api-0.0.1/src/moneywiz_api/managers/transaction_manager.py +90 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/__init__.py +0 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/account.py +116 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/category.py +37 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/investment_holding.py +70 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/payee.py +23 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/record.py +68 -0
- moneywiz-api-0.0.1/src/moneywiz_api/model/transaction.py +412 -0
- moneywiz-api-0.0.1/src/moneywiz_api/moneywiz_api.py +32 -0
- moneywiz-api-0.0.1/src/moneywiz_api/shell.py +109 -0
- moneywiz-api-0.0.1/src/moneywiz_api/types.py +6 -0
- moneywiz-api-0.0.1/src/moneywiz_api/utils.py +15 -0
- moneywiz-api-0.0.1/src/moneywiz_api.egg-info/PKG-INFO +61 -0
- moneywiz-api-0.0.1/src/moneywiz_api.egg-info/SOURCES.txt +29 -0
- moneywiz-api-0.0.1/src/moneywiz_api.egg-info/dependency_links.txt +1 -0
- moneywiz-api-0.0.1/src/moneywiz_api.egg-info/top_level.txt +2 -0
- moneywiz-api-0.0.1/tests/test_config.py +22 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Tianwei Dong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: moneywiz-api
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A Python api to access moneywiz sqlite database
|
|
5
|
+
Author-email: iLeoDo <iLeoDo@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/ileodo/moneywiz-api
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/ileodo/moneywiz-api/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# MoneyWiz-API
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
<a href="https://www.buymeacoffee.com/Ileodo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
22
|
+
|
|
23
|
+
A Python API to access MoneyWiz Sqlite database.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Get Started
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install moneywiz-api
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
|
|
34
|
+
from moneywiz_api import MoneywizApi
|
|
35
|
+
|
|
36
|
+
moneywizApi = MoneywizApi("<path_to_your_sqlite_file>")
|
|
37
|
+
|
|
38
|
+
(
|
|
39
|
+
accessor,
|
|
40
|
+
account_manager,
|
|
41
|
+
payee_manager,
|
|
42
|
+
category_manager,
|
|
43
|
+
transaction_manager,
|
|
44
|
+
investment_holding_manager,
|
|
45
|
+
) = (
|
|
46
|
+
moneywizApi.accessor,
|
|
47
|
+
moneywizApi.account_manager,
|
|
48
|
+
moneywizApi.payee_manager,
|
|
49
|
+
moneywizApi.category_manager,
|
|
50
|
+
moneywizApi.transaction_manager,
|
|
51
|
+
moneywizApi.investment_holding_manager,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
record = accessor.get_record(record_id)
|
|
55
|
+
print(record)
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Contribution
|
|
60
|
+
|
|
61
|
+
This project is in very early stage, all contributions are welcomed!
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# MoneyWiz-API
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
<a href="https://www.buymeacoffee.com/Ileodo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
|
8
|
+
|
|
9
|
+
A Python API to access MoneyWiz Sqlite database.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Get Started
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install moneywiz-api
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
|
|
20
|
+
from moneywiz_api import MoneywizApi
|
|
21
|
+
|
|
22
|
+
moneywizApi = MoneywizApi("<path_to_your_sqlite_file>")
|
|
23
|
+
|
|
24
|
+
(
|
|
25
|
+
accessor,
|
|
26
|
+
account_manager,
|
|
27
|
+
payee_manager,
|
|
28
|
+
category_manager,
|
|
29
|
+
transaction_manager,
|
|
30
|
+
investment_holding_manager,
|
|
31
|
+
) = (
|
|
32
|
+
moneywizApi.accessor,
|
|
33
|
+
moneywizApi.account_manager,
|
|
34
|
+
moneywizApi.payee_manager,
|
|
35
|
+
moneywizApi.category_manager,
|
|
36
|
+
moneywizApi.transaction_manager,
|
|
37
|
+
moneywizApi.investment_holding_manager,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
record = accessor.get_record(record_id)
|
|
41
|
+
print(record)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Contribution
|
|
46
|
+
|
|
47
|
+
This project is in very early stage, all contributions are welcomed!
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "moneywiz-api"
|
|
8
|
+
version = "0.0.1"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="iLeoDo", email="iLeoDo@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
description = "A Python api to access moneywiz sqlite database"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
"Homepage" = "https://github.com/ileodo/moneywiz-api"
|
|
23
|
+
"Bug Tracker" = "https://github.com/ileodo/moneywiz-api/issues"
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Any, Callable, Tuple
|
|
5
|
+
|
|
6
|
+
from src.moneywiz_api.model.record import Record
|
|
7
|
+
from src.moneywiz_api.types import ENT_ID, ID
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatabaseAccessor:
|
|
11
|
+
def __init__(self, db_path: Path):
|
|
12
|
+
self._con = sqlite3.connect(db_path, uri=True)
|
|
13
|
+
|
|
14
|
+
def dict_factory(cursor, row):
|
|
15
|
+
record = {}
|
|
16
|
+
for idx, col in enumerate(cursor.description):
|
|
17
|
+
record[col[0]] = row[idx]
|
|
18
|
+
return record
|
|
19
|
+
|
|
20
|
+
self._con.row_factory = dict_factory
|
|
21
|
+
|
|
22
|
+
self._ent_to_typename: Dict[ENT_ID, str] = self._load_primarykey()
|
|
23
|
+
self._typename_to_ent: Dict[str, ENT_ID] = {
|
|
24
|
+
v: k for k, v in self._ent_to_typename.items()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def _load_primarykey(self) -> Dict[int, str]:
|
|
28
|
+
cur = self._con.cursor()
|
|
29
|
+
res = cur.execute(
|
|
30
|
+
"""
|
|
31
|
+
SELECT * FROM "Z_PRIMARYKEY" ORDER BY "Z_ENT" LIMIT 1000 OFFSET 0;
|
|
32
|
+
"""
|
|
33
|
+
)
|
|
34
|
+
ent_to_typename: Dict[int, str] = {}
|
|
35
|
+
for row in res.fetchall():
|
|
36
|
+
ent_to_typename[row["Z_ENT"]] = row["Z_NAME"]
|
|
37
|
+
return ent_to_typename
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return "\n".join(
|
|
41
|
+
f"{key}: {value}" for key, value in self._ent_to_typename.items()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def typename_for(self, ent_id: ENT_ID) -> str:
|
|
45
|
+
return self._ent_to_typename.get(ent_id)
|
|
46
|
+
|
|
47
|
+
def ent_for(self, typename: str) -> ENT_ID:
|
|
48
|
+
return self._typename_to_ent.get(typename)
|
|
49
|
+
|
|
50
|
+
def query_objects(self, typenames: List[str]) -> List[Any]:
|
|
51
|
+
cur = self._con.cursor()
|
|
52
|
+
res = cur.execute(
|
|
53
|
+
"""
|
|
54
|
+
SELECT * FROM ZSYNCOBJECT WHERE Z_ENT in (%s)
|
|
55
|
+
"""
|
|
56
|
+
% (",".join("?" * len(typenames))),
|
|
57
|
+
[self.ent_for(x) for x in typenames],
|
|
58
|
+
)
|
|
59
|
+
return res.fetchall()
|
|
60
|
+
|
|
61
|
+
def get_record(self, pk_id: ID, constructor: Callable = Record):
|
|
62
|
+
cur = self._con.cursor()
|
|
63
|
+
res = cur.execute(
|
|
64
|
+
"""
|
|
65
|
+
SELECT * FROM ZSYNCOBJECT WHERE Z_PK = ?
|
|
66
|
+
|
|
67
|
+
""",
|
|
68
|
+
[pk_id],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return constructor(res.fetchone())
|
|
72
|
+
|
|
73
|
+
def get_category_assignment(self) -> Dict[ID, List[Tuple[ID, float]]]:
|
|
74
|
+
transaction_map: Dict[ID, List[Tuple[ID, float]]] = defaultdict(list)
|
|
75
|
+
cur = self._con.cursor()
|
|
76
|
+
res = cur.execute(
|
|
77
|
+
"""
|
|
78
|
+
SELECT ZCATEGORY, ZTRANSACTION, ZAMOUNT FROM ZCATEGORYASSIGMENT WHERE ZTRANSACTION IS NOT NULL
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
)
|
|
82
|
+
for row in res.fetchall():
|
|
83
|
+
transaction_map[row["ZTRANSACTION"]].append(
|
|
84
|
+
(row["ZCATEGORY"], row["ZAMOUNT"])
|
|
85
|
+
)
|
|
86
|
+
return transaction_map
|
|
87
|
+
|
|
88
|
+
def get_refund_maps(self) -> Dict[ID, ID]:
|
|
89
|
+
refund_to_withdraw: Dict[ID, ID] = {}
|
|
90
|
+
cur = self._con.cursor()
|
|
91
|
+
res = cur.execute(
|
|
92
|
+
"""
|
|
93
|
+
SELECT ZREFUNDTRANSACTION, ZWITHDRAWTRANSACTION FROM ZWITHDRAWREFUNDTRANSACTIONLINK
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
)
|
|
97
|
+
for row in res.fetchall():
|
|
98
|
+
refund_to_withdraw[row["ZREFUNDTRANSACTION"]] = row["ZWITHDRAWTRANSACTION"]
|
|
99
|
+
return refund_to_withdraw
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from typing import Dict, Callable, List
|
|
2
|
+
|
|
3
|
+
from src.moneywiz_api.model.account import Account
|
|
4
|
+
from src.moneywiz_api.managers.record_manager import RecordManager
|
|
5
|
+
|
|
6
|
+
from src.moneywiz_api.model.account import (
|
|
7
|
+
BankChequeAccount,
|
|
8
|
+
BankSavingAccount,
|
|
9
|
+
CashAccount,
|
|
10
|
+
CreditCardAccount,
|
|
11
|
+
LoanAccount,
|
|
12
|
+
InvestmentAccount,
|
|
13
|
+
ForexAccount,
|
|
14
|
+
)
|
|
15
|
+
from src.moneywiz_api.types import ID
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AccountManager(RecordManager[Account]):
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def ents(self) -> Dict[str, Callable]:
|
|
24
|
+
return {
|
|
25
|
+
"BankChequeAccount": BankChequeAccount,
|
|
26
|
+
"BankSavingAccount": BankSavingAccount,
|
|
27
|
+
"CashAccount": CashAccount,
|
|
28
|
+
"CreditCardAccount": CreditCardAccount,
|
|
29
|
+
"LoanAccount": LoanAccount,
|
|
30
|
+
"InvestmentAccount": InvestmentAccount,
|
|
31
|
+
"ForexAccount": ForexAccount,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def records(self) -> Dict[ID, Account]:
|
|
35
|
+
return dict(
|
|
36
|
+
sorted(super().records().items(), key=lambda x: x[1]._display_order)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def get_accounts_for_user(self, user_id: ID) -> List[Account]:
|
|
40
|
+
return sorted(
|
|
41
|
+
[x for _, x in self.records().items() if x.user == user_id],
|
|
42
|
+
key=lambda x: (x._group_id, x._display_order),
|
|
43
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Dict, Callable, List
|
|
2
|
+
|
|
3
|
+
from src.moneywiz_api.model.category import Category
|
|
4
|
+
from src.moneywiz_api.managers.record_manager import RecordManager
|
|
5
|
+
from src.moneywiz_api.types import ID
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CategoryManager(RecordManager[Category]):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__()
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def ents(self) -> Dict[str, Callable]:
|
|
14
|
+
return {
|
|
15
|
+
"Category": Category,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def get_name_chain(self, category_id: ID) -> List[str]:
|
|
19
|
+
ret: List[str] = []
|
|
20
|
+
current = self.get(category_id)
|
|
21
|
+
while current:
|
|
22
|
+
ret.insert(0, current.name)
|
|
23
|
+
if not current.parentId:
|
|
24
|
+
break
|
|
25
|
+
else:
|
|
26
|
+
current = self.get(current.parentId)
|
|
27
|
+
return ret
|
|
28
|
+
|
|
29
|
+
def get_categories_for_user(self, user_id: ID) -> List[Category]:
|
|
30
|
+
return sorted(
|
|
31
|
+
[x for _, x in self.records().items() if x.user == user_id],
|
|
32
|
+
key=lambda x: x.type,
|
|
33
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Dict, Callable, List
|
|
2
|
+
|
|
3
|
+
from src.moneywiz_api.model.investment_holding import InvestmentHolding
|
|
4
|
+
from src.moneywiz_api.managers.record_manager import RecordManager
|
|
5
|
+
from src.moneywiz_api.types import ID
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InvestmentHoldingManager(RecordManager[InvestmentHolding]):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__()
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def ents(self) -> Dict[str, Callable]:
|
|
14
|
+
return {
|
|
15
|
+
"InvestmentHolding": InvestmentHolding,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def get_holdings_for_account(self, account_id: ID) -> List[InvestmentHolding]:
|
|
19
|
+
return [x for _, x in self.records().items() if x.account == account_id]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Dict, Callable
|
|
2
|
+
|
|
3
|
+
from src.moneywiz_api.model.payee import Payee
|
|
4
|
+
from src.moneywiz_api.managers.record_manager import RecordManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PayeeManager(RecordManager[Payee]):
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def ents(self) -> Dict[str, Callable]:
|
|
13
|
+
return {
|
|
14
|
+
"Payee": Payee,
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Generic, TypeVar, Callable
|
|
3
|
+
|
|
4
|
+
from src.moneywiz_api.database_accessor import DatabaseAccessor
|
|
5
|
+
from src.moneywiz_api.model.record import Record
|
|
6
|
+
from src.moneywiz_api.types import ID
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound=Record)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RecordManager(ABC, Generic[T]):
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._records: Dict[ID, T] = {}
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def ents(self) -> Dict[str, Callable]:
|
|
19
|
+
raise NotImplementedError()
|
|
20
|
+
|
|
21
|
+
def load(self, db_accessor: DatabaseAccessor) -> None:
|
|
22
|
+
records = db_accessor.query_objects(self.ents.keys())
|
|
23
|
+
|
|
24
|
+
for record in records:
|
|
25
|
+
typename = db_accessor.typename_for(record["Z_ENT"])
|
|
26
|
+
if typename in self.ents:
|
|
27
|
+
obj = self.ents[typename](record)
|
|
28
|
+
self.add(obj)
|
|
29
|
+
|
|
30
|
+
def add(self, record: T) -> None:
|
|
31
|
+
self._records[record.id] = record
|
|
32
|
+
|
|
33
|
+
def get(self, record_id: ID) -> T | None:
|
|
34
|
+
return self._records.get(record_id)
|
|
35
|
+
|
|
36
|
+
def records(self) -> Dict[ID, T]:
|
|
37
|
+
return self._records
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
return "\n".join(f"{key}: {value}" for key, value in self.records().items())
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Dict, Callable, List, Tuple
|
|
3
|
+
|
|
4
|
+
from src.moneywiz_api import utils
|
|
5
|
+
from src.moneywiz_api.database_accessor import DatabaseAccessor
|
|
6
|
+
from src.moneywiz_api.model.transaction import (
|
|
7
|
+
Transaction,
|
|
8
|
+
DepositTransaction,
|
|
9
|
+
InvestmentExchangeTransaction,
|
|
10
|
+
InvestmentBuyTransaction,
|
|
11
|
+
InvestmentSellTransaction,
|
|
12
|
+
ReconcileTransaction,
|
|
13
|
+
RefundTransaction,
|
|
14
|
+
TransferBudgetTransaction,
|
|
15
|
+
TransferDepositTransaction,
|
|
16
|
+
TransferWithdrawTransaction,
|
|
17
|
+
WithdrawTransaction,
|
|
18
|
+
)
|
|
19
|
+
from src.moneywiz_api.managers.record_manager import RecordManager
|
|
20
|
+
from src.moneywiz_api.types import ID
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TransactionManager(RecordManager[Transaction]):
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.category_assignment: Dict[ID, List[Tuple[ID, float]]] = {}
|
|
27
|
+
self.refund_maps: Dict[ID, ID] = {}
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def ents(self) -> Dict[str, Callable]:
|
|
31
|
+
return {
|
|
32
|
+
"DepositTransaction": DepositTransaction,
|
|
33
|
+
"InvestmentExchangeTransaction": InvestmentExchangeTransaction,
|
|
34
|
+
"InvestmentBuyTransaction": InvestmentBuyTransaction,
|
|
35
|
+
"InvestmentSellTransaction": InvestmentSellTransaction,
|
|
36
|
+
"ReconcileTransaction": ReconcileTransaction,
|
|
37
|
+
"RefundTransaction": RefundTransaction,
|
|
38
|
+
"TransferBudgetTransaction": TransferBudgetTransaction,
|
|
39
|
+
"TransferDepositTransaction": TransferDepositTransaction,
|
|
40
|
+
"TransferWithdrawTransaction": TransferWithdrawTransaction,
|
|
41
|
+
"WithdrawTransaction": WithdrawTransaction,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def load(self, db_accessor: DatabaseAccessor) -> None:
|
|
45
|
+
super().load(db_accessor)
|
|
46
|
+
self.category_assignment: Dict[
|
|
47
|
+
ID, List[Tuple[ID, float]]
|
|
48
|
+
] = db_accessor.get_category_assignment()
|
|
49
|
+
self.refund_maps: Dict[ID, ID] = db_accessor.get_refund_maps()
|
|
50
|
+
|
|
51
|
+
def category_for_transaction(
|
|
52
|
+
self, transaction_id: ID
|
|
53
|
+
) -> List[Tuple[ID, float]] | None:
|
|
54
|
+
return self.category_assignment.get(transaction_id)
|
|
55
|
+
|
|
56
|
+
def original_transaction_for_refund_transaction(
|
|
57
|
+
self, transaction_id: ID
|
|
58
|
+
) -> ID | None:
|
|
59
|
+
return self.refund_maps.get(transaction_id)
|
|
60
|
+
|
|
61
|
+
def get_all_for_account(
|
|
62
|
+
self, account_id: ID, until: datetime = datetime.now()
|
|
63
|
+
) -> List[Transaction]:
|
|
64
|
+
"""
|
|
65
|
+
Get all transactions for a given account
|
|
66
|
+
:param account_id:
|
|
67
|
+
:param until: inclusive
|
|
68
|
+
:return:
|
|
69
|
+
"""
|
|
70
|
+
return sorted(
|
|
71
|
+
[
|
|
72
|
+
x
|
|
73
|
+
for _, x in self.records().items()
|
|
74
|
+
if not isinstance(x, TransferBudgetTransaction)
|
|
75
|
+
and x.account == account_id
|
|
76
|
+
and x.date <= utils.get_date(until)
|
|
77
|
+
],
|
|
78
|
+
key=lambda x: x.date,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def get_all(self, until: datetime = datetime.now()) -> List[Transaction]:
|
|
82
|
+
return sorted(
|
|
83
|
+
[
|
|
84
|
+
x
|
|
85
|
+
for _, x in self.records().items()
|
|
86
|
+
if not isinstance(x, TransferBudgetTransaction)
|
|
87
|
+
and x.date <= utils.get_date(until)
|
|
88
|
+
],
|
|
89
|
+
key=lambda x: x.date,
|
|
90
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from src.moneywiz_api.model.record import Record
|
|
5
|
+
from src.moneywiz_api.types import ID
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Account(Record, ABC):
|
|
10
|
+
"""
|
|
11
|
+
ENT: 9
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
_display_order: int = field(repr=False)
|
|
15
|
+
_group_id: int = field(repr=False)
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
currency: str
|
|
19
|
+
opening_balance: float
|
|
20
|
+
info: str
|
|
21
|
+
user: ID
|
|
22
|
+
|
|
23
|
+
def __init__(self, row):
|
|
24
|
+
super().__init__(row)
|
|
25
|
+
self._display_order = row["ZDISPLAYORDER"]
|
|
26
|
+
self._group_id = row["ZGROUPID"]
|
|
27
|
+
|
|
28
|
+
self.name = row["ZNAME"]
|
|
29
|
+
self.currency = row["ZCURRENCYNAME"]
|
|
30
|
+
self.opening_balance = row["ZOPENINGBALANCE"]
|
|
31
|
+
self.info = row["ZINFO"]
|
|
32
|
+
|
|
33
|
+
self.user = row["ZUSER"]
|
|
34
|
+
|
|
35
|
+
# Validate
|
|
36
|
+
assert self.name is not None
|
|
37
|
+
assert self.currency is not None
|
|
38
|
+
assert self.opening_balance is not None
|
|
39
|
+
assert self.info is not None
|
|
40
|
+
assert self.user is not None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class BankChequeAccount(Account):
|
|
45
|
+
"""
|
|
46
|
+
ENT: 10
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, row):
|
|
50
|
+
super().__init__(row)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class BankSavingAccount(Account):
|
|
55
|
+
"""
|
|
56
|
+
ENT: 11
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, row):
|
|
60
|
+
super().__init__(row)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class CashAccount(Account):
|
|
65
|
+
"""
|
|
66
|
+
ENT: 12
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, row):
|
|
70
|
+
super().__init__(row)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class CreditCardAccount(Account):
|
|
75
|
+
"""
|
|
76
|
+
ENT: 13
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
statement_day: int # day in the month
|
|
80
|
+
|
|
81
|
+
def __init__(self, row):
|
|
82
|
+
super().__init__(row)
|
|
83
|
+
self.statement_day = row["ZSTATEMENTENDDAY"]
|
|
84
|
+
|
|
85
|
+
# Validate
|
|
86
|
+
assert self.statement_day is not None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class LoanAccount(CreditCardAccount):
|
|
91
|
+
"""
|
|
92
|
+
ENT: 14
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, row):
|
|
96
|
+
super().__init__(row)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class InvestmentAccount(Account):
|
|
101
|
+
"""
|
|
102
|
+
ENT: 15
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, row):
|
|
106
|
+
super().__init__(row)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ForexAccount(InvestmentAccount):
|
|
111
|
+
"""
|
|
112
|
+
ENT: 16
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, row):
|
|
116
|
+
super().__init__(row)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from src.moneywiz_api.model.record import Record
|
|
5
|
+
from src.moneywiz_api.types import CategoryType, ID
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Category(Record):
|
|
10
|
+
"""
|
|
11
|
+
ENT: 19
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
parentId: Optional[int]
|
|
16
|
+
type: CategoryType
|
|
17
|
+
user: ID
|
|
18
|
+
|
|
19
|
+
def __init__(self, row):
|
|
20
|
+
super().__init__(row)
|
|
21
|
+
self.name = row["ZNAME2"]
|
|
22
|
+
self.parentId = row["ZPARENTCATEGORY"]
|
|
23
|
+
self.type = self._convert_type(row["ZTYPE2"])
|
|
24
|
+
self.user = row["ZUSER3"]
|
|
25
|
+
|
|
26
|
+
# Validate
|
|
27
|
+
assert self.name is not None
|
|
28
|
+
assert self.type is not None
|
|
29
|
+
assert self.user is not None
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def _convert_type(type_: Optional[int]) -> CategoryType:
|
|
33
|
+
if not type_:
|
|
34
|
+
raise Exception("Invalid type")
|
|
35
|
+
if type_ not in [1, 2]:
|
|
36
|
+
raise Exception("Invalid type")
|
|
37
|
+
return "Expenses" if type_ == 1 else "Income"
|