Compare commits
3 Commits
ace5195164
...
fd24ac3318
| Author | SHA1 | Date | |
|---|---|---|---|
| fd24ac3318 | |||
| 36e7f84bd9 | |||
| 5235fcdfc3 |
@ -0,0 +1,40 @@
|
|||||||
|
"""Split member of base transaction
|
||||||
|
|
||||||
|
Revision ID: 18572111d9ff
|
||||||
|
Revises: 28556ab17c56
|
||||||
|
Create Date: 2023-01-23 20:09:37.892997+00:00
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "18572111d9ff"
|
||||||
|
down_revision = "28556ab17c56"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"transactions",
|
||||||
|
"split",
|
||||||
|
existing_type=sa.BOOLEAN(),
|
||||||
|
nullable=False,
|
||||||
|
schema="transactions",
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"transactions",
|
||||||
|
"split",
|
||||||
|
existing_type=sa.BOOLEAN(),
|
||||||
|
nullable=True,
|
||||||
|
schema="transactions",
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -1,12 +1,2 @@
|
|||||||
__all__ = ["argparser", "Manager", "parse_data", "categorize_data"]
|
|
||||||
__author__ = "Luís Murta"
|
__author__ = "Luís Murta"
|
||||||
__version__ = "0.1"
|
__version__ = "0.1"
|
||||||
|
|
||||||
from pfbudget.common.types import Operation
|
|
||||||
from pfbudget.core.categories import categorize_data
|
|
||||||
from pfbudget.core.manager import Manager
|
|
||||||
from pfbudget.cli.runnable import argparser
|
|
||||||
from pfbudget.input.parsers import parse_data
|
|
||||||
from pfbudget.utils.utils import parse_args_period
|
|
||||||
|
|
||||||
import pfbudget.db.model as t
|
|
||||||
|
|||||||
@ -1,12 +1,57 @@
|
|||||||
import pfbudget
|
from pfbudget.cli.runnable import argparser
|
||||||
|
from pfbudget.common.types import Operation
|
||||||
|
from pfbudget.core.manager import Manager
|
||||||
|
import pfbudget.db.model as type
|
||||||
|
from pfbudget.utils.utils import parse_args_period
|
||||||
|
|
||||||
|
|
||||||
|
def interactive(manager: Manager):
|
||||||
|
with manager.db.session() as session:
|
||||||
|
categories = session.get(type.Category)
|
||||||
|
print(f"Available categories: {categories}")
|
||||||
|
print(f"Available tags: {session.get(type.Tag)}")
|
||||||
|
transactions = session.get(type.Transaction, ~type.Transaction.category.has())
|
||||||
|
print(f"{len(transactions)} transactions left to categorize")
|
||||||
|
|
||||||
|
for transaction in sorted(transactions):
|
||||||
|
print(f"{transaction}")
|
||||||
|
quit = False
|
||||||
|
next = True
|
||||||
|
while next:
|
||||||
|
match (input("(<category>/split/tag/note/quit): ")):
|
||||||
|
case "quit" | "exit":
|
||||||
|
next = False
|
||||||
|
quit = True
|
||||||
|
|
||||||
|
case "tag":
|
||||||
|
tag = input("tag: ")
|
||||||
|
transaction.tags.add(type.TransactionTag(tag))
|
||||||
|
|
||||||
|
case "note":
|
||||||
|
note = input("note: ")
|
||||||
|
transaction.note = type.Note(note)
|
||||||
|
|
||||||
|
case other:
|
||||||
|
if other not in [c.name for c in categories]:
|
||||||
|
print(f"{other} is not a valid category")
|
||||||
|
continue
|
||||||
|
|
||||||
|
transaction.category = type.TransactionCategory(
|
||||||
|
other,
|
||||||
|
type.CategorySelector(type.Selector_T.manual),
|
||||||
|
)
|
||||||
|
next = False
|
||||||
|
|
||||||
|
if quit:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
argparser = pfbudget.argparser()
|
argparser = argparser()
|
||||||
args = vars(argparser.parse_args())
|
args = vars(argparser.parse_args())
|
||||||
|
|
||||||
assert "op" in args, "No pfbudget.Operation selected"
|
assert "op" in args, "No Operation selected"
|
||||||
op: pfbudget.Operation = args.pop("op")
|
op: Operation = args.pop("op")
|
||||||
|
|
||||||
assert "database" in args, "No database selected"
|
assert "database" in args, "No database selected"
|
||||||
db = args.pop("database")
|
db = args.pop("database")
|
||||||
@ -16,23 +61,27 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params = []
|
params = []
|
||||||
match (op):
|
match (op):
|
||||||
case pfbudget.Operation.Parse:
|
case Operation.ManualCategorization:
|
||||||
|
interactive(Manager(db, verbosity))
|
||||||
|
exit()
|
||||||
|
|
||||||
|
case Operation.Parse:
|
||||||
keys = {"path", "bank", "creditcard"}
|
keys = {"path", "bank", "creditcard"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [args["path"], args["bank"], args["creditcard"]]
|
params = [args["path"], args["bank"], args["creditcard"]]
|
||||||
|
|
||||||
case pfbudget.Operation.RequisitionId:
|
case Operation.RequisitionId:
|
||||||
keys = {"name", "country"}
|
keys = {"name", "country"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [args["name"][0], args["country"][0]]
|
params = [args["name"][0], args["country"][0]]
|
||||||
|
|
||||||
case pfbudget.Operation.Download:
|
case Operation.Download:
|
||||||
keys = {"all", "banks", "interval", "start", "end", "year", "dry_run"}
|
keys = {"all", "banks", "interval", "start", "end", "year", "dry_run"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
start, end = pfbudget.parse_args_period(args)
|
start, end = parse_args_period(args)
|
||||||
params = [start, end, args["dry_run"]]
|
params = [start, end, args["dry_run"]]
|
||||||
|
|
||||||
if not args["all"]:
|
if not args["all"]:
|
||||||
@ -40,19 +89,19 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
params.append([])
|
params.append([])
|
||||||
|
|
||||||
case pfbudget.Operation.BankAdd:
|
case Operation.BankAdd:
|
||||||
keys = {"bank", "bic", "type"}
|
keys = {"bank", "bic", "type"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
pfbudget.t.Bank(
|
type.Bank(
|
||||||
args["bank"][0],
|
args["bank"][0],
|
||||||
args["bic"][0],
|
args["bic"][0],
|
||||||
args["type"][0],
|
args["type"][0],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case pfbudget.Operation.BankMod:
|
case Operation.BankMod:
|
||||||
keys = {"bank", "bic", "type", "remove"}
|
keys = {"bank", "bic", "type", "remove"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -64,16 +113,16 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params = [param]
|
params = [param]
|
||||||
|
|
||||||
case pfbudget.Operation.BankDel:
|
case Operation.BankDel:
|
||||||
assert len(args["bank"]) > 0, "argparser ill defined"
|
assert len(args["bank"]) > 0, "argparser ill defined"
|
||||||
params = args["bank"]
|
params = args["bank"]
|
||||||
|
|
||||||
case pfbudget.Operation.NordigenAdd:
|
case Operation.NordigenAdd:
|
||||||
keys = {"bank", "bank_id", "requisition_id", "invert"}
|
keys = {"bank", "bank_id", "requisition_id", "invert"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
pfbudget.t.Nordigen(
|
type.Nordigen(
|
||||||
args["bank"][0],
|
args["bank"][0],
|
||||||
args["bank_id"][0] if args["bank_id"] else None,
|
args["bank_id"][0] if args["bank_id"] else None,
|
||||||
args["requisition_id"][0] if args["requisition_id"] else None,
|
args["requisition_id"][0] if args["requisition_id"] else None,
|
||||||
@ -81,7 +130,7 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
case pfbudget.Operation.NordigenMod:
|
case Operation.NordigenMod:
|
||||||
keys = {"bank", "bank_id", "requisition_id", "invert", "remove"}
|
keys = {"bank", "bank_id", "requisition_id", "invert", "remove"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -95,52 +144,50 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params = [param]
|
params = [param]
|
||||||
|
|
||||||
case pfbudget.Operation.NordigenDel:
|
case Operation.NordigenDel:
|
||||||
assert len(args["bank"]) > 0, "argparser ill defined"
|
assert len(args["bank"]) > 0, "argparser ill defined"
|
||||||
params = args["bank"]
|
params = args["bank"]
|
||||||
|
|
||||||
case pfbudget.Operation.NordigenCountryBanks:
|
case Operation.NordigenCountryBanks:
|
||||||
keys = {"country"}
|
keys = {"country"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [args["country"][0]]
|
params = [args["country"][0]]
|
||||||
|
|
||||||
case pfbudget.Operation.CategoryAdd:
|
case Operation.CategoryAdd:
|
||||||
keys = {"category", "group"}
|
keys = {"category", "group"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [type.Category(cat, args["group"]) for cat in args["category"]]
|
||||||
pfbudget.t.Category(cat, args["group"]) for cat in args["category"]
|
|
||||||
]
|
|
||||||
|
|
||||||
case pfbudget.Operation.CategoryUpdate:
|
case Operation.CategoryUpdate:
|
||||||
keys = {"category", "group"}
|
keys = {"category", "group"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [pfbudget.t.Category(cat) for cat in args["category"]]
|
params = [type.Category(cat) for cat in args["category"]]
|
||||||
params.append(args["group"])
|
params.append(args["group"])
|
||||||
|
|
||||||
case pfbudget.Operation.CategoryRemove:
|
case Operation.CategoryRemove:
|
||||||
assert "category" in args, "argparser ill defined"
|
assert "category" in args, "argparser ill defined"
|
||||||
params = [pfbudget.t.Category(cat) for cat in args["category"]]
|
params = [type.Category(cat) for cat in args["category"]]
|
||||||
|
|
||||||
case pfbudget.Operation.CategorySchedule:
|
case Operation.CategorySchedule:
|
||||||
keys = {"category", "period", "frequency"}
|
keys = {"category", "period", "frequency"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
pfbudget.t.CategorySchedule(
|
type.CategorySchedule(
|
||||||
cat, args["period"][0], args["frequency"][0], None
|
cat, args["period"][0], args["frequency"][0], None
|
||||||
)
|
)
|
||||||
for cat in args["category"]
|
for cat in args["category"]
|
||||||
]
|
]
|
||||||
|
|
||||||
case pfbudget.Operation.RuleAdd:
|
case Operation.RuleAdd:
|
||||||
keys = {"category", "date", "description", "bank", "min", "max"}
|
keys = {"category", "date", "description", "bank", "min", "max"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
pfbudget.t.CategoryRule(
|
type.CategoryRule(
|
||||||
args["date"][0] if args["date"] else None,
|
args["date"][0] if args["date"] else None,
|
||||||
args["description"][0] if args["description"] else None,
|
args["description"][0] if args["description"] else None,
|
||||||
args["regex"][0] if args["regex"] else None,
|
args["regex"][0] if args["regex"] else None,
|
||||||
@ -152,13 +199,13 @@ if __name__ == "__main__":
|
|||||||
for cat in args["category"]
|
for cat in args["category"]
|
||||||
]
|
]
|
||||||
|
|
||||||
case pfbudget.Operation.RuleRemove | pfbudget.Operation.TagRuleRemove:
|
case Operation.RuleRemove | Operation.TagRuleRemove:
|
||||||
keys = {"id"}
|
keys = {"id"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = args["id"]
|
params = args["id"]
|
||||||
|
|
||||||
case pfbudget.Operation.RuleModify:
|
case Operation.RuleModify:
|
||||||
keys = {
|
keys = {
|
||||||
"id",
|
"id",
|
||||||
"category",
|
"category",
|
||||||
@ -180,18 +227,18 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params.append(param)
|
params.append(param)
|
||||||
|
|
||||||
case pfbudget.Operation.TagAdd:
|
case Operation.TagAdd:
|
||||||
keys = {"tag"}
|
keys = {"tag"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [pfbudget.t.Tag(tag) for tag in args["tag"]]
|
params = [type.Tag(tag) for tag in args["tag"]]
|
||||||
|
|
||||||
case pfbudget.Operation.TagRuleAdd:
|
case Operation.TagRuleAdd:
|
||||||
keys = {"tag", "date", "description", "bank", "min", "max"}
|
keys = {"tag", "date", "description", "bank", "min", "max"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
pfbudget.t.TagRule(
|
type.TagRule(
|
||||||
args["date"][0] if args["date"] else None,
|
args["date"][0] if args["date"] else None,
|
||||||
args["description"][0] if args["description"] else None,
|
args["description"][0] if args["description"] else None,
|
||||||
args["regex"][0] if args["regex"] else None,
|
args["regex"][0] if args["regex"] else None,
|
||||||
@ -203,7 +250,7 @@ if __name__ == "__main__":
|
|||||||
for tag in args["tag"]
|
for tag in args["tag"]
|
||||||
]
|
]
|
||||||
|
|
||||||
case pfbudget.Operation.TagRuleModify:
|
case Operation.TagRuleModify:
|
||||||
keys = {"id", "tag", "date", "description", "bank", "min", "max", "remove"}
|
keys = {"id", "tag", "date", "description", "bank", "min", "max", "remove"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
@ -216,39 +263,37 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
params.append(param)
|
params.append(param)
|
||||||
|
|
||||||
case pfbudget.Operation.GroupAdd:
|
case Operation.GroupAdd:
|
||||||
assert "group" in args, "argparser ill defined"
|
assert "group" in args, "argparser ill defined"
|
||||||
params = [pfbudget.t.CategoryGroup(group) for group in args["group"]]
|
params = [type.CategoryGroup(group) for group in args["group"]]
|
||||||
|
|
||||||
case pfbudget.Operation.GroupRemove:
|
case Operation.GroupRemove:
|
||||||
assert "group" in args, "argparser ill defined"
|
assert "group" in args, "argparser ill defined"
|
||||||
params = [pfbudget.t.CategoryGroup(group) for group in args["group"]]
|
params = [type.CategoryGroup(group) for group in args["group"]]
|
||||||
|
|
||||||
case pfbudget.Operation.Forge | pfbudget.Operation.Dismantle:
|
case Operation.Forge | Operation.Dismantle:
|
||||||
keys = {"original", "links"}
|
keys = {"original", "links"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [type.Link(args["original"][0], link) for link in args["links"]]
|
||||||
pfbudget.t.Link(args["original"][0], link) for link in args["links"]
|
|
||||||
]
|
|
||||||
|
|
||||||
case (
|
case (
|
||||||
pfbudget.Operation.Export
|
Operation.Export
|
||||||
| pfbudget.Operation.Import
|
| Operation.Import
|
||||||
| pfbudget.Operation.ExportBanks
|
| Operation.ExportBanks
|
||||||
| pfbudget.Operation.ImportBanks
|
| Operation.ImportBanks
|
||||||
| pfbudget.Operation.ExportCategoryRules
|
| Operation.ExportCategoryRules
|
||||||
| pfbudget.Operation.ImportCategoryRules
|
| Operation.ImportCategoryRules
|
||||||
| pfbudget.Operation.ExportTagRules
|
| Operation.ExportTagRules
|
||||||
| pfbudget.Operation.ImportTagRules
|
| Operation.ImportTagRules
|
||||||
| pfbudget.Operation.ExportCategories
|
| Operation.ExportCategories
|
||||||
| pfbudget.Operation.ImportCategories
|
| Operation.ImportCategories
|
||||||
| pfbudget.Operation.ExportCategoryGroups
|
| Operation.ExportCategoryGroups
|
||||||
| pfbudget.Operation.ImportCategoryGroups
|
| Operation.ImportCategoryGroups
|
||||||
):
|
):
|
||||||
keys = {"file"}
|
keys = {"file"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = args["file"]
|
params = args["file"]
|
||||||
|
|
||||||
pfbudget.Manager(db, verbosity).action(op, params)
|
Manager(db, verbosity).action(op, params)
|
||||||
|
|||||||
@ -11,25 +11,13 @@ from pfbudget.db.model import AccountType, Period
|
|||||||
from pfbudget.db.sqlite import DatabaseClient
|
from pfbudget.db.sqlite import DatabaseClient
|
||||||
import pfbudget.reporting.graph
|
import pfbudget.reporting.graph
|
||||||
import pfbudget.reporting.report
|
import pfbudget.reporting.report
|
||||||
import pfbudget.utils
|
import pfbudget.utils.utils
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
DEFAULT_DB = os.environ.get("DEFAULT_DB")
|
DEFAULT_DB = os.environ.get("DEFAULT_DB")
|
||||||
|
|
||||||
|
|
||||||
class PfBudgetInitialized(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PfBudgetNotInitialized(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DataFileMissing(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def argparser() -> argparse.ArgumentParser:
|
def argparser() -> argparse.ArgumentParser:
|
||||||
universal = argparse.ArgumentParser(add_help=False)
|
universal = argparse.ArgumentParser(add_help=False)
|
||||||
universal.add_argument(
|
universal.add_argument(
|
||||||
@ -175,7 +163,7 @@ def graph(args):
|
|||||||
Args:
|
Args:
|
||||||
args (dict): argparse variables
|
args (dict): argparse variables
|
||||||
"""
|
"""
|
||||||
start, end = pfbudget.utils.parse_args_period(args)
|
start, end = pfbudget.utils.utils.parse_args_period(args)
|
||||||
if args.option == "monthly":
|
if args.option == "monthly":
|
||||||
pfbudget.reporting.graph.monthly(
|
pfbudget.reporting.graph.monthly(
|
||||||
DatabaseClient(args.database), vars(args), start, end
|
DatabaseClient(args.database), vars(args), start, end
|
||||||
@ -196,7 +184,7 @@ def report(args):
|
|||||||
Args:
|
Args:
|
||||||
args (dict): argparse variables
|
args (dict): argparse variables
|
||||||
"""
|
"""
|
||||||
start, end = pfbudget.utils.parse_args_period(args)
|
start, end = pfbudget.utils.utils.parse_args_period(args)
|
||||||
if args.option == "net":
|
if args.option == "net":
|
||||||
pfbudget.reporting.report.net(DatabaseClient(args.database), start, end)
|
pfbudget.reporting.report.net(DatabaseClient(args.database), start, end)
|
||||||
elif args.option == "detailed":
|
elif args.option == "detailed":
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class Operation(Enum):
|
|||||||
TagRuleModify = auto()
|
TagRuleModify = auto()
|
||||||
Forge = auto()
|
Forge = auto()
|
||||||
Dismantle = auto()
|
Dismantle = auto()
|
||||||
|
Split = auto()
|
||||||
BankAdd = auto()
|
BankAdd = auto()
|
||||||
BankMod = auto()
|
BankMod = auto()
|
||||||
BankDel = auto()
|
BankDel = auto()
|
||||||
|
|||||||
@ -33,21 +33,6 @@ class Categorizer:
|
|||||||
self._rule_based_categories(transactions, categories)
|
self._rule_based_categories(transactions, categories)
|
||||||
self._rule_based_tags(transactions, tags)
|
self._rule_based_tags(transactions, tags)
|
||||||
|
|
||||||
def manual(
|
|
||||||
self,
|
|
||||||
transactions: Sequence[t.Transaction],
|
|
||||||
categories: Sequence[t.Category],
|
|
||||||
tags: Sequence[t.Tag],
|
|
||||||
):
|
|
||||||
"""Manual categorization input
|
|
||||||
|
|
||||||
Args:
|
|
||||||
transactions (Sequence[Transaction]): uncategorized transactions
|
|
||||||
categories (Sequence[Category]): available categories
|
|
||||||
tags (Sequence[Tag]): currently available tags
|
|
||||||
"""
|
|
||||||
self._manual(transactions)
|
|
||||||
|
|
||||||
@Timer(name="nullify")
|
@Timer(name="nullify")
|
||||||
def _nullify(self, transactions: Sequence[t.BankTransaction]):
|
def _nullify(self, transactions: Sequence[t.BankTransaction]):
|
||||||
count = 0
|
count = 0
|
||||||
@ -151,21 +136,3 @@ class Categorizer:
|
|||||||
|
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
print(f"{v}: {k}")
|
print(f"{v}: {k}")
|
||||||
|
|
||||||
def _manual(self, transactions: Sequence[t.Transaction]):
|
|
||||||
uncategorized = [t for t in transactions if not t.category]
|
|
||||||
print(f"{len(uncategorized)} transactions left to categorize")
|
|
||||||
|
|
||||||
for transaction in uncategorized:
|
|
||||||
while True:
|
|
||||||
category = input(f"{transaction} category: ")
|
|
||||||
if category == "quit":
|
|
||||||
return
|
|
||||||
if not category:
|
|
||||||
print("{category} doesn't exist")
|
|
||||||
continue
|
|
||||||
transaction.category = t.TransactionCategory(
|
|
||||||
category, t.CategorySelector(t.Selector_T.manual)
|
|
||||||
)
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from pfbudget.db.model import (
|
|||||||
MoneyTransaction,
|
MoneyTransaction,
|
||||||
Nordigen,
|
Nordigen,
|
||||||
Rule,
|
Rule,
|
||||||
|
SplitTransaction,
|
||||||
Tag,
|
Tag,
|
||||||
TagRule,
|
TagRule,
|
||||||
Transaction,
|
Transaction,
|
||||||
@ -87,15 +88,6 @@ class Manager:
|
|||||||
tags = session.get(Tag)
|
tags = session.get(Tag)
|
||||||
Categorizer().rules(uncategorized, categories, tags)
|
Categorizer().rules(uncategorized, categories, tags)
|
||||||
|
|
||||||
case Operation.ManualCategorization:
|
|
||||||
with self.db.session() as session:
|
|
||||||
uncategorized = session.get(
|
|
||||||
Transaction, ~Transaction.category.has()
|
|
||||||
)
|
|
||||||
categories = session.get(Category)
|
|
||||||
tags = session.get(Tag)
|
|
||||||
Categorizer().manual(uncategorized, categories, tags)
|
|
||||||
|
|
||||||
case Operation.BankMod:
|
case Operation.BankMod:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
session.update(Bank, params)
|
session.update(Bank, params)
|
||||||
@ -180,6 +172,32 @@ class Manager:
|
|||||||
links = [link.link for link in params]
|
links = [link.link for link in params]
|
||||||
session.remove_links(original, links)
|
session.remove_links(original, links)
|
||||||
|
|
||||||
|
case Operation.Split:
|
||||||
|
if len(params) < 1 and not all(
|
||||||
|
isinstance(p, Transaction) for p in params
|
||||||
|
):
|
||||||
|
raise TypeError(f"{params} are not transactions")
|
||||||
|
|
||||||
|
# t -> t1, t2, t3; t.value == Σti.value
|
||||||
|
original: Transaction = params[0]
|
||||||
|
if not original.amount == sum(t.amount for t in params[1:]):
|
||||||
|
raise ValueError(
|
||||||
|
f"{original.amount}€ != {sum(v for v, _ in params[1:])}€"
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.db.session() as session:
|
||||||
|
originals = session.get(Transaction, Transaction.id, [original.id])
|
||||||
|
assert len(originals) == 1, ">1 transactions matched {original.id}!"
|
||||||
|
|
||||||
|
originals[0].split = True
|
||||||
|
transactions = [
|
||||||
|
SplitTransaction(
|
||||||
|
originals[0].date, t.description, t.amount, originals[0].id
|
||||||
|
)
|
||||||
|
for t in params[1:]
|
||||||
|
]
|
||||||
|
session.add(transactions)
|
||||||
|
|
||||||
case Operation.Export:
|
case Operation.Export:
|
||||||
with self.db.session() as session:
|
with self.db.session() as session:
|
||||||
self.dump(params[0], sorted(session.get(Transaction)))
|
self.dump(params[0], sorted(session.get(Transaction)))
|
||||||
@ -194,12 +212,11 @@ class Manager:
|
|||||||
row["description"],
|
row["description"],
|
||||||
row["amount"],
|
row["amount"],
|
||||||
row["bank"],
|
row["bank"],
|
||||||
False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case "money":
|
case "money":
|
||||||
transaction = MoneyTransaction(
|
transaction = MoneyTransaction(
|
||||||
row["date"], row["description"], row["amount"], False
|
row["date"], row["description"], row["amount"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO case "split" how to match to original transaction?? also save ids?
|
# TODO case "split" how to match to original transaction?? also save ids?
|
||||||
|
|||||||
@ -91,6 +91,8 @@ class Transaction(Base, Export):
|
|||||||
description: Mapped[Optional[str]]
|
description: Mapped[Optional[str]]
|
||||||
amount: Mapped[money]
|
amount: Mapped[money]
|
||||||
|
|
||||||
|
split: Mapped[bool] = mapped_column(init=False)
|
||||||
|
|
||||||
type: Mapped[str] = mapped_column(init=False)
|
type: Mapped[str] = mapped_column(init=False)
|
||||||
|
|
||||||
category: Mapped[Optional[TransactionCategory]] = relationship(init=False)
|
category: Mapped[Optional[TransactionCategory]] = relationship(init=False)
|
||||||
@ -105,6 +107,7 @@ class Transaction(Base, Export):
|
|||||||
date=self.date,
|
date=self.date,
|
||||||
description=self.description,
|
description=self.description,
|
||||||
amount=self.amount,
|
amount=self.amount,
|
||||||
|
split=self.split,
|
||||||
type=self.type,
|
type=self.type,
|
||||||
category=self.category.format if self.category else None,
|
category=self.category.format if self.category else None,
|
||||||
# TODO note
|
# TODO note
|
||||||
@ -122,7 +125,6 @@ idfk = Annotated[
|
|||||||
|
|
||||||
class BankTransaction(Transaction):
|
class BankTransaction(Transaction):
|
||||||
bank: Mapped[bankfk] = mapped_column(nullable=True)
|
bank: Mapped[bankfk] = mapped_column(nullable=True)
|
||||||
split: Mapped[bool] = mapped_column(use_existing_column=True, nullable=True)
|
|
||||||
|
|
||||||
__mapper_args__ = {"polymorphic_identity": "bank", "polymorphic_load": "inline"}
|
__mapper_args__ = {"polymorphic_identity": "bank", "polymorphic_load": "inline"}
|
||||||
|
|
||||||
@ -132,8 +134,6 @@ class BankTransaction(Transaction):
|
|||||||
|
|
||||||
|
|
||||||
class MoneyTransaction(Transaction):
|
class MoneyTransaction(Transaction):
|
||||||
split: Mapped[bool] = mapped_column(use_existing_column=True, nullable=True)
|
|
||||||
|
|
||||||
__mapper_args__ = {"polymorphic_identity": "money"}
|
__mapper_args__ = {"polymorphic_identity": "money"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import pfbudget.db.model as t
|
import pfbudget.db.model as t
|
||||||
import pfbudget.utils as utils
|
from pfbudget.utils.converters import convert
|
||||||
|
|
||||||
from .input import Input
|
from .input import Input
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ class NordigenInput(Input):
|
|||||||
json.dump(downloaded, f)
|
json.dump(downloaded, f)
|
||||||
|
|
||||||
converted = [
|
converted = [
|
||||||
utils.convert(t, bank) for t in downloaded["transactions"]["booked"]
|
convert(t, bank) for t in downloaded["transactions"]["booked"]
|
||||||
]
|
]
|
||||||
|
|
||||||
transactions.extend(
|
transactions.extend(
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
from .converters import convert
|
|
||||||
from .utils import *
|
|
||||||
Loading…
x
Reference in New Issue
Block a user