This commit is contained in:
2026-07-25 10:01:55 +09:00
parent 42de855ab3
commit 5d6c6aa1be
15 changed files with 845 additions and 62 deletions
@@ -86,6 +86,63 @@ async def get_latest_section_options(
return options if isinstance(options, dict) else None
async def list_recent_company_projects(
connection: aiomysql.Connection, company_id: int, exclude_project_id: UUID
) -> list[dict[str, Any]]:
"""같은 회사의 최근 프로젝트 5개(최근 갱신순, 현재·삭제 프로젝트 제외).
설계값 보유 여부와 무관하게 보여준다 — 선택 시 설계값 조회에서 없으면 안내한다.
"""
async with connection.cursor(aiomysql.DictCursor) as cursor:
await cursor.execute(
"""
SELECT p.id AS project_id, p.name AS name
FROM projects p
WHERE p.company_id = %s
AND p.id <> %s
AND p.deleted_at IS NULL
ORDER BY p.updated_at DESC
LIMIT 5
""",
(company_id, str(exclude_project_id)),
)
rows = await cursor.fetchall()
return [{"project_id": row["project_id"], "name": row["name"]} for row in rows]
async def get_project_standard_cross_section(
connection: aiomysql.Connection, company_id: int, project_id: UUID
) -> dict[str, Any] | None:
"""회사 스코프로 특정 프로젝트의 표준횡단 설정값을 반환한다(타 회사 접근 차단).
company_id 조건으로 남의 회사 프로젝트 값은 조회되지 않는다(권한 강제).
"""
async with connection.cursor() as cursor:
await cursor.execute(
"""
SELECT l.data
FROM longitudinal_sections l
JOIN projects p ON p.id = l.project_id
WHERE l.project_id = %s
AND p.company_id = %s
AND p.deleted_at IS NULL
AND JSON_EXTRACT(l.data, '$.options.standard_cross_section') IS NOT NULL
ORDER BY l.id DESC
LIMIT 1
""",
(str(project_id), company_id),
)
row = await cursor.fetchone()
if not row or not row[0]:
return None
data = row[0]
if isinstance(data, str):
data = json.loads(data)
options = data.get("options") if isinstance(data, dict) else None
standard = options.get("standard_cross_section") if isinstance(options, dict) else None
return standard if isinstance(standard, dict) else None
async def get_latest_grade_options(
connection: aiomysql.Connection, project_id: UUID
) -> dict[str, Any] | None:
@@ -274,11 +331,14 @@ async def update_cross_section_design(
route_id: int,
chainage_m: float,
design: dict[str, Any],
project_id: UUID | None = None,
) -> bool:
"""측점 하나의 data.design(잠정 설계 지정·단면적)을 병합 저장한다.
기존 data 요약을 보존하고 design 키만 갱신한다. 대상 측점을 chainage 근사로
찾으며(부동소수 오차 허용), 갱신 여부를 반환한다.
구조물(비정규) 측점은 B05 확정이 파일만 쓰고 DB 행을 만들지 않으므로, 행이 없고
project_id가 오면 새 행을 삽입한다(upsert — 구조물 측점 설계 저장 보장).
"""
async with connection.cursor() as cursor:
await cursor.execute(
@@ -292,7 +352,21 @@ async def update_cross_section_design(
)
row = await cursor.fetchone()
if not row:
return False
if project_id is None:
return False
await cursor.execute(
"""
INSERT INTO cross_sections (project_id, route_id, chainage_m, data, status)
VALUES (%s, %s, %s, %s, 'DRAFT')
""",
(
str(project_id),
route_id,
chainage_m,
json.dumps({"design": design}, ensure_ascii=False),
),
)
return True
data = row[1]
if isinstance(data, str):
data = json.loads(data)