- POST /api/m02/structures/{열}/combo — 고친 담은 로직으로 새 UA(같은 로직 집합이면 있는 키) · 집계표 그 열만 새 키 · 판 어긋나면 조합 만들기 전 409 · SS 장부 맞춤
- 일위대가 조합 줄에 본뜬키 칸(원래 키) · 기존 37 줄 null · M01 고치기에서 안 보내도 유지
- 자재 조합 표 resources/master_data/자재조합.json(키 UM · 대장 등록 · 틀 검사) · POST /structures/{열}/material-combo · GET /material-combos/{키}
- 구조물집계표조합 칸 자재조합 · 분류 더하기로 복사한 열은 저장 때 원본 열의 일위대가 · 자재조합 키와 구조물 도면 · 면적산출 · 재료산출 내용을 새 번호로 받음
- Api_Fetch branchCombo · branchMaterialCombo · fetchMaterialCombo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011vX53nJGC8ucBRFJns2n7B
212 lines
10 KiB
Python
212 lines
10 KiB
Python
"""조합 갈라 내기 · 분류 더하기 복사 (PLAN 15장).
|
|
|
|
M02 견본 표(`write_sample_table`) + M01 마스터 사본(조합 줄은 비워 시작)으로만 봄.
|
|
· 일위대가(UA) — 새로 만듦(본뜬키 = 원래 키) · 이 열만 새 키 · 다른 열은 옛 키 · 원래 조합 그대로 ·
|
|
SS 장부 맞춤 · 담은 로직 집합(차례 · 메모 무시)이 같으면 있는 키 · 또 고치면 또 새로 · 같은 이름 (2)
|
|
· 자재 조합(UM) — 같은 규칙 · 처음 만드는 열(본뜬키 없음) · 읽기
|
|
· 집계표 판 어긋남 = 409 · 조합 안 만듦
|
|
· 분류 더하기로 복사한 열(`본뜬열`) — [저장] 때 원본 키 · 세 파일 내용을 새 번호로 받음
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
|
from M02_MasterTemplete import M02_MasterTemplete_Branch as branch
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete.M02_MasterTemplete_Router import router
|
|
from resources.tester.test_m02_structure import write_sample_table
|
|
|
|
REAL = m01.FOLDER
|
|
A, B, C = "GF000219", "GF000220", "GF000211" # 돌쌓기 메쌓기(인력 · 장비) · 채집(인력)
|
|
M1, M2 = (
|
|
{"코드": "MT000001", "이름": "시멘트", "규격": "40kg", "단위": "포"},
|
|
{"코드": "MT000002", "이름": "모래", "규격": None, "단위": "㎥"},
|
|
)
|
|
TABLE = "/api/m02/templates/table/구조물집계표"
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(tmp_path, monkeypatch):
|
|
master = tmp_path / "master"
|
|
master.mkdir()
|
|
for path in REAL.glob("*.json"):
|
|
if not path.name.startswith("_") or path.name == m01.mk.BOOK.name:
|
|
shutil.copy(path, master / path.name)
|
|
for name in (combo_store.FILE, branch.MATERIAL_FILE):
|
|
own = master / name
|
|
data = json.loads(own.read_text(encoding="utf-8"))
|
|
data["줄"] = []
|
|
own.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
monkeypatch.setattr(m01, "FOLDER", master)
|
|
template = tmp_path / "template"
|
|
template.mkdir()
|
|
write_sample_table(template)
|
|
monkeypatch.setattr(store, "FOLDER", template)
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
return TestClient(app)
|
|
|
|
|
|
def _source(client) -> str:
|
|
"""원래 조합(A · B) — rv · c1 두 열이 같이 가리킴."""
|
|
got = combo_store.combo_new(
|
|
{
|
|
"이름": "기슭막이",
|
|
"구분": "실무 일위대가",
|
|
"상세구분": "13장 구조물",
|
|
"단위": "m",
|
|
"담은로직": [{"로직": A, "메모": None}, {"로직": B, "메모": None}],
|
|
"출처": "자체",
|
|
},
|
|
"현장",
|
|
)
|
|
table = client.get(TABLE).json()
|
|
for col in table["문서"]["열"]:
|
|
if col["id"] in ("rv", "c1"):
|
|
col["일위대가"] = got["key"]
|
|
assert client.put(TABLE, json={"판": table["판"], "문서": table["문서"]}).status_code == 200
|
|
return got["key"]
|
|
|
|
|
|
def _branch(client, column, source, keys, name="", 판="", want=200):
|
|
body = {"본뜬키": source, "담은로직": [{"로직": k} for k in keys], "이름": name, "판": 판}
|
|
res = client.post(f"/api/m02/structures/{column}/combo", json=body)
|
|
assert res.status_code == want, res.text
|
|
return res.json()
|
|
|
|
|
|
def _cols(client, field="일위대가") -> dict[str, str | None]:
|
|
return {c["id"]: c.get(field) for c in client.get(TABLE).json()["문서"]["열"]}
|
|
|
|
|
|
def _ss(client, field="일위대가") -> dict[str, str | None]:
|
|
rows = client.get("/api/m02/structure-combos").json()["줄"]
|
|
return {r["열"]: r[field] for r in rows}
|
|
|
|
|
|
def test_새로_만듦_이_열만_새_키_원래_조합_그대로(client):
|
|
ua = _source(client)
|
|
판 = client.get(TABLE).json()["판"]
|
|
got = _branch(client, "rv", ua, [A, B, C], "기슭막이 · 돌기슭막이", 판)
|
|
assert got["새로만듦"] is True and got["키"] != ua
|
|
assert got["이름"] == "기슭막이 · 돌기슭막이"
|
|
assert got["집계표판"] == client.get(TABLE).json()["판"] != 판
|
|
cols = _cols(client)
|
|
assert (cols["rv"], cols["c1"]) == (got["키"], ua) # 다른 열은 옛 키
|
|
assert _ss(client)["rv"] == got["키"] and _ss(client)["c1"] == ua # SS 장부 맞춤
|
|
new = combo_store.combo(got["키"])["combo"]
|
|
assert tuple(new) == combo_store.mcb.COLS # 열 한 벌 · 본뜬키 칸 포함
|
|
assert [x["로직"] for x in new["담은로직"]] == [A, B, C]
|
|
assert (new["본뜬키"], new["구분"], new["상세구분"], new["단위"], new["출처"], new["소유"]) == (
|
|
ua,
|
|
"실무 일위대가",
|
|
"13장 구조물",
|
|
"m",
|
|
"자체",
|
|
"현장",
|
|
)
|
|
assert [x["로직"] for x in combo_store.combo(ua)["combo"]["담은로직"]] == [A, B]
|
|
# M01 에서 고쳐도(화면이 본뜬키를 안 보내도) 계보는 남음
|
|
one = combo_store.combo(got["키"])
|
|
edited = {k: v for k, v in one["combo"].items() if k != "본뜬키"} | {"이름": "고친 이름"}
|
|
combo_store.combo_edit(got["키"], one["version"], edited)
|
|
assert combo_store.combo(got["키"])["combo"]["본뜬키"] == ua
|
|
|
|
|
|
def test_같은_로직_집합이면_있는_키_또_고치면_또_새로_같은_이름은_2(client):
|
|
ua = _source(client)
|
|
same = _branch(client, "c1", ua, [B, A]) # 차례만 바꿈 = 원래 조합
|
|
assert (same["키"], same["새로만듦"]) == (ua, False)
|
|
first = _branch(client, "rv", ua, [A, B, C], "갈래")
|
|
again = _branch(client, "rv", first["키"], [A, C], "갈래") # 갈라 낸 것을 또 고침 = 또 새로
|
|
assert again["새로만듦"] is True and again["키"] not in (ua, first["키"])
|
|
assert again["이름"] == "갈래 (2)"
|
|
assert combo_store.combo(again["키"])["combo"]["본뜬키"] == first["키"]
|
|
back = _branch(client, "c1", ua, [C, A, B]) # 다른 열이 같게 고침 = 있는 키
|
|
assert (back["키"], back["새로만듦"]) == (first["키"], False)
|
|
assert _cols(client)["c1"] == first["키"] and _ss(client)["c1"] == first["키"]
|
|
|
|
|
|
def test_집계표_판_어긋나면_409_조합_안_만듦(client):
|
|
ua = _source(client)
|
|
before = len(m01.read(combo_store.FILE)[0]["줄"])
|
|
_branch(client, "rv", ua, [A, B, C], "x", "0000000000000000", want=409)
|
|
assert len(m01.read(combo_store.FILE)[0]["줄"]) == before
|
|
assert _cols(client)["rv"] == ua
|
|
_branch(client, "no", ua, [A, C], want=404) # 글 열(구조물 아님)
|
|
_branch(client, "rv", "UA999999", [A, C], want=404) # 없는 원래 조합
|
|
|
|
|
|
def _material(client, column, source, items, name="", want=200):
|
|
body = {"본뜬키": source, "담은자재": items, "이름": name}
|
|
res = client.post(f"/api/m02/structures/{column}/material-combo", json=body)
|
|
assert res.status_code == want, res.text
|
|
return res.json()
|
|
|
|
|
|
def test_자재_조합_처음_만듦_같은_내용이면_있는_키_고치면_새로(client):
|
|
first = _material(client, "rv", "", [M1, M2])
|
|
assert first["새로만듦"] is True and first["키"].startswith("UM")
|
|
assert first["이름"] == "돌기슭막이 (형태마다) H=(높이마다)" # 이름 없으면 열 머리
|
|
row = client.get(f"/api/m02/material-combos/{first['키']}").json()
|
|
assert tuple(row) == ("키", "이름", "담은자재", "본뜬키", "소유", "비고")
|
|
assert (row["본뜬키"], row["소유"], row["담은자재"]) == (None, "현장", [M1, M2])
|
|
same = _material(client, "c1", "", [M2, M1]) # 차례만 다름 = 있는 키
|
|
assert (same["키"], same["새로만듦"]) == (first["키"], False)
|
|
fork = _material(client, "c1", first["키"], [M1], "시멘트만")
|
|
assert fork["새로만듦"] is True and fork["키"] != first["키"]
|
|
assert client.get(f"/api/m02/material-combos/{fork['키']}").json()["본뜬키"] == first["키"]
|
|
assert (_cols(client, "자재조합")["rv"], _cols(client, "자재조합")["c1"]) == (
|
|
first["키"],
|
|
fork["키"],
|
|
)
|
|
assert _ss(client, "자재조합")["c1"] == fork["키"]
|
|
assert client.get("/api/m02/material-combos/UM999999").status_code == 404
|
|
_material(client, "rv", "", [{"코드": "없는모양"}], want=422) # M01 저장 검사
|
|
|
|
|
|
def test_분류_더하기로_복사한_열은_원본_키_세_파일_내용을_받음(client):
|
|
ua = _source(client)
|
|
table = client.get(TABLE).json()
|
|
doc = table["문서"]
|
|
rv = next(c for c in doc["열"] if c["id"] == "rv")
|
|
rv["자재조합"] = "UM000001"
|
|
assert client.put(TABLE, json={"판": table["판"], "문서": doc}).status_code == 200
|
|
drawing = client.get("/api/m02/templates/structure/rv").json()
|
|
marked = {**drawing["문서"], "도면": {"format": 6, "entities": [{"type": "line"}]}}
|
|
put = client.put("/api/m02/templates/structure/rv", json={"판": drawing["판"], "문서": marked})
|
|
assert put.status_code == 200
|
|
table = client.get(TABLE).json()
|
|
doc = table["문서"]
|
|
at = next(i for i, c in enumerate(doc["열"]) if c["id"] == "rv")
|
|
head = ["돌기슭막이", "(형태마다)", "H=(높이마다) 2", None]
|
|
doc["열"].insert(at + 1, {"id": "c9", "머리": head, "단위": None, "꼴": "수", "본뜬열": "rv"})
|
|
assert client.put(TABLE, json={"판": table["판"], "문서": doc}).status_code == 200
|
|
saved = next(c for c in client.get(TABLE).json()["문서"]["열"] if c["id"] == "c9")
|
|
assert "본뜬열" not in saved # 뗌
|
|
assert (saved["일위대가"], saved["자재조합"]) == (ua, "UM000001")
|
|
copy = client.get("/api/m02/templates/structure/c9").json()["문서"]
|
|
assert copy["도면"]["entities"] == [{"type": "line"}] and copy["열"] == "c9"
|
|
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"):
|
|
assert client.get(f"/api/m02/templates/{kind}/c9").json()["문서"]["열"] == "c9"
|
|
# 그 뒤로는 열마다 따로 — 원본을 고쳐도 복사본은 그대로
|
|
drawing = client.get("/api/m02/templates/structure/rv").json()
|
|
client.put(
|
|
"/api/m02/templates/structure/rv",
|
|
json={
|
|
"판": drawing["판"],
|
|
"문서": {**drawing["문서"], "도면": marked["도면"] | {"entities": []}},
|
|
},
|
|
)
|
|
again = client.get("/api/m02/templates/structure/c9").json()["문서"]
|
|
assert again["도면"]["entities"] == [{"type": "line"}]
|