feat: Implement modular plugin architecture
- Convert invcount to self-contained module - Add Module Manager for install/uninstall - Create module_registry database table - Support hot-reloading of modules - Move data imports into invcount module - Update all templates and routes to new structure Version bumped to 0.16.0
This commit is contained in:
160
migrations.py
160
migrations.py
@@ -1,12 +1,8 @@
|
||||
"""
|
||||
ScanLook Database Migration System
|
||||
ScanLook Core Database Migration System
|
||||
|
||||
Simple migration system that tracks and applies database changes.
|
||||
Each migration has a version number and an up() function.
|
||||
|
||||
Usage:
|
||||
from migrations import run_migrations
|
||||
run_migrations() # Call on app startup
|
||||
IMPORTANT: This file only contains CORE system migrations.
|
||||
Module-specific migrations are in each module's migrations.py file.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
@@ -75,19 +71,13 @@ def table_exists(table):
|
||||
|
||||
|
||||
# ============================================
|
||||
# MIGRATIONS
|
||||
# CORE SYSTEM MIGRATIONS ONLY
|
||||
# ============================================
|
||||
# Add new migrations to this list.
|
||||
# Each migration is a tuple: (version, name, up_function)
|
||||
#
|
||||
# RULES:
|
||||
# - Never modify an existing migration
|
||||
# - Always add new migrations at the end with the next version number
|
||||
# - Check if changes are needed before applying (idempotent)
|
||||
# Module-specific migrations are handled by each module's migrations.py
|
||||
# ============================================
|
||||
|
||||
def migration_001_add_modules_tables():
|
||||
"""Add Modules and UserModules tables"""
|
||||
"""Add Modules and UserModules tables (if not created by init_db)"""
|
||||
conn = get_db()
|
||||
|
||||
if not table_exists('Modules'):
|
||||
@@ -141,132 +131,42 @@ def migration_002_add_usermodules_granted_columns():
|
||||
conn.close()
|
||||
|
||||
|
||||
def migration_003_add_default_modules():
|
||||
"""Add default modules if they don't exist"""
|
||||
def migration_003_add_module_registry():
|
||||
"""Add module_registry table for new module manager system"""
|
||||
conn = get_db()
|
||||
|
||||
# Check if modules exist
|
||||
existing = conn.execute('SELECT COUNT(*) as cnt FROM Modules').fetchone()
|
||||
|
||||
if existing['cnt'] == 0:
|
||||
if not table_exists('module_registry'):
|
||||
conn.execute('''
|
||||
INSERT INTO Modules (module_name, module_key, description, icon, is_active, display_order)
|
||||
VALUES ('Inventory Counts', 'counting', 'Cycle counts and physical inventory', 'fa-clipboard-check', 1, 1)
|
||||
CREATE TABLE module_registry (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
module_key TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
author TEXT,
|
||||
description TEXT,
|
||||
is_installed INTEGER DEFAULT 0,
|
||||
is_active INTEGER DEFAULT 0,
|
||||
installed_at TEXT,
|
||||
config_json TEXT
|
||||
)
|
||||
''')
|
||||
conn.execute('''
|
||||
INSERT INTO Modules (module_name, module_key, description, icon, is_active, display_order)
|
||||
VALUES ('Consumption Sheets', 'cons_sheets', 'Production consumption tracking', 'fa-clipboard-list', 1, 2)
|
||||
''')
|
||||
print(" Added default modules")
|
||||
print(" Created module_registry table")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def migration_004_assign_modules_to_admins():
|
||||
"""Auto-assign all modules to owner and admin users"""
|
||||
conn = get_db()
|
||||
|
||||
# Get admin users
|
||||
admins = conn.execute('SELECT user_id FROM Users WHERE role IN ("owner", "admin")').fetchall()
|
||||
modules = conn.execute('SELECT module_id FROM Modules').fetchall()
|
||||
|
||||
for user in admins:
|
||||
for module in modules:
|
||||
try:
|
||||
conn.execute('''
|
||||
INSERT INTO UserModules (user_id, module_id)
|
||||
VALUES (?, ?)
|
||||
''', [user['user_id'], module['module_id']])
|
||||
except sqlite3.IntegrityError:
|
||||
pass # Already assigned
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(" Assigned modules to admin users")
|
||||
|
||||
|
||||
def migration_005_add_cons_process_fields_duplicate_key():
|
||||
"""Add is_duplicate_key column to cons_process_fields if missing"""
|
||||
conn = get_db()
|
||||
|
||||
if table_exists('cons_process_fields'):
|
||||
if not column_exists('cons_process_fields', 'is_duplicate_key'):
|
||||
conn.execute('ALTER TABLE cons_process_fields ADD COLUMN is_duplicate_key INTEGER DEFAULT 0')
|
||||
print(" Added is_duplicate_key column to cons_process_fields")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def migration_006_add_is_deleted_to_locationcounts():
|
||||
"""Add is_deleted column to LocationCounts table"""
|
||||
conn = get_db()
|
||||
|
||||
if table_exists('LocationCounts'):
|
||||
if not column_exists('LocationCounts', 'is_deleted'):
|
||||
conn.execute('ALTER TABLE LocationCounts ADD COLUMN is_deleted INTEGER DEFAULT 0')
|
||||
print(" Added is_deleted column to LocationCounts")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def migration_007_add_detail_end_row():
|
||||
"""Add detail_end_row column to cons_processes table"""
|
||||
conn = get_db()
|
||||
|
||||
if table_exists('cons_processes'):
|
||||
if not column_exists('cons_processes', 'detail_end_row'):
|
||||
conn.execute('ALTER TABLE cons_processes ADD COLUMN detail_end_row INTEGER')
|
||||
print(" Added detail_end_row column to cons_processes")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def migration_008_add_page_height():
|
||||
"""Add page_height column to cons_processes table"""
|
||||
conn = get_db()
|
||||
|
||||
if table_exists('cons_processes'):
|
||||
if not column_exists('cons_processes', 'page_height'):
|
||||
conn.execute('ALTER TABLE cons_processes ADD COLUMN page_height INTEGER')
|
||||
print(" Added page_height column to cons_processes")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def migration_009_add_print_columns():
|
||||
"""Add print_start_col and print_end_col to cons_processes"""
|
||||
conn = get_db()
|
||||
if table_exists('cons_processes'):
|
||||
if not column_exists('cons_processes', 'print_start_col'):
|
||||
conn.execute('ALTER TABLE cons_processes ADD COLUMN print_start_col TEXT DEFAULT "A"')
|
||||
print(" Added print_start_col")
|
||||
if not column_exists('cons_processes', 'print_end_col'):
|
||||
conn.execute('ALTER TABLE cons_processes ADD COLUMN print_end_col TEXT')
|
||||
print(" Added print_end_col")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# List of all migrations in order
|
||||
# List of CORE migrations only
|
||||
MIGRATIONS = [
|
||||
(1, 'add_modules_tables', migration_001_add_modules_tables),
|
||||
(2, 'add_usermodules_granted_columns', migration_002_add_usermodules_granted_columns),
|
||||
(3, 'add_default_modules', migration_003_add_default_modules),
|
||||
(4, 'assign_modules_to_admins', migration_004_assign_modules_to_admins),
|
||||
(5, 'add_cons_process_fields_duplicate_key', migration_005_add_cons_process_fields_duplicate_key),
|
||||
(6, 'add_is_deleted_to_locationcounts', migration_006_add_is_deleted_to_locationcounts),
|
||||
(7, 'add_detail_end_row', migration_007_add_detail_end_row),
|
||||
(8, 'add_page_height', migration_008_add_page_height),
|
||||
(9, 'add_print_columns', migration_009_add_print_columns),
|
||||
(3, 'add_module_registry', migration_003_add_module_registry),
|
||||
]
|
||||
|
||||
|
||||
def run_migrations():
|
||||
"""Run all pending migrations"""
|
||||
print("🔄 Checking database migrations...")
|
||||
"""Run all pending core migrations"""
|
||||
print("🔄 Checking core database migrations...")
|
||||
|
||||
# Make sure migrations table exists
|
||||
init_migrations_table()
|
||||
@@ -278,10 +178,10 @@ def run_migrations():
|
||||
pending = [(v, n, f) for v, n, f in MIGRATIONS if v not in applied]
|
||||
|
||||
if not pending:
|
||||
print("✅ Database is up to date")
|
||||
print("✅ Core database is up to date")
|
||||
return
|
||||
|
||||
print(f"📦 Running {len(pending)} migration(s)...")
|
||||
print(f"📦 Running {len(pending)} core migration(s)...")
|
||||
|
||||
for version, name, func in pending:
|
||||
print(f"\n Migration {version}: {name}")
|
||||
@@ -293,8 +193,8 @@ def run_migrations():
|
||||
print(f" ❌ Migration {version} failed: {e}")
|
||||
raise
|
||||
|
||||
print("\n✅ All migrations complete")
|
||||
print("\n✅ All core migrations complete")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_migrations()
|
||||
run_migrations()
|
||||
Reference in New Issue
Block a user