Budget: - Modelle + Migration 0003: budget_template_lines, planned_income_lines, budget_periods, budget_period_lines. - services/budget: Perioden-Grenzen (einstellbarer Starttag, Label nach Endmonat), Standard-Vorlage vs. Monats-Override (Snapshot-on-Customize), Soll/Ist inkl. budgetierter Sparen-Transfers, geplante Einnahmen (Soll/Ist). - routers/budgets: Vorlage, geplante Einnahmen (CRUD), Monats-Soll/Ist, Monats-Override, Zurücksetzen. pytest: Perioden, Soll/Ist, Override/Reset, Einnahmen. - Frontend: Budget-Seite (Monat + Standard-Budget), Fortschrittsbalken grün/rot, Monats-Navigation, editierbare Soll-Beträge; Sparen-Option im Transfer-Formular. Deployment-Fix (Nutzer-Anforderung: http://IP:Port muss funktionieren): - Dienst bindet an APP_BIND_HOST=0.0.0.0 (direkt im LAN erreichbar). - APP_COOKIE_SECURE=false als Standard (sonst kein Login über reines HTTP); für reinen HTTPS-Betrieb umstellbar. Doku + install.sh entsprechend angepasst. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
3.4 KiB
Python
70 lines
3.4 KiB
Python
"""Budget: Standard-Vorlage, geplante Einnahmen, Monats-Perioden mit Overrides.
|
|
|
|
Revision ID: 0003
|
|
Revises: 0002
|
|
Create Date: 2026-08-19
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "0003"
|
|
down_revision = "0002"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"budget_template_lines",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("owner_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("category_id", sa.Integer(), sa.ForeignKey("categories.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("planned_cents", sa.Integer(), nullable=False, server_default=sa.text("0")),
|
|
sa.UniqueConstraint("owner_id", "category_id", name="uq_tpl_owner_category"),
|
|
)
|
|
op.create_index("ix_budget_template_lines_owner_id", "budget_template_lines", ["owner_id"])
|
|
|
|
op.create_table(
|
|
"planned_income_lines",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("owner_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("label", sa.String(length=120), nullable=False),
|
|
sa.Column("category_id", sa.Integer(), sa.ForeignKey("categories.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("target_account_id", sa.Integer(), sa.ForeignKey("accounts.id", ondelete="SET NULL"), nullable=True),
|
|
sa.Column("planned_cents", sa.Integer(), nullable=False, server_default=sa.text("0")),
|
|
sa.Column("position", sa.Integer(), nullable=False, server_default=sa.text("0")),
|
|
)
|
|
op.create_index("ix_planned_income_lines_owner_id", "planned_income_lines", ["owner_id"])
|
|
|
|
op.create_table(
|
|
"budget_periods",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("owner_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("label", sa.String(length=7), nullable=False),
|
|
sa.Column("customized", sa.Boolean(), nullable=False, server_default=sa.text("0")),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
|
sa.UniqueConstraint("owner_id", "label", name="uq_period_owner_label"),
|
|
)
|
|
op.create_index("ix_budget_periods_owner_id", "budget_periods", ["owner_id"])
|
|
|
|
op.create_table(
|
|
"budget_period_lines",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("budget_period_id", sa.Integer(), sa.ForeignKey("budget_periods.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("category_id", sa.Integer(), sa.ForeignKey("categories.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("planned_cents", sa.Integer(), nullable=False, server_default=sa.text("0")),
|
|
sa.UniqueConstraint("budget_period_id", "category_id", name="uq_periodline_period_category"),
|
|
)
|
|
op.create_index("ix_budget_period_lines_period_id", "budget_period_lines", ["budget_period_id"])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_budget_period_lines_period_id", table_name="budget_period_lines")
|
|
op.drop_table("budget_period_lines")
|
|
op.drop_index("ix_budget_periods_owner_id", table_name="budget_periods")
|
|
op.drop_table("budget_periods")
|
|
op.drop_index("ix_planned_income_lines_owner_id", table_name="planned_income_lines")
|
|
op.drop_table("planned_income_lines")
|
|
op.drop_index("ix_budget_template_lines_owner_id", table_name="budget_template_lines")
|
|
op.drop_table("budget_template_lines")
|