Merge remote-tracking branch 'origin/main_desktop_1' into sub_laptop_2
This commit is contained in:
@@ -355,6 +355,8 @@ async function saveWith({
|
||||
setTempPipeChainage() {},
|
||||
summary: () => summaryValue,
|
||||
setSummary: (value) => (summaryValue = value),
|
||||
groupItems: () => ({}), // 옵션 그룹(PLAN 46-3) — 이 시험은 그룹 없음
|
||||
setGroupItems() {},
|
||||
};
|
||||
if (summaryOnly) {
|
||||
const done = commitModule.commitSummaryOnly(ctx);
|
||||
|
||||
@@ -85,8 +85,11 @@ def test_화면과_서버가_같은_답():
|
||||
def test_화면_보관소_끌기_되돌리기_때_속성이_따라감():
|
||||
"""BOX(100) · 옵션 없는 관(80) — 관을 끌거나 되돌려도 BOX 속성은 BOX 관에만."""
|
||||
body = FACILITY.read_text(encoding="utf-8")
|
||||
# 연결 칸 도우미(`linkFields` · `hasLinks` · PLAN 46-3) — import 없는 파일이라 통째로 붙임
|
||||
links = (ROOT / "common_util" / "common_util_summary_item.ts").read_text(encoding="utf-8")
|
||||
script = (
|
||||
"type PipeFacility = string; type PipeSource = string; type DetailPipeInput = any;\n"
|
||||
links
|
||||
+ "\ntype PipeFacility = string; type PipeSource = string; type DetailPipeInput = any;\n"
|
||||
"interface FacilityAttributes { facility: string; start_m?: number; end_m?: number;"
|
||||
" options?: Record<string, string | number> }\n"
|
||||
+ "\n".join(
|
||||
|
||||
@@ -24,6 +24,7 @@ def out():
|
||||
["node", os.path.join(HERE, "helper_b05_structures_form_summary.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8", # 한글 출력 — Windows 기본(cp949)으로 읽으면 깨짐
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
|
||||
@@ -225,7 +225,13 @@ def test_나무_오른쪽_클릭은_메뉴_항목을_밖에서_주는_자리():
|
||||
|
||||
|
||||
def test_B05_구조물_폼이_구조물_id_를_모달에_넘김():
|
||||
# 배선은 `_Structures_Groups.bindSummaryPickers`(PLAN 46-3) — 그룹은 `{id}__{그룹 id}`
|
||||
picks = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Groups.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "opts.editingId() ?? (pipe !== null ? pipeStructureId(pipe) : null)" in picks
|
||||
assert "`${id}${GROUP_JOIN}${groupId}`" in picks
|
||||
panel = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "pipeStructureId(selectedPipeChainage)" in panel and "editingId ??" in panel
|
||||
assert "bindSummaryPickers({" in panel
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
"""옵션 그룹별 집계 항목 `group_items` (PLAN 46-3 · 46-4).
|
||||
|
||||
못박는 것
|
||||
- 칸 모양: `{그룹 id: summary_item}` · null 항목 뺌 · 빈 묶음 = None · 그룹 id · 항목이 틀리면 거절.
|
||||
- 구조물 기록 · 관 지점 기록 둘 다 칸을 읽고 씀(옛 파일 = 칸 없음).
|
||||
- [저장] 뒤 복사본: 그룹 연결마다 `{구조물 id}__{그룹 id}` 폴더(본체와 따로).
|
||||
- 집계표 채움: 정의된 그룹마다 한 건 더(그 그룹이 고른 열) · 미지정 그룹은 「미지정」 알림에 셈 ·
|
||||
정의가 사라진 그룹 연결은 표에 안 들어감(기록 · 복사본은 남음).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance
|
||||
from common_util.common_util_drainage_pipes import parse_pipe_points
|
||||
from common_util.common_util_summary_item import parse_group_items
|
||||
from M02_MasterTemplete import M02_Item_Snapshot as snap
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Table_Fill as fill
|
||||
from M02_MasterTemplete.StructOptions import M02_SO_Groups as groups
|
||||
from resources.tester.test_structures_summary_snapshot import ( # noqa: F401 — world 고정물
|
||||
MS,
|
||||
PIPE,
|
||||
RV,
|
||||
_dry,
|
||||
_items,
|
||||
_pipe,
|
||||
_save,
|
||||
world,
|
||||
)
|
||||
|
||||
|
||||
def _define(system: Path, monkeypatch, doc: dict) -> None:
|
||||
monkeypatch.setattr(store, "FOLDER", system)
|
||||
(system / groups.FILE).write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8")
|
||||
|
||||
|
||||
def test_칸_모양():
|
||||
assert parse_group_items(None) is None
|
||||
assert parse_group_items({}) is None
|
||||
assert parse_group_items({"g1": None}) is None
|
||||
assert parse_group_items({"g1": MS, "g2": None}) == {"g1": MS}
|
||||
for bad in ({"G1": MS}, {"g1": {"layer": "x"}}, ["g1"], {"g_1": MS}):
|
||||
with pytest.raises(ValueError):
|
||||
parse_group_items(bad)
|
||||
|
||||
|
||||
def test_구조물_관_기록이_칸을_읽고_씀():
|
||||
item = StructureInstance(
|
||||
type_id="x", placement="point", chainage_m=1, group_items={"g1": MS, "g2": None}
|
||||
)
|
||||
assert item.group_items == {"g1": MS}
|
||||
assert StructureInstance(type_id="x", placement="point", chainage_m=1).group_items is None
|
||||
point = parse_pipe_points([{"chainage_m": 10.0, "source": "user", "group_items": {"g9": RV}}])
|
||||
assert point[0].group_items == {"g9": RV}
|
||||
assert point[0].as_dict()["group_items"] == {"g9": RV}
|
||||
plain = parse_pipe_points([{"chainage_m": 10.0, "source": "user"}])[0]
|
||||
assert "group_items" not in plain.as_dict()
|
||||
|
||||
|
||||
def test_그룹_연결마다_복사본_폴더가_따로(world):
|
||||
root = world["root"]
|
||||
wall = {**_dry(MS), "group_items": {"g1": RV}}
|
||||
pipe = {**_pipe(PIPE), "group_items": {"gin": MS}}
|
||||
result = _save(root, [wall], [pipe])
|
||||
assert result["만듦"] == ["pipe@40.0", "pipe@40.0__gin", "st0", "st0__g1"]
|
||||
assert snap.FOLDER_PATTERN.match("pipe@40.0__gin")
|
||||
manifest = json.loads((_items(root) / "st0__g1/manifest.json").read_text("utf-8"))
|
||||
assert manifest["원본"]["key"] == RV["key"] and manifest["구조물"] == ["st0__g1"]
|
||||
# 그룹 연결을 풀면 그 복사본만 지움
|
||||
again = _save(root, [_dry(MS)], [pipe])
|
||||
assert again["지움"] == ["st0__g1"] and again["만듦"] == []
|
||||
|
||||
|
||||
def test_채움은_정의된_그룹마다_한_건_더(world, monkeypatch):
|
||||
"""메쌓기 벽 = 본체(돌쌓기 ms) + 그룹 「기슭막이」(rv) — 한 구조물이 두 열에 · 길이 10."""
|
||||
root, system = world["root"], world["system"]
|
||||
_define(system, monkeypatch, {"masonry_dry": [{"id": "g1", "이름": "기슭막이", "옵션": []}]})
|
||||
linked = {**_dry(MS), "group_items": {"g1": RV, "gold": PIPE}} # gold = 정의 없음
|
||||
bare = {**_dry(None), "chainage_m": 125, "start_m": 120, "end_m": 130}
|
||||
_save(root, [linked, bare], [])
|
||||
table = json.loads((system / "table/구조물집계표.json").read_text("utf-8"))
|
||||
doc = fill.fill_table(root, table)
|
||||
values = {row["id"]: row["값"] for row in doc["줄"] if not row.get("고정")}
|
||||
row = {key.split("|")[0]: value for key, value in values["s80.00~90.00"].items()}
|
||||
assert row["ms"] == 10 # 본체 = 돌쌓기
|
||||
assert row["rv"] == 10 # 그룹 = 기슭막이(같은 벽 길이)
|
||||
assert set(values) == {"s80.00~90.00"} # 미지정 벽은 줄도 없음
|
||||
# 둘째 벽: 본체 미지정 + 그룹 미지정 = 2건 · 정의 없는 gold 는 셈 밖
|
||||
assert "집계 항목 미지정 2건 — 표에 안 들어감" in doc["알림"]
|
||||
items = fill.collect_items(root)
|
||||
assert [i["복사본"] for i in items] == ["st0", "st0__g1", "st1", "st1__g1"]
|
||||
assert items[1]["그룹"] == "기슭막이" and items[1]["summary_item"] == RV
|
||||
@@ -48,5 +48,6 @@ def test_관_시설_보관소는_응답을_정본으로_집계_항목이_없으
|
||||
"""횡단 보기에서 [해제]한 관 초안이 응답으로 돌아올 때 옛 지정이 되살아나면 안 됨."""
|
||||
store = _text("B05_Profile/B05_Profile_UI_Drainage_Facility.ts")
|
||||
body = store.split("replaceFromResponse(")[1].split("},\n /** 한 지점의 시설을 바꾼다")[0]
|
||||
assert "const summary = point.summary_item ?? null;" in body
|
||||
# 응답 칸만(본체 · 옵션 그룹 · PLAN 46-3) — 없으면 미지정
|
||||
assert "...linkFields(point)," in body
|
||||
assert "before.find" not in body # 화면이 쥔 값을 이어받던 옛 규칙
|
||||
|
||||
Reference in New Issue
Block a user