- 번호 [종류][분류1][분류2][분류3] · 없는 층 00 · 12장 6자리는 끝에 00 을 붙여 읽음
- 양식 종류 basis → area(면적산출) · material(재료산출) 새로 · 집계표 저장 때 세 파일 자동 만들기/지우기
- 복사 길 POST /structures · /area · /material/{받는 열}/copy?from= · 알약 연결은 문서와 같이 복사
- 번호 목록 {열 id: {도번, 면적번, 재료번}} · 저장 때 번호 칸은 서버 것 그대로
- renumber: basis/ → area/ 옮김(표번 → 면적번 · 시트 이름 면적산출) · 빠진 재료산출 만듦
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
147 lines
7.1 KiB
Python
147 lines
7.1 KiB
Python
"""F — 면적산출(area) · 재료산출(material) 저장 · 옛 산출근거(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"}}}
|
|
LINKS = {"p1": {"시트": "s1", "칸": "B2"}} # 알약 연결 — 서버는 속을 안 봄(sub7 계약)
|
|
|
|
|
|
@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 _write(folder, kind, column, doc):
|
|
(folder / kind).mkdir(exist_ok=True)
|
|
(folder / kind / f"{column}.json").write_text(json.dumps(doc, ensure_ascii=False), "utf-8")
|
|
|
|
|
|
def test_옛_산출근거를_면적산출로_옮기고_번호_8자리(client, tmp_path):
|
|
"""12장 파일(SD · SC 6자리 · basis/) → SA 로 옮김 · 끝에 00 · 재료산출 새로 · 내용 그대로."""
|
|
structure = {"양식": "구조물도면", "종류": "structure", "판": 1, "열": "rv", "도번": "SD010101"}
|
|
_write(tmp_path, "structure", "rv", {**structure, "도면": {"entities": [{"type": "line"}]}})
|
|
old = {**store._blank("area", "rv"), "표번": "SC010101", "연결": LINKS}
|
|
old["시트"][0].update({"이름": "산출근거", "칸": {"A2": {"값": "터파기"}}})
|
|
_write(tmp_path, "basis", "rv", old)
|
|
done = store.renumber()
|
|
assert done["옮김"] == ["rv"] and not (tmp_path / "basis").exists()
|
|
area = store.read("area", "rv")["문서"]
|
|
assert (area["면적번"], "표번" in area, area["연결"]) == ("SA01010100", False, LINKS)
|
|
assert area["시트"][0]["이름"] == "면적산출" and area["시트"][0]["칸"] == {
|
|
"A2": {"값": "터파기"}
|
|
}
|
|
assert store.read("structure", "rv")["문서"]["도번"] == "SD01010100"
|
|
material = store.read("material", "rv")["문서"]
|
|
assert (material["재료번"], material["시트"][0]["이름"]) == ("SM01010100", "재료산출")
|
|
assert material["시트"][0]["칸"] == {} and material["서식"] == [{}] # 진짜 빈 시트
|
|
before = {p: p.read_bytes() for p in tmp_path.rglob("*.json")}
|
|
assert store.renumber() == {"옮김": [], "지움": [], "바꿈": [], "새로": []}
|
|
assert before == {p: p.read_bytes() for p in tmp_path.rglob("*.json")} # 두 번 돌려도 같음
|
|
|
|
|
|
def test_새_구조물_도면은_면적_재료_진짜_빈_시트_같은_8자리(client):
|
|
made1 = client.post("/api/m02/structures/rv").json()["문서"]
|
|
made2 = client.post("/api/m02/structures/pv_b").json()["문서"]
|
|
assert made1["도번"] == "SD01010100" and made2["도번"] == "SD02010100"
|
|
for kind, key, head in (("area", "면적번", "SA"), ("material", "재료번", "SM")):
|
|
doc = client.get(f"/api/m02/templates/{kind}/pv_b").json()["문서"]
|
|
assert doc[key] == f"{head}02010100"
|
|
assert doc["시트"][0]["칸"] == {} and doc["서식"] == [{}] # 머리 줄 없음
|
|
numbers = client.get("/api/m02/structures/numbers").json()
|
|
assert numbers["pv_b"] == {"도번": "SD02010100", "면적번": "SA02010100", "재료번": "SM02010100"}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("kind", "key", "head"), [("area", "면적번", "SA"), ("material", "재료번", "SM")]
|
|
)
|
|
def test_다른_열_내용을_복사해_가져옴_연결도_같이(client, kind, key, head):
|
|
store.renumber()
|
|
got = client.get(f"/api/m02/templates/{kind}/rv").json()
|
|
got["문서"]["시트"][0]["칸"] = {"A1": {"식": "1+1"}}
|
|
got["문서"]["연결"] = LINKS
|
|
client.put(f"/api/m02/templates/{kind}/rv", json=got)
|
|
r = client.post(f"/api/m02/{kind}/ms/copy", params={"from": "rv"})
|
|
assert r.status_code == 200
|
|
ms = client.get(f"/api/m02/templates/{kind}/ms").json()["문서"]
|
|
assert (ms["시트"][0]["칸"], ms["연결"]) == ({"A1": {"식": "1+1"}}, LINKS)
|
|
assert (ms[key], ms["열"]) == (f"{head}10010100", "ms") # 번호 · 열은 받는 쪽 그대로
|
|
assert ms["계산값"] == SERVER # 서버가 다시 풂
|
|
assert client.post(f"/api/m02/{kind}/ms/copy", params={"from": "없는열"}).status_code == 404
|
|
|
|
|
|
def test_저장은_서버_계산값만_적음_연결_두고_번호는_서버_것(client):
|
|
store._ensure("area", "rv")
|
|
got = client.get("/api/m02/templates/area/rv").json()
|
|
number = got["문서"]["면적번"]
|
|
doc = {
|
|
**got["문서"],
|
|
"계산값": {"s1": {"A1": {"수": "브라우저"}}},
|
|
"연결": LINKS,
|
|
"면적번": "x",
|
|
}
|
|
saved = client.put("/api/m02/templates/area/rv", json={"판": got["판"], "문서": doc})
|
|
assert saved.status_code == 200
|
|
now = client.get("/api/m02/templates/area/rv").json()["문서"]
|
|
assert (now["계산값"], now["연결"], now["면적번"]) == (SERVER, LINKS, number)
|
|
# 뼈대 없는 문서 · 판이 다르면 막음
|
|
bad = client.put("/api/m02/templates/area/rv", json={"판": saved.json()["판"], "문서": {}})
|
|
assert bad.status_code == 400
|
|
stale = client.put("/api/m02/templates/area/rv", json={"판": got["판"], "문서": doc})
|
|
assert stale.status_code == 409
|
|
|
|
|
|
def test_못_풀면_저장을_막음(client, monkeypatch):
|
|
store._ensure("material", "rv")
|
|
got = client.get("/api/m02/templates/material/rv").json()
|
|
|
|
def broken(_book):
|
|
raise spreadsheet.RecalcError("스프레드시트 계산 실패 — 시험")
|
|
|
|
monkeypatch.setattr(spreadsheet, "with_server_values", broken)
|
|
r = client.put("/api/m02/templates/material/rv", json={"판": got["판"], "문서": got["문서"]})
|
|
assert r.status_code == 503
|
|
assert client.get("/api/m02/templates/material/rv").json()["판"] == got["판"]
|
|
|
|
|
|
def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch):
|
|
client.post("/api/m02/structures/rv")
|
|
for kind in ("area", "material"):
|
|
doc = store.read(kind, "rv")["문서"]
|
|
layers.check_skeleton(kind, doc)
|
|
for bad in ({}, {**doc, "시트": {}}):
|
|
with pytest.raises(ValueError):
|
|
layers.check_skeleton(kind, bad)
|
|
layers.check_skeleton("structure", {"도면": {"entities": []}}) # 산출근거 없어도 됨
|
|
monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path)
|
|
copied = layers.seed_project(tmp_path / "project")
|
|
for kind in ("area", "material"):
|
|
assert f"{kind}/rv" in copied["작업본"] and f"{kind}/rv" in copied["초기"]
|