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:
@@ -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)
|
||||||
)
|
)
|
||||||
|
""")
|
||||||
|
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: kein überlappender Zeitraum pro User
|
# Exclusion Constraint (nur falls noch nicht vorhanden)
|
||||||
op.execute(
|
op.execute("""
|
||||||
"""
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'special_assignments_no_overlap'
|
||||||
|
) THEN
|
||||||
ALTER TABLE special_assignments
|
ALTER TABLE special_assignments
|
||||||
ADD CONSTRAINT special_assignments_no_overlap
|
ADD CONSTRAINT special_assignments_no_overlap
|
||||||
EXCLUDE USING gist (
|
EXCLUDE USING gist (
|
||||||
user_id WITH =,
|
user_id WITH =,
|
||||||
daterange(date_from, date_to, '[]') 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")
|
||||||
|
|||||||
Reference in New Issue
Block a user