"""M02 구조물 도면(structure) — 새로 · 도번 SD+8자리 · 409 · 저장 · 지운 뒤 번호 · 제목행 번호 매김(분류3 까지) · 집계표 저장 때 만들기/지우기 · 복사 가져오기 · 프로젝트 복사. 면적산출(area) · 재료산출(material)은 따로 파일 — `resources/tester/spreadsheet/test_spreadsheet_store.py`.""" import json import shutil 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 REAL = store.FOLDER @pytest.fixture() def client(tmp_path, monkeypatch): for kind in store.KINDS: (tmp_path / kind).mkdir() shutil.copy(REAL / "table/구조물집계표.json", tmp_path / "table") 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", "fb": "06010000", # 세월교 · (관경 × 련수마다) · 분류2 없음 = 00 "be": "14000000", # 소단 — 분류 모두 없음 "h_rip_c": "27010000", # 돌붙임 · 찰붙임 "h_rip_m": "27020000", # 돌붙임 · 메붙임 — 분류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("02010600") # 지운 04 는 비워 둠 · 무리 다음 assert numbers["zz"] == _num("37000000") # 새 종류 = 종류 다음 번호 assert numbers["pv_y"] == _num("02010101") # pv_b 무리 안 분류3 다음 번호 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["열"]) == ("SD10010100", "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})