"""create repairs module Revision ID: c8e2f1a9b704 Revises: b6f3d8c1a920 Create Date: 2026-07-04 11:00:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = "c8e2f1a9b704" down_revision: Union[str, Sequence[str], None] = "b6f3d8c1a920" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None REPAIR_PERMISSIONS = [ ("repairs.read", "Reparaturen lesen", "Reparaturen anzeigen", "repairs"), ("repairs.create", "Reparaturen erstellen", "Reparaturen anlegen", "repairs"), ("repairs.update", "Reparaturen bearbeiten", "Reparaturen aktualisieren", "repairs"), ("repairs.delete", "Reparaturen löschen", "Reparaturen stornieren", "repairs"), ("repairs.status.update", "Reparaturstatus ändern", "Status von Reparaturen ändern", "repairs"), ("repairs.intake", "Reparatur-Intake", "Website-Reparaturanfragen übernehmen", "repairs"), ("repairs.assign", "Reparaturen zuweisen", "Reparaturen Benutzern zuweisen", "repairs"), ] ROLE_PERMISSIONS = { "administrator": [permission[0] for permission in REPAIR_PERMISSIONS], "management": ["repairs.read", "repairs.create", "repairs.update", "repairs.status.update"], "technician": ["repairs.read", "repairs.update", "repairs.status.update"], "support": ["repairs.read", "repairs.create", "repairs.update", "repairs.status.update", "repairs.intake"], } def upgrade() -> None: op.create_table( "repairs", sa.Column("id", sa.Integer(), nullable=False), sa.Column("repair_number", sa.String(length=20), nullable=False), sa.Column("customer_id", sa.Integer(), nullable=True), sa.Column("contact_id", sa.Integer(), nullable=True), sa.Column("status", sa.String(length=50), nullable=False), sa.Column("priority", sa.String(length=30), nullable=False), sa.Column("source", sa.String(length=50), nullable=False), sa.Column("source_reference", sa.String(length=120), nullable=True), sa.Column("customer_name", sa.String(length=255), nullable=False), sa.Column("customer_email", sa.String(length=255), server_default="", nullable=False), sa.Column("customer_phone", sa.String(length=80), server_default="", nullable=False), sa.Column("device_manufacturer", sa.String(length=160), nullable=False), sa.Column("device_model", sa.String(length=160), nullable=False), sa.Column("device_serial_number", sa.String(length=160), nullable=True), sa.Column("device_type", sa.String(length=120), server_default="", nullable=False), sa.Column("accessories", sa.Text(), server_default="", nullable=False), sa.Column("fault_description", sa.Text(), nullable=False), sa.Column("previous_work", sa.Text(), server_default="", nullable=False), sa.Column("device_opened", sa.Boolean(), server_default="false", nullable=False), sa.Column("intake_notes", sa.Text(), server_default="", nullable=False), sa.Column("diagnosis_notes", sa.Text(), server_default="", nullable=False), sa.Column("repair_notes", sa.Text(), server_default="", nullable=False), sa.Column("estimate_notes", sa.Text(), server_default="", nullable=False), sa.Column("estimated_cost_cents", sa.Integer(), nullable=True), sa.Column("approved_at", sa.DateTime(timezone=True), nullable=True), sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.CheckConstraint("status in ('new','accepted','diagnosis','estimate','waiting_for_customer','approved','repair','final_test','ready_for_pickup','shipped','completed','cancelled')", name="ck_repairs_status"), sa.CheckConstraint("priority in ('low','normal','high','urgent')", name="ck_repairs_priority"), sa.CheckConstraint("source in ('manual','website','customer_portal','email','phone')", name="ck_repairs_source"), sa.ForeignKeyConstraint(["contact_id"], ["customer_contacts.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["customer_id"], ["customers.id"], ondelete="SET NULL"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("repair_number"), ) op.create_index(op.f("ix_repairs_contact_id"), "repairs", ["contact_id"], unique=False) op.create_index(op.f("ix_repairs_customer_id"), "repairs", ["customer_id"], unique=False) op.create_index(op.f("ix_repairs_customer_name"), "repairs", ["customer_name"], unique=False) op.create_index(op.f("ix_repairs_device_manufacturer"), "repairs", ["device_manufacturer"], unique=False) op.create_index(op.f("ix_repairs_device_model"), "repairs", ["device_model"], unique=False) op.create_index(op.f("ix_repairs_priority"), "repairs", ["priority"], unique=False) op.create_index(op.f("ix_repairs_repair_number"), "repairs", ["repair_number"], unique=True) op.create_index(op.f("ix_repairs_source"), "repairs", ["source"], unique=False) op.create_index(op.f("ix_repairs_source_reference"), "repairs", ["source_reference"], unique=False) op.create_index(op.f("ix_repairs_status"), "repairs", ["status"], unique=False) op.create_table( "repair_status_history", sa.Column("id", sa.Integer(), nullable=False), sa.Column("repair_id", sa.Integer(), nullable=False), sa.Column("old_status", sa.String(length=50), nullable=True), sa.Column("new_status", sa.String(length=50), nullable=False), sa.Column("note", sa.Text(), nullable=True), sa.Column("actor_user_id", sa.Integer(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.ForeignKeyConstraint(["actor_user_id"], ["users.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["repair_id"], ["repairs.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_repair_status_history_actor_user_id"), "repair_status_history", ["actor_user_id"], unique=False) op.create_index(op.f("ix_repair_status_history_new_status"), "repair_status_history", ["new_status"], unique=False) op.create_index(op.f("ix_repair_status_history_repair_id"), "repair_status_history", ["repair_id"], unique=False) op.create_table( "repair_intake_events", sa.Column("id", sa.Integer(), nullable=False), sa.Column("repair_id", sa.Integer(), nullable=True), sa.Column("external_source", sa.String(length=80), nullable=False), sa.Column("external_reference", sa.String(length=160), nullable=True), sa.Column("payload", sa.JSON(), nullable=False), sa.Column("status", sa.String(length=50), nullable=False), sa.Column("error_message", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.Column("processed_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(["repair_id"], ["repairs.id"], ondelete="SET NULL"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_repair_intake_events_external_reference"), "repair_intake_events", ["external_reference"], unique=False) op.create_index(op.f("ix_repair_intake_events_external_source"), "repair_intake_events", ["external_source"], unique=False) op.create_index(op.f("ix_repair_intake_events_repair_id"), "repair_intake_events", ["repair_id"], unique=False) op.create_index(op.f("ix_repair_intake_events_status"), "repair_intake_events", ["status"], unique=False) op.create_table( "repair_documents", sa.Column("id", sa.Integer(), nullable=False), sa.Column("repair_id", sa.Integer(), nullable=False), sa.Column("file_id", sa.Integer(), nullable=True), sa.Column("storage_path", sa.String(length=500), nullable=True), sa.Column("title", sa.String(length=255), nullable=False), sa.Column("document_type", sa.String(length=80), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.ForeignKeyConstraint(["repair_id"], ["repairs.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_repair_documents_document_type"), "repair_documents", ["document_type"], unique=False) op.create_index(op.f("ix_repair_documents_repair_id"), "repair_documents", ["repair_id"], unique=False) for name, display_name, description, module in REPAIR_PERMISSIONS: op.execute( sa.text( """ insert into permissions (name, display_name, description, module) values (:name, :display_name, :description, :module) on conflict (name) do update set display_name = excluded.display_name, description = excluded.description, module = excluded.module """ ).bindparams(name=name, display_name=display_name, description=description, module=module) ) for role_name, permissions in ROLE_PERMISSIONS.items(): for permission_name in permissions: op.execute( sa.text( """ insert into role_permissions (role_id, permission_id) select roles.id, permissions.id from roles, permissions where roles.name = :role_name and permissions.name = :permission_name on conflict do nothing """ ).bindparams(role_name=role_name, permission_name=permission_name) ) def downgrade() -> None: for permission_name, *_ in REPAIR_PERMISSIONS: op.execute( sa.text( """ delete from role_permissions using permissions where role_permissions.permission_id = permissions.id and permissions.name = :permission_name """ ).bindparams(permission_name=permission_name) ) op.execute(sa.text("delete from permissions where name = :name").bindparams(name=permission_name)) op.drop_index(op.f("ix_repair_documents_repair_id"), table_name="repair_documents") op.drop_index(op.f("ix_repair_documents_document_type"), table_name="repair_documents") op.drop_table("repair_documents") op.drop_index(op.f("ix_repair_intake_events_status"), table_name="repair_intake_events") op.drop_index(op.f("ix_repair_intake_events_repair_id"), table_name="repair_intake_events") op.drop_index(op.f("ix_repair_intake_events_external_source"), table_name="repair_intake_events") op.drop_index(op.f("ix_repair_intake_events_external_reference"), table_name="repair_intake_events") op.drop_table("repair_intake_events") op.drop_index(op.f("ix_repair_status_history_repair_id"), table_name="repair_status_history") op.drop_index(op.f("ix_repair_status_history_new_status"), table_name="repair_status_history") op.drop_index(op.f("ix_repair_status_history_actor_user_id"), table_name="repair_status_history") op.drop_table("repair_status_history") op.drop_index(op.f("ix_repairs_status"), table_name="repairs") op.drop_index(op.f("ix_repairs_source_reference"), table_name="repairs") op.drop_index(op.f("ix_repairs_source"), table_name="repairs") op.drop_index(op.f("ix_repairs_repair_number"), table_name="repairs") op.drop_index(op.f("ix_repairs_priority"), table_name="repairs") op.drop_index(op.f("ix_repairs_device_model"), table_name="repairs") op.drop_index(op.f("ix_repairs_device_manufacturer"), table_name="repairs") op.drop_index(op.f("ix_repairs_customer_name"), table_name="repairs") op.drop_index(op.f("ix_repairs_customer_id"), table_name="repairs") op.drop_index(op.f("ix_repairs_contact_id"), table_name="repairs") op.drop_table("repairs")