diff --git a/alembic/versions/6b293f78cc97_rule_inheritance.py b/alembic/versions/6b293f78cc97_rule_inheritance.py new file mode 100644 index 0000000..e1e259d --- /dev/null +++ b/alembic/versions/6b293f78cc97_rule_inheritance.py @@ -0,0 +1,152 @@ +"""Rule inheritance + +Revision ID: 6b293f78cc97 +Revises: 37d80de801a7 +Create Date: 2023-01-22 20:05:32.887092+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "6b293f78cc97" +down_revision = "37d80de801a7" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "rules", + sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), + sa.Column("date", sa.Date(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.Column("regex", sa.String(), nullable=True), + sa.Column("bank", sa.String(), nullable=True), + sa.Column("min", sa.Numeric(precision=16, scale=2), nullable=True), + sa.Column("max", sa.Numeric(precision=16, scale=2), nullable=True), + sa.Column("type", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id", name=op.f("pk_rules")), + schema="transactions", + ) + op.create_foreign_key( + op.f("fk_categories_rules_id_rules"), + "categories_rules", + "rules", + ["id"], + ["id"], + source_schema="transactions", + referent_schema="transactions", + ondelete="CASCADE", + ) + op.drop_column("categories_rules", "bank", schema="transactions") + op.drop_column("categories_rules", "min", schema="transactions") + op.drop_column("categories_rules", "date", schema="transactions") + op.drop_column("categories_rules", "regex", schema="transactions") + op.drop_column("categories_rules", "description", schema="transactions") + op.drop_column("categories_rules", "max", schema="transactions") + op.create_foreign_key( + op.f("fk_tag_rules_id_rules"), + "tag_rules", + "rules", + ["id"], + ["id"], + source_schema="transactions", + referent_schema="transactions", + ondelete="CASCADE", + ) + op.drop_column("tag_rules", "bank", schema="transactions") + op.drop_column("tag_rules", "min", schema="transactions") + op.drop_column("tag_rules", "date", schema="transactions") + op.drop_column("tag_rules", "regex", schema="transactions") + op.drop_column("tag_rules", "description", schema="transactions") + op.drop_column("tag_rules", "max", schema="transactions") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "tag_rules", + sa.Column( + "max", sa.NUMERIC(precision=16, scale=2), autoincrement=False, nullable=True + ), + schema="transactions", + ) + op.add_column( + "tag_rules", + sa.Column("description", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "tag_rules", + sa.Column("regex", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "tag_rules", + sa.Column("date", sa.DATE(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "tag_rules", + sa.Column( + "min", sa.NUMERIC(precision=16, scale=2), autoincrement=False, nullable=True + ), + schema="transactions", + ) + op.add_column( + "tag_rules", + sa.Column("bank", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.drop_constraint( + op.f("fk_tag_rules_id_rules"), + "tag_rules", + schema="transactions", + type_="foreignkey", + ) + op.add_column( + "categories_rules", + sa.Column( + "max", sa.NUMERIC(precision=16, scale=2), autoincrement=False, nullable=True + ), + schema="transactions", + ) + op.add_column( + "categories_rules", + sa.Column("description", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "categories_rules", + sa.Column("regex", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "categories_rules", + sa.Column("date", sa.DATE(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.add_column( + "categories_rules", + sa.Column( + "min", sa.NUMERIC(precision=16, scale=2), autoincrement=False, nullable=True + ), + schema="transactions", + ) + op.add_column( + "categories_rules", + sa.Column("bank", sa.VARCHAR(), autoincrement=False, nullable=True), + schema="transactions", + ) + op.drop_constraint( + op.f("fk_categories_rules_id_rules"), + "categories_rules", + schema="transactions", + type_="foreignkey", + ) + op.drop_table("rules", schema="transactions") + # ### end Alembic commands ### diff --git a/pfbudget/core/manager.py b/pfbudget/core/manager.py index 0c515b5..20e9c68 100644 --- a/pfbudget/core/manager.py +++ b/pfbudget/core/manager.py @@ -223,8 +223,13 @@ class Manager: case Operation.ImportCategoryRules: rules = [CategoryRule(**row) for row in self.load(params[0])] - with self.db.session() as session: - session.add(rules) + if ( + len(rules) > 0 + and input(f"{rules[:5]}\nDoes the import seem correct? (y/n)") + == "y" + ): + with self.db.session() as session: + session.add(rules) case Operation.ExportTagRules: with self.db.session() as session: @@ -233,8 +238,13 @@ class Manager: case Operation.ImportTagRules: rules = [TagRule(**row) for row in self.load(params[0])] - with self.db.session() as session: - session.add(rules) + if ( + len(rules) > 0 + and input(f"{rules[:5]}\nDoes the import seem correct? (y/n)") + == "y" + ): + with self.db.session() as session: + session.add(rules) # def init(self): # client = DatabaseClient(self.__db) diff --git a/pfbudget/db/model.py b/pfbudget/db/model.py index 7e129b5..7ed3dec 100644 --- a/pfbudget/db/model.py +++ b/pfbudget/db/model.py @@ -283,7 +283,10 @@ class Link(Base): link: Mapped[idfk] = mapped_column(primary_key=True) -class Rule(Export): +class Rule(Base, Export): + __tablename__ = "rules" + + id: Mapped[idpk] = mapped_column(init=False) date: Mapped[Optional[dt.date]] description: Mapped[Optional[str]] regex: Mapped[Optional[str]] @@ -291,6 +294,13 @@ class Rule(Export): min: Mapped[Optional[money]] max: Mapped[Optional[money]] + type: Mapped[str] = mapped_column(init=False) + + __mapper_args__ = { + "polymorphic_identity": "rule", + "polymorphic_on": "type", + } + def matches(self, transaction: BankTransaction) -> bool: if ( (self.date and self.date < transaction.date) @@ -322,15 +332,25 @@ class Rule(Export): bank=self.bank, min=self.min, max=self.max, + type=self.type, ) -class CategoryRule(Base, Rule): +class CategoryRule(Rule): __tablename__ = "categories_rules" - id: Mapped[idpk] = mapped_column(init=False) + id: Mapped[int] = mapped_column( + BigInteger, + ForeignKey(Rule.id, ondelete="CASCADE"), + primary_key=True, + init=False, + ) name: Mapped[catfk] + __mapper_args__ = { + "polymorphic_identity": "category_rule", + } + @property def format(self) -> dict[str, Any]: return super().format | dict(name=self.name) @@ -339,12 +359,21 @@ class CategoryRule(Base, Rule): return hash(self.id) -class TagRule(Base, Rule): +class TagRule(Rule): __tablename__ = "tag_rules" - id: Mapped[idpk] = mapped_column(init=False) + id: Mapped[int] = mapped_column( + BigInteger, + ForeignKey(Rule.id, ondelete="CASCADE"), + primary_key=True, + init=False, + ) tag: Mapped[str] = mapped_column(ForeignKey(Tag.name, ondelete="CASCADE")) + __mapper_args__ = { + "polymorphic_identity": "tag_rule", + } + @property def format(self) -> dict[str, Any]: return super().format | dict(tag=self.tag)