feat(repairs): add repair management foundation
This commit is contained in:
parent
22e54f212c
commit
e9ec207617
37 changed files with 2149 additions and 7 deletions
|
|
@ -8,6 +8,11 @@ from alembic import context
|
|||
from app.core.config import settings
|
||||
from app.db.database import Base
|
||||
import app.models.user
|
||||
import app.models.customer
|
||||
import app.models.knowledge
|
||||
import app.models.audit
|
||||
import app.models.rbac
|
||||
import app.models.repair
|
||||
|
||||
config = context.config
|
||||
|
||||
|
|
|
|||
204
backend/hermes/alembic/versions/c8e2f1a9b704_create_repairs.py
Normal file
204
backend/hermes/alembic/versions/c8e2f1a9b704_create_repairs.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
"""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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue