""" Migration Script: Make CURRENT baseline global (not session-specific) Run this once to update your database schema """ import sqlite3 import os # Database path DB_PATH = os.path.join(os.path.dirname(__file__), 'database', 'scanlook.db') def run_migration(): print("Starting migration...") print(f"Database: {DB_PATH}") # Backup first! backup_path = DB_PATH + '.backup_before_current_migration' import shutil shutil.copy2(DB_PATH, backup_path) print(f"✅ Backup created: {backup_path}") conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() try: # Step 1: Create new global CURRENT table print("\nStep 1: Creating new BaselineInventory_Current table...") cursor.execute(''' CREATE TABLE IF NOT EXISTS BaselineInventory_Current_New ( current_id INTEGER PRIMARY KEY AUTOINCREMENT, lot_number TEXT NOT NULL, item TEXT NOT NULL, description TEXT, system_location TEXT, system_bin TEXT NOT NULL, system_quantity REAL NOT NULL, upload_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE(lot_number, system_bin) ) ''') print("✅ New CURRENT table created") # Step 2: Copy latest CURRENT data print("\nStep 2: Copying latest CURRENT data...") cursor.execute(''' INSERT OR IGNORE INTO BaselineInventory_Current_New (lot_number, item, description, system_location, system_bin, system_quantity, upload_timestamp) SELECT lot_number, item, description, system_location, system_bin, system_quantity, uploaded_timestamp FROM BaselineInventory_Current WHERE is_deleted = 0 AND baseline_version = (SELECT MAX(baseline_version) FROM BaselineInventory_Current WHERE is_deleted = 0) ''') rows = cursor.rowcount print(f"✅ Copied {rows} CURRENT baseline records") # Step 3: Drop old CURRENT table print("\nStep 3: Dropping old CURRENT table...") cursor.execute('DROP TABLE IF EXISTS BaselineInventory_Current') print("✅ Old CURRENT table dropped") # Step 4: Rename new table print("\nStep 4: Renaming new CURRENT table...") cursor.execute('ALTER TABLE BaselineInventory_Current_New RENAME TO BaselineInventory_Current') print("✅ Table renamed") # Step 5: Create new ScanEntries without CURRENT columns print("\nStep 5: Creating new ScanEntries table (without CURRENT columns)...") cursor.execute(''' CREATE TABLE ScanEntries_New ( entry_id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL, location_count_id INTEGER NOT NULL, lot_number TEXT NOT NULL, item TEXT, description TEXT, scanned_location TEXT NOT NULL, actual_weight REAL NOT NULL, scanned_by INTEGER NOT NULL, scan_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, master_status TEXT, master_expected_location TEXT, master_expected_weight REAL, master_variance_lbs REAL, master_variance_pct REAL, duplicate_status TEXT DEFAULT '00', duplicate_info TEXT, comment TEXT, is_deleted INTEGER DEFAULT 0, deleted_by INTEGER, deleted_timestamp DATETIME, modified_timestamp DATETIME, FOREIGN KEY (session_id) REFERENCES CountSessions(session_id), FOREIGN KEY (location_count_id) REFERENCES LocationCounts(location_count_id), FOREIGN KEY (scanned_by) REFERENCES Users(user_id), FOREIGN KEY (deleted_by) REFERENCES Users(user_id) ) ''') print("✅ New ScanEntries table created") # Step 6: Copy scan data print("\nStep 6: Copying scan data...") cursor.execute(''' INSERT INTO ScanEntries_New SELECT entry_id, session_id, location_count_id, lot_number, item, description, scanned_location, actual_weight, scanned_by, scan_timestamp, master_status, master_expected_location, master_expected_weight, master_variance_lbs, master_variance_pct, duplicate_status, duplicate_info, comment, is_deleted, deleted_by, deleted_timestamp, modified_timestamp FROM ScanEntries ''') rows = cursor.rowcount print(f"✅ Copied {rows} scan entries") # Step 7: Drop old and rename print("\nStep 7: Replacing old ScanEntries table...") cursor.execute('DROP TABLE ScanEntries') cursor.execute('ALTER TABLE ScanEntries_New RENAME TO ScanEntries') print("✅ ScanEntries table updated") # Step 8: Recreate indexes print("\nStep 8: Recreating indexes...") cursor.execute('CREATE INDEX idx_scanentries_session ON ScanEntries(session_id)') cursor.execute('CREATE INDEX idx_scanentries_location ON ScanEntries(location_count_id)') cursor.execute('CREATE INDEX idx_scanentries_lot ON ScanEntries(lot_number)') cursor.execute('CREATE INDEX idx_scanentries_deleted ON ScanEntries(is_deleted)') print("✅ Indexes created") # Step 9: Create new CountSessions print("\nStep 9: Creating new CountSessions table...") cursor.execute(''' CREATE TABLE CountSessions_New ( session_id INTEGER PRIMARY KEY AUTOINCREMENT, session_name TEXT NOT NULL, session_type TEXT NOT NULL CHECK(session_type IN ('cycle_count', 'full_physical')), created_by INTEGER NOT NULL, created_timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, master_baseline_timestamp DATETIME, current_baseline_timestamp DATETIME, status TEXT DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')), branch TEXT DEFAULT 'Main', FOREIGN KEY (created_by) REFERENCES Users(user_id) ) ''') print("✅ New CountSessions table created") # Step 10: Copy session data print("\nStep 10: Copying session data...") cursor.execute(''' INSERT INTO CountSessions_New SELECT session_id, session_name, session_type, created_by, created_timestamp, master_baseline_timestamp, current_baseline_timestamp, status, branch FROM CountSessions ''') rows = cursor.rowcount print(f"✅ Copied {rows} sessions") # Step 11: Replace CountSessions print("\nStep 11: Replacing old CountSessions table...") cursor.execute('DROP TABLE CountSessions') cursor.execute('ALTER TABLE CountSessions_New RENAME TO CountSessions') print("✅ CountSessions table updated") # Commit all changes conn.commit() print("\n" + "="*50) print("🎉 MIGRATION COMPLETE!") print("="*50) print("\nChanges made:") print(" ✅ BaselineInventory_Current is now GLOBAL (not session-specific)") print(" ✅ Removed CURRENT columns from ScanEntries") print(" ✅ Removed current_baseline_version from CountSessions") print(" ✅ CURRENT data will now always show latest via JOIN") print("\nNext steps:") print(" 1. Replace app.py with updated version") print(" 2. Restart Flask") print(" 3. Test uploading CURRENT baseline") except Exception as e: print(f"\n❌ ERROR: {e}") print("Rolling back...") conn.rollback() print("Migration failed. Database unchanged.") print(f"Backup available at: {backup_path}") raise finally: conn.close() if __name__ == '__main__': print("="*50) print("CURRENT Baseline Migration") print("="*50) response = input("\nThis will modify your database structure. Continue? (yes/no): ") if response.lower() == 'yes': run_migration() else: print("Migration cancelled.")