Files
Aislo/D04_PlanConfirm/D04_PlanConfirm_Repository.py
T
eomsangdonandClaude Opus 5.5 7c94294f85 feat(563-2): D04 확정 · 업체 지정 · 내보내기 · 연결 = 조각 단위 — 옛 연도 행 프로젝트 그대로
- 단위 = plan_route_pieces 한 줄 · 조각 없는 연도 행 = 연도 하나 · 확정은 연도 단위 그대로
- 요약 pieces(이름 「본선 0+0~12+10」) · 업체 행 piece_id · 지정 회사 = 자기 조각만
- 내보내기 폴더 = 연도_선_조각 · 전체 = 선마다 레코드 · 좌표표 선 · 조각 칸
- 확정 메일 = 업체마다 자기 조각 zip · 연결 · 가져오기 = 조각 꺾은선
- 받은 예정노선 목록 = 조각 이름 · 길이
- 시험 531 둘 = 조각 없음 가짜 더함 · 새 563_2 지킴 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:37:18 +09:00

160 lines
6.0 KiB
Python

"""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()