Adds links between transactions
Sometimes transctions are directly related to one another w/o being of an equal value, e.g. someone pays for the meal w/ the CC, and everyone pays him/her. Clear leftover __repr__ methods in the model classes, the database decorator will create those automatically.
This commit is contained in:
parent
f20cf685ad
commit
1d256d7def
46
alembic/versions/8cc9870b0d74_links.py
Normal file
46
alembic/versions/8cc9870b0d74_links.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""Links
|
||||
|
||||
Revision ID: 8cc9870b0d74
|
||||
Revises: a910e1b2214d
|
||||
Create Date: 2022-12-19 22:10:25.136479+00:00
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8cc9870b0d74"
|
||||
down_revision = "a910e1b2214d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"links",
|
||||
sa.Column("original", sa.BigInteger(), nullable=False),
|
||||
sa.Column("link", sa.BigInteger(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
["link"],
|
||||
["transactions.originals.id"],
|
||||
name=op.f("fk_links_link_originals"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["original"],
|
||||
["transactions.originals.id"],
|
||||
name=op.f("fk_links_original_originals"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("original", "link", name=op.f("pk_links")),
|
||||
schema="transactions",
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("links", schema="transactions")
|
||||
# ### end Alembic commands ###
|
||||
@ -149,4 +149,10 @@ if __name__ == "__main__":
|
||||
assert "group" in args, "argparser ill defined"
|
||||
params = [pfbudget.types.CategoryGroup(group) for group in args["group"]]
|
||||
|
||||
case pfbudget.Operation.Forge | pfbudget.Operation.Dismantle:
|
||||
assert args.keys() >= {"original", "links"}, "argparser ill defined"
|
||||
params = [
|
||||
pfbudget.types.Link(args["original"][0], link) for link in args["links"]
|
||||
]
|
||||
|
||||
pfbudget.Manager(db, verbosity, args).action(op, params)
|
||||
|
||||
@ -5,9 +5,7 @@ import decimal
|
||||
import re
|
||||
|
||||
from pfbudget.common.types import Operation
|
||||
from pfbudget.core.categories import categorize_data
|
||||
from pfbudget.db.model import Period
|
||||
from pfbudget.input.json import JsonParser
|
||||
from pfbudget.input.nordigen import NordigenInput
|
||||
from pfbudget.db.sqlite import DatabaseClient
|
||||
import pfbudget.reporting.graph
|
||||
@ -253,6 +251,9 @@ def argparser() -> argparse.ArgumentParser:
|
||||
# Tag
|
||||
tags(subparsers.add_parser("tag", parents=[universal]), universal)
|
||||
|
||||
# Link
|
||||
link(subparsers.add_parser("link"))
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
@ -420,6 +421,20 @@ def rules(parser: argparse.ArgumentParser):
|
||||
parser.add_argument("--max", nargs=1, type=decimal.Decimal)
|
||||
|
||||
|
||||
def link(parser: argparse.ArgumentParser):
|
||||
commands = parser.add_subparsers(required=True)
|
||||
|
||||
forge = commands.add_parser("forge")
|
||||
forge.set_defaults(op=Operation.Forge)
|
||||
forge.add_argument("original", nargs=1, type=int)
|
||||
forge.add_argument("links", nargs="+", type=int)
|
||||
|
||||
dismantle = commands.add_parser("dismantle")
|
||||
dismantle.set_defaults(op=Operation.Dismantle)
|
||||
dismantle.add_argument("original", nargs=1, type=int)
|
||||
dismantle.add_argument("links", nargs="+", type=int)
|
||||
|
||||
|
||||
def run():
|
||||
args = vars(argparser().parse_args())
|
||||
assert "op" in args, "No operation selected"
|
||||
|
||||
@ -27,6 +27,8 @@ class Operation(Enum):
|
||||
TagRuleAdd = auto()
|
||||
TagRuleRemove = auto()
|
||||
TagRuleModify = auto()
|
||||
Forge = auto()
|
||||
Dismantle = auto()
|
||||
|
||||
|
||||
class TransactionError(Exception):
|
||||
|
||||
@ -105,6 +105,16 @@ class Manager:
|
||||
with self.db.session() as session:
|
||||
session.remove_by_name(CategoryGroup, params)
|
||||
|
||||
case Operation.Forge:
|
||||
with self.db.session() as session:
|
||||
session.add(params)
|
||||
|
||||
case Operation.Dismantle:
|
||||
with self.db.session() as session:
|
||||
original = params[0].original
|
||||
links = [link.link for link in params]
|
||||
session.remove_links(original, links)
|
||||
|
||||
# def init(self):
|
||||
# client = DatabaseClient(self.__db)
|
||||
# client.init()
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from copy import deepcopy
|
||||
from dataclasses import asdict
|
||||
from sqlalchemy import create_engine, delete, select, update
|
||||
from sqlalchemy.dialects.postgresql import insert
|
||||
@ -10,6 +9,7 @@ from pfbudget.db.model import (
|
||||
CategoryGroup,
|
||||
CategoryRule,
|
||||
CategorySchedule,
|
||||
Link,
|
||||
Tag,
|
||||
TagRule,
|
||||
Transaction,
|
||||
@ -83,12 +83,7 @@ class DbClient:
|
||||
def commit(self):
|
||||
self.__session.commit()
|
||||
|
||||
def add(
|
||||
self,
|
||||
rows: list[
|
||||
Category | CategoryGroup | CategoryRule | Tag | TagRule | Transaction
|
||||
],
|
||||
):
|
||||
def add(self, rows: list):
|
||||
self.__session.add_all(rows)
|
||||
|
||||
def remove_by_name(self, type: Category | Tag | Transaction, rows: list):
|
||||
@ -122,6 +117,10 @@ class DbClient:
|
||||
def updaterules(self, rules: list[dict]):
|
||||
self.__session.execute(update(CategoryRule), rules)
|
||||
|
||||
def remove_links(self, original, links: list):
|
||||
stmt = delete(Link).where(Link.original == original, Link.link.in_(link for link in links))
|
||||
self.__session.execute(stmt)
|
||||
|
||||
def uncategorized(self) -> list[Transaction]:
|
||||
stmt = select(Transaction).where(~Transaction.category.has())
|
||||
return self.__session.scalars(stmt).all()
|
||||
|
||||
@ -63,9 +63,6 @@ class Bank(Base):
|
||||
back_populates="bank", lazy="joined"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Bank(name={self.name}, BIC={self.BIC}, type={self.type}, nordigen={self.nordigen})"
|
||||
|
||||
|
||||
bankfk = Annotated[str, mapped_column(Text, ForeignKey(Bank.name))]
|
||||
|
||||
@ -83,13 +80,9 @@ class Transaction(Base):
|
||||
amount: Mapped[money]
|
||||
|
||||
category: Mapped[Optional[TransactionCategory]] = relationship()
|
||||
)
|
||||
note: Mapped[Optional[Note]] = relationship(back_populates="original")
|
||||
tags: Mapped[Optional[set[TransactionTag]]] = relationship()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Transaction(date={self.date}, description={self.description}, bank={self.bank}, amount={self.amount}, category={self.category})"
|
||||
|
||||
|
||||
idfk = Annotated[
|
||||
int, mapped_column(BigInteger, ForeignKey(Transaction.id, ondelete="CASCADE"))
|
||||
@ -158,9 +151,6 @@ class Nordigen(Base):
|
||||
|
||||
bank: Mapped[Bank] = relationship(back_populates="nordigen")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"(bank_id={self.bank_id}, requisition_id={self.requisition_id}, invert={self.invert})"
|
||||
|
||||
|
||||
class Tag(Base):
|
||||
__tablename__ = "tags_available"
|
||||
@ -229,8 +219,12 @@ class CategorySchedule(Base):
|
||||
|
||||
category: Mapped[Category] = relationship(back_populates="schedule")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.name} schedule=Schedule(period={self.period}, multiplier={self.period_multiplier}, amount={self.amount})"
|
||||
|
||||
class Link(Base):
|
||||
__tablename__ = "links"
|
||||
|
||||
original: Mapped[idfk] = mapped_column(primary_key=True)
|
||||
link: Mapped[idfk] = mapped_column(primary_key=True)
|
||||
|
||||
|
||||
class Rule:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user