Merge remote-tracking branch 'origin/sub_laptop_1' into main_laptop_1

This commit is contained in:
2026-10-30 09:53:28 +09:00
18 changed files with 1032 additions and 798 deletions
+23 -12
View File
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta
from typing import Any
import aiomysql
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query
from common_util.common_util_auth import is_forest_email, verify_session
from config.config_db import get_db_pool
@@ -138,22 +138,33 @@ async def _company(cursor: Any, company_id: int) -> dict[str, Any]:
async def _forest(cursor: Any, company_id: int) -> dict[str, Any]:
"""예정노선 연도 상태별 수(분석 전 · 분석됨 · 확정) · 업체 지정 대기(확정 · 업체 없음)."""
"""예정노선 수 — 분석 전 = 노선을 아직 안 그은 프로젝트 · 분석됨 · 확정 = 연도(설계 내용 파일) ·
업체 지정 대기 = 확정 연도 중 그 연도 조각에 업체가 하나도 없는 것."""
from D03_PlanAnalysis import D03_PlanAnalysis_Store as store
await cursor.execute(
"""SELECT y.route_json IS NOT NULL AS analyzed, y.confirmed_at IS NOT NULL AS confirmed,
EXISTS (SELECT 1 FROM plan_route_assignees a
WHERE a.project_id = y.project_id AND a.year = y.year) AS assigned
FROM plan_route_years y
JOIN projects p ON p.id = y.project_id AND p.deleted_at IS NULL
WHERE p.company_id = %s""",
"""SELECT p.id, a.piece_id FROM projects p
LEFT JOIN plan_route_assignees a ON a.project_id = p.id
WHERE p.company_id = %s AND p.project_kind = 'PLAN' AND p.deleted_at IS NULL""",
(company_id,),
)
assigned: dict[str, set] = {}
for r in await cursor.fetchall():
assigned.setdefault(str(r["id"]), set()).add(r["piece_id"])
status = {"draft": 0, "analyzed": 0, "confirmed": 0}
waiting = 0
for r in await cursor.fetchall():
key = "confirmed" if r["confirmed"] else "analyzed" if r["analyzed"] else "draft"
status[key] += 1
waiting += bool(r["confirmed"] and not r["assigned"])
for project_id, pieces in assigned.items():
try:
state = await store.plan_state(project_id)
except HTTPException: # 노선 파일이 틀린 프로젝트 = 건너뜀
continue
if not state["plan"]["lines"]:
status["draft"] += 1
for year, row in state["years"].items():
key = "confirmed" if row["confirmed_at"] else "analyzed"
status[key] += 1
ids = {p["id"] for p in row["route"]["pieces"]}
waiting += bool(row["confirmed_at"] and not ids & pieces)
return {"plan_status": status, "assign_waiting": waiting}
+16 -4
View File
@@ -232,6 +232,17 @@ def _link(kind: str, row: dict[str, Any], note: str | None = None) -> dict[str,
}
async def _plan_year_note(project_id: str, piece_id: str | None) -> str | None:
"""「N 년」 — 그 조각의 연도(설계 내용 파일) · 조각 · 파일이 없으면 None."""
from D04_PlanConfirm.D04_PlanConfirm_Link import piece_route
try:
year = (await piece_route(project_id, piece_id))["year"]
except HTTPException:
return None
return f"{year} 년" if year else None
async def _links(cursor: Any, row: dict[str, Any]) -> list[dict[str, Any]]:
"""연결된 것 — 원본 · 이 프로젝트에서 나온 것 · 연결한 예정노선(원본과 같으면 한 번만)."""
links: list[dict[str, Any]] = []
@@ -249,16 +260,17 @@ async def _links(cursor: Any, row: dict[str, Any]) -> list[dict[str, Any]]:
)
links += [_link("derived", r) for r in await cursor.fetchall()]
await cursor.execute(
"""SELECT p.id, p.name, a.year FROM plan_route_assignees a
"""SELECT p.id, p.name, a.piece_id FROM plan_route_assignees a
JOIN projects p ON p.id = a.project_id AND p.deleted_at IS NULL
WHERE a.linked_project_id = %s ORDER BY a.year""",
WHERE a.linked_project_id = %s ORDER BY a.id""",
(row["id"],),
)
for r in await cursor.fetchall():
note = await _plan_year_note(str(r["id"]), r["piece_id"])
if r["id"] == source:
links[0]["note"] = f"{r['year']} 년"
links[0]["note"] = note
else:
links.append(_link("plan", r, f"{r['year']} 년"))
links.append(_link("plan", r, note))
return links