""" Migrazione LINKER_REPOSITORY da Access a SQLite. Eseguire una sola volta dalla root di MyICR_Suite: python\python.exe app\modules\linker\migrate_linker.py Legge da: config.json → db_path (Access .accdb) Scrive in: sync_db\linker.db (creato se non esiste) """ import json import logging import sqlite3 import sys from pathlib import Path logging.basicConfig(level=logging.INFO, format='%(levelname)s %(message)s') logger = logging.getLogger(__name__) _HERE = Path(__file__).resolve().parent _APP_ROOT = _HERE.parent.parent.parent # MyICR_Suite/ _CONFIG_PATH = _HERE / 'config.json' _DEST_DB = _APP_ROOT / 'sync_db' / 'linker.db' DDL = """ CREATE TABLE IF NOT EXISTS LINKER_REPOSITORY ( id INTEGER PRIMARY KEY AUTOINCREMENT, FORNITORE TEXT NOT NULL, TITOLO TEXT NOT NULL, TIPO_TITOLO TEXT DEFAULT 'TO', ANNO TEXT, REGISTA TEXT, RIFER TEXT, COD_IMDB TEXT, data_creazione TEXT, data_update TEXT, UNIQUE(FORNITORE, TITOLO) ); """ def main(): config = json.loads(_CONFIG_PATH.read_text(encoding='utf-8')) access_path = config.get('db_path', '') if not access_path: logger.error("db_path non trovato in config.json"); sys.exit(1) if not Path(access_path).exists(): logger.error(f"File Access non trovato: {access_path}"); sys.exit(1) try: import pyodbc except ImportError: logger.error("pyodbc non disponibile"); sys.exit(1) conn_str = ( f"DRIVER={{Microsoft Access Driver (*.mdb, *.accdb)}};" f"DBQ={access_path};Exclusive=0;" ) logger.info(f"Connessione a Access: {access_path}") try: src = pyodbc.connect(conn_str, timeout=15) except pyodbc.Error as e: logger.error(f"Connessione Access fallita: {e}"); sys.exit(1) cursor = src.cursor() cursor.execute("SELECT * FROM LINKER_REPOSITORY") cols = [c[0] for c in cursor.description] rows = cursor.fetchall() src.close() logger.info(f"Lette {len(rows)} righe da Access — colonne: {cols}") _DEST_DB.parent.mkdir(parents=True, exist_ok=True) dst = sqlite3.connect(str(_DEST_DB)) dst.execute(DDL) dst.commit() inserted = updated = skipped = 0 for row in rows: rec = dict(zip(cols, row)) fornitore = str(rec.get('FORNITORE') or '').strip() titolo = str(rec.get('TITOLO') or '').strip() if not fornitore or not titolo: skipped += 1 continue def _s(v): if v is None: return None s = str(v).strip() # normalizza float "20345.0" → "20345" if s.endswith('.0') and s[:-2].isdigit(): s = s[:-2] return s or None # Access può restituire datetime objects — converti a stringa ISO def _dt(v): if v is None: return None if hasattr(v, 'isoformat'): return v.isoformat(sep=' ') return str(v) params = ( fornitore, titolo, _s(rec.get('TIPO_TITOLO')) or 'TO', _s(rec.get('ANNO')), _s(rec.get('REGISTA')), _s(rec.get('RIFER')), _s(rec.get('COD_IMDB')), _dt(rec.get('data_creazione')), _dt(rec.get('data_update')), ) try: dst.execute( "INSERT INTO LINKER_REPOSITORY " "(FORNITORE,TITOLO,TIPO_TITOLO,ANNO,REGISTA,RIFER,COD_IMDB,data_creazione,data_update) " "VALUES (?,?,?,?,?,?,?,?,?)", params, ) inserted += 1 except sqlite3.IntegrityError: dst.execute( "UPDATE LINKER_REPOSITORY " "SET TIPO_TITOLO=?,ANNO=?,REGISTA=?,RIFER=?,COD_IMDB=?,data_update=? " "WHERE FORNITORE=? AND TITOLO=?", (params[2], params[3], params[4], params[5], params[6], params[8], fornitore, titolo), ) updated += 1 dst.commit() dst.close() logger.info( f"Migrazione completata → {_DEST_DB}\n" f" inseriti: {inserted} aggiornati: {updated} saltati: {skipped}" ) if __name__ == '__main__': main()