"""D04 확정 · 업체 지정 DB (Raw SQL · 표는 db_management/020_project_groups.sql).""" from __future__ import annotations import json from typing import Any import aiomysql from config.config_db import get_db_pool async def get_plan_project(project_id: str) -> dict[str, Any] | None: async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( "SELECT id, name, region, project_kind, route_start_m FROM projects" " WHERE id = %s AND deleted_at IS NULL", (project_id,), ) return await cursor.fetchone() async def list_year_routes(project_id: str) -> list[dict[str, Any]]: """연도 구간(오름차순) — `route_json` 은 파싱해서 돌려줌(없으면 None).""" async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( "SELECT year, epsg, route_json, fingerprint, confirmed_at FROM plan_route_years" " WHERE project_id = %s ORDER BY year", (project_id,), ) rows = await cursor.fetchall() for row in rows: raw = row["route_json"] row["route_json"] = json.loads(raw) if isinstance(raw, str | bytes) else raw return rows async def list_pieces(project_id: str) -> list[dict[str, Any]]: """조각 행(연도 · 선 차례 · 선 위 거리 차례) + `line_name`(본선 · 갈래 N = 정본 선 차례).""" async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( "SELECT piece_id, line_id, year, from_m, to_m, length_m, epsg, route_json" " FROM plan_route_pieces WHERE project_id = %s", (project_id,), ) rows = list(await cursor.fetchall()) await cursor.execute( "SELECT network_json FROM plan_networks WHERE project_id = %s", (project_id,) ) network = await cursor.fetchone() lines = [] if network: raw = network["network_json"] body = json.loads(raw) if isinstance(raw, str | bytes) else raw lines = [line["id"] for line in (body or {}).get("lines", [])] order = {line: i for i, line in enumerate(lines)} for row in rows: raw = row["route_json"] row["route_json"] = json.loads(raw) if isinstance(raw, str | bytes) else raw index = order.get(row["line_id"], len(order)) row["line_name"] = "본선" if index == 0 else f"갈래 {index}" rows.sort(key=lambda r: (r["year"], order.get(r["line_id"], len(order)), r["from_m"])) return rows async def list_assignees(project_id: str) -> list[dict[str, Any]]: async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( "SELECT id, year, piece_id, company_id, company_name, email, notified_at," " linked_project_id" " FROM plan_route_assignees WHERE project_id = %s ORDER BY year, id", (project_id,), ) return await cursor.fetchall() async def get_company_contact(company_id: int) -> dict[str, Any] | None: """등록 회사 이름 + 대표 메일(회사 마스터 사용자).""" async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( "SELECT c.id, c.name, u.email FROM companies c" " LEFT JOIN users u ON u.id = c.master_user_id AND u.deleted_at IS NULL" " WHERE c.id = %s AND c.deleted_at IS NULL", (company_id,), ) return await cursor.fetchone() async def apply_assignee_changes( project_id: str, delete_ids: list[int], updates: list[dict], inserts: list[dict] ) -> None: async with get_db_pool().acquire() as connection, connection.cursor() as cursor: await connection.begin() try: for assignee_id in delete_ids: await cursor.execute( "DELETE FROM plan_route_assignees WHERE id = %s AND project_id = %s", (assignee_id, project_id), ) for row in updates: await cursor.execute( "UPDATE plan_route_assignees SET company_name = %s, email = %s" " WHERE id = %s AND project_id = %s", (row["company_name"], row["email"], row["id"], project_id), ) for row in inserts: await cursor.execute( "INSERT INTO plan_route_assignees" " (project_id, year, piece_id, company_id, company_name, email)" " VALUES (%s,%s,%s,%s,%s,%s)", ( project_id, row["year"], row.get("piece_id"), row["company_id"], row["company_name"], row["email"], ), ) await connection.commit() except Exception: await connection.rollback() raise async def mark_confirmed(project_id: str, years: list[int]) -> None: if not years: return marks = ",".join(["%s"] * len(years)) async with get_db_pool().acquire() as connection, connection.cursor() as cursor: await cursor.execute( f"UPDATE plan_route_years SET confirmed_at = NOW() WHERE project_id = %s" f" AND year IN ({marks})", (project_id, *years), ) await connection.commit() async def mark_notified(assignee_id: int) -> None: async with get_db_pool().acquire() as connection, connection.cursor() as cursor: await cursor.execute( "UPDATE plan_route_assignees SET notified_at = NOW() WHERE id = %s", (assignee_id,) ) await connection.commit()