""" CRUD для таблицы scenario_definitions. Функции: list_definitions(client_id, stand) — список активных get_definition(def_id, cid, stand) — один create_definition(cid, stand, name, steps, updated_by) → id update_definition(def_id, cid, stand, name, steps, version, updated_by) → ok|conflict delete_definition(def_id, cid, stand) — мягкое (is_active=false) lock_check(cid, stand) — есть ли RUNNING запуск → 409 если да """ import json from db.pool import get_conn, put_conn def list_definitions(client_id, stand): conn = get_conn() if not conn: return [] try: cur = conn.cursor() cur.execute(""" SELECT id, name, steps, version, is_active, created_at, updated_at, client_id, stand FROM scenario_definitions WHERE is_active = TRUE AND ((client_id = %s AND stand = %s) OR (client_id = '' AND stand = '')) ORDER BY name """, (client_id, stand)) rows = cur.fetchall() cols = [d[0] for d in cur.description] result = [] for r in rows: d = dict(zip(cols, r)) d["created_at"] = d["created_at"].isoformat() if d["created_at"] else None d["updated_at"] = d["updated_at"].isoformat() if d["updated_at"] else None d["is_seed"] = (d.get("client_id") == "" and d.get("stand") == "") result.append(d) return result except Exception as e: print(f"[DEFS] list error: {e}", flush=True) return [] finally: put_conn(conn) def get_definition(def_id, client_id, stand): conn = get_conn() if not conn: return None try: cur = conn.cursor() cur.execute(""" SELECT id, name, steps, version, is_active, created_at, updated_at, updated_by FROM scenario_definitions WHERE id = %s AND ((client_id = %s AND stand = %s) OR (client_id = '' AND stand = '')) """, (def_id, client_id, stand)) row = cur.fetchone() if not row: return None cols = [d[0] for d in cur.description] d = dict(zip(cols, row)) d["created_at"] = d["created_at"].isoformat() if d["created_at"] else None d["updated_at"] = d["updated_at"].isoformat() if d["updated_at"] else None return d except Exception as e: print(f"[DEFS] get error: {e}", flush=True) return None finally: put_conn(conn) def create_definition(client_id, stand, name, steps, updated_by=""): conn = get_conn() if not conn: return None try: cur = conn.cursor() cur.execute(""" INSERT INTO scenario_definitions (client_id, stand, name, steps, updated_by) VALUES (%s, %s, %s, %s, %s) ON CONFLICT (client_id, stand, LOWER(name)) DO UPDATE SET steps = EXCLUDED.steps, version = scenario_definitions.version + 1, is_active = TRUE, updated_at = NOW(), updated_by = EXCLUDED.updated_by RETURNING id, version """, (client_id, stand, name, json.dumps(steps), updated_by)) row = cur.fetchone() conn.commit() cur.close() return {"id": row[0], "version": row[1]} except Exception as e: print(f"[DEFS] create error: {e}", flush=True) conn.rollback() return None finally: put_conn(conn) def update_definition(def_id, client_id, stand, name, steps, version, updated_by=""): """Возвращает dict {ok, version} или {conflict: True}.""" conn = get_conn() if not conn: return {"error": "no db"} try: cur = conn.cursor() cur.execute(""" UPDATE scenario_definitions SET name = %s, steps = %s, version = version + 1, updated_at = NOW(), updated_by = %s WHERE id = %s AND client_id = %s AND stand = %s AND version = %s RETURNING version """, (name, json.dumps(steps), updated_by, def_id, client_id, stand, version)) row = cur.fetchone() conn.commit() cur.close() if row: return {"ok": True, "version": row[0]} return {"conflict": True} except Exception as e: print(f"[DEFS] update error: {e}", flush=True) conn.rollback() return {"error": str(e)} finally: put_conn(conn) def delete_definition(def_id, client_id, stand): """Мягкое удаление.""" conn = get_conn() if not conn: return False try: cur = conn.cursor() cur.execute(""" UPDATE scenario_definitions SET is_active = FALSE, updated_at = NOW() WHERE id = %s AND client_id = %s AND stand = %s """, (def_id, client_id, stand)) ok = cur.rowcount > 0 conn.commit() cur.close() return ok except Exception as e: print(f"[DEFS] delete error: {e}", flush=True) conn.rollback() return False finally: put_conn(conn) def lock_check(client_id, stand): """Проверить нет ли активного RUNNING запуска. True = можно запускать.""" conn = get_conn() if not conn: return True # без БД — разрешаем try: cur = conn.cursor() cur.execute(""" SELECT id FROM scenario_runs WHERE client_id = %s AND stand = %s AND status = 'RUNNING' LIMIT 1 """, (client_id, stand)) row = cur.fetchone() cur.close() return row is None except Exception as e: print(f"[DEFS] lock_check error: {e}", flush=True) return True finally: put_conn(conn)