"""D01 예정노선 등록 API · 대시보드 「받은 예정노선」 (요구 345 · 요구서 3-1 · 2-2).""" from typing import Any import aiomysql from fastapi import APIRouter, Depends, HTTPException, Request from pydantic import BaseModel, Field from B05_Profile.B05_Profile_Engine_Grade import resolve_design_speed from B05_Profile.B05_Profile_PageSettings import grade_class_of from common_util.common_util_auth import is_forest_service, require_company from common_util.common_util_project_kind import create_kind_project from config.config_db import get_db_pool router = APIRouter(prefix="/api/d01", tags=["D01_PlanRegister"]) dashboard_router = APIRouter(prefix="/api/dashboard", tags=["D01_PlanRegister"]) class CreatePlanRequest(BaseModel): project_name: str = Field(..., min_length=1, max_length=255) project_year: int = Field(..., ge=2000, le=2100) project_year_end: int | None = Field(default=None, ge=2000, le=2100) region: str = Field(..., min_length=1, max_length=100) road_type: str = Field(..., pattern="^(main|fire|work)$") design_speed_kph: int | None = None start_chainage_m: float | None = Field(default=None, ge=0) end_chainage_m: float | None = Field(default=None, ge=0) @router.post("/project") async def post_plan_project( request: Request, payload: CreatePlanRequest, session: dict[str, Any] = Depends(require_company), ) -> dict[str, str]: """예정노선 프로젝트 생성 — 산림청(시스템관리자 · *.go.kr)만.""" if not is_forest_service(session): raise HTTPException(status_code=403, detail="예정노선은 산림청만 만들 수 있습니다.") if session.get("company_id") is None: raise HTTPException(status_code=403, detail="회사 연결이 필요합니다.") year_end = payload.project_year_end or payload.project_year if year_end < payload.project_year: raise HTTPException(status_code=400, detail="사업연도 끝이 시작보다 앞입니다.") start_m, end_m = payload.start_chainage_m, payload.end_chainage_m if start_m is not None and end_m is not None and start_m >= end_m: raise HTTPException( status_code=400, detail="노선 시작 누가거리는 종료 누가거리보다 작아야 합니다." ) speed = resolve_design_speed(grade_class_of(payload.road_type, {}), payload.design_speed_kph) project_id = await create_kind_project( request=request, user_id=int(session["user_id"]), company_id=int(session["company_id"]), kind="PLAN", name=payload.project_name.strip(), region=payload.region.strip(), road_type=payload.road_type, project_year=payload.project_year, project_year_end=year_end, design_speed_kph=speed, route_start_m=start_m, route_end_m=end_m, ) return {"project_id": project_id} @dashboard_router.get("/plan-routes/assigned") async def assigned_plan_routes( session: dict[str, Any] = Depends(require_company), ) -> dict[str, Any]: """우리 회사에 지정된 예정노선 연도 — 보기 · 연결 상태.""" async with ( get_db_pool().acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor, ): await cursor.execute( """SELECT a.project_id, p.name AS project_name, a.year, a.linked_project_id FROM plan_route_assignees a JOIN projects p ON p.id = a.project_id AND p.deleted_at IS NULL WHERE a.company_id = %s ORDER BY p.name, a.year""", (session.get("company_id"),), ) rows = await cursor.fetchall() # ponytail: update_available 자리만 — 지문 비교는 531-3. routes = [{**r, "update_available": False} for r in rows] return {"status": "success", "routes": routes}