diff --git a/alembic/versions/2d0891f1be11_available_categories_and_rules.py b/alembic/versions/2d0891f1be11_available_categories_and_rules.py new file mode 100644 index 0000000..2aa1168 --- /dev/null +++ b/alembic/versions/2d0891f1be11_available_categories_and_rules.py @@ -0,0 +1,49 @@ +"""Available categories and rules + +Revision ID: 2d0891f1be11 +Revises: 287fe9e6682a +Create Date: 2022-12-04 11:15:22.758487+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "2d0891f1be11" +down_revision = "287fe9e6682a" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "categories_available", + sa.Column("name", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("name", name=op.f("pk_categories_available")), + schema="transactions", + ) + op.create_table( + "categories_rules", + sa.Column("name", sa.String(), nullable=False), + sa.Column("rule", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["name"], + ["transactions.categories_available.name"], + name=op.f("fk_categories_rules_name_categories_available"), + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("name", "rule", name=op.f("pk_categories_rules")), + schema="transactions", + ) + op.alter_column("categorized", "category", new_column_name="name", schema="transactions") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column("categorized", "name", new_column_name="category", schema="transactions") + op.drop_table("categories_rules", schema="transactions") + op.drop_table("categories_available", schema="transactions") + # ### end Alembic commands ### diff --git a/pfbudget/db/model.py b/pfbudget/db/model.py index 16837c5..daed50d 100644 --- a/pfbudget/db/model.py +++ b/pfbudget/db/model.py @@ -75,7 +75,7 @@ class Transaction(Base): bank: Mapped[bankfk] amount: Mapped[money] - category: Mapped[Optional[Category]] = relationship( + category: Mapped[Optional[TransactionCategory]] = relationship( back_populates="original", lazy="joined" ) note: Mapped[Optional[Note]] = relationship(back_populates="original") @@ -92,16 +92,17 @@ idfk = Annotated[ ] -class Category(Base): +class TransactionCategory(Base): __tablename__ = "categorized" id: Mapped[idfk] = mapped_column(primary_key=True) - category: Mapped[str] + name: Mapped[str] original: Mapped[Transaction] = relationship(back_populates="category") + category: Mapped[AvailableCategory] = relationship(back_populates="category") def __repr__(self) -> str: - return f"Category({self.category})" + return f"Category({self.name})" class Note(Base): @@ -134,3 +135,25 @@ class Tag(Base): tag: Mapped[str] = mapped_column(primary_key=True) original: Mapped[Transaction] = relationship(back_populates="tags") + + +class AvailableCategory(Base): + __tablename__ = "categories_available" + + name: Mapped[str] = mapped_column(primary_key=True) + + rules: Mapped[Optional[set[CategoryRule]]] = relationship( + back_populates="original", cascade="all, delete-orphan", passive_deletes=True + ) + category: Mapped[TransactionCategory] = relationship(back_populates="category") + + +class CategoryRule(Base): + __tablename__ = "categories_rules" + + name: Mapped[str] = mapped_column( + ForeignKey(AvailableCategory.name, ondelete="CASCADE"), primary_key=True + ) + rule: Mapped[str] = mapped_column(primary_key=True) + + category: Mapped[AvailableCategory] = relationship(back_populates="rules")