"""create inventory Revision ID: ef8a2d5c9017 Revises: b4e8f1c7a205 Create Date: 2026-07-05 12:00:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = "ef8a2d5c9017" down_revision: Union[str, Sequence[str], None] = "b4e8f1c7a205" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None INVENTORY_PERMISSIONS = [ ("inventory.read", "Lager lesen", "Ersatzteile und Lagerdaten anzeigen", "inventory"), ("inventory.create", "Lagerartikel erstellen", "Ersatzteile anlegen", "inventory"), ("inventory.update", "Lagerartikel bearbeiten", "Ersatzteile aktualisieren", "inventory"), ("inventory.delete", "Lagerartikel deaktivieren", "Ersatzteile entfernen oder deaktivieren", "inventory"), ("inventory.stock.adjust", "Bestand anpassen", "Lagerbestand korrigieren", "inventory"), ("inventory.stock.reserve", "Bestand reservieren", "Lagerbestand reservieren oder freigeben", "inventory"), ("inventory.stock.consume", "Bestand verbrauchen", "Lagerbestand verbuchen", "inventory"), ("inventory.manage.masterdata", "Lagerstammdaten verwalten", "Kategorien, Lagerorte und Lieferanten verwalten", "inventory"), ] def upgrade() -> None: op.create_table( "inventory_categories", sa.Column("id", sa.Integer(), nullable=False), sa.Column("name", sa.String(length=160), nullable=False), sa.Column("slug", sa.String(length=180), nullable=False), sa.Column("description", sa.Text(), 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.PrimaryKeyConstraint("id"), sa.UniqueConstraint("name"), sa.UniqueConstraint("slug"), ) op.create_index(op.f("ix_inventory_categories_name"), "inventory_categories", ["name"], unique=True) op.create_index(op.f("ix_inventory_categories_slug"), "inventory_categories", ["slug"], unique=True) op.create_table( "inventory_locations", sa.Column("id", sa.Integer(), nullable=False), sa.Column("name", sa.String(length=160), nullable=False), sa.Column("description", sa.Text(), 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.PrimaryKeyConstraint("id"), sa.UniqueConstraint("name"), ) op.create_index(op.f("ix_inventory_locations_name"), "inventory_locations", ["name"], unique=True) op.create_table( "inventory_suppliers", sa.Column("id", sa.Integer(), nullable=False), sa.Column("name", sa.String(length=180), nullable=False), sa.Column("contact_name", sa.String(length=180), nullable=True), sa.Column("email", sa.String(length=255), nullable=True), sa.Column("phone", sa.String(length=80), nullable=True), sa.Column("website", sa.String(length=255), nullable=True), sa.Column("customer_number", sa.String(length=120), nullable=True), sa.Column("notes", sa.Text(), 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.PrimaryKeyConstraint("id"), sa.UniqueConstraint("name"), ) op.create_index(op.f("ix_inventory_suppliers_name"), "inventory_suppliers", ["name"], unique=True) op.create_table( "inventory_items", sa.Column("id", sa.Integer(), nullable=False), sa.Column("sku", sa.String(length=40), nullable=False), sa.Column("name", sa.String(length=255), nullable=False), sa.Column("description", sa.Text(), nullable=True), sa.Column("category_id", sa.Integer(), nullable=True), sa.Column("manufacturer", sa.String(length=180), nullable=True), sa.Column("manufacturer_part_number", sa.String(length=180), nullable=True), sa.Column("supplier_id", sa.Integer(), nullable=True), sa.Column("supplier_part_number", sa.String(length=180), nullable=True), sa.Column("compatible_devices", sa.Text(), nullable=True), sa.Column("location_id", sa.Integer(), nullable=True), sa.Column("quantity_on_hand", sa.Integer(), server_default="0", nullable=False), sa.Column("quantity_reserved", sa.Integer(), server_default="0", nullable=False), sa.Column("quantity_available", sa.Integer(), server_default="0", nullable=False), sa.Column("reorder_level", sa.Integer(), server_default="0", nullable=False), sa.Column("unit", sa.String(length=40), server_default="Stk.", nullable=False), sa.Column("purchase_price_cents", sa.Integer(), nullable=True), sa.Column("selling_price_cents", sa.Integer(), nullable=True), sa.Column("currency", sa.String(length=3), server_default="EUR", nullable=False), sa.Column("tax_rate_percent", sa.Numeric(5, 2), server_default="19.00", nullable=False), sa.Column("notes", sa.Text(), nullable=True), sa.Column("is_active", sa.Boolean(), server_default="true", nullable=False), 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(["category_id"], ["inventory_categories.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["location_id"], ["inventory_locations.id"], ondelete="SET NULL"), sa.ForeignKeyConstraint(["supplier_id"], ["inventory_suppliers.id"], ondelete="SET NULL"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("sku", name="uq_inventory_items_sku"), ) op.create_index(op.f("ix_inventory_items_category_id"), "inventory_items", ["category_id"], unique=False) op.create_index(op.f("ix_inventory_items_is_active"), "inventory_items", ["is_active"], unique=False) op.create_index(op.f("ix_inventory_items_location_id"), "inventory_items", ["location_id"], unique=False) op.create_index(op.f("ix_inventory_items_manufacturer"), "inventory_items", ["manufacturer"], unique=False) op.create_index(op.f("ix_inventory_items_manufacturer_part_number"), "inventory_items", ["manufacturer_part_number"], unique=False) op.create_index(op.f("ix_inventory_items_name"), "inventory_items", ["name"], unique=False) op.create_index(op.f("ix_inventory_items_quantity_available"), "inventory_items", ["quantity_available"], unique=False) op.create_index(op.f("ix_inventory_items_sku"), "inventory_items", ["sku"], unique=True) op.create_index(op.f("ix_inventory_items_supplier_id"), "inventory_items", ["supplier_id"], unique=False) op.create_index(op.f("ix_inventory_items_supplier_part_number"), "inventory_items", ["supplier_part_number"], unique=False) op.create_table( "inventory_stock_movements", sa.Column("id", sa.Integer(), nullable=False), sa.Column("item_id", sa.Integer(), nullable=False), sa.Column("movement_type", sa.String(length=40), nullable=False), sa.Column("quantity", sa.Integer(), nullable=False), sa.Column("reason", sa.String(length=255), server_default="", nullable=False), sa.Column("reference_type", sa.String(length=80), nullable=True), sa.Column("reference_id", sa.Integer(), nullable=True), 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(["item_id"], ["inventory_items.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index(op.f("ix_inventory_stock_movements_actor_user_id"), "inventory_stock_movements", ["actor_user_id"], unique=False) op.create_index(op.f("ix_inventory_stock_movements_item_id"), "inventory_stock_movements", ["item_id"], unique=False) op.create_index(op.f("ix_inventory_stock_movements_movement_type"), "inventory_stock_movements", ["movement_type"], unique=False) op.create_index(op.f("ix_inventory_stock_movements_reference_id"), "inventory_stock_movements", ["reference_id"], unique=False) op.create_index(op.f("ix_inventory_stock_movements_reference_type"), "inventory_stock_movements", ["reference_type"], unique=False) for name, display_name, description, module in INVENTORY_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) ) role_permissions = { "administrator": [item[0] for item in INVENTORY_PERMISSIONS], "management": ["inventory.read", "inventory.create", "inventory.update", "inventory.stock.adjust", "inventory.stock.reserve", "inventory.stock.consume", "inventory.manage.masterdata"], "technician": ["inventory.read", "inventory.stock.reserve", "inventory.stock.consume"], "support": ["inventory.read"], "warehouse": ["inventory.read", "inventory.create", "inventory.update", "inventory.stock.adjust", "inventory.stock.reserve", "inventory.stock.consume", "inventory.manage.masterdata"], } for role_name, permission_names in role_permissions.items(): for permission_name in permission_names: 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: op.execute( sa.text( """ DELETE FROM role_permissions WHERE permission_id IN ( SELECT id FROM permissions WHERE module = 'inventory' ) """ ) ) op.execute("DELETE FROM permissions WHERE module = 'inventory'") op.drop_index(op.f("ix_inventory_stock_movements_reference_type"), table_name="inventory_stock_movements") op.drop_index(op.f("ix_inventory_stock_movements_reference_id"), table_name="inventory_stock_movements") op.drop_index(op.f("ix_inventory_stock_movements_movement_type"), table_name="inventory_stock_movements") op.drop_index(op.f("ix_inventory_stock_movements_item_id"), table_name="inventory_stock_movements") op.drop_index(op.f("ix_inventory_stock_movements_actor_user_id"), table_name="inventory_stock_movements") op.drop_table("inventory_stock_movements") op.drop_index(op.f("ix_inventory_items_supplier_part_number"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_supplier_id"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_sku"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_quantity_available"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_name"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_manufacturer_part_number"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_manufacturer"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_location_id"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_is_active"), table_name="inventory_items") op.drop_index(op.f("ix_inventory_items_category_id"), table_name="inventory_items") op.drop_table("inventory_items") op.drop_index(op.f("ix_inventory_suppliers_name"), table_name="inventory_suppliers") op.drop_table("inventory_suppliers") op.drop_index(op.f("ix_inventory_locations_name"), table_name="inventory_locations") op.drop_table("inventory_locations") op.drop_index(op.f("ix_inventory_categories_slug"), table_name="inventory_categories") op.drop_index(op.f("ix_inventory_categories_name"), table_name="inventory_categories") op.drop_table("inventory_categories")