refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)
B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음). 화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음. B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry 로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠. B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져 부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음. B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함. B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -74,15 +74,13 @@ async def haul_inputs_for(project_id: Any) -> dict[str, Any]:
|
||||
|
||||
⚠ **여기서 다시 세지 않는다** — 두 값 다 B08 전개에서 나오는 것이라 이쪽이 세면
|
||||
같은 계산이 두 벌이 된다(CLAUDE.md 5장). 못 읽으면 빈 값으로 두고 **0 으로 눅이지 않는다**.
|
||||
"""
|
||||
try:
|
||||
from B08_Quantity.B08_Quantity_Router_Material import project_haul_inputs
|
||||
|
||||
data = await project_haul_inputs(project_id)
|
||||
return data if isinstance(data, dict) else {}
|
||||
except Exception:
|
||||
logger.exception("구조물 몫(공제·잔토) 조회 실패 — 값 없이 진행: project_id=%s", project_id)
|
||||
return {}
|
||||
⚠ **지금은 늘 빈 값이다**(PLAN 7-3) — B08 구조물 전개가 `old_code/B08_Quantity/` 로 가고
|
||||
화면이 빈 틀이라 낼 값이 없다. 값을 지어내지 않고 빈 값으로 두어 화면은 그대로 선다.
|
||||
B08 을 마스터 얼개로 다시 지을 때 그쪽이 낼 값을 여기로 이으면 된다.
|
||||
"""
|
||||
_ = project_id
|
||||
return {}
|
||||
|
||||
|
||||
async def conversion_factors_for(project_id: Any) -> dict[str, dict[str, float]]:
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
표가 `source["quantities"]` 를 보는데 **그 키가 원본 파일에 아예 없다**(실측:
|
||||
`cross_00960m.json` 에 `samples`·`center_z`·`frame` 뿐). 반면 **저장된 횡단 설계에는
|
||||
단면적이 그대로 있고**(`cut_soil_area_m2` 등), **사면길이도 그 설계선에서 유도된다**
|
||||
(`B08_Quantity_Engine_SlopeLength.station_slope`). 즉 **통로만 없었다.**
|
||||
(`B07_DesignDetail_Engine_SlopeGeometry.station_slope`). 즉 **통로만 없었다.**
|
||||
|
||||
⚠ 계산을 새로 짜지 않는다 (CLAUDE.md 5장)
|
||||
단면적은 B06 이 낸 저장값을 **그대로 읽고**, 사면 계열은 **B08 이 쓰는 그 함수**를 부른다.
|
||||
계열 이름과 「어느 면을 쓰나」도 `B08_Quantity_Engine_SlopeArea` 의 정의를 빌려 쓴다 —
|
||||
거기서 밑수가 바뀌면 이 표도 같이 움직여야 하기 때문이다.
|
||||
단면적은 B06 이 낸 저장값을 **그대로 읽고**, 사면 계열은 `_SlopeGeometry` 의 함수를 부른다.
|
||||
계열 이름과 「어느 면을 쓰나」도 그 파일의 정의를 빌려 쓴다 — 거기서 밑수가 바뀌면 이 표도
|
||||
같이 움직여야 하기 때문이다. (옛 자리 = `old_code/B08_Quantity/…_SlopeLength`·`_SlopeArea`,
|
||||
PLAN 7-3 에서 B07 로 옮겨 적음.)
|
||||
|
||||
⚠ 사면 계열 칸의 **단위**
|
||||
B08 은 측점 사이를 평균단면적법으로 적분해 **면적(㎡)** 을 내지만, 그것은 두 측점이 있어야
|
||||
@@ -34,8 +35,12 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from B08_Quantity.B08_Quantity_Engine_SlopeArea import PROTECTION_SOURCE, _key, _length_of
|
||||
from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slope
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_SlopeGeometry import (
|
||||
PROTECTION_SOURCE,
|
||||
length_of,
|
||||
series_key,
|
||||
station_slope,
|
||||
)
|
||||
|
||||
#: 저장된 설계 단면적을 그대로 옮기는 칸 — `표 키 → 설계 키`.
|
||||
#: ⚠ 측구 둘은 B06 이 **절토 분리와 같은 근거**(지반 유형 + 암반 경계선)로 갈라 낸 값이다
|
||||
@@ -115,9 +120,9 @@ def derived_cells(chainage_m: float, design: dict[str, Any] | None) -> dict[str,
|
||||
|
||||
slope = station_slope(float(chainage_m), design)
|
||||
lengths = {
|
||||
_key(series, face): _length_of(slope, series, face)
|
||||
series_key(series, face): length_of(slope, series, face)
|
||||
for _table_key, series, face in SLOPE_KEYS
|
||||
}
|
||||
for table_key, series, face in SLOPE_KEYS:
|
||||
cells[table_key] = lengths[_key(series, face)]
|
||||
cells[table_key] = lengths[series_key(series, face)]
|
||||
return cells
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
"""횡단 사면 기하 — 저장된 횡단 설계선에서 절토·성토 사면 구간을 가려낸다.
|
||||
|
||||
어디서 왔나 (PLAN 7-3)
|
||||
옛 `B08_Quantity_Engine_SlopeLength` · `_SlopeArea` 에 있던 것을 **B07 이 쓰는 몫만**
|
||||
옮겨 적었다. 옛 코드는 `old_code/B08_Quantity/` 에 그대로 있다(지우지 않음).
|
||||
흐름이 B07 → B08 → B09 이므로 도면(B07)이 수량(B08)을 부르던 거꾸로를 여기서 끊는다.
|
||||
|
||||
옮겨 적지 않은 것 — 면적 적분(`build_rows`·`totals`·`build_table`) · 노면 면적
|
||||
(`road_surface_area`) · 측점 묶음(`station_slopes`). 수량 쪽 몫이라 B07 이 안 쓴다.
|
||||
|
||||
왜 유도하나 (B06 무접촉)
|
||||
설계 엔진이 `cut_slope_segments` 를 내기는 하나 **정본에 저장되지 않는다**. 저장되는 것은
|
||||
화면이 보낸 설계 지정이고 조회는 저장분을 그대로 싣는다. 그래서 그 값을 원천으로 쓰면
|
||||
사면적이 조용히 0 이 된다. 대신 **`design_line`(설계선 폴리라인) + 저장된 경사비**로
|
||||
유도한다. 필요한 입력이 전부 정본에 있어 B06 을 고치지 않아도 된다.
|
||||
|
||||
가려내는 방법
|
||||
노체 끝(`road_edges`)에서 바깥으로 나아가며, 구간 기울기가 **저장된 설계 경사비와 맞는
|
||||
동안**이 사면이다. 원지반은 기울기가 안 맞아 저절로 끊긴다. 2단 사면(암/토사)도 경사비가
|
||||
달라 그대로 갈린다.
|
||||
|
||||
⚠ 두 가지를 조심한다
|
||||
· **끝 조각은 딱 안 떨어진다** — 샘플 격자에 걸려 잘리면 `n=1.025` 처럼 나온다. 허용오차를 둔다.
|
||||
· **지형이 우연히 같은 경사면** 사면이 길게 잡힐 수 있다. 노체에서 바깥으로 **연속**인
|
||||
구간만 세고 끊기면 멈추는 것으로 막는다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
# 경사비 일치 허용오차(비율). 끝 조각이 격자에 잘려 생기는 오차를 덮는 크기다.
|
||||
_RATIO_TOLERANCE = 0.12
|
||||
# 평탄부로 볼 기울기 — 소단·측구 바닥은 오름이 거의 없다.
|
||||
_FLAT_RISE_M = 1e-6
|
||||
|
||||
# 법면보호공이 참조하는 계열 — 기본은 면고르기다(같은 사면길이를 쓴다).
|
||||
PROTECTION_SOURCE = "face_dressing"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class SlopeSegment:
|
||||
"""사면 한 조각. `side` 는 `left`/`right`, `role` 은 `cut`/`fill`."""
|
||||
|
||||
side: str
|
||||
role: str
|
||||
from_offset_m: float
|
||||
to_offset_m: float
|
||||
rise_m: float
|
||||
length_m: float
|
||||
ratio: float
|
||||
material: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class StationSlope:
|
||||
"""측점 하나의 사면길이 묶음."""
|
||||
|
||||
chainage_m: float
|
||||
cut_length_m: float = 0.0
|
||||
fill_length_m: float = 0.0
|
||||
#: 절토 사면길이의 토사·암 몫 — 면고르기가 9-19-1(토사)·9-19-2·3(암)으로 갈림.
|
||||
#: 둘의 합이 `cut_length_m` 보다 작으면 그 차이는 **못 가른 몫**(설계 지반 프리셋 없음).
|
||||
cut_soil_length_m: float = 0.0
|
||||
cut_rock_length_m: float = 0.0
|
||||
#: 층따기 밑수 — **원지반 표면**의 경사길이(m). B06 설계가 측점마다 낸다
|
||||
#: (`design.bench_cut_length_m`).
|
||||
#: ⚠ **성토 비탈면 길이와 다른 면이다** — 층따기는 성토부 **아래 원지반**을 계단으로
|
||||
#: 깎는 일이라(교본 6장 4절) 비탈면이 아니라 지표면을 따라간다.
|
||||
bench_cut_length_m: float = 0.0
|
||||
berm_width_m: float = 0.0
|
||||
# 성토고(m) — 성토 사면 조각들의 **수직 낙차 합**. 노면 끝에서 원지반까지 내려간 높이다.
|
||||
# ⚠ 좌우가 다르면 **큰 쪽**을 쓴다. 「중심점 성토고 5m 이상」(품셈 11-3 [주]①) 판정은
|
||||
# 가장 높은 쪽이 기준이고, 양쪽을 더하면 실제보다 두 배가 된다.
|
||||
fill_height_m: float = 0.0
|
||||
#: 노면 폭(m) — 노체 끝(`road_edges`) 좌우 사이.
|
||||
#: 노체 끝이 저장에 없으면 `None` — 0 으로 때우지 않는다.
|
||||
roadbed_width_m: float | None = None
|
||||
segments: tuple[SlopeSegment, ...] = ()
|
||||
# 사면이 샘플 범위 끝까지 원지반을 못 만나 **면적이 잘린** 측점.
|
||||
# 설계 엔진이 `slope_unclosed` 로 이미 경고하는 값을 그대로 물고 온다.
|
||||
unclosed: bool = False
|
||||
|
||||
|
||||
def _num(value: Any) -> float | None:
|
||||
return float(value) if isinstance(value, (int, float)) else None
|
||||
|
||||
|
||||
def _ratios(design: dict[str, Any]) -> dict[str, list[float]]:
|
||||
"""역할별로 받아들일 경사비 목록. 2단 사면이면 암·토사 둘 다 절토로 본다."""
|
||||
cut = [
|
||||
value
|
||||
for value in (
|
||||
_num(design.get("cut_slope_ratio")),
|
||||
_num(design.get("soil_cut_slope_ratio")),
|
||||
)
|
||||
if value is not None and value > 0
|
||||
]
|
||||
fill = [value for value in (_num(design.get("fill_slope_ratio")),) if value and value > 0]
|
||||
return {"cut": cut, "fill": fill}
|
||||
|
||||
|
||||
def _match(ratio: float, candidates: list[float]) -> float | None:
|
||||
"""구간 경사비가 후보 중 하나와 맞으면 그 후보를 돌려준다."""
|
||||
for candidate in candidates:
|
||||
if abs(ratio - candidate) <= max(_RATIO_TOLERANCE * candidate, _RATIO_TOLERANCE):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _outward(
|
||||
line: list[dict[str, float]], edge_offset: float, side: str
|
||||
) -> list[tuple[float, float, float, float]]:
|
||||
"""노체 끝에서 **바깥으로** 향하는 구간 목록 `(시작오프셋, 끝오프셋, run, rise)`.
|
||||
|
||||
좌측은 오프셋이 커지는 쪽, 우측은 작아지는 쪽이 바깥이다(설계선 좌표 관례).
|
||||
"""
|
||||
points = sorted(
|
||||
((float(p["offset_m"]), float(p["elevation_m"])) for p in line), key=lambda p: p[0]
|
||||
)
|
||||
if side == "left":
|
||||
outer = [p for p in points if p[0] >= edge_offset]
|
||||
else:
|
||||
outer = [p for p in points if p[0] <= edge_offset][::-1]
|
||||
return [
|
||||
(
|
||||
outer[i - 1][0],
|
||||
outer[i][0],
|
||||
abs(outer[i][0] - outer[i - 1][0]),
|
||||
outer[i][1] - outer[i - 1][1],
|
||||
)
|
||||
for i in range(1, len(outer))
|
||||
]
|
||||
|
||||
|
||||
def slope_start_offset(design: dict[str, Any], side: str) -> float | None:
|
||||
"""사면이 시작하는 오프셋 — 노체 끝, 측구가 있으면 **측구 바깥 끝**.
|
||||
|
||||
⚠ 이것이 없으면 **측구 바깥 벽이 사면으로 잡힌다.** 측구 벽 경사가 토사 절토비와 같으면
|
||||
그대로 걸린다. 측구는 노체 배수 시설이지 사면이 아니므로 그 바깥 끝에서부터 세어야 한다.
|
||||
"""
|
||||
edges = design.get("road_edges") or {}
|
||||
edge = _num((edges.get(side) or {}).get("offset_m"))
|
||||
if edge is None:
|
||||
return None
|
||||
if not design.get("ditch_enabled"):
|
||||
return edge
|
||||
ditch_side = design.get("ditch_side")
|
||||
if ditch_side not in (side, "both", None):
|
||||
return edge
|
||||
width = _num((design.get("ditch") or {}).get("top_width_m")) or 0.0
|
||||
# 좌측은 오프셋이 커지는 쪽, 우측은 작아지는 쪽이 바깥이다.
|
||||
return edge + width if side == "left" else edge - width
|
||||
|
||||
|
||||
def _material(design: dict[str, Any], ratio: float) -> str | None:
|
||||
"""경사비로 재료를 가른다 — 그린 대로 적는다(B06 `cut_slope_segments` 와 같은 규칙)."""
|
||||
if not design.get("two_stage_slope"):
|
||||
return None
|
||||
rock = _num(design.get("cut_slope_ratio"))
|
||||
soil = _num(design.get("soil_cut_slope_ratio"))
|
||||
if rock is None or soil is None or abs(rock - soil) < 1e-9:
|
||||
return None
|
||||
return "rock" if abs(ratio - rock) < abs(ratio - soil) else "soil"
|
||||
|
||||
|
||||
def _side_segments(
|
||||
design: dict[str, Any], side: str, ratios: dict[str, list[float]]
|
||||
) -> list[SlopeSegment]:
|
||||
"""한쪽 사면 구간 목록. 경사비가 안 맞는 구간을 만나면 거기서 멈춘다."""
|
||||
line = design.get("design_line") or []
|
||||
edge = slope_start_offset(design, side)
|
||||
if not line or edge is None:
|
||||
return []
|
||||
|
||||
berm = design.get("berm") or {}
|
||||
berm_width = _num(berm.get("width_m")) or 0.0
|
||||
# 소단 안쪽 기울기(°) 만큼은 평탄부로 봄 — 기울인 소단이 비탈로 읽혀 끊기지 않게.
|
||||
berm_grade = math.tan(math.radians(_num(berm.get("slope_deg")) or 0.0))
|
||||
# 설계선 표고는 넷째 자리 반올림 — 기울인 소단 조각이 그만큼 더 가파르게 읽힘.
|
||||
berm_slack = 2e-4 if berm_width > 0 else 0.0
|
||||
|
||||
segments: list[SlopeSegment] = []
|
||||
started = False
|
||||
# ⚠ 설계선이 격자(0.5m)로 찍혀 소단 평탄부가 **여러 조각**으로 쪼개짐 — 이어진 평탄 조각을 모아
|
||||
# 합이 소단 폭일 때만 소단으로 넘김(한 조각만 보면 첫 소단에서 사면이 끊긴다).
|
||||
flat_run = 0.0
|
||||
for start, end, run, rise in _outward(line, float(edge), side):
|
||||
if run <= 1e-9:
|
||||
continue
|
||||
if abs(rise) <= _FLAT_RISE_M + run * berm_grade + berm_slack:
|
||||
# 평탄부 — 측구 바닥·소단. 사면이 시작된 뒤라면 모아 두고 다음 조각에서 판정.
|
||||
if started:
|
||||
flat_run += run
|
||||
continue
|
||||
if flat_run > 0:
|
||||
if not (berm_width > 0 and abs(flat_run - berm_width) < 0.05):
|
||||
break # 사면이 끝나고 평지를 만난 것이다
|
||||
flat_run = 0.0
|
||||
ratio = run / abs(rise)
|
||||
# 절토는 바깥으로 갈수록 오르고, 성토는 내려간다.
|
||||
role = "cut" if rise > 0 else "fill"
|
||||
matched = _match(ratio, ratios[role])
|
||||
if matched is None:
|
||||
if started:
|
||||
break # 원지반에 닿았다
|
||||
continue # 아직 노체·측구 구간이다
|
||||
started = True
|
||||
segments.append(
|
||||
SlopeSegment(
|
||||
side=side,
|
||||
role=role,
|
||||
from_offset_m=start,
|
||||
to_offset_m=end,
|
||||
rise_m=rise,
|
||||
length_m=math.hypot(run, rise),
|
||||
ratio=matched,
|
||||
material=_material(design, matched),
|
||||
)
|
||||
)
|
||||
return segments
|
||||
|
||||
|
||||
def _roadbed_width(design: dict[str, Any]) -> float | None:
|
||||
"""노면 폭 — 노체 끝 좌(+)·우(−) 오프셋 사이. 한쪽이라도 없으면 `None`."""
|
||||
edges = design.get("road_edges") or {}
|
||||
left = _num((edges.get("left") or {}).get("offset_m"))
|
||||
right = _num((edges.get("right") or {}).get("offset_m"))
|
||||
return None if left is None or right is None else abs(left - right)
|
||||
|
||||
|
||||
def station_slope(chainage_m: float, design: dict[str, Any]) -> StationSlope:
|
||||
"""측점 하나의 사면길이. 좌우를 합쳐 절토·성토 각각의 총 사면길이를 낸다."""
|
||||
ratios = _ratios(design)
|
||||
segments: list[SlopeSegment] = []
|
||||
for side in ("left", "right"):
|
||||
segments.extend(_side_segments(design, side, ratios))
|
||||
berm = design.get("berm") or {}
|
||||
fill_by_side = {
|
||||
side: sum(abs(s.rise_m) for s in segments if s.role == "fill" and s.side == side)
|
||||
for side in ("left", "right")
|
||||
}
|
||||
# 2단 비탈은 경사비가 토사·암을 가름 · 1단은 그 측점 설계의 지반 프리셋(토사/암)이 비탈 전체
|
||||
preset = {"soil": "soil", "rock": "rock"}.get(str(design.get("geometry_preset") or ""))
|
||||
cut_by = {"soil": 0.0, "rock": 0.0}
|
||||
for segment in segments:
|
||||
material = segment.material or preset
|
||||
if segment.role == "cut" and material in cut_by:
|
||||
cut_by[material] += segment.length_m
|
||||
return StationSlope(
|
||||
chainage_m=float(chainage_m),
|
||||
cut_length_m=sum(s.length_m for s in segments if s.role == "cut"),
|
||||
cut_soil_length_m=cut_by["soil"],
|
||||
cut_rock_length_m=cut_by["rock"],
|
||||
fill_length_m=sum(s.length_m for s in segments if s.role == "fill"),
|
||||
# ⚠ 없는 측점은 0 이다 — 성토 사면길이로 **대신 채우지 않는다**(면이 다름).
|
||||
bench_cut_length_m=_num(design.get("bench_cut_length_m")) or 0.0,
|
||||
fill_height_m=max(fill_by_side.values(), default=0.0),
|
||||
roadbed_width_m=_roadbed_width(design),
|
||||
berm_width_m=_num(berm.get("width_m")) or 0.0,
|
||||
segments=tuple(segments),
|
||||
unclosed=bool(design.get("slope_unclosed")),
|
||||
)
|
||||
|
||||
|
||||
def series_key(series: str, face: str) -> str:
|
||||
"""계열·면 묶음 키. 옛 `B08_Quantity_Engine_SlopeArea._key` 와 같은 글자를 낸다."""
|
||||
return f"{series}_{face}"
|
||||
|
||||
|
||||
def length_of(slope: StationSlope, series: str, face: str) -> float:
|
||||
"""계열·면별 「거리」 = 그 측점의 사면길이.
|
||||
|
||||
법면보호공은 면고르기를 참조한다 — 같은 사면길이를 쓴다. 끊고 싶으면 이 함수만 고친다.
|
||||
층따기는 성토면만 대상이다.
|
||||
"""
|
||||
if series == "bench_cut":
|
||||
# ⚠ 층따기는 **원지반 표면**을 깎는 일이라 밑수가 성토 비탈면이 아니다(교본 6장 4절).
|
||||
# B06 설계가 측점마다 내는 값을 그대로 쓴다. 없으면 0 — 성토 사면길이로 대신 채우면
|
||||
# **다른 면을 세게 된다**.
|
||||
return slope.bench_cut_length_m if face == "fill" else 0.0
|
||||
return slope.fill_length_m if face == "fill" else slope.cut_length_m
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,106 +1,39 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Shell.ts
|
||||
* 로그인 후 09: 6차 워크플로우 (원가계산) — 화면 **틀** (PLAN 12장 · 2026-09-14 브레인 판정)
|
||||
* 로그인 후 09: 6차 워크플로우 (원가계산) — **빈 화면** (PLAN 7-3)
|
||||
*
|
||||
* - 틀은 탭 줄과 등록만. 탭마다 파일 하나(`B09_Estimation_UI_Tab_<이름>.ts`) — 계약은 `_Shell_Types`.
|
||||
* - ⚠ 등록 권한 = 랩탑_서브. 다른 창은 탭 파일을 만들고 이름을 알려 등록을 부탁함.
|
||||
* - 탭을 고를 때마다 본문·좌측 칸을 비우고 `render(ctx, arg)`. `ctx.open(key, arg)` = 다른 탭으로 들어가기.
|
||||
* - 옛 원가계산 코드는 통째로 `old_code/B09_Estimation/` 에 있음(지우지 않음).
|
||||
* - 다시 짤 때까지 메뉴와 주소만 남김 — 제목·단계 막대만 뜨고 탭·계산은 없음.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createWorkflowLayout } from "@ui/ui_template_workflow_layout";
|
||||
import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
|
||||
import { CURRENT_PROJECT_ID_KEY } from "@config/config_frontend";
|
||||
import { workflowSteps } from "../A00_Common/b_page_scaffold";
|
||||
import { goToWorkflowStage, WORKFLOW_STEP_ROUTES } from "../A00_Common/b_workflow_nav";
|
||||
import type { B09Tab, B09TabContext } from "./B09_Estimation_UI_Shell_Types";
|
||||
import { L, el, injectSheetStyles } from "./B09_Estimation_UI_Sheet";
|
||||
import { costSheetTab } from "./B09_Estimation_UI_Tab_CostSheet";
|
||||
import { rateTableTab } from "./B09_Estimation_UI_Tab_RateTable";
|
||||
import { billTab } from "./B09_Estimation_UI_Tab_Bill";
|
||||
import { unitPriceTab } from "./B09_Estimation_UI_Tab_UnitPrice";
|
||||
import { priceBasisTab } from "./B09_Estimation_UI_Tab_PriceBasis";
|
||||
import { machineTab } from "./B09_Estimation_UI_Tab_Machine";
|
||||
import { priceCompareTab } from "./B09_Estimation_UI_Tab_PriceCompare";
|
||||
import { materialPricesTab } from "./B09_Estimation_UI_Tab_MaterialPrices";
|
||||
import { summaryTab } from "./B09_Estimation_UI_Tab_Summary";
|
||||
import { listsTab } from "./B09_Estimation_UI_Tab_Lists";
|
||||
import { designDocTab } from "./B09_Estimation_UI_Tab_DesignDoc";
|
||||
import { basisSheetTab } from "./B09_Estimation_UI_Tab_BasisSheet";
|
||||
import { supplyTab } from "./B09_Estimation_UI_Tab_Supply";
|
||||
import { baseDataTab } from "./B09_Estimation_UI_Tab_BaseData";
|
||||
import { contractTab } from "./B09_Estimation_UI_Tab_Contract";
|
||||
import { executionTab } from "./B09_Estimation_UI_Tab_Execution";
|
||||
import { progressTab } from "./B09_Estimation_UI_Tab_Progress";
|
||||
import { completionTab } from "./B09_Estimation_UI_Tab_Completion";
|
||||
|
||||
/** 탭 등록 — 한 줄에 탭 하나. */
|
||||
const TABS: B09Tab[] = [
|
||||
costSheetTab, // 랩탑_메인
|
||||
rateTableTab, // 랩탑_메인
|
||||
billTab,
|
||||
unitPriceTab,
|
||||
priceBasisTab,
|
||||
machineTab,
|
||||
priceCompareTab,
|
||||
materialPricesTab, // 랩탑_메인 — 자재단가대비표 옆(자재 수동 단가)
|
||||
summaryTab,
|
||||
listsTab,
|
||||
supplyTab,
|
||||
baseDataTab,
|
||||
designDocTab,
|
||||
basisSheetTab,
|
||||
contractTab, // 랩탑_메인 — 설계 뒤 계약 단계라 맨 끝
|
||||
executionTab, // 랩탑_메인 — 계약 뒤 실행 단계
|
||||
progressTab, // 랩탑_메인 — 실행 뒤 기성 단계
|
||||
completionTab, // 랩탑_메인 — 기성 뒤 준공 단계
|
||||
];
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
|
||||
export async function renderB09Estimation(root: HTMLElement): Promise<void> {
|
||||
injectSheetStyles();
|
||||
export function renderB09Estimation(root: HTMLElement): void {
|
||||
const projectId = localStorage.getItem(CURRENT_PROJECT_ID_KEY);
|
||||
const page = el("div", "b09s-page");
|
||||
const bar = el("div", "b09s-tabs");
|
||||
const body = el("div", "b09s-body");
|
||||
const panel = el("div");
|
||||
page.append(bar, body);
|
||||
|
||||
let active = TABS[0].key;
|
||||
const open = (key: string, arg?: string): void => {
|
||||
const tab = TABS.find((item) => item.key === key);
|
||||
if (!tab) return;
|
||||
active = key;
|
||||
drawBar();
|
||||
body.replaceChildren();
|
||||
panel.replaceChildren();
|
||||
const ctx: B09TabContext = { projectId, body, panel, root, open };
|
||||
tab.render(ctx, arg);
|
||||
};
|
||||
const drawBar = (): void => {
|
||||
bar.replaceChildren(
|
||||
...TABS.map((tab) => {
|
||||
const button = el(
|
||||
"button",
|
||||
`b09s-tab${tab.key === active ? " is-active" : ""}`,
|
||||
tab.label(),
|
||||
);
|
||||
button.type = "button";
|
||||
button.dataset.tab = tab.key;
|
||||
button.addEventListener("click", () => open(tab.key));
|
||||
return button;
|
||||
}),
|
||||
);
|
||||
};
|
||||
const note = document.createElement("p");
|
||||
note.className = "b09-estimation__rebuilding";
|
||||
note.textContent = L("B09_Estimation_Rebuilding");
|
||||
const body = document.createElement("div");
|
||||
body.append(note);
|
||||
|
||||
const layout = createWorkflowLayout({
|
||||
title: L("B09_Estimation_Title"),
|
||||
steps: workflowSteps(),
|
||||
activeStep: 6,
|
||||
leftPanel: panel,
|
||||
mainContent: page,
|
||||
mainContent: body,
|
||||
routes: WORKFLOW_STEP_ROUTES,
|
||||
onStepClick: (stepIndex) => {
|
||||
onStepClick: (stepIndex: number) => {
|
||||
if (projectId) goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);
|
||||
},
|
||||
});
|
||||
root.append(layout.root);
|
||||
open(active);
|
||||
}
|
||||
|
||||
@@ -60,21 +60,6 @@ from B06_Section.B06_Section_Router_HaulPlan import (
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Router import router as b07_design_router
|
||||
from B07_DesignDetail.B07_DesignDetail_Router_Frame import router as b07_frame_router
|
||||
from B08_Quantity.B08_Quantity_Router import router as b08_quantity_router
|
||||
from B08_Quantity.B08_Quantity_Router_Earthwork import router as b08_earthwork_router
|
||||
from B08_Quantity.B08_Quantity_Router_Material import router as b08_material_router
|
||||
from B08_Quantity.B08_Quantity_Router_StructureSheet import router as b08_structure_sheet_router
|
||||
from B08_Quantity.B08_Quantity_Router_StmateLibrary import router as b08_stmate_library_router
|
||||
from B09_Estimation.B09_Estimation_Router import router as b09_estimation_router
|
||||
from B09_Estimation.B09_Estimation_Router_Contract import router as b09_contract_router
|
||||
from B09_Estimation.B09_Estimation_Router_Execution import router as b09_execution_router
|
||||
from B09_Estimation.B09_Estimation_Router_Progress import router as b09_progress_router
|
||||
from B09_Estimation.B09_Estimation_Router_CostSheet import router as b09_cost_sheet_router
|
||||
from B09_Estimation.B09_Estimation_Router_Edits import router as b09_edits_router
|
||||
from B09_Estimation.B09_Estimation_Router_Factors import router as b09_factors_router
|
||||
from B09_Estimation.B09_Estimation_Router_MaterialPrices import (
|
||||
router as b09_material_prices_router,
|
||||
)
|
||||
from M01_MasterData.M01_MasterData_Router import router as m01_master_data_router
|
||||
from common_util.common_util_audit import note_api_call, record_call_burst
|
||||
from common_util.common_util_auth import (
|
||||
@@ -643,19 +628,7 @@ app.include_router(b06_section_confirm_router, dependencies=protected_with_compa
|
||||
app.include_router(b06_section_haul_plan_router, dependencies=protected_with_company)
|
||||
app.include_router(b07_design_router, dependencies=protected_with_company)
|
||||
app.include_router(b07_frame_router, dependencies=protected_with_company)
|
||||
app.include_router(b08_quantity_router, dependencies=protected_with_company)
|
||||
app.include_router(b08_earthwork_router, dependencies=protected_with_company)
|
||||
app.include_router(b08_material_router, dependencies=protected_with_company)
|
||||
app.include_router(b08_structure_sheet_router, dependencies=protected_with_company)
|
||||
app.include_router(b08_stmate_library_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_estimation_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_cost_sheet_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_contract_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_execution_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_progress_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_edits_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_factors_router, dependencies=protected_with_company)
|
||||
app.include_router(b09_material_prices_router, dependencies=protected_with_company)
|
||||
# B08 수량·B09 원가계산 라우터 13 개는 끊음 — 코드는 `old_code/` 에 있고 화면은 빈 틀만 뜬다(PLAN 7-3).
|
||||
# 마스터 데이터 — 회사·프로젝트가 아니라 시스템 관리자만 본다.
|
||||
system_admin_only = [Depends(verify_session), Depends(require_system_admin)]
|
||||
app.include_router(m01_master_data_router, dependencies=system_admin_only)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user