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.
Files changed (31) hide show
  1. moneywiz-api-0.0.1/LICENSE +21 -0
  2. moneywiz-api-0.0.1/PKG-INFO +61 -0
  3. moneywiz-api-0.0.1/README.md +47 -0
  4. moneywiz-api-0.0.1/pyproject.toml +23 -0
  5. moneywiz-api-0.0.1/setup.cfg +4 -0
  6. moneywiz-api-0.0.1/src/__init__.py +0 -0
  7. moneywiz-api-0.0.1/src/moneywiz_api/__init__.py +3 -0
  8. moneywiz-api-0.0.1/src/moneywiz_api/database_accessor.py +99 -0
  9. moneywiz-api-0.0.1/src/moneywiz_api/managers/__init__.py +0 -0
  10. moneywiz-api-0.0.1/src/moneywiz_api/managers/account_manager.py +43 -0
  11. moneywiz-api-0.0.1/src/moneywiz_api/managers/category_manager.py +33 -0
  12. moneywiz-api-0.0.1/src/moneywiz_api/managers/investment_holding_manager.py +19 -0
  13. moneywiz-api-0.0.1/src/moneywiz_api/managers/payee_manager.py +15 -0
  14. moneywiz-api-0.0.1/src/moneywiz_api/managers/record_manager.py +40 -0
  15. moneywiz-api-0.0.1/src/moneywiz_api/managers/transaction_manager.py +90 -0
  16. moneywiz-api-0.0.1/src/moneywiz_api/model/__init__.py +0 -0
  17. moneywiz-api-0.0.1/src/moneywiz_api/model/account.py +116 -0
  18. moneywiz-api-0.0.1/src/moneywiz_api/model/category.py +37 -0
  19. moneywiz-api-0.0.1/src/moneywiz_api/model/investment_holding.py +70 -0
  20. moneywiz-api-0.0.1/src/moneywiz_api/model/payee.py +23 -0
  21. moneywiz-api-0.0.1/src/moneywiz_api/model/record.py +68 -0
  22. moneywiz-api-0.0.1/src/moneywiz_api/model/transaction.py +412 -0
  23. moneywiz-api-0.0.1/src/moneywiz_api/moneywiz_api.py +32 -0
  24. moneywiz-api-0.0.1/src/moneywiz_api/shell.py +109 -0
  25. moneywiz-api-0.0.1/src/moneywiz_api/types.py +6 -0
  26. moneywiz-api-0.0.1/src/moneywiz_api/utils.py +15 -0
  27. moneywiz-api-0.0.1/src/moneywiz_api.egg-info/PKG-INFO +61 -0
  28. moneywiz-api-0.0.1/src/moneywiz_api.egg-info/SOURCES.txt +29 -0
  29. moneywiz-api-0.0.1/src/moneywiz_api.egg-info/dependency_links.txt +1 -0
  30. moneywiz-api-0.0.1/src/moneywiz_api.egg-info/top_level.txt +2 -0
  31. 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
+ ![Static Badge](https://img.shields.io/badge/Python-3-blue?style=flat&logo=Python)
18
+ ![PyPI](https://img.shields.io/pypi/v/moneywiz-api)
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
+ ![Static Badge](https://img.shields.io/badge/Python-3-blue?style=flat&logo=Python)
4
+ ![PyPI](https://img.shields.io/pypi/v/moneywiz-api)
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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,3 @@
1
+ from src.moneywiz_api.moneywiz_api import MoneywizApi
2
+
3
+ __all__ = ["MoneywizApi"]
@@ -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
@@ -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"