"""platform service visits Revision ID: 202605120005 Revises: 202605120004 Create Date: 2026-05-12 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "202605120005" down_revision: str | None = "202605120004" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.add_column("users", sa.Column("platform_role", sa.String(length=32), server_default="user", nullable=False)) op.create_index(op.f("ix_users_platform_role"), "users", ["platform_role"]) op.add_column("cars", sa.Column("license_plate_display", sa.String(length=32), nullable=True)) op.add_column("cars", sa.Column("license_plate_normalized", sa.String(length=32), nullable=True)) op.add_column("cars", sa.Column("license_plate_country", sa.String(length=2), nullable=True)) op.add_column("cars", sa.Column("vin_normalized", sa.String(length=17), nullable=True)) op.execute( """ update cars set license_plate_display = plate_number, license_plate_normalized = upper(replace(replace(plate_number, ' ', ''), '-', '')), vin_normalized = upper(vin) where plate_number is not null or vin is not null """ ) op.create_index(op.f("ix_cars_license_plate_normalized"), "cars", ["license_plate_normalized"]) op.create_index(op.f("ix_cars_license_plate_country"), "cars", ["license_plate_country"]) op.create_index(op.f("ix_cars_vin_normalized"), "cars", ["vin_normalized"], unique=True) op.create_index( "uq_cars_country_license_plate", "cars", ["license_plate_country", "license_plate_normalized"], unique=True, postgresql_where=sa.text("license_plate_country is not null and license_plate_normalized is not null"), ) op.add_column("service_centers", sa.Column("legal_name", sa.String(length=240), nullable=True)) op.add_column("service_centers", sa.Column("display_name", sa.String(length=160), nullable=True)) op.add_column("service_centers", sa.Column("country", sa.String(length=2), nullable=True)) op.add_column("service_centers", sa.Column("city", sa.String(length=120), nullable=True)) op.add_column("service_centers", sa.Column("phone", sa.String(length=40), nullable=True)) op.add_column("service_centers", sa.Column("business_registration_number", sa.String(length=80), nullable=True)) op.add_column( "service_centers", sa.Column("verification_status", sa.String(length=24), server_default="pending", nullable=False), ) op.add_column("service_centers", sa.Column("owner_user_id", sa.Integer(), nullable=True)) op.add_column("service_centers", sa.Column("verified_at", sa.DateTime(timezone=True), nullable=True)) op.add_column("service_centers", sa.Column("suspended_at", sa.DateTime(timezone=True), nullable=True)) op.create_foreign_key( "fk_service_centers_owner_user_id_users", "service_centers", "users", ["owner_user_id"], ["id"], ondelete="SET NULL", ) op.create_index(op.f("ix_service_centers_display_name"), "service_centers", ["display_name"]) op.create_index(op.f("ix_service_centers_country"), "service_centers", ["country"]) op.create_index(op.f("ix_service_centers_verification_status"), "service_centers", ["verification_status"]) op.create_index(op.f("ix_service_centers_owner_user_id"), "service_centers", ["owner_user_id"]) op.execute("update service_centers set display_name = name where display_name is null") op.create_table( "vehicle_access", sa.Column("id", sa.Integer(), nullable=False), sa.Column("vehicle_id", sa.Integer(), nullable=False), sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("role", sa.String(length=24), server_default="owner", nullable=False), sa.Column("status", sa.String(length=24), server_default="active", nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["vehicle_id"], ["cars.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("vehicle_id", "user_id", "role", name="uq_vehicle_access_user_role"), ) op.create_index(op.f("ix_vehicle_access_vehicle_id"), "vehicle_access", ["vehicle_id"]) op.create_index(op.f("ix_vehicle_access_user_id"), "vehicle_access", ["user_id"]) op.create_index(op.f("ix_vehicle_access_role"), "vehicle_access", ["role"]) op.create_index(op.f("ix_vehicle_access_status"), "vehicle_access", ["status"]) op.execute( """ insert into vehicle_access (vehicle_id, user_id, role, status) select id, owner_id, 'owner', 'active' from cars on conflict do nothing """ ) op.create_table( "service_center_verifications", sa.Column("id", sa.Integer(), nullable=False), sa.Column("service_center_id", sa.Integer(), nullable=False), sa.Column("submitted_documents", sa.JSON(), nullable=True), sa.Column("comment", sa.Text(), nullable=True), sa.Column("status", sa.String(length=24), server_default="pending", nullable=False), sa.Column("reviewed_by", sa.Integer(), nullable=True), sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.ForeignKeyConstraint(["reviewed_by"], ["users.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["service_center_id"], ["service_centers.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_service_center_verifications_service_center_id"), "service_center_verifications", ["service_center_id"]) op.create_index(op.f("ix_service_center_verifications_status"), "service_center_verifications", ["status"]) op.create_index(op.f("ix_service_center_verifications_reviewed_by"), "service_center_verifications", ["reviewed_by"]) op.create_table( "service_employees", sa.Column("id", sa.Integer(), nullable=False), sa.Column("service_center_id", sa.Integer(), nullable=False), sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("role", sa.String(length=32), server_default="receptionist", nullable=False), sa.Column("permissions", sa.JSON(), nullable=True), sa.Column("status", sa.String(length=24), server_default="active", nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.ForeignKeyConstraint(["service_center_id"], ["service_centers.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("service_center_id", "user_id", name="uq_service_employee_user"), ) op.create_index(op.f("ix_service_employees_service_center_id"), "service_employees", ["service_center_id"]) op.create_index(op.f("ix_service_employees_user_id"), "service_employees", ["user_id"]) op.create_index(op.f("ix_service_employees_role"), "service_employees", ["role"]) op.create_index(op.f("ix_service_employees_status"), "service_employees", ["status"]) op.create_table( "service_visits", sa.Column("id", sa.Integer(), nullable=False), sa.Column("service_center_id", sa.Integer(), nullable=False), sa.Column("vehicle_id", sa.Integer(), nullable=False), sa.Column("created_by_employee_id", sa.Integer(), nullable=True), sa.Column("visit_date", sa.Date(), nullable=False), sa.Column("odometer", sa.Integer(), nullable=True), sa.Column("status", sa.String(length=40), server_default="draft", nullable=False), sa.Column("notes", sa.Text(), nullable=True), sa.Column("total_cost", sa.Numeric(12, 2), nullable=True), sa.Column("currency", sa.String(length=3), server_default="RUB", nullable=False), sa.Column("owner_resolved_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.ForeignKeyConstraint(["created_by_employee_id"], ["service_employees.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["service_center_id"], ["service_centers.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["vehicle_id"], ["cars.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_service_visits_service_center_id"), "service_visits", ["service_center_id"]) op.create_index(op.f("ix_service_visits_vehicle_id"), "service_visits", ["vehicle_id"]) op.create_index(op.f("ix_service_visits_created_by_employee_id"), "service_visits", ["created_by_employee_id"]) op.create_index(op.f("ix_service_visits_visit_date"), "service_visits", ["visit_date"]) op.create_index(op.f("ix_service_visits_status"), "service_visits", ["status"]) op.create_table( "service_work_items", sa.Column("id", sa.Integer(), nullable=False), sa.Column("service_visit_id", sa.Integer(), nullable=False), sa.Column("work_type", sa.String(length=40), server_default="other", nullable=False), sa.Column("title", sa.String(length=180), nullable=False), sa.Column("description", sa.Text(), nullable=True), sa.Column("parts", sa.JSON(), nullable=True), sa.Column("oil_brand", sa.String(length=80), nullable=True), sa.Column("oil_viscosity", sa.String(length=40), nullable=True), sa.Column("oil_volume", sa.Numeric(5, 2), nullable=True), sa.Column("next_due_odometer", sa.Integer(), nullable=True), sa.Column("next_due_date", sa.Date(), nullable=True), sa.Column("price", sa.Numeric(12, 2), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.ForeignKeyConstraint(["service_visit_id"], ["service_visits.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_service_work_items_service_visit_id"), "service_work_items", ["service_visit_id"]) op.create_index(op.f("ix_service_work_items_work_type"), "service_work_items", ["work_type"]) op.create_table( "vehicle_data_change_requests", sa.Column("id", sa.Integer(), nullable=False), sa.Column("vehicle_id", sa.Integer(), nullable=False), sa.Column("requested_by_service_center_id", sa.Integer(), nullable=True), sa.Column("requested_by_employee_id", sa.Integer(), nullable=True), sa.Column("field_name", sa.String(length=80), nullable=False), sa.Column("old_value", sa.Text(), nullable=True), sa.Column("new_value", sa.Text(), nullable=True), sa.Column("status", sa.String(length=24), server_default="pending", nullable=False), sa.Column("owner_user_id", sa.Integer(), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False), sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(["owner_user_id"], ["users.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["requested_by_employee_id"], ["service_employees.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["requested_by_service_center_id"], ["service_centers.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["vehicle_id"], ["cars.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_vehicle_data_change_requests_vehicle_id"), "vehicle_data_change_requests", ["vehicle_id"]) op.create_index(op.f("ix_vehicle_data_change_requests_requested_by_service_center_id"), "vehicle_data_change_requests", ["requested_by_service_center_id"]) op.create_index(op.f("ix_vehicle_data_change_requests_requested_by_employee_id"), "vehicle_data_change_requests", ["requested_by_employee_id"]) op.create_index(op.f("ix_vehicle_data_change_requests_field_name"), "vehicle_data_change_requests", ["field_name"]) op.create_index(op.f("ix_vehicle_data_change_requests_status"), "vehicle_data_change_requests", ["status"]) op.create_index(op.f("ix_vehicle_data_change_requests_owner_user_id"), "vehicle_data_change_requests", ["owner_user_id"]) op.create_table( "audit_logs", sa.Column("id", sa.Integer(), nullable=False), sa.Column("actor_user_id", sa.Integer(), nullable=True), sa.Column("actor_role", sa.String(length=64), nullable=True), sa.Column("action", sa.String(length=120), nullable=False), sa.Column("target_type", sa.String(length=80), nullable=False), sa.Column("target_id", sa.String(length=80), nullable=True), sa.Column("ip", sa.String(length=64), nullable=True), sa.Column("user_agent", sa.String(length=256), nullable=True), sa.Column("metadata_json", sa.JSON(), 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.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_audit_logs_actor_user_id"), "audit_logs", ["actor_user_id"]) op.create_index(op.f("ix_audit_logs_action"), "audit_logs", ["action"]) op.create_index(op.f("ix_audit_logs_target_type"), "audit_logs", ["target_type"]) op.create_index(op.f("ix_audit_logs_target_id"), "audit_logs", ["target_id"]) op.create_index(op.f("ix_audit_logs_created_at"), "audit_logs", ["created_at"]) def downgrade() -> None: op.drop_index(op.f("ix_audit_logs_created_at"), table_name="audit_logs") op.drop_index(op.f("ix_audit_logs_target_id"), table_name="audit_logs") op.drop_index(op.f("ix_audit_logs_target_type"), table_name="audit_logs") op.drop_index(op.f("ix_audit_logs_action"), table_name="audit_logs") op.drop_index(op.f("ix_audit_logs_actor_user_id"), table_name="audit_logs") op.drop_table("audit_logs") op.drop_index(op.f("ix_vehicle_data_change_requests_owner_user_id"), table_name="vehicle_data_change_requests") op.drop_index(op.f("ix_vehicle_data_change_requests_status"), table_name="vehicle_data_change_requests") op.drop_index(op.f("ix_vehicle_data_change_requests_field_name"), table_name="vehicle_data_change_requests") op.drop_index(op.f("ix_vehicle_data_change_requests_requested_by_employee_id"), table_name="vehicle_data_change_requests") op.drop_index(op.f("ix_vehicle_data_change_requests_requested_by_service_center_id"), table_name="vehicle_data_change_requests") op.drop_index(op.f("ix_vehicle_data_change_requests_vehicle_id"), table_name="vehicle_data_change_requests") op.drop_table("vehicle_data_change_requests") op.drop_index(op.f("ix_service_work_items_work_type"), table_name="service_work_items") op.drop_index(op.f("ix_service_work_items_service_visit_id"), table_name="service_work_items") op.drop_table("service_work_items") op.drop_index(op.f("ix_service_visits_status"), table_name="service_visits") op.drop_index(op.f("ix_service_visits_visit_date"), table_name="service_visits") op.drop_index(op.f("ix_service_visits_created_by_employee_id"), table_name="service_visits") op.drop_index(op.f("ix_service_visits_vehicle_id"), table_name="service_visits") op.drop_index(op.f("ix_service_visits_service_center_id"), table_name="service_visits") op.drop_table("service_visits") op.drop_index(op.f("ix_service_employees_status"), table_name="service_employees") op.drop_index(op.f("ix_service_employees_role"), table_name="service_employees") op.drop_index(op.f("ix_service_employees_user_id"), table_name="service_employees") op.drop_index(op.f("ix_service_employees_service_center_id"), table_name="service_employees") op.drop_table("service_employees") op.drop_index(op.f("ix_service_center_verifications_reviewed_by"), table_name="service_center_verifications") op.drop_index(op.f("ix_service_center_verifications_status"), table_name="service_center_verifications") op.drop_index(op.f("ix_service_center_verifications_service_center_id"), table_name="service_center_verifications") op.drop_table("service_center_verifications") op.drop_index(op.f("ix_vehicle_access_status"), table_name="vehicle_access") op.drop_index(op.f("ix_vehicle_access_role"), table_name="vehicle_access") op.drop_index(op.f("ix_vehicle_access_user_id"), table_name="vehicle_access") op.drop_index(op.f("ix_vehicle_access_vehicle_id"), table_name="vehicle_access") op.drop_table("vehicle_access") op.drop_index(op.f("ix_service_centers_owner_user_id"), table_name="service_centers") op.drop_index(op.f("ix_service_centers_verification_status"), table_name="service_centers") op.drop_index(op.f("ix_service_centers_country"), table_name="service_centers") op.drop_index(op.f("ix_service_centers_display_name"), table_name="service_centers") op.drop_constraint("fk_service_centers_owner_user_id_users", "service_centers", type_="foreignkey") op.drop_column("service_centers", "suspended_at") op.drop_column("service_centers", "verified_at") op.drop_column("service_centers", "owner_user_id") op.drop_column("service_centers", "verification_status") op.drop_column("service_centers", "business_registration_number") op.drop_column("service_centers", "phone") op.drop_column("service_centers", "city") op.drop_column("service_centers", "country") op.drop_column("service_centers", "display_name") op.drop_column("service_centers", "legal_name") op.drop_index("uq_cars_country_license_plate", table_name="cars") op.drop_index(op.f("ix_cars_vin_normalized"), table_name="cars") op.drop_index(op.f("ix_cars_license_plate_country"), table_name="cars") op.drop_index(op.f("ix_cars_license_plate_normalized"), table_name="cars") op.drop_column("cars", "vin_normalized") op.drop_column("cars", "license_plate_country") op.drop_column("cars", "license_plate_normalized") op.drop_column("cars", "license_plate_display") op.drop_index(op.f("ix_users_platform_role"), table_name="users") op.drop_column("users", "platform_role")