Add start date rule
Rename date to end.
This commit is contained in:
parent
23eb2c80bd
commit
f7df033d58
32
alembic/versions/952de57a3c43_start_end_date_rule.py
Normal file
32
alembic/versions/952de57a3c43_start_end_date_rule.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Start/End date rule
|
||||||
|
|
||||||
|
Revision ID: 952de57a3c43
|
||||||
|
Revises: 18572111d9ff
|
||||||
|
Create Date: 2023-02-06 21:57:57.545327+00:00
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "952de57a3c43"
|
||||||
|
down_revision = "18572111d9ff"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column(
|
||||||
|
"rules", sa.Column("start", sa.Date(), nullable=True), schema="transactions"
|
||||||
|
)
|
||||||
|
op.alter_column("rules", column_name="date", new_column_name="end", schema="transactions")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column("rules", column_name="end", new_column_name="date", schema="transactions")
|
||||||
|
op.drop_column("rules", "start", schema="transactions")
|
||||||
|
# ### end Alembic commands ###
|
||||||
@ -1,6 +1,3 @@
|
|||||||
from decimal import Decimal
|
|
||||||
from typing import Sequence
|
|
||||||
|
|
||||||
from pfbudget.cli.argparser import argparser
|
from pfbudget.cli.argparser import argparser
|
||||||
from pfbudget.cli.interactive import Interactive
|
from pfbudget.cli.interactive import Interactive
|
||||||
from pfbudget.common.types import Operation
|
from pfbudget.common.types import Operation
|
||||||
@ -146,12 +143,13 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
|
|
||||||
case Operation.RuleAdd:
|
case Operation.RuleAdd:
|
||||||
keys = {"category", "date", "description", "bank", "min", "max"}
|
keys = {"category", "start", "end", "description", "regex", "bank", "min", "max"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
type.CategoryRule(
|
type.CategoryRule(
|
||||||
args["date"][0] if args["date"] else None,
|
args["start"][0] if args["start"] else None,
|
||||||
|
args["end"][0] if args["end"] 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,
|
||||||
args["bank"][0] if args["bank"] else None,
|
args["bank"][0] if args["bank"] else None,
|
||||||
@ -197,12 +195,13 @@ if __name__ == "__main__":
|
|||||||
params = [type.Tag(tag) for tag in args["tag"]]
|
params = [type.Tag(tag) for tag in args["tag"]]
|
||||||
|
|
||||||
case Operation.TagRuleAdd:
|
case Operation.TagRuleAdd:
|
||||||
keys = {"tag", "date", "description", "bank", "min", "max"}
|
keys = {"tag", "start", "end", "description", "regex", "bank", "min", "max"}
|
||||||
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
assert args.keys() >= keys, f"missing {args.keys() - keys}"
|
||||||
|
|
||||||
params = [
|
params = [
|
||||||
type.TagRule(
|
type.TagRule(
|
||||||
args["date"][0] if args["date"] else None,
|
args["start"][0] if args["start"] else None,
|
||||||
|
args["end"][0] if args["end"] 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,
|
||||||
args["bank"][0] if args["bank"] else None,
|
args["bank"][0] if args["bank"] else None,
|
||||||
|
|||||||
@ -374,7 +374,8 @@ def tag_rule(parser: argparse.ArgumentParser):
|
|||||||
|
|
||||||
|
|
||||||
def rules(parser: argparse.ArgumentParser):
|
def rules(parser: argparse.ArgumentParser):
|
||||||
parser.add_argument("--date", nargs=1, type=dt.date.fromisoformat)
|
parser.add_argument("--start", nargs=1, type=dt.date.fromisoformat)
|
||||||
|
parser.add_argument("--end", nargs=1, type=dt.date.fromisoformat)
|
||||||
parser.add_argument("--description", nargs=1, type=str)
|
parser.add_argument("--description", nargs=1, type=str)
|
||||||
parser.add_argument("--regex", nargs=1, type=str)
|
parser.add_argument("--regex", nargs=1, type=str)
|
||||||
parser.add_argument("--bank", nargs=1, type=str)
|
parser.add_argument("--bank", nargs=1, type=str)
|
||||||
|
|||||||
@ -335,7 +335,8 @@ class Rule(Base, Export):
|
|||||||
__tablename__ = "rules"
|
__tablename__ = "rules"
|
||||||
|
|
||||||
id: Mapped[idpk] = mapped_column(init=False)
|
id: Mapped[idpk] = mapped_column(init=False)
|
||||||
date: Mapped[Optional[dt.date]]
|
start: Mapped[Optional[dt.date]]
|
||||||
|
end: Mapped[Optional[dt.date]]
|
||||||
description: Mapped[Optional[str]]
|
description: Mapped[Optional[str]]
|
||||||
regex: Mapped[Optional[str]]
|
regex: Mapped[Optional[str]]
|
||||||
bank: Mapped[Optional[str]]
|
bank: Mapped[Optional[str]]
|
||||||
@ -349,32 +350,34 @@ class Rule(Base, Export):
|
|||||||
"polymorphic_on": "type",
|
"polymorphic_on": "type",
|
||||||
}
|
}
|
||||||
|
|
||||||
def matches(self, transaction: BankTransaction) -> bool:
|
def matches(self, t: BankTransaction) -> bool:
|
||||||
if (
|
valid = None
|
||||||
(self.date and self.date < transaction.date)
|
if self.regex:
|
||||||
or (
|
valid = re.compile(self.regex, re.IGNORECASE)
|
||||||
self.description
|
|
||||||
and transaction.description
|
ops = (
|
||||||
and self.description not in transaction.description
|
Rule.exists(self.start, lambda r: r < t.date),
|
||||||
)
|
Rule.exists(self.end, lambda r: r > t.date),
|
||||||
or (
|
Rule.exists(self.description, lambda r: r == t.description),
|
||||||
self.regex
|
Rule.exists(
|
||||||
and transaction.description
|
valid,
|
||||||
and not re.compile(self.regex, re.IGNORECASE).search(
|
lambda r: r.search(t.description) if t.description else False,
|
||||||
transaction.description
|
),
|
||||||
)
|
Rule.exists(self.bank, lambda r: r == t.bank),
|
||||||
)
|
Rule.exists(self.min, lambda r: r < t.amount),
|
||||||
or (self.bank and self.bank != transaction.bank)
|
Rule.exists(self.max, lambda r: r > t.amount),
|
||||||
or (self.min and self.min > transaction.amount)
|
)
|
||||||
or (self.max and self.max < transaction.amount)
|
|
||||||
):
|
if all(ops):
|
||||||
return False
|
return True
|
||||||
return True
|
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def format(self) -> dict[str, Any]:
|
def format(self) -> dict[str, Any]:
|
||||||
return dict(
|
return dict(
|
||||||
date=self.date,
|
start=self.start,
|
||||||
|
end=self.end,
|
||||||
description=self.description,
|
description=self.description,
|
||||||
regex=self.regex,
|
regex=self.regex,
|
||||||
bank=self.bank,
|
bank=self.bank,
|
||||||
@ -383,6 +386,10 @@ class Rule(Base, Export):
|
|||||||
type=self.type,
|
type=self.type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exists(r, op) -> bool:
|
||||||
|
return op(r) if r is not None else True
|
||||||
|
|
||||||
|
|
||||||
class CategoryRule(Rule):
|
class CategoryRule(Rule):
|
||||||
__table_args__ = {"schema": "category"}
|
__table_args__ = {"schema": "category"}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user