- 도번 SD · 표번 SC + [종류][분류1][분류2] — 제목행(열 머리 세 층) 차례 · 없는 층 00 · 한 열 두 번호 같은 6자리 · 준 번호 유지(새 열 = 그 무리 다음 번호 · 지운 번호 비움)
- 구조물 = 꼴=수 열만(NO · 측점 · 비고 빠짐 · 브레인 판정)
- 집계표 [저장] — 새 열 두 파일 만들고 지운 열 두 파일 지움
- 새 산출근거 = 진짜 빈 시트(머리 줄 없음) · renumber() = 옛 번호 한 번 옮김 + 옛 기본 머리 줄 지움
- POST /structures/{열}/copy?from= · /basis/{열}/copy?from= — 내용만 · 번호 · 열은 받는 쪽
- GET /structures/numbers = {열 id: {도번, 표번}} · /basis/numbers 뺌
- 시험: test_m02_structure · test_spreadsheet_store 고쳐 씀
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
175 lines
8.0 KiB
Python
175 lines
8.0 KiB
Python
"""F — 산출근거 저장(basis 종류) · 옛 산출근거 옮김 · [저장] 때 서버 계산값.
|
|
|
|
서버 Node 풀이는 `test_spreadsheet_node.py` 가 따로 봄 — 여기는 `with_server_values` 를 바꿔 끼워
|
|
「브라우저 값은 버리고 서버 값만 적는다」 · 「못 풀면 저장을 막는다」 만 잼.
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from common_util import common_util_spreadsheet as spreadsheet
|
|
from M02_MasterTemplete import M02_MasterTemplete_Router as router_module
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Template_Layers as layers
|
|
|
|
REAL = store.FOLDER
|
|
SERVER = {"s1": {"A1": {"수": "42"}}}
|
|
|
|
|
|
@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)
|
|
|
|
def fake(book):
|
|
clean = {k: v for k, v in book.items() if k != "계산값"}
|
|
return {**clean, "계산값": SERVER}
|
|
|
|
monkeypatch.setattr(spreadsheet, "with_server_values", fake)
|
|
app = FastAPI()
|
|
app.include_router(router_module.router)
|
|
return TestClient(app)
|
|
|
|
|
|
def _old_structure(folder, column, basis):
|
|
doc = {"양식": "구조물도면", "종류": "structure", "판": 1, "열": column, "도번": "구-01"}
|
|
doc.update({"도면": {"format": 6, "entities": []}, "산출근거": basis})
|
|
(folder / "structure" / f"{column}.json").write_text(
|
|
json.dumps(doc, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
|
|
|
|
def test_옛_산출근거를_따로_파일로_옮김(client, tmp_path):
|
|
old = {
|
|
"열": [
|
|
{"id": "work", "머리": ["공종"]},
|
|
{"id": "detail", "머리": ["산출", "내역"]},
|
|
{"id": "qty", "머리": ["수량(m당)"]},
|
|
],
|
|
"줄": [{"id": "1", "값": {"work": "터파기", "qty": 1.5}}, {"id": "2", "값": {}}],
|
|
"보기": {"열너비": {"detail": 210}},
|
|
}
|
|
_old_structure(tmp_path, "pp_len", old)
|
|
done = store.migrate_basis()
|
|
assert done["옮김"] == ["pp_len"]
|
|
columns = store._column_ids()
|
|
assert set(done["새로"]) == set(columns) - {"pp_len"}
|
|
structure = json.loads((tmp_path / "structure/pp_len.json").read_text(encoding="utf-8"))
|
|
assert "산출근거" not in structure and structure["도번"] == "구-01"
|
|
basis = store.read("basis", "pp_len")["문서"]
|
|
sheet = basis["시트"][0]
|
|
assert (basis["종류"], basis["열"], sheet["id"]) == ("통합문서", "pp_len", "s1")
|
|
assert sheet["칸"] == {
|
|
"A1": {"값": "공종", "서식": 1},
|
|
"B1": {"값": "산출 내역", "서식": 1},
|
|
"C1": {"값": "수량(m당)", "서식": 1},
|
|
"A2": {"값": "터파기"},
|
|
"C2": {"값": 1.5},
|
|
}
|
|
assert sheet["열"] == {"B": {"폭": 30.0}}
|
|
assert "계산값" not in basis # 식 없는 새 문서 — 첫 [저장] 때 서버가 적음
|
|
# 두 번 돌려도 같음
|
|
before = {p.name: p.read_bytes() for p in (tmp_path / "basis").iterdir()}
|
|
assert store.migrate_basis() == {"옮김": [], "새로": []}
|
|
assert before == {p.name: p.read_bytes() for p in (tmp_path / "basis").iterdir()}
|
|
|
|
|
|
def test_새_구조물_도면은_산출근거_따로_진짜_빈_시트(client):
|
|
made = client.post("/api/m02/structures/rv").json()["문서"]
|
|
assert "산출근거" not in made
|
|
basis = client.get("/api/m02/templates/basis/rv").json()["문서"]
|
|
assert basis["시트"][0]["칸"] == {} and basis["서식"] == [{}] # 머리 줄 없음
|
|
|
|
|
|
def test_표번은_도번과_같은_6자리(client):
|
|
made1 = client.post("/api/m02/structures/rv").json()["문서"]
|
|
made2 = client.post("/api/m02/structures/pv_b").json()["문서"]
|
|
assert made1["도번"] == "SD010101" and made2["도번"] == "SD020101"
|
|
basis1 = client.get("/api/m02/templates/basis/rv").json()["문서"]
|
|
basis2 = client.get("/api/m02/templates/basis/pv_b").json()["문서"]
|
|
assert basis1["표번"] == "SC010101" and basis2["표번"] == "SC020101"
|
|
numbers = client.get("/api/m02/structures/numbers").json()
|
|
assert numbers["pv_b"] == {"도번": "SD020101", "표번": "SC020101"}
|
|
|
|
|
|
def _old_basis(folder, column, cells):
|
|
doc = {**store.basis_doc(column), "표번": "표-03", "서식": [{}, {"굵게": True}]}
|
|
doc["시트"][0]["칸"] = cells
|
|
(folder / "basis" / f"{column}.json").write_text(
|
|
json.dumps(doc, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
|
|
|
|
def test_옛_빈_산출근거_머리_줄을_지우고_새_번호(client, tmp_path):
|
|
head = {f"{c}1": {"값": v, "서식": 1} for c, v in zip("ABCDE", store._BASIS_HEAD)}
|
|
_old_basis(tmp_path, "rv", dict(head))
|
|
_old_basis(tmp_path, "ms", {**head, "A1": {"값": "공종명", "서식": 1}}) # 사용자가 고친 머리
|
|
_old_basis(tmp_path, "sg", {**head, "A2": {"값": "터파기"}}) # 아래 줄에 내용
|
|
store.renumber()
|
|
rv = store.read("basis", "rv")["문서"]
|
|
assert (rv["표번"], rv["시트"][0]["칸"], rv["서식"]) == ("SC010101", {}, [{}])
|
|
assert store.read("basis", "ms")["문서"]["시트"][0]["칸"]["A1"]["값"] == "공종명" # 둠
|
|
sg = store.read("basis", "sg")["문서"]
|
|
assert sg["시트"][0]["칸"] == {"A2": {"값": "터파기"}} # 머리만 지우고 내용은 둠
|
|
assert store.read("structure", "rv")["문서"]["도번"] == "SD010101" # 짝 파일도 만듦
|
|
|
|
|
|
def test_다른_열_산출근거를_복사해_가져옴(client):
|
|
store.renumber()
|
|
got = client.get("/api/m02/templates/basis/rv").json()
|
|
got["문서"]["시트"][0]["칸"] = {"A1": {"식": "1+1"}}
|
|
client.put("/api/m02/templates/basis/rv", json=got)
|
|
r = client.post("/api/m02/basis/ms/copy", params={"from": "rv"})
|
|
assert r.status_code == 200
|
|
ms = client.get("/api/m02/templates/basis/ms").json()["문서"]
|
|
assert ms["시트"][0]["칸"] == {"A1": {"식": "1+1"}}
|
|
assert (ms["표번"], ms["열"]) == ("SC100101", "ms") # 번호 · 열은 받는 쪽 그대로
|
|
assert ms["계산값"] == SERVER # 서버가 다시 풂
|
|
assert client.post("/api/m02/basis/ms/copy", params={"from": "없는열"}).status_code == 404
|
|
|
|
|
|
def test_저장은_서버_계산값만_적음(client):
|
|
store.ensure_basis("rv")
|
|
got = client.get("/api/m02/templates/basis/rv").json()
|
|
doc = {**got["문서"], "계산값": {"s1": {"A1": {"수": "브라우저"}}}}
|
|
saved = client.put("/api/m02/templates/basis/rv", json={"판": got["판"], "문서": doc})
|
|
assert saved.status_code == 200
|
|
assert client.get("/api/m02/templates/basis/rv").json()["문서"]["계산값"] == SERVER
|
|
# 뼈대 없는 문서 · 판이 다르면 막음
|
|
bad = client.put("/api/m02/templates/basis/rv", json={"판": saved.json()["판"], "문서": {}})
|
|
assert bad.status_code == 400
|
|
stale = client.put("/api/m02/templates/basis/rv", json={"판": got["판"], "문서": doc})
|
|
assert stale.status_code == 409
|
|
|
|
|
|
def test_못_풀면_저장을_막음(client, monkeypatch):
|
|
store.ensure_basis("rv")
|
|
got = client.get("/api/m02/templates/basis/rv").json()
|
|
|
|
def broken(_book):
|
|
raise spreadsheet.RecalcError("스프레드시트 계산 실패 — 시험")
|
|
|
|
monkeypatch.setattr(spreadsheet, "with_server_values", broken)
|
|
r = client.put("/api/m02/templates/basis/rv", json={"판": got["판"], "문서": got["문서"]})
|
|
assert r.status_code == 503
|
|
assert client.get("/api/m02/templates/basis/rv").json()["판"] == got["판"]
|
|
|
|
|
|
def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch):
|
|
store.ensure_basis("rv")
|
|
doc = store.read("basis", "rv")["문서"]
|
|
layers.check_skeleton("basis", doc)
|
|
for bad in ({}, {**doc, "시트": {}}):
|
|
with pytest.raises(ValueError):
|
|
layers.check_skeleton("basis", bad)
|
|
layers.check_skeleton("structure", {"도면": {"entities": []}}) # 산출근거 없어도 됨
|
|
monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path)
|
|
copied = layers.seed_project(tmp_path / "project")
|
|
assert "basis/rv" in copied["작업본"] and "basis/rv" in copied["초기"]
|