refactor(M02): 재료산출 시트 지움 — 종류 material · 재료번 SM · 좌측 줄 · 집계표 「재료산출」 줄 · 카드 · 전체 편집 · 복사 길 · 프로젝트 복사본 「재료」 · 고르기 모달 · 시스템 층 · fixture · 시험

- 새 프로젝트 씨앗(KINDS)에서도 빠짐 · 새 열은 구조물 도면 · 면적산출만 만듦
- storage 층(회사 · 개인 · 프로젝트) material/ 파일은 지우지 않음(읽기만 끊음)
- 장부 재료번 칸은 다음 집계표 [저장] 때 빠짐

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
2026-10-04 17:56:49 +09:00
co-authored by Claude Opus 5.5
parent 54ce67ceb8
commit 61d2ed5997
53 changed files with 198 additions and 1811 deletions
@@ -1,17 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "item1",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {}
}
],
"활성": "s1",
"재료번": "SM-000001"
}
@@ -1,17 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "item2",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {}
}
],
"활성": "s1",
"재료번": "SM-000002"
}
@@ -1,17 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "item3",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {}
}
],
"활성": "s1",
"재료번": "SM-000003"
}
@@ -1,36 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "c4",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {
"I5": {
"값": 6
}
},
"연결": [
{
"키": "MT003810",
"이름": "모 래, 서울, 도착도 강모래",
"칸": "I5",
"단위": "㎥",
"자재": {
"코드": "MT003810",
"이름": "모 래, 서울, 도착도",
"규격": "강모래",
"단위": "㎥"
}
}
]
}
],
"활성": "s1",
"재료번": "SM01010101",
"계산값": {}
}
@@ -1,17 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "ms",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {}
}
],
"활성": "s1",
"재료번": "SM10010100"
}
@@ -1,17 +0,0 @@
{
"종류": "통합문서",
"판": 1,
"열": "pp_len",
"서식": [
{}
],
"시트": [
{
"id": "s1",
"이름": "재료산출",
"칸": {}
}
],
"활성": "s1",
"재료번": "SM03010100"
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
"""시험 안 마스터 견본(PLAN 26-5a) — 초기화 전 마스터 층을 줄여 얼린 것
(`fixtures/m02_master_sample`).
집계표(바인딩 열 54) · SS 장부(51 줄) · 자재조합 · 세 파일은 rv · c4 · pp_len · ms 넷(도면 줄임) —
집계표(바인딩 열 54) · SS 장부(51 줄) · 도면 · 면적 파일은 rv · c4 · pp_len · ms 넷(도면 줄임) —
시험은 실제 `resources/master_template` 을 안 읽고 이 견본을 임시 폴더에 복사해 씀.
"""
@@ -1,4 +1,4 @@
"""저장 뒤 식 · 값 유지 — M02 면적산출 · 재료산출 저장본을 서버 엔진으로 다시 풀면 저장된 `계산값` 과 같음.
"""저장 뒤 식 · 값 유지 — M02 면적산출 저장본을 서버 엔진으로 다시 풀면 저장된 `계산값` 과 같음.
시험 안 견본 저장본(`fixtures/m02_master_sample` · PLAN 26-5a)을 읽기만 함.
저장 때 서버가 적은 값과 지금 엔진 풀이가 어긋나면 화면을 다시 열 때
@@ -18,7 +18,7 @@ pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가
def _books() -> list[tuple[str, dict]]:
out = []
for kind in ("area", "material"):
for kind in ("area",):
for path in sorted((FOLDER / kind).glob("[!.]*.json")):
book = json.loads(path.read_text(encoding="utf-8"))
if isinstance(book, dict) and book.get("시트"):
@@ -1,4 +1,4 @@
"""F — 면적산출(area) · 재료산출(material) 저장 · 옛 산출근거(basis) 옮김 · [저장] 때 서버 계산값 ·
"""F — 면적산출(area) 저장 · 옛 산출근거(basis) 옮김 · [저장] 때 서버 계산값 ·
알약 연결(`연결`)은 저장 · 복사 때 문서와 같이.
서버 Node 풀이는 `test_spreadsheet_node.py` 가 따로 봄 — 여기는 `with_server_values` 를 바꿔 끼워
@@ -42,7 +42,7 @@ def _write(folder, kind, column, doc):
def test_옛_산출근거를_면적산출로_옮기고_번호_8자리(client, tmp_path):
"""12장 파일(SD · SC 6자리 · basis/) → SA 로 옮김 · 끝에 00 · 재료산출 새로 · 내용 그대로."""
"""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}
@@ -56,30 +56,26 @@ def test_옛_산출근거를_면적산출로_옮기고_번호_8자리(client, tm
"A2": {"값": "터파기"}
}
assert store.read("structure", "rv")["문서"]["도번"] == "SD01010100"
material = store.read("material", "rv")["문서"]
assert (material["재료번"], material["시트"][0]["이름"]) == ("SM01010100", "재료산출")
assert material["시트"][0]["칸"] == {} and material["서식"] == [{}] # 진짜 빈 시트
assert not (tmp_path / "material").exists() # 재료산출은 없앰
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):
def test_새_구조물_도면은_면적_진짜_빈_시트_같은_8자리(client):
made1 = client.post("/api/m02/structures/rv").json()["문서"]
made2 = client.post("/api/m02/structures/pv_b").json()["문서"]
# 새 항목 번호 = 자리 뜻 없는 일련번호(PLAN 26-1)
assert made1["도번"] == "SD-000001" and made2["도번"] == "SD-000002"
for kind, key, head in (("area", "면적번", "SA"), ("material", "재료번", "SM")):
for kind, key, head in (("area", "면적번", "SA"),):
doc = client.get(f"/api/m02/templates/{kind}/pv_b").json()["문서"]
assert doc[key] == f"{head}-000002"
assert doc["시트"][0]["칸"] == {} and doc["서식"] == [{}] # 머리 줄 없음
numbers = client.get("/api/m02/structures/numbers").json()
assert numbers["pv_b"] == {"도번": "SD-000002", "면적번": "SA-000002", "재료번": "SM-000002"}
assert numbers["pv_b"] == {"도번": "SD-000002", "면적번": "SA-000002"}
@pytest.mark.parametrize(
("kind", "key", "head"), [("area", "면적번", "SA"), ("material", "재료번", "SM")]
)
@pytest.mark.parametrize(("kind", "key", "head"), [("area", "면적번", "SA")])
def test_다른_열_내용을_복사해_가져옴_연결도_같이(client, kind, key, head):
store.renumber()
got = client.get(f"/api/m02/templates/{kind}/rv").json()
@@ -117,21 +113,21 @@ def test_저장은_서버_계산값만_적음_연결_두고_번호는_서버_것
def test_못_풀면_저장을_막음(client, monkeypatch):
store._ensure("material", "rv")
got = client.get("/api/m02/templates/material/rv").json()
store._ensure("area", "rv")
got = client.get("/api/m02/templates/area/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["문서"]})
r = client.put("/api/m02/templates/area/rv", json={"판": got["판"], "문서": got["문서"]})
assert r.status_code == 503
assert client.get("/api/m02/templates/material/rv").json()["판"] == got["판"]
assert client.get("/api/m02/templates/area/rv").json()["판"] == got["판"]
def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch):
client.post("/api/m02/structures/rv")
for kind in ("area", "material"):
for kind in ("area",):
doc = store.read(kind, "rv")["문서"]
layers.check_skeleton(kind, doc)
for bad in ({}, {**doc, "시트": {}}):
@@ -140,5 +136,6 @@ def test_층_규칙과_프로젝트_복사(client, tmp_path, monkeypatch):
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 not any(k.startswith("material/") for k in copied["작업본"]) # 재료산출 없앰
for kind in ("area",):
assert f"{kind}/rv" in copied["작업본"] and f"{kind}/rv" in copied["초기"]
+1 -1
View File
@@ -54,7 +54,7 @@ def world(tmp_path: Path, monkeypatch, master_copy: Path) -> dict[str, Any]:
system.mkdir()
for name in (layers.COMBO_FILE,):
shutil.copy2(REAL / name, system / name)
for kind in ("table", "structure", "area", "material"):
for kind in ("table", "structure", "area"):
shutil.copytree(REAL / kind, system / kind)
monkeypatch.setattr(layers, "SYSTEM_ROOT", system)
monkeypatch.setattr(m01, "FOLDER", master_copy)
+1 -1
View File
@@ -196,7 +196,7 @@ def test_분류_더하기로_복사한_열은_원본_키_세_파일_내용을_
numbers = client.get("/api/m02/structures/numbers").json()
assert numbers["c9"]["도번"] != numbers["rv"]["도번"] # 새 번호
assert numbers["c9"]["면적번"][2:] == numbers["c9"]["도번"][2:]
for kind in ("area", "material"):
for kind in ("area",):
assert client.get(f"/api/m02/templates/{kind}/c9").json()["문서"]["열"] == "c9"
# 그 뒤로는 열마다 따로 — 원본을 고쳐도 복사본은 그대로
drawing = client.get("/api/m02/templates/structure/rv").json()
+1 -1
View File
@@ -80,7 +80,7 @@ def test_지운_뒤_항목_0_틀은_남음(empty):
combos = client.get("/api/m02/structure-combos").json()
assert combos["줄"] == [] and combos["다음"] == 52
assert len(combos["지운번호"]) == 4 # 견본에 세 파일이 있던 넷(rv · c4 · pp_len · ms)
for kind in ("structure", "area", "material", "inputs"):
for kind in ("structure", "area", "inputs"):
assert not list((system / kind).glob("*.json")), kind
+2 -2
View File
@@ -164,7 +164,7 @@ def test_새_번호는_옛_번호와_모양이_달라_안_겹침(world):
doc = json.loads(path.read_bytes())
path.write_text(json.dumps({**doc, key: f"{head}{code}"}), encoding="utf-8")
old = {cid: _numbers(system, cid) for cid in store._column_ids()}
assert old["rv"] == {"SD01010100", "SA01010100", "SM01010100"}
assert old["rv"] == {"SD01010100", "SA01010100"}
first = max(int(n[3:]) for nums in old.values() for n in nums if NEW.match(n)) + 1
with store.at(system):
got = store.read("table", store.STRUCTURE_TABLE)
@@ -191,7 +191,7 @@ def test_새_번호는_옛_번호와_모양이_달라_안_겹침(world):
got["문서"]["열"].append({"id": "y", "머리": ["y"], "꼴": "수"})
store.write("table", store.STRUCTURE_TABLE, got["판"], got["문서"])
last = f"-{first + 120:06d}" # 지운 x119 번호(first + 119) 다음
assert _numbers(system, "y") == {f"SD{last}", f"SA{last}", f"SM{last}"}
assert _numbers(system, "y") == {f"SD{last}", f"SA{last}"}
def test_층마다_번호는_따로_파일은_열_id_로(world):
+1 -2
View File
@@ -68,7 +68,7 @@ def test_시스템_층은_그대로_항목_목록은_SS_줄(world):
ss = store.read_combos()["줄"]
assert [r["key"] for r in rows] == [r["키"] for r in ss] and rows[0]["key"] == "SS000001"
assert set(rows[0]) == {"key", "names", *items_router.items_store.ITEM_FIELDS}
assert set(rows[0]["names"]) == {"일위대가", "도번", "면적번", "재료번"}
assert set(rows[0]["names"]) == {"일위대가", "도번", "면적번"}
assert client.get("/api/m02/layers/system/structure-combos").json() == store.read_combos()
# 시스템 층 쓰기는 SYSTEM_ADMIN 만
assert client.post("/api/m02/layers/system/structures/rv").status_code == 403
@@ -145,7 +145,6 @@ def test_회사_항목을_개인으로_복사(world):
"일위대가": ua_name,
"도번": "돌기슭막이 찰쌓기 회사형",
"면적번": "돌기슭막이 찰쌓기 회사형",
"재료번": "돌기슭막이 찰쌓기 회사형",
}
user = _as(world)
+8 -8
View File
@@ -1,7 +1,7 @@
"""M02 구조물 도면(structure) — 새로 · 도번 SD+8자리 · 409 · 저장 · 지운 뒤 번호 ·
번호 매김(새 항목 = 자리 뜻 없는 일련번호 · PLAN 26-1) · 집계표 저장 때 만들기/지우기 ·
복사 가져오기 · 프로젝트 복사.
면적산출(area) · 재료산출(material)은 따로 파일 —
면적산출(area)은 따로 파일(재료산출은 없앰) —
`resources/tester/spreadsheet/test_spreadsheet_store.py`."""
import json
@@ -64,8 +64,8 @@ def client(tmp_path, monkeypatch):
def _num(code: str) -> dict:
"""세 번호 — 같은 8자리."""
return {"도번": f"SD{code}", "면적번": f"SA{code}", "재료번": f"SM{code}"}
"""두 번호 — 같은 8자리."""
return {"도번": f"SD{code}", "면적번": f"SA{code}"}
def test_새로_도번_자동_409_없는_열(client):
@@ -82,10 +82,10 @@ def test_새로_도번_자동_409_없는_열(client):
assert doc["도면"]["format"] == 6
assert doc["도면"]["entities"] == []
assert len(doc["도면"]["layers"]) == 1
# 면적산출 · 재료산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서씩
# 면적산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서 · 재료산출은 안 만듦
assert "산출근거" not in doc
for kind in ("area", "material"):
assert client.get(f"/api/m02/templates/{kind}/pp_len").json()["문서"]["종류"] == "통합문서"
assert client.get("/api/m02/templates/area/pp_len").json()["문서"]["종류"] == "통합문서"
assert client.get("/api/m02/templates/material/pp_len").status_code == 404
assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD-000002"
assert client.post("/api/m02/structures/pp_len").status_code == 409
@@ -127,7 +127,7 @@ def test_지워도_번호를_당기지_않음_짝은_같은_번호(client):
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("-000002"), "도번": ""} # 면적 · 재료는 남아 번호를 쥠
assert numbers["rv"] == {**_num("-000002"), "도번": ""} # 면적은 남아 번호를 쥠
assert numbers["ms"] == _num("-000003") # 다른 열 번호는 그대로
# 새 열 = 다음 일련번호(자리 뜻 없음) · 다시 만든 도면은 짝(면적산출) 번호를 이어 받음
assert client.post("/api/m02/structures/pp_cp").json()["문서"]["도번"] == "SD-000004"
@@ -181,7 +181,7 @@ def test_집계표_저장_때_새_열은_만들고_지운_열은_같이_지움(c
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"):
for kind_ in ("structure", "area"):
assert client.get(f"/api/m02/templates/{kind_}/pv_a").status_code == 404
# 새 열 = 열 차례로 다음 일련번호 · 지운 pv_a(5) 는 비워 둠(다른 번호를 안 당김)
assert numbers["pv_x"] == _num("-000014")
+2 -2
View File
@@ -84,9 +84,9 @@ def test_집계표_저장_때_새_열_지운_열_고친_열을_맞춤_키_유지
assert (rv["분류1"], rv["분류3"]) == (old_rv["분류1"], old_rv["분류3"])
assert (rv["단위"], rv["일위대가"]) == ("m", "UA000004")
assert (rv["바인딩"], rv["펼침"]) == ({"종류": "revetment"}, True)
# 번호는 세 파일과 같음 — 새 열도 [저장] 한 번에 SD · SA · SM 이 섬
# 번호는 파일과 같음 — 새 열도 [저장] 한 번에 SD · SA 가 섬
numbers = client.get("/api/m02/structures/numbers").json()
assert {k: rows["new1"][k] for k in ("도번", "면적번", "재료번")} == numbers["new1"]
assert {k: rows["new1"][k] for k in ("도번", "면적번")} == numbers["new1"]
assert rows["new1"]["도번"].startswith("SD")
# 또 저장해도(바뀐 것 없음) 키 · 다음 그대로
+7 -9
View File
@@ -51,8 +51,8 @@ def test_소스가_700줄을_넘지_않음():
),
# 산출근거 저장은 컨테이너가 안 함 — [산출근거 수정] 이 여는 메인 칸 전체 편집이 함
("M02_MasterTemplete_Api_Fetch.ts", ["saveTemplate"], MAIN),
# 좌측 구조물 도면 · 면적산출 · 재료산출 줄 번호는 한 번에 받음
# (`{열 id: {도번, 면적번, 재료번}}`)
# 좌측 구조물 도면 · 면적산출 줄 번호는 한 번에 받음
# (`{열 id: {도번, 면적번}}`)
("M02_MasterTemplete_Api_Fetch.ts", ["fetchStructureNumbers"], SIDE),
("M02_MasterTemplete_Structure.ts", ["structureLabel", "isStructureColumn"], SIDE),
# 도면 카드(31-4)는 카드 파일에서 뗀 `DrawingCard` 가 부름
@@ -72,15 +72,13 @@ def test_컨테이너가_쓰는_이름이_내보내진_것(source: str, names: l
def test_sub2_가_부르는_이름과_모양():
"""브레인 확정 이름 · 모양 — sub2 가 이대로 부름(바꾸면 브레인에게)."""
body = _text(M02 / "M02_MasterTemplete_Api_Fetch.ts")
for name in ("copyStructure", "copyArea", "copyMaterial"):
for name in ("copyStructure", "copyArea"):
assert re.search(
rf"export const {name} = async \(column: string, from: string\): Promise<void>", body
), name
assert "fetchStructureNumbers = (): Promise<Record<string, StructureNumbers>>" in body
assert re.search(
r"interface StructureNumbers \{\s*도번: string;\s*면적번: string;\s*재료번: string;", body
)
assert re.search(r'export type Kind = [^;]*"area" \| "material"', body)
assert re.search(r"interface StructureNumbers \{\s*도번: string;\s*면적번: string;\s*\}", body)
assert re.search(r'export type Kind = [^;]*"area";', body)
def test_집계표만_컨테이너를_붙임():
@@ -154,7 +152,7 @@ def test_좌측_구조물_집계_양식_아래_하위_넷_새로_본떠_지우
assert "createSelectField" not in side # 새로 = 종류 고르기 없이 이름만
assert "cur?.종류 !== kind" in side # 본떠 · 지우기 = 그 컨테이너에서 고른 것이 있을 때만
assert "i.이름 === STRUCTURE_TABLE" in side # 표는 구조물집계표 한 줄만
# 구조물 도면 · 면적산출 · 재료산출 = 구조물 집계 양식 컨테이너 안(같이 접힘) · 자재 조합 없앰
# 구조물 도면 · 면적산출 = 구조물 집계 양식 컨테이너 안(같이 접힘) · 자재 조합 없앰
summary = side.split("const summary = section(")[1].split(");")[0]
assert "COLUMN_KINDS.map" in summary and "matset" not in side
assert 'M02_KindTable: ["구조물 조합"' in _text( # 화면 이름(26-7a)
@@ -214,7 +212,7 @@ def test_표_도구_막대에_줄_더하기_줄_지우기_손_입력_열_없음(
def test_상세_화면_돌아가기는_집계표로_그_열_고른_채():
main = _text(MAIN)
assert "children: [title, back, slot, reload, save, " in main # 제목 바로 오른쪽
assert "!(sel.kind in NUMBER_FIELD)" in main # 구조물 도면 · 면적산출 · 재료산출만
assert "!(sel.kind in NUMBER_FIELD)" in main # 구조물 도면 · 면적산출만
body = main.split('back.addEventListener("click"')[1]
assert "confirmLeave()" in body and "STRUCTURE_TABLE" in body
assert "sheet?.selectColumn(column)" in body
+1 -1
View File
@@ -188,7 +188,7 @@ def test_빈_층에서_폴더_항목_깊이_1_2_5_이름_옮기기_지우기(wor
(root / "inputs" / f"{gone['열']}.json").write_text('{"판": 1, "줄": []}', encoding="utf-8")
note = t.do(op="delete", 키=gone["key"])["알림"]
assert note["지운항목"] == [gone["열"]]
for part in ("structure", "area", "material", "inputs"):
for part in ("structure", "area", "inputs"):
assert not (root / part / f"{gone['열']}.json").is_file()
t.do(op="add-item", 경로=["돌쌓기"], 이름="새것") # 지운 번호는 다시 안 줌
assert _numbers(root, t.item(["돌쌓기", "새것"])["열"]) == {"-000004"}
+1 -1
View File
@@ -50,7 +50,7 @@ def test_회사_항목_폴더를_개인에_붙임_새_키_파일_이름2(world):
table = json.loads((root / "table/구조물집계표.json").read_bytes())
made = next(c for c in table["열"] if c.get("경로") == ["옹벽"])
assert made["본뜬"] == {"layer": "company", "owner": "7", "key": wall}
for kind in ("structure", "area", "material"):
for kind in ("structure", "area"):
assert (root / kind / f"{made['id']}.json").is_file()
# 다시 붙이면 같은 이름 「(2)」 · 붙일 폴더 안으로
again = client.post(PASTE.format("personal"), json={**body, "경로": []}).json()
@@ -1,6 +1,6 @@
"""표 도면 줄 · 칸 고르기 신호 — `ui_template_sheet_ops.ts` 를 Node 로 바로 돌려 봄.
마스터 줄 = 일위대가 · 도면 `s:drawing` · 면적산출 `s:area` · 재료산출 `s:material`(식 · 들어갈 것 줄 없음) ·
마스터 줄 = 일위대가 · 도면 `s:drawing` · 면적산출 `s:area`(식 · 들어갈 것 줄 없음) ·
master 만 · 단위 줄 바로 아래 차례(키보드) · project 에는 없음.
`selectionOf` = 격자가 `onSelect` 로 넘기는 모양({row, col, colId} · 모르는 열은 col -1 · colId null).
"""
@@ -26,7 +26,7 @@ console.log(JSON.stringify({
master: navRows(doc, "master"),
project: navRows(doc, "project"),
pick: [selectionOf(doc, "s:drawing", "c2"), selectionOf(doc, "d:r1", "c1"), selectionOf(doc, "s:unit", "zz")],
blank: ["s:price", "s:drawing", "s:area", "s:material", "s:unit", "d:r1"].map((k) => [isBlankPick(k, { id: "t", 머리: [], 꼴: "글" }), isBlankPick(k, { id: "n", 머리: [], 꼴: "수" })]),
blank: ["s:price", "s:drawing", "s:area", "s:unit", "d:r1"].map((k) => [isBlankPick(k, { id: "t", 머리: [], 꼴: "글" }), isBlankPick(k, { id: "n", 머리: [], 꼴: "수" })]),
}));
"""
@@ -53,7 +53,7 @@ def _run() -> dict:
def test_drawing_row_under_price_master_only():
got = _run()
assert got["master"] == [
"s:unit", "s:price", "s:drawing", "s:area", "s:material", "d:r1", "t:t1",
"s:unit", "s:price", "s:drawing", "s:area", "d:r1", "t:t1",
] # fmt: skip
assert got["project"] == ["s:unit", "d:r1", "t:t1"]
@@ -68,5 +68,5 @@ def test_selection_shape():
def test_text_column_pick_rows_blank():
assert _run()["blank"] == [
[True, False], [True, False], [True, False], [True, False], [False, False], [False, False],
[True, False], [True, False], [True, False], [False, False], [False, False],
] # fmt: skip
@@ -36,7 +36,7 @@ def _copy_layer(target: Path) -> None:
target.mkdir(parents=True)
for name in (layers.COMBO_FILE,):
shutil.copy2(REAL / name, target / name)
for kind in ("table", "structure", "area", "material"):
for kind in ("table", "structure", "area"):
shutil.copytree(REAL / kind, target / kind)
@@ -131,7 +131,12 @@ def test_지정하면_폴더가_생기고_내용이_원본과_같음(world):
assert result == {"만듦": ["pipe@40.0", "st0"], "지움": [], "못만듦": [], "옮겨받음": []}
folder = _items(root) / "st0"
got = {path.name for path in folder.iterdir()}
assert {"집계.json", "도면.json", "면적.json", "재료.json", "manifest.json"} <= got
assert {
"집계.json",
"도면.json",
"면적.json",
"manifest.json",
} <= got and "재료.json" not in got
total = json.loads((folder / "집계.json").read_text(encoding="utf-8"))
assert total["줄"]["키"] == "SS000022" and total["열"]["id"] == "ms"
drawing = json.loads((world["system"] / "structure/ms.json").read_text(encoding="utf-8"))
+4 -6
View File
@@ -43,7 +43,6 @@ const row = (
일위대가: "UA000004",
도번: "SD01010100",
면적번: "SA01010100",
재료번: "SM01010100",
layer,
owner: layer === "company" ? "샘플회사" : layer === "personal" ? "샘플회사/사용자" : null,
...extra,
@@ -55,7 +54,6 @@ const rows: PickerRow[] = [
일위대가: "돌기슭막이 메쌓기 일위대가",
도번: "돌기슭막이 도면",
면적번: "돌기슭막이 면적산출",
재료번: "돌기슭막이 재료산출",
},
}),
row("SS000002", "rv2", "돌기슭막이", ["메쌓기", "1.5m", "기초유"], "system"),
@@ -219,20 +217,20 @@ assert.deepEqual(pathIds(rows[0], labels), [
"system|돌기슭막이|메쌓기|1.2m",
]);
// 폴더 모양 — 네 줄 · 이름은 서버 names · 없으면 키만(작은 글) · 둘 다 없으면 빈 키
// 폴더 모양 — 세 줄 · 이름은 서버 names · 없으면 키만(작은 글) · 둘 다 없으면 빈 키
const lines = folderLines(rows[0]);
assert.deepEqual(
lines.map((l) => l.kind),
["drawing", "area", "material", "unitPrice"],
["drawing", "area", "unitPrice"],
);
assert.deepEqual(lines[0], { kind: "drawing", name: "돌기슭막이 도면", key: "SD01010100" });
assert.deepEqual(lines[3], {
assert.deepEqual(lines[2], {
kind: "unitPrice",
name: "돌기슭막이 메쌓기 일위대가",
key: "UA000004",
});
assert.deepEqual(folderLines(rows[1])[0], { kind: "drawing", name: null, key: "SD01010100" }); // names 자체가 없음
assert.deepEqual(folderLines({ key: "SS1", 열: "x" })[3], {
assert.deepEqual(folderLines({ key: "SS1", 열: "x" })[2], {
kind: "unitPrice",
name: null,
key: "",