From eb7e47bcbec205838673ba94c42d0e507aaa9e7c Mon Sep 17 00:00:00 2001 From: archeious Date: Fri, 17 Apr 2026 13:03:53 -0600 Subject: [PATCH] feat(db): add MonthState enum and lifecycle columns state defaults to 'planning' (server default plus SQLAlchemy default). activated_at and closed_at are nullable timestamps that record when the month crossed each boundary. Alembic batch_alter_table handles the SQLite rewrite. MonthState is a Python string enum mapped to a non-native VARCHAR(16). Refs #15 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../a4ec4f8f6e9f_add_month_lifecycle_state.py | 40 +++++++++++++++++++ src/quartermaster/models.py | 18 +++++++++ 2 files changed, 58 insertions(+) create mode 100644 alembic/versions/a4ec4f8f6e9f_add_month_lifecycle_state.py diff --git a/alembic/versions/a4ec4f8f6e9f_add_month_lifecycle_state.py b/alembic/versions/a4ec4f8f6e9f_add_month_lifecycle_state.py new file mode 100644 index 0000000..fd89b7c --- /dev/null +++ b/alembic/versions/a4ec4f8f6e9f_add_month_lifecycle_state.py @@ -0,0 +1,40 @@ +"""add month lifecycle state + +Revision ID: a4ec4f8f6e9f +Revises: ec804bdf366d +Create Date: 2026-04-17 12:59:25.811354 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'a4ec4f8f6e9f' +down_revision: Union[str, Sequence[str], None] = 'ec804bdf366d' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('month', schema=None) as batch_op: + batch_op.add_column(sa.Column('state', sa.Enum('planning', 'active', 'closed', name='monthstate', native_enum=False, length=16), server_default='planning', nullable=False)) + batch_op.add_column(sa.Column('activated_at', sa.DateTime(timezone=True), nullable=True)) + batch_op.add_column(sa.Column('closed_at', sa.DateTime(timezone=True), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('month', schema=None) as batch_op: + batch_op.drop_column('closed_at') + batch_op.drop_column('activated_at') + batch_op.drop_column('state') + + # ### end Alembic commands ### diff --git a/src/quartermaster/models.py b/src/quartermaster/models.py index c2edacb..6d8f25a 100644 --- a/src/quartermaster/models.py +++ b/src/quartermaster/models.py @@ -27,6 +27,12 @@ class Section(str, enum.Enum): sinking_fund = "sinking_fund" +class MonthState(str, enum.Enum): + planning = "planning" + active = "active" + closed = "closed" + + SECTION_LABELS: dict[Section, str] = { Section.income: "Incomes", Section.fixed_bill: "Fixed Amount Bills", @@ -86,6 +92,18 @@ class Month(Base): id: Mapped[int] = mapped_column(primary_key=True) year_month: Mapped[str] = mapped_column(String(7), nullable=False, unique=True) + state: Mapped[MonthState] = mapped_column( + Enum(MonthState, native_enum=False, length=16), + nullable=False, + default=MonthState.planning, + server_default=MonthState.planning.value, + ) + activated_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) + closed_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False )