Olympus/backend/hermes/alembic/versions/c8e2f1a9b704_create_repairs.py
2026-07-04 11:46:58 +02:00

204 lines
12 KiB
Python

"""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")