fix: migration 0029 idempotent (IF NOT EXISTS für Enum + Tabelle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 00:57:29 +02:00
parent d60349df67
commit 82ce592f17
@@ -12,9 +12,7 @@ Neue Tabelle special_assignments:
- label / description (optional) - label / description (optional)
- Overlap-Check per Constraint (date_from <= date_to) + App-seitige Prüfung - Overlap-Check per Constraint (date_from <= date_to) + App-seitige Prüfung
""" """
import sqlalchemy as sa
from alembic import op from alembic import op
from sqlalchemy.dialects import postgresql
revision = "0029" revision = "0029"
down_revision = "0028" down_revision = "0028"
@@ -25,37 +23,46 @@ depends_on = None
def upgrade() -> None: def upgrade() -> None:
op.execute("CREATE EXTENSION IF NOT EXISTS btree_gist") op.execute("CREATE EXTENSION IF NOT EXISTS btree_gist")
# Enum erzeugen # Enum erzeugen (IF NOT EXISTS: falls create_all in lifespan den Typ schon angelegt hat)
op.execute("CREATE TYPE assignment_mode AS ENUM ('fza', 'payroll', 'both')") op.execute("CREATE TYPE IF NOT EXISTS assignment_mode AS ENUM ('fza', 'payroll', 'both')")
op.create_table( # Tabelle nur anlegen falls noch nicht vorhanden (idempotent)
"special_assignments", op.execute("""
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()")), CREATE TABLE IF NOT EXISTS special_assignments (
sa.Column("user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
sa.Column("company_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("companies.id", ondelete="CASCADE"), nullable=False, index=True), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
sa.Column("date_from", sa.Date, nullable=False), company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
sa.Column("date_to", sa.Date, nullable=False), date_from DATE NOT NULL,
sa.Column("factor", sa.Numeric(5, 3), nullable=False), date_to DATE NOT NULL,
sa.Column("mode", sa.Enum("fza", "payroll", "both", name="assignment_mode", create_type=False), nullable=False, server_default="both"), factor NUMERIC(5,3) NOT NULL,
sa.Column("label", sa.String(100), nullable=True), mode assignment_mode NOT NULL DEFAULT 'both',
sa.Column("description", sa.Text, nullable=True), label VARCHAR(100),
sa.CheckConstraint("factor > 0 AND factor <= 10", name="ck_special_assignment_factor"), description TEXT,
sa.CheckConstraint("date_from <= date_to", name="ck_special_assignment_dates"), CONSTRAINT ck_special_assignment_factor CHECK (factor > 0 AND factor <= 10),
) CONSTRAINT ck_special_assignment_dates CHECK (date_from <= date_to)
# Exclusion Constraint: kein überlappender Zeitraum pro User
op.execute(
"""
ALTER TABLE special_assignments
ADD CONSTRAINT special_assignments_no_overlap
EXCLUDE USING gist (
user_id WITH =,
daterange(date_from, date_to, '[]') WITH &&
) )
""" """)
) op.execute("CREATE INDEX IF NOT EXISTS ix_special_assignments_user_id ON special_assignments(user_id)")
op.execute("CREATE INDEX IF NOT EXISTS ix_special_assignments_company_id ON special_assignments(company_id)")
# Exclusion Constraint (nur falls noch nicht vorhanden)
op.execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'special_assignments_no_overlap'
) THEN
ALTER TABLE special_assignments
ADD CONSTRAINT special_assignments_no_overlap
EXCLUDE USING gist (
user_id WITH =,
daterange(date_from, date_to, '[]') WITH &&
);
END IF;
END $$
""")
def downgrade() -> None: def downgrade() -> None:
op.drop_table("special_assignments") op.execute("DROP TABLE IF EXISTS special_assignments")
op.execute("DROP TYPE IF EXISTS assignment_mode") op.execute("DROP TYPE IF EXISTS assignment_mode")