tariochbctools 0.38.1__py2.py3-none-any.whl → 1.0.1__py2.py3-none-any.whl
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.
- tariochbctools/importers/bcge/importer.py +4 -3
- tariochbctools/importers/bitst/importer.py +28 -19
- tariochbctools/importers/blockchain/importer.py +10 -9
- tariochbctools/importers/cembrastatement/importer.py +44 -28
- tariochbctools/importers/general/mailAdapterImporter.py +12 -13
- tariochbctools/importers/general/mt940importer.py +18 -19
- tariochbctools/importers/general/priceLookup.py +6 -6
- tariochbctools/importers/ibkr/importer.py +22 -19
- tariochbctools/importers/neon/importer.py +17 -15
- tariochbctools/importers/netbenefits/importer.py +24 -16
- tariochbctools/importers/nordigen/importer.py +7 -7
- tariochbctools/importers/nordigen/nordigen_config.py +12 -11
- tariochbctools/importers/postfinance/importer.py +20 -17
- tariochbctools/importers/quickfile/importer.py +11 -11
- tariochbctools/importers/raiffeisench/importer.py +3 -7
- tariochbctools/importers/revolut/importer.py +23 -18
- tariochbctools/importers/schedule/importer.py +11 -8
- tariochbctools/importers/swisscard/importer.py +17 -15
- tariochbctools/importers/transferwise/importer.py +15 -14
- tariochbctools/importers/truelayer/importer.py +25 -16
- tariochbctools/importers/viseca/importer.py +24 -16
- tariochbctools/importers/zak/importer.py +51 -44
- tariochbctools/importers/zkb/importer.py +3 -2
- tariochbctools/plugins/prices/ibkr.py +6 -3
- {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.1.dist-info}/METADATA +6 -3
- tariochbctools-1.0.1.dist-info/RECORD +55 -0
- {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.1.dist-info}/WHEEL +1 -1
- tariochbctools-0.38.1.dist-info/RECORD +0 -55
- {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.1.dist-info}/LICENSE.txt +0 -0
- {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.1.dist-info}/entry_points.txt +0 -0
- {tariochbctools-0.38.1.dist-info → tariochbctools-1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,24 +1,23 @@
|
|
1
1
|
import csv
|
2
|
+
import re
|
2
3
|
from collections.abc import Iterable
|
3
4
|
from datetime import date
|
4
|
-
from io import StringIO
|
5
5
|
|
6
|
+
import beangulp
|
6
7
|
from beancount.core import amount, data
|
7
8
|
from beancount.core.number import D
|
8
9
|
from beancount.core.position import CostSpec
|
9
|
-
from beancount.ingest import importer
|
10
|
-
from beancount.ingest.importers.mixins import identifier
|
11
10
|
from dateutil.parser import parse
|
12
11
|
|
13
12
|
from tariochbctools.importers.general.priceLookup import PriceLookup
|
14
13
|
|
15
14
|
|
16
|
-
class Importer(
|
15
|
+
class Importer(beangulp.Importer):
|
17
16
|
"""An importer for Fidelity Netbenefits Activity CSV files."""
|
18
17
|
|
19
18
|
def __init__(
|
20
19
|
self,
|
21
|
-
|
20
|
+
filepattern: str,
|
22
21
|
cashAccount: str,
|
23
22
|
investmentAccount: str,
|
24
23
|
dividendAccount: str,
|
@@ -28,7 +27,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
28
27
|
ignoreTypes: Iterable[str],
|
29
28
|
baseCcy: str,
|
30
29
|
):
|
31
|
-
|
30
|
+
self.filepattern = filepattern
|
32
31
|
self.cashAccount = cashAccount
|
33
32
|
self.investmentAccount = investmentAccount
|
34
33
|
self.dividendAccount = dividendAccount
|
@@ -38,18 +37,21 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
38
37
|
self.ignoreTypes = ignoreTypes
|
39
38
|
self.baseCcy = baseCcy
|
40
39
|
|
41
|
-
def name(self):
|
40
|
+
def name(self) -> str:
|
42
41
|
return super().name() + self.cashAccount
|
43
42
|
|
44
|
-
def
|
43
|
+
def identify(self, filepath: str) -> bool:
|
44
|
+
return re.search(self.filepattern, filepath) is not None
|
45
|
+
|
46
|
+
def account(self, filepath: str) -> data.Account:
|
45
47
|
return self.cashAccount
|
46
48
|
|
47
|
-
def extract(self,
|
49
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
48
50
|
entries = []
|
49
51
|
|
50
|
-
self.priceLookup = PriceLookup(
|
52
|
+
self.priceLookup = PriceLookup(existing, self.baseCcy)
|
51
53
|
|
52
|
-
with
|
54
|
+
with open(filepath) as csvfile:
|
53
55
|
reader = csv.DictReader(
|
54
56
|
csvfile,
|
55
57
|
[
|
@@ -76,12 +78,12 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
76
78
|
if row["Shares"] != "-":
|
77
79
|
shares = amount.Amount(D(row["Shares"]), self.symbol)
|
78
80
|
|
79
|
-
metakv = {}
|
81
|
+
metakv: data.Meta = {}
|
80
82
|
|
81
83
|
if not amt and not shares:
|
82
84
|
continue
|
83
85
|
|
84
|
-
meta = data.new_metadata(
|
86
|
+
meta = data.new_metadata(filepath, 0, metakv)
|
85
87
|
description = row["Transaction type"].strip()
|
86
88
|
|
87
89
|
if "TAX" in description:
|
@@ -120,7 +122,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
120
122
|
|
121
123
|
return entries
|
122
124
|
|
123
|
-
def __createBuy(
|
125
|
+
def __createBuy(
|
126
|
+
self, amt: amount, shares: amount, book_date: date
|
127
|
+
) -> list[data.Posting]:
|
124
128
|
price = self.priceLookup.fetchPrice("USD", book_date)
|
125
129
|
cost = CostSpec(
|
126
130
|
number_per=None,
|
@@ -137,7 +141,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
137
141
|
|
138
142
|
return postings
|
139
143
|
|
140
|
-
def __createSell(
|
144
|
+
def __createSell(
|
145
|
+
self, amt: amount, shares: amount, book_date: date
|
146
|
+
) -> list[data.Posting]:
|
141
147
|
price = self.priceLookup.fetchPrice("USD", book_date)
|
142
148
|
cost = CostSpec(
|
143
149
|
number_per=None,
|
@@ -155,7 +161,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
155
161
|
|
156
162
|
return postings
|
157
163
|
|
158
|
-
def __createDividend(
|
164
|
+
def __createDividend(
|
165
|
+
self, amt: amount, book_date: date, incomeAccount: str
|
166
|
+
) -> list[data.Posting]:
|
159
167
|
price = self.priceLookup.fetchPrice("USD", book_date)
|
160
168
|
postings = [
|
161
169
|
data.Posting(
|
@@ -1,28 +1,28 @@
|
|
1
1
|
from datetime import date
|
2
2
|
from os import path
|
3
3
|
|
4
|
+
import beangulp
|
4
5
|
import requests
|
5
6
|
import yaml
|
6
7
|
from beancount.core import amount, data
|
7
8
|
from beancount.core.number import D
|
8
|
-
from beancount.ingest import importer
|
9
9
|
|
10
10
|
|
11
11
|
class HttpServiceException(Exception):
|
12
12
|
pass
|
13
13
|
|
14
14
|
|
15
|
-
class Importer(
|
15
|
+
class Importer(beangulp.Importer):
|
16
16
|
"""An importer for Nordigen API (e.g. for Revolut)."""
|
17
17
|
|
18
|
-
def identify(self,
|
19
|
-
return path.basename(
|
18
|
+
def identify(self, filepath: str) -> bool:
|
19
|
+
return path.basename(filepath).endswith("nordigen.yaml")
|
20
20
|
|
21
|
-
def
|
21
|
+
def account(self, filepath: str) -> data.Entries:
|
22
22
|
return ""
|
23
23
|
|
24
|
-
def extract(self,
|
25
|
-
with open(
|
24
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
25
|
+
with open(filepath, "r") as f:
|
26
26
|
config = yaml.safe_load(f)
|
27
27
|
|
28
28
|
r = requests.post(
|
@@ -1,21 +1,22 @@
|
|
1
1
|
import argparse
|
2
2
|
import sys
|
3
|
+
from typing import Any
|
3
4
|
|
4
5
|
import requests
|
5
6
|
|
6
7
|
|
7
|
-
def build_header(token):
|
8
|
+
def build_header(token: str) -> dict[str, str]:
|
8
9
|
return {"Authorization": "Bearer " + token}
|
9
10
|
|
10
11
|
|
11
|
-
def check_result(result):
|
12
|
+
def check_result(result: requests.Response) -> None:
|
12
13
|
try:
|
13
14
|
result.raise_for_status()
|
14
15
|
except requests.exceptions.HTTPError as e:
|
15
16
|
raise Exception(e, e.response.text)
|
16
17
|
|
17
18
|
|
18
|
-
def get_token(secret_id, secret_key):
|
19
|
+
def get_token(secret_id: str, secret_key: str) -> str:
|
19
20
|
r = requests.post(
|
20
21
|
"https://bankaccountdata.gocardless.com/api/v2/token/new/",
|
21
22
|
data={
|
@@ -28,7 +29,7 @@ def get_token(secret_id, secret_key):
|
|
28
29
|
return r.json()["access"]
|
29
30
|
|
30
31
|
|
31
|
-
def list_bank(token, country):
|
32
|
+
def list_bank(token: str, country: str) -> None:
|
32
33
|
r = requests.get(
|
33
34
|
"https://bankaccountdata.gocardless.com/api/v2/institutions/",
|
34
35
|
params={"country": country},
|
@@ -40,7 +41,7 @@ def list_bank(token, country):
|
|
40
41
|
print(asp["name"] + ": " + asp["id"]) # noqa: T201
|
41
42
|
|
42
43
|
|
43
|
-
def create_link(token, reference, bank):
|
44
|
+
def create_link(token: str, reference: str, bank: str) -> None:
|
44
45
|
if not bank:
|
45
46
|
raise Exception("Please specify --bank it is required for create_link")
|
46
47
|
requisitionId = _find_requisition_id(token, reference)
|
@@ -61,7 +62,7 @@ def create_link(token, reference, bank):
|
|
61
62
|
print(f"Go to {link} for connecting to your bank.") # noqa: T201
|
62
63
|
|
63
64
|
|
64
|
-
def list_accounts(token):
|
65
|
+
def list_accounts(token: str) -> None:
|
65
66
|
headers = build_header(token)
|
66
67
|
r = requests.get(
|
67
68
|
"https://bankaccountdata.gocardless.com/api/v2/requisitions/", headers=headers
|
@@ -93,7 +94,7 @@ def list_accounts(token):
|
|
93
94
|
print(f"{account}: {asp} {owner} {iban} {currency}") # noqa: T201
|
94
95
|
|
95
96
|
|
96
|
-
def delete_link(token, reference):
|
97
|
+
def delete_link(token: str, reference: str) -> None:
|
97
98
|
requisitionId = _find_requisition_id(token, reference)
|
98
99
|
if requisitionId:
|
99
100
|
r = requests.delete(
|
@@ -103,7 +104,7 @@ def delete_link(token, reference):
|
|
103
104
|
check_result(r)
|
104
105
|
|
105
106
|
|
106
|
-
def _find_requisition_id(token, userId):
|
107
|
+
def _find_requisition_id(token: str, userId: str) -> str | None:
|
107
108
|
headers = build_header(token)
|
108
109
|
r = requests.get(
|
109
110
|
"https://bankaccountdata.gocardless.com/api/v2/requisitions/", headers=headers
|
@@ -116,7 +117,7 @@ def _find_requisition_id(token, userId):
|
|
116
117
|
return None
|
117
118
|
|
118
119
|
|
119
|
-
def parse_args(args):
|
120
|
+
def parse_args(args: Any) -> Any:
|
120
121
|
parser = argparse.ArgumentParser(description="nordigen-config")
|
121
122
|
parser.add_argument(
|
122
123
|
"--secret_id",
|
@@ -154,7 +155,7 @@ def parse_args(args):
|
|
154
155
|
return parser.parse_args(args)
|
155
156
|
|
156
157
|
|
157
|
-
def main(args):
|
158
|
+
def main(args: Any) -> None:
|
158
159
|
args = parse_args(args)
|
159
160
|
|
160
161
|
token = get_token(args.secret_id, args.secret_key)
|
@@ -169,6 +170,6 @@ def main(args):
|
|
169
170
|
delete_link(token, args.reference)
|
170
171
|
|
171
172
|
|
172
|
-
def run():
|
173
|
+
def run() -> None:
|
173
174
|
"""Entry point for console_scripts"""
|
174
175
|
main(sys.argv[1:])
|
@@ -1,49 +1,52 @@
|
|
1
1
|
import csv
|
2
2
|
import logging
|
3
|
+
import re
|
3
4
|
from datetime import datetime, timedelta
|
4
5
|
from decimal import Decimal
|
5
6
|
|
7
|
+
import beangulp
|
6
8
|
from beancount.core import data
|
7
|
-
from beancount.ingest.importer import ImporterProtocol
|
8
|
-
from beancount.ingest.importers.mixins.identifier import IdentifyMixin
|
9
9
|
|
10
10
|
|
11
|
-
class Importer(
|
11
|
+
class Importer(beangulp.Importer):
|
12
12
|
"""An importer for PostFinance CSV."""
|
13
13
|
|
14
|
-
def __init__(self,
|
15
|
-
|
16
|
-
self.
|
14
|
+
def __init__(self, filepattern: str, account: data.Account, currency: str = "CHF"):
|
15
|
+
self._filepattern = filepattern
|
16
|
+
self._account = account
|
17
17
|
self.currency = currency
|
18
18
|
|
19
|
-
def
|
20
|
-
return self.
|
19
|
+
def identify(self, filepath: str) -> bool:
|
20
|
+
return re.search(self._filepattern, filepath) is not None
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def account(self, filepath: str) -> data.Account:
|
23
|
+
return self._account
|
24
|
+
|
25
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
26
|
+
csvfile = open(file=filepath, encoding="windows_1252")
|
24
27
|
reader = csv.reader(csvfile, delimiter=";")
|
25
|
-
meta = data.new_metadata(
|
28
|
+
meta = data.new_metadata(filepath, 0)
|
26
29
|
entries = []
|
27
30
|
|
28
31
|
for row in reader:
|
29
32
|
try:
|
30
|
-
|
31
|
-
book_date = datetime.strptime(
|
33
|
+
book_date_str, text, credit, debit, val_date, balance_str = tuple(row)
|
34
|
+
book_date = datetime.strptime(book_date_str, "%Y-%m-%d").date()
|
32
35
|
if credit:
|
33
36
|
amount = data.Amount(Decimal(credit), self.currency)
|
34
37
|
elif debit:
|
35
38
|
amount = data.Amount(Decimal(debit), self.currency)
|
36
39
|
else:
|
37
40
|
amount = None
|
38
|
-
if
|
39
|
-
balance = data.Amount(Decimal(
|
41
|
+
if balance_str:
|
42
|
+
balance = data.Amount(Decimal(balance_str), self.currency)
|
40
43
|
else:
|
41
44
|
balance = None
|
42
45
|
except Exception as e:
|
43
46
|
logging.debug(e)
|
44
47
|
else:
|
45
48
|
logging.debug((book_date, text, amount, val_date, balance))
|
46
|
-
posting = data.Posting(self.
|
49
|
+
posting = data.Posting(self._account, amount, None, None, None, None)
|
47
50
|
entry = data.Transaction(
|
48
51
|
meta,
|
49
52
|
book_date,
|
@@ -59,7 +62,7 @@ class Importer(IdentifyMixin, ImporterProtocol):
|
|
59
62
|
book_date = book_date + timedelta(days=1)
|
60
63
|
if balance and book_date.day == 1:
|
61
64
|
entry = data.Balance(
|
62
|
-
meta, book_date, self.
|
65
|
+
meta, book_date, self._account, balance, None, None
|
63
66
|
)
|
64
67
|
entries.append(entry)
|
65
68
|
|
@@ -5,11 +5,11 @@ from hashlib import md5
|
|
5
5
|
from os import path
|
6
6
|
from typing import Dict, List, NamedTuple
|
7
7
|
|
8
|
+
import beangulp
|
8
9
|
import requests
|
9
10
|
import yaml
|
10
11
|
from beancount.core import amount, data
|
11
12
|
from beancount.core.number import D
|
12
|
-
from beancount.ingest import importer
|
13
13
|
from undictify import type_checked_constructor
|
14
14
|
|
15
15
|
|
@@ -152,32 +152,32 @@ class QuickFile:
|
|
152
152
|
return QuickFileBankSearch(**body)
|
153
153
|
|
154
154
|
|
155
|
-
class Importer(
|
155
|
+
class Importer(beangulp.Importer):
|
156
156
|
"""An importer for QuickFile"""
|
157
157
|
|
158
158
|
def __init__(self):
|
159
159
|
self.quickfile = None
|
160
160
|
self.config = None
|
161
|
-
self.
|
161
|
+
self.existing = None
|
162
162
|
|
163
|
-
def _configure(self,
|
164
|
-
with open(
|
163
|
+
def _configure(self, filepath, existing):
|
164
|
+
with open(filepath, "r") as config_file:
|
165
165
|
self.config = yaml.safe_load(config_file)
|
166
166
|
self.quickfile = QuickFile(
|
167
167
|
account_number=self.config["account_number"],
|
168
168
|
api_key=self.config["api_key"],
|
169
169
|
app_id=self.config["app_id"],
|
170
170
|
)
|
171
|
-
self.
|
171
|
+
self.existing = existing
|
172
172
|
|
173
|
-
def identify(self,
|
174
|
-
return path.basename(
|
173
|
+
def identify(self, filepath):
|
174
|
+
return path.basename(filepath) == "quickfile.yaml"
|
175
175
|
|
176
|
-
def
|
176
|
+
def account(self, filepath):
|
177
177
|
return ""
|
178
178
|
|
179
|
-
def extract(self,
|
180
|
-
self._configure(
|
179
|
+
def extract(self, filepath, existing=None):
|
180
|
+
self._configure(filepath, existing)
|
181
181
|
entries = []
|
182
182
|
|
183
183
|
for bank_account in self.config["accounts"].keys():
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import re
|
2
|
+
from typing import Any
|
2
3
|
|
3
4
|
from tariochbctools.importers.general import mt940importer
|
4
5
|
|
@@ -8,21 +9,16 @@ class RaiffeisenCHImporter(mt940importer.Importer):
|
|
8
9
|
|
9
10
|
"""To get the correct file, choose SWIFT -> 'Période prédéfinie du relevé de compte' -> Sans détails"""
|
10
11
|
|
11
|
-
def prepare_payee(self, trxdata):
|
12
|
+
def prepare_payee(self, trxdata: dict[str, Any]) -> str:
|
12
13
|
return ""
|
13
14
|
|
14
|
-
def prepare_narration(self, trxdata):
|
15
|
+
def prepare_narration(self, trxdata: dict[str, Any]) -> str:
|
15
16
|
extra = trxdata["extra_details"]
|
16
17
|
details = trxdata["transaction_details"]
|
17
18
|
|
18
|
-
extraReplacements = {}
|
19
|
-
|
20
19
|
detailsReplacements = {}
|
21
20
|
detailsReplacements[r"\n"] = ", "
|
22
21
|
|
23
|
-
for pattern, replacement in extraReplacements.items():
|
24
|
-
extra = re.sub(pattern, replacement, extra)
|
25
|
-
|
26
22
|
for pattern, replacement in detailsReplacements.items():
|
27
23
|
details = re.sub(pattern, replacement, details)
|
28
24
|
|
@@ -1,34 +1,39 @@
|
|
1
1
|
import csv
|
2
2
|
import logging
|
3
|
+
import re
|
3
4
|
from datetime import timedelta
|
4
|
-
from
|
5
|
+
from typing import Any
|
5
6
|
|
7
|
+
import beangulp
|
6
8
|
from beancount.core import amount, data
|
7
9
|
from beancount.core.number import ZERO, D
|
8
|
-
from beancount.ingest import importer
|
9
|
-
from beancount.ingest.importers.mixins import identifier
|
10
10
|
from dateutil.parser import parse
|
11
11
|
|
12
12
|
|
13
|
-
class Importer(
|
13
|
+
class Importer(beangulp.Importer):
|
14
14
|
"""An importer for Revolut CSV files."""
|
15
15
|
|
16
|
-
def __init__(
|
17
|
-
|
18
|
-
|
16
|
+
def __init__(
|
17
|
+
self, filepattern: str, account: data.Account, currency: str, fee: Any = None
|
18
|
+
):
|
19
|
+
self._filepattern = filepattern
|
20
|
+
self._account = account
|
19
21
|
self.currency = currency
|
20
22
|
self._fee = fee
|
21
23
|
|
22
|
-
def name(self):
|
23
|
-
return super().name() + self.
|
24
|
+
def name(self) -> str:
|
25
|
+
return super().name() + self._account
|
24
26
|
|
25
|
-
def
|
26
|
-
return self.
|
27
|
+
def identify(self, filepath: str) -> bool:
|
28
|
+
return re.search(self._filepattern, filepath) is not None
|
27
29
|
|
28
|
-
def
|
30
|
+
def account(self, filepath: str) -> data.Account:
|
31
|
+
return self._account
|
32
|
+
|
33
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
29
34
|
entries = []
|
30
35
|
|
31
|
-
with
|
36
|
+
with open(filepath) as csvfile:
|
32
37
|
reader = csv.DictReader(
|
33
38
|
csvfile,
|
34
39
|
[
|
@@ -65,12 +70,12 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
65
70
|
continue
|
66
71
|
|
67
72
|
postings = [
|
68
|
-
data.Posting(self.
|
73
|
+
data.Posting(self._account, amt, None, None, None, None),
|
69
74
|
]
|
70
75
|
description = row["Description"].strip()
|
71
76
|
if is_fee_mode:
|
72
77
|
postings = [
|
73
|
-
data.Posting(self.
|
78
|
+
data.Posting(self._account, fee, None, None, None, None),
|
74
79
|
data.Posting(
|
75
80
|
self._fee["account"], -fee, None, None, None, None
|
76
81
|
),
|
@@ -82,7 +87,7 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
82
87
|
), "Actual type of description is " + str(type(description))
|
83
88
|
|
84
89
|
entry = data.Transaction(
|
85
|
-
data.new_metadata(
|
90
|
+
data.new_metadata(filepath, 0, {}),
|
86
91
|
book_date,
|
87
92
|
"*",
|
88
93
|
"",
|
@@ -98,9 +103,9 @@ class Importer(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
98
103
|
try:
|
99
104
|
book_date = book_date + timedelta(days=1)
|
100
105
|
entry = data.Balance(
|
101
|
-
data.new_metadata(
|
106
|
+
data.new_metadata(filepath, 0, {}),
|
102
107
|
book_date,
|
103
|
-
self.
|
108
|
+
self._account,
|
104
109
|
balance,
|
105
110
|
None,
|
106
111
|
None,
|
@@ -1,24 +1,25 @@
|
|
1
1
|
import datetime
|
2
2
|
from os import path
|
3
|
+
from typing import Any
|
3
4
|
|
5
|
+
import beangulp
|
4
6
|
import yaml
|
5
7
|
from beancount.core import amount, data
|
6
8
|
from beancount.core.number import D
|
7
|
-
from beancount.ingest import importer
|
8
9
|
from dateutil.relativedelta import relativedelta
|
9
10
|
|
10
11
|
|
11
|
-
class Importer(
|
12
|
+
class Importer(beangulp.Importer):
|
12
13
|
"""An importer for Scheduled/Recurring Transactions."""
|
13
14
|
|
14
|
-
def identify(self,
|
15
|
-
return path.basename(
|
15
|
+
def identify(self, filepath: str) -> bool:
|
16
|
+
return path.basename(filepath).endswith("schedule.yaml")
|
16
17
|
|
17
|
-
def
|
18
|
+
def account(self, filepath: str) -> data.Account:
|
18
19
|
return ""
|
19
20
|
|
20
|
-
def extract(self,
|
21
|
-
with open(
|
21
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
22
|
+
with open(filepath, "r") as f:
|
22
23
|
config = yaml.safe_load(f)
|
23
24
|
self.transactions = config["transactions"]
|
24
25
|
|
@@ -30,7 +31,9 @@ class Importer(importer.ImporterProtocol):
|
|
30
31
|
|
31
32
|
return result
|
32
33
|
|
33
|
-
def createForDate(
|
34
|
+
def createForDate(
|
35
|
+
self, trx: dict[str, Any], date: datetime.date
|
36
|
+
) -> data.Transaction:
|
34
37
|
postings = []
|
35
38
|
for post in trx["postings"]:
|
36
39
|
amt = None
|
@@ -1,29 +1,31 @@
|
|
1
1
|
import csv
|
2
|
-
|
2
|
+
import re
|
3
3
|
|
4
4
|
from beancount.core import amount, data
|
5
5
|
from beancount.core.number import D
|
6
|
-
from
|
7
|
-
from beancount.ingest.importers.mixins import identifier
|
6
|
+
from beangulp import Importer
|
8
7
|
from dateutil.parser import parse
|
9
8
|
|
10
9
|
|
11
|
-
class SwisscardImporter(
|
10
|
+
class SwisscardImporter(Importer):
|
12
11
|
"""An importer for Swisscard's cashback CSV files."""
|
13
12
|
|
14
|
-
def __init__(self,
|
15
|
-
|
16
|
-
self.
|
13
|
+
def __init__(self, filepattern: str, account: data.Account):
|
14
|
+
self._filepattern = filepattern
|
15
|
+
self._account = account
|
17
16
|
|
18
|
-
def name(self):
|
19
|
-
return super().name() + self.
|
17
|
+
def name(self) -> str:
|
18
|
+
return super().name() + self._account
|
20
19
|
|
21
|
-
def
|
22
|
-
return self.
|
20
|
+
def identify(self, filepath: str) -> bool:
|
21
|
+
return re.search(self._filepattern, filepath) is not None
|
23
22
|
|
24
|
-
def
|
23
|
+
def account(self, filepath: str) -> data.Account:
|
24
|
+
return self._account
|
25
|
+
|
26
|
+
def extract(self, filepath: str, existing: data.Entries) -> data.Entries:
|
25
27
|
entries = []
|
26
|
-
with
|
28
|
+
with open(filepath) as csvfile:
|
27
29
|
reader = csv.DictReader(
|
28
30
|
csvfile,
|
29
31
|
delimiter=",",
|
@@ -36,7 +38,7 @@ class SwisscardImporter(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
36
38
|
"merchant": row["Merchant Category"],
|
37
39
|
"category": row["Registered Category"],
|
38
40
|
}
|
39
|
-
meta = data.new_metadata(
|
41
|
+
meta = data.new_metadata(filepath, 0, metakv)
|
40
42
|
description = row["Description"].strip()
|
41
43
|
entry = data.Transaction(
|
42
44
|
meta,
|
@@ -47,7 +49,7 @@ class SwisscardImporter(identifier.IdentifyMixin, importer.ImporterProtocol):
|
|
47
49
|
data.EMPTY_SET,
|
48
50
|
data.EMPTY_SET,
|
49
51
|
[
|
50
|
-
data.Posting(self.
|
52
|
+
data.Posting(self._account, amt, None, None, None, None),
|
51
53
|
],
|
52
54
|
)
|
53
55
|
entries.append(entry)
|