- 지운 구조물 열의 도번 8자리를 조합 장부 지운번호에 남겨 새 열이 다시 안 받음(다음 저장에서도) - 공용 창 틀: 몸의 첫 입력 칸에 커서(붙기 전 focus 가 안 먹던 것) - 좌측 새로 · 본떠 만들기 이름 창에서 Enter = 만들기 - 표: 바인딩 · 계산 열은 손 입력 열 단추 꺼짐(켜도 프로젝트에서 잠김) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011vX53nJGC8ucBRFJns2n7B
233 lines
12 KiB
Python
233 lines
12 KiB
Python
"""M02 구조물 도면(structure) — 새로 · 도번 SD+8자리 · 409 · 저장 · 지운 뒤 번호 ·
|
||
제목행 번호 매김(분류3 까지) · 집계표 저장 때 만들기/지우기 · 복사 가져오기 · 프로젝트 복사.
|
||
면적산출(area) · 재료산출(material)은 따로 파일 —
|
||
`resources/tester/spreadsheet/test_spreadsheet_store.py`."""
|
||
|
||
import json
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||
from M02_MasterTemplete.M02_MasterTemplete_Router import router
|
||
|
||
# 견본 구조물집계표 — 실제 마스터 양식은 사용자가 화면에서 고치므로 시험은 이 견본만 읽음
|
||
_SAMPLE_COLUMNS = [
|
||
("no", ["NO", None, None, None], "글"),
|
||
("sta", ["측점", None, None, None], "글"),
|
||
("rv", ["돌기슭막이", "(형태마다)", "H=(높이마다)", None], "수"),
|
||
("c1", ["돌기슭막이", "(형태마다)", "새 열", None], "수"),
|
||
("pv_b", ["콘크리트포장", "T=(두께마다)", "B", None], "수"),
|
||
("pv_l", ["콘크리트포장", "T=(두께마다)", "L", None], "수"),
|
||
("pv_a", ["콘크리트포장", "T=(두께마다)", "A", None], "수"),
|
||
("pv_jt", ["콘크리트포장", "T=(두께마다)", "줄눈", None], "수"),
|
||
("pp_len", ["관공", "(관종 · 관경마다)", "L", None], "수"),
|
||
("pp_cp", ["관공", "(관종 · 관경마다)", "커플링밴드", None], "수"),
|
||
("fb", ["세월교", "(관경 × 련수마다)", None, None], "수"),
|
||
("ms", ["돌쌓기", "(쌓기마다)", "H=(높이마다)", None], "수"),
|
||
("be", ["소단", None, None, None], "수"),
|
||
("h_rip_c", ["돌붙임", "찰붙임", None, None], "수"),
|
||
("h_rip_m", ["돌붙임", "메붙임", None, None], "수"),
|
||
("memo", ["비고", None, None, None], "글"),
|
||
]
|
||
|
||
|
||
def write_sample_table(folder) -> None:
|
||
"""임시 폴더에 종류 폴더 · 견본 구조물집계표를 만듦(`store.FOLDER` 자리)."""
|
||
for kind in store.KINDS:
|
||
(folder / kind).mkdir(exist_ok=True)
|
||
table = {
|
||
"양식": store.STRUCTURE_TABLE,
|
||
"종류": "표",
|
||
"판": 1,
|
||
"층": ["종류", "분류1", "분류2", "분류3"],
|
||
"열": [{"id": i, "머리": h, "단위": None, "꼴": k} for i, h, k in _SAMPLE_COLUMNS],
|
||
"줄": [],
|
||
"합계줄": [],
|
||
"보기": {},
|
||
}
|
||
(folder / "table" / f"{store.STRUCTURE_TABLE}.json").write_text(
|
||
json.dumps(table, ensure_ascii=False), encoding="utf-8"
|
||
)
|
||
|
||
|
||
@pytest.fixture()
|
||
def client(tmp_path, monkeypatch):
|
||
write_sample_table(tmp_path)
|
||
monkeypatch.setattr(store, "FOLDER", tmp_path)
|
||
app = FastAPI()
|
||
app.include_router(router)
|
||
return TestClient(app)
|
||
|
||
|
||
def _num(code: str) -> dict:
|
||
"""세 번호 — 같은 8자리."""
|
||
return {"도번": f"SD{code}", "면적번": f"SA{code}", "재료번": f"SM{code}"}
|
||
|
||
|
||
def test_새로_도번_자동_409_없는_열(client):
|
||
made = client.post("/api/m02/structures/pp_len")
|
||
assert made.status_code == 200
|
||
doc = made.json()["문서"]
|
||
assert (doc["양식"], doc["종류"], doc["열"], doc["도번"]) == (
|
||
"구조물도면",
|
||
"structure",
|
||
"pp_len",
|
||
"SD01010100",
|
||
)
|
||
# 도각 없는 전용 화면 — 그린 만큼이 설계 영역 · 기본 도면층 하나(없으면 CAD 가 못 엶)
|
||
assert doc["도면"]["format"] == 6
|
||
assert doc["도면"]["entities"] == []
|
||
assert len(doc["도면"]["layers"]) == 1
|
||
# 면적산출 · 재료산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서씩
|
||
assert "산출근거" not in doc
|
||
for kind in ("area", "material"):
|
||
assert client.get(f"/api/m02/templates/{kind}/pp_len").json()["문서"]["종류"] == "통합문서"
|
||
|
||
assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD02010100"
|
||
assert client.post("/api/m02/structures/pp_len").status_code == 409
|
||
assert client.post("/api/m02/structures/없는열").status_code == 404
|
||
assert client.post("/api/m02/structures/no").status_code == 404 # 글 열은 구조물 아님
|
||
assert client.get("/api/m02/structures/numbers").json() == {
|
||
"pp_len": _num("01010100"),
|
||
"rv": _num("02010100"),
|
||
}
|
||
rows = client.get("/api/m02/templates").json()
|
||
assert {(r["종류"], r["이름"]) for r in rows if r["종류"] == "structure"} == {
|
||
("structure", "pp_len"),
|
||
("structure", "rv"),
|
||
}
|
||
|
||
|
||
def test_저장_뼈대_거름(client):
|
||
got = client.post("/api/m02/structures/pp_len").json()
|
||
doc = got["문서"]
|
||
doc["도면"]["entities"] = [{"type": "line"}]
|
||
saved = client.put("/api/m02/templates/structure/pp_len", json={"판": got["판"], "문서": doc})
|
||
assert saved.status_code == 200 and saved.json()["판"] != got["판"]
|
||
for bad in ({}, {**doc, "도면": {}}, {**doc, "도면": {"entities": "x"}}, {**doc, "도면": []}):
|
||
r = client.put(
|
||
"/api/m02/templates/structure/pp_len", json={"판": saved.json()["판"], "문서": bad}
|
||
)
|
||
assert r.status_code == 400
|
||
assert client.get("/api/m02/templates/structure/pp_len").json()["문서"] == doc
|
||
now = client.get("/api/m02/templates/structure/pp_len").json()["판"]
|
||
client.put(
|
||
"/api/m02/templates/structure/pp_len", json={"판": now, "문서": {**doc, "도번": "SD9"}}
|
||
)
|
||
got = client.get("/api/m02/templates/structure/pp_len").json()["문서"]
|
||
assert got["도번"] == "SD01010100" # 번호는 서버 것 — 화면 값을 안 받음
|
||
|
||
|
||
def test_지워도_번호를_당기지_않음_짝은_같은_번호(client):
|
||
for column in ("pp_len", "rv", "ms"):
|
||
client.post(f"/api/m02/structures/{column}")
|
||
assert client.delete("/api/m02/templates/structure/rv").status_code == 200
|
||
numbers = client.get("/api/m02/structures/numbers").json()
|
||
assert numbers["rv"] == {**_num("02010100"), "도번": ""} # 면적 · 재료는 남아 번호를 쥠
|
||
assert numbers["ms"] == _num("03010100") # 다른 열 번호는 그대로
|
||
# 같은 종류(관공) 새 열 = 그 무리 다음 번호 · 다시 만든 도면은 짝(면적산출) 번호를 이어 받음
|
||
assert client.post("/api/m02/structures/pp_cp").json()["문서"]["도번"] == "SD01010200"
|
||
assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD02010100"
|
||
|
||
|
||
def test_제목행으로_번호_매김_한_번_옮김(client, tmp_path):
|
||
"""옛 구-NN 파일 · 글 열 파일 → 새 번호(열 차례 = 종류 · 분류1 · 분류2 · 분류3 차례) ·
|
||
내용 그대로."""
|
||
for column in ("no", "rv", "pv_l"):
|
||
doc = {"양식": "구조물도면", "종류": "structure", "판": 1, "열": column, "도번": "구-07"}
|
||
doc["도면"] = {"format": 6, "entities": [{"type": "line"}], "layers": []}
|
||
(tmp_path / "structure" / f"{column}.json").write_text(
|
||
json.dumps(doc, ensure_ascii=False), encoding="utf-8"
|
||
)
|
||
done = store.renumber()
|
||
assert done["지움"] == ["structure/no"] # 글 열(NO · 측점 · 비고)은 구조물 아님
|
||
numbers = client.get("/api/m02/structures/numbers").json()
|
||
assert set(numbers) == set(store._column_ids()) and "sta" not in numbers
|
||
expect = {
|
||
"rv": "01010100", # 돌기슭막이 · (형태마다) · H=(높이마다) · 분류3 없음 = 00
|
||
"c1": "01010200", # 같은 종류 · 분류1 · 다음 분류2
|
||
"pv_b": "02010100", # 콘크리트포장 · T=(두께마다) · B
|
||
"pv_l": "02010200",
|
||
"pp_len": "03010100",
|
||
"fb": "04010000", # 세월교 · (관경 × 련수마다) · 분류2 없음 = 00
|
||
"be": "06000000", # 소단 — 분류 모두 없음
|
||
"h_rip_c": "07010000", # 돌붙임 · 찰붙임
|
||
"h_rip_m": "07020000", # 돌붙임 · 메붙임 — 분류1 다음 번호
|
||
}
|
||
for column, code in expect.items():
|
||
assert numbers[column] == _num(code), column
|
||
assert store.read("structure", "rv")["문서"]["도면"]["entities"] == [{"type": "line"}]
|
||
again = {"옮김": [], "지움": [], "바꿈": [], "새로": []}
|
||
assert store.renumber() == again # 두 번 돌려도 같음
|
||
|
||
|
||
def test_집계표_저장_때_새_열은_만들고_지운_열은_같이_지움(client):
|
||
store.renumber()
|
||
got = client.get("/api/m02/templates/table/구조물집계표").json()
|
||
table = got["문서"]
|
||
table["열"] = [c for c in table["열"] if c["id"] != "pv_a"]
|
||
new = {"id": "pv_x", "머리": ["콘크리트포장", "T=(두께마다)", "X"], "단위": "m", "꼴": "수"}
|
||
kind = {"id": "zz", "머리": ["새 구조물", None, None], "단위": "개소", "꼴": "수"}
|
||
text = {"id": "note2", "머리": ["메모", None, None], "단위": None, "꼴": "글"}
|
||
third = {"id": "pv_y", "머리": ["콘크리트포장", "T=(두께마다)", "B", "H=1"], "꼴": "수"}
|
||
table["열"] += [new, kind, text, third]
|
||
saved = client.put(
|
||
"/api/m02/templates/table/구조물집계표", json={"판": got["판"], "문서": table}
|
||
)
|
||
assert saved.status_code == 200
|
||
numbers = client.get("/api/m02/structures/numbers").json()
|
||
assert "pv_a" not in numbers and "note2" not in numbers
|
||
for kind_ in ("structure", "area", "material"):
|
||
assert client.get(f"/api/m02/templates/{kind_}/pv_a").status_code == 404
|
||
assert numbers["pv_x"] == _num("02010500") # 지운 pv_a(03)는 비워 둠 · 무리 다음
|
||
assert numbers["zz"] == _num("08000000") # 새 종류 = 종류 다음 번호
|
||
assert numbers["pv_y"] == _num("02010101") # pv_b 무리 안 분류3 다음 번호
|
||
|
||
|
||
def test_지운_열_번호는_다음_저장에도_다시_안_줌(client):
|
||
"""무리 맨 끝 열(pv_jt 02010400)을 지워 저장한 뒤 새 열을 더해도 04 를 다시 안 받음."""
|
||
store.renumber()
|
||
url = "/api/m02/templates/table/구조물집계표"
|
||
got = client.get(url).json()
|
||
got["문서"]["열"] = [c for c in got["문서"]["열"] if c["id"] != "pv_jt"]
|
||
assert client.put(url, json={"판": got["판"], "문서": got["문서"]}).status_code == 200
|
||
assert client.get("/api/m02/structure-combos").json()["지운번호"] == ["02010400"]
|
||
got = client.get(url).json()
|
||
new = {"id": "pv_z", "머리": ["콘크리트포장", "T=(두께마다)", "Z", None], "꼴": "수"}
|
||
got["문서"]["열"].append(new)
|
||
assert client.put(url, json={"판": got["판"], "문서": got["문서"]}).status_code == 200
|
||
assert client.get("/api/m02/structures/numbers").json()["pv_z"] == _num("02010500")
|
||
|
||
|
||
def test_다른_열_도면을_복사해_가져옴(client):
|
||
store.renumber()
|
||
src = client.get("/api/m02/templates/structure/rv").json()
|
||
src["문서"]["도면"]["entities"] = [{"type": "circle"}]
|
||
client.put("/api/m02/templates/structure/rv", json=src)
|
||
r = client.post("/api/m02/structures/ms/copy", params={"from": "rv"})
|
||
assert r.status_code == 200
|
||
ms = client.get("/api/m02/templates/structure/ms").json()["문서"]
|
||
assert ms["도면"]["entities"] == [{"type": "circle"}]
|
||
assert (ms["도번"], ms["열"]) == ("SD05010100", "ms") # 번호 · 열은 받는 쪽 그대로
|
||
assert client.get("/api/m02/templates/structure/rv").json()["문서"]["도번"] == "SD01010100"
|
||
bad = client.post("/api/m02/structures/ms/copy", params={"from": "없는열"})
|
||
assert bad.status_code == 404
|
||
|
||
|
||
def test_프로젝트_복사와_층_저장에_실림(client, tmp_path, monkeypatch):
|
||
doc = client.post("/api/m02/structures/pp_len").json()["문서"]
|
||
monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path)
|
||
project = tmp_path / "project"
|
||
copied = layers.seed_project(project)
|
||
assert "structure/pp_len" in copied["작업본"] and "structure/pp_len" in copied["초기"]
|
||
work = layers.project_dir(project)
|
||
assert layers.read_template(work, "structure", "pp_len")["문서"] == doc
|
||
assert layers.read_template(layers.initial_dir(project), "structure", "pp_len")["문서"] == doc
|
||
assert ("structure", "pp_len") in {(r["종류"], r["이름"]) for r in layers.list_templates(work)}
|
||
layers.check_skeleton("structure", doc)
|
||
with pytest.raises(ValueError):
|
||
layers.check_skeleton("structure", {**doc, "도면": None})
|