""" Migration: thêm các cột Hikvision vào bảng attendance. R8.3: ALTER thay vì DROP — không phá dữ liệu hiện có. R3.8: Backup DB trước khi migrate. Idempotent: chạy nhiều lần OK (PRAGMA table_info kiểm tra trước khi ALTER). Chạy: python migrate_add_cols.py """ import os, sys, io, sqlite3, shutil from datetime import datetime try: sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') except Exception: pass BASE = os.path.dirname(os.path.abspath(__file__)) DB = os.path.join(BASE, 'data', 'attendance.db') BACKUP_DIR = os.path.join(BASE, 'data', 'backups') # Cột mới (tên SQL : kiểu) NEW_COLS = { 'card_reader_no': 'TEXT', # 1 = vào, 2 = ra (tùy cấu hình thiết bị) 'event_type': 'INTEGER', 'sub_event_type': 'INTEGER', 'current_verify_mode': 'TEXT', # face/card/fingerprint/... } def main(): if not os.path.exists(DB): print(f'DB không tồn tại: {DB}') return 1 # R3.8: Backup os.makedirs(BACKUP_DIR, exist_ok=True) bk = os.path.join(BACKUP_DIR, f'attendance_premigrate_{datetime.now().strftime("%Y%m%d_%H%M%S")}.db') shutil.copy(DB, bk) print(f'[1/3] Backup: {bk}') conn = sqlite3.connect(DB) c = conn.cursor() c.execute("PRAGMA table_info(attendance)") existing = {r[1] for r in c.fetchall()} print(f'[2/3] Cột hiện có: {sorted(existing)}') added = [] skipped = [] for col, typ in NEW_COLS.items(): if col in existing: skipped.append(col) continue c.execute(f'ALTER TABLE attendance ADD COLUMN {col} {typ}') added.append(col) conn.commit() conn.close() print(f'[3/3] Đã thêm: {added} | Bỏ qua (đã có): {skipped}') print() print('Bước tiếp:') print(' 1) Khởi động lại API server (start_server.bat).') print(' 2) Sync 1 ngày mới để verify 4 cột này có dữ liệu thực.') print(' (Data CŨ vẫn rỗng - là dữ liệu từ CSV import).') return 0 if __name__ == '__main__': sys.exit(main())