Files
Aislo/resources/tester/test_m02_hopyo_build.py
T
eomsangdonandClaude Opus 5.5 736aa405cf feat(일위대가실무): 324-3 실무 호표 → 우리 일위대가 사전조합 만들기 · 사전조합 줄 별칭 살림
- M02_Hopyo_Build: 호표 = 사전조합 하나(이름 = 품명 + 규격 · 엑셀 단위 · 구분 실무자료 · 상세구분 파일 · 본뜬 실무자료 표시) · 로직 줄 {별칭 = 엑셀 품명 40 자 · 메모 「실무 0.14㎥ × 1 · 규격」 · 입력값 · 수량 손 = 엑셀 × 환산} · 자재 줄(값 없음 = 별도계상 · 아니면 직접계산 · 같은 자재 수량 더함) · 같은 로직 둘째 = 한 줄 사전조합으로 감쌈 · 일위N = 먼저 만들어 참조 · 순환 = 빠진 줄 · 같은 층 · 파일 · 호표 = 다시 씀 · 새로 = 그 키에 덮음 · 빠진 줄 + 엑셀 금액 · 만든 값 ↔ 엑셀 단가
- M02_Labor_Ref._combo_lines: 별칭 있으면 남김(한 줄)
- 길 POST /layers/{층}/hopyo-build(이 층 쓰기 · 파일 층 읽기)
- 시험 test_m02_hopyo_build 5 통과 · 곁 시험 84 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 22:15:59 +09:00

153 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""일위대가 실무자료 → 사전조합 만들기(PLAN 324-3 · `M02_Hopyo_Build`) — 별칭 남음 ·
수량 = 엑셀 × 환산 · 자재 · 같은 로직 둘 = 감쌈 · 일위N 참조 · 순환 ·
같은 호표 다시 = 같은 사전조합 · 새로 = 덮음 ·
빠진 줄 · 값 비교 · 길 권한 · master_data 원본 그대로. 층은 임시 폴더.
"""
import json
from M02_MasterTemplete import M02_Hopyo_Store as hs
from M02_MasterTemplete import M02_Template_Layers as layers
# 올리기 길 시험의 `client` 마당을 같이 씀
# ruff: noqa: F811
from resources.tester.test_m02_hopyo_excel import URL, _post, client, master_hash # noqa: F401
DIG = "구조물터파기(인력100%)|토사|m3"
FILL = "구조물되메우기(인력100%)||m3"
SOIL = "잔토처리(인력100%)||m3"
STONE = "임도표지석(받침,글새김포함)|900x600x200x150|m3"
MEMORY = "/api/m02/layers/{}/hopyo-memory"
BUILD = "/api/m02/layers/{}/hopyo-build"
def _remember(client, layer="personal", **lines):
res = client.post(MEMORY.format(layer), json={"줄": lines})
assert res.status_code == 200, res.text
def _build(client, no, layer="personal", want=200, **extra):
body = {"층": "personal", "키": "HP-000001", "번": no, **extra}
res = client.post(BUILD.format(layer), json=body)
assert res.status_code == want, res.text
return res.json()
def _doc(layer_dir, key):
return json.loads((layer_dir / "precombo" / f"{key}.json").read_bytes())
def test_만들기_별칭_수량_메모_빠진_줄_비교(client):
before = master_hash()
assert _post(client).status_code == 200
_remember(
client,
**{
DIG: {"로직": "GF000101", "입력값": {"지역": "서울"}, "환산": 1},
FILL: {"로직": "GF000113", "환산": 2},
},
)
got = _build(client, 3)
mine = layers.personal_dir(7, 42)
doc = _doc(mine, got["참조"]["키"])
assert (doc["이름"], doc["단위"], doc["구분"]) == ("임도표지석 910×600×150", "개소", "실무자료")
assert doc["상세구분"].endswith("(공통단가).xls")
assert doc["본뜬"]["층"] == "실무자료" and doc["본뜬"]["키"] == "HP-000001 제3호표"
assert doc["담은로직"] == [
{
"로직": "GF000101",
"입력값": {"지역": "서울"},
"별칭": "구조물터파기(인력100%)",
"수량": {"손": 0.14},
"메모": "실무 0.14㎥ × 1 · 토사",
},
{
"로직": "GF000113",
"입력값": {},
"별칭": "구조물되메우기(인력100%)",
"수량": {"손": 0.1}, # 엑셀 0.05 × 환산 2
"메모": "실무 0.05㎥ × 2",
},
]
assert [(x["이름"], x["금액"]) for x in got["빠진줄"]] == [
("임도표지석(받침,글새김포함)", 1400000),
("잔토처리(인력100%)", 3097.1),
]
assert got["비교"]["엑셀"] == 1415169 and got["비교"]["우리"] > 0
assert got["비교"]["차이"] == round(got["비교"]["우리"] - 1415169, 1)
assert master_hash() == before
def test_자재_같은_로직_감쌈_다시씀_새로(client):
assert _post(client).status_code == 200
_remember(
client,
**{
DIG: {"로직": "GF000101", "환산": 1},
SOIL: {"로직": "GF000101", "환산": 1},
STONE: {"자재": "MT004258", "환산": 1},
},
)
got = _build(client, 3)
mine = layers.personal_dir(7, 42)
doc = _doc(mine, got["참조"]["키"])
first, wrapped = doc["담은로직"]
assert first["로직"] == "GF000101" and first["별칭"] == "구조물터파기(인력100%)"
assert wrapped["별칭"] == "잔토처리(인력100%)" and wrapped["수량"] == {"손": 0.09}
inner = _doc(mine, wrapped["참조"]["키"])
assert inner["본뜬"]["키"] == "HP-000001 제3호표 4째 줄"
assert inner["담은로직"][0]["로직"] == "GF000101" and inner["담은로직"][0]["수량"] == {"손": 1}
assert [(m["코드"], m["수량"], m["별도계상"]) for m in doc["자재"]] == [
("MT004258", {"손": 1}, "직접계산")
]
files = sorted(p.name for p in (mine / "precombo").glob("*.json"))
again = _build(client, 3)
assert again["다시씀"] is True and again["참조"] == got["참조"]
assert sorted(p.name for p in (mine / "precombo").glob("*.json")) == files
_remember(client, **{SOIL: {"빼기": True}})
fresh = _build(client, 3, 새로=True)
assert fresh["참조"] == got["참조"] and fresh["다시씀"] is True
assert len(_doc(mine, got["참조"]["키"])["담은로직"]) == 1 # 덮음
assert sorted(p.name for p in (mine / "precombo").glob("*.json")) == files
def test_일위N_참조_먼저_만듦(client):
assert _post(client).status_code == 200
got = _build(client, 45)
mine = layers.personal_dir(7, 42)
lines = _doc(mine, got["참조"]["키"])["담은로직"]
ref = lines[0]
assert ref["별칭"] == "고화매트재료비" and ref["수량"] == {"손": 2.3}
child = _doc(mine, ref["참조"]["키"])
assert child["이름"] == "고화매트재료비" and child["본뜬"]["키"] == "HP-000001 제46호표"
assert any(x["까닭"].startswith("% 줄") for x in _build(client, 46)["빠진줄"])
def test_순환은_빠진_줄(client):
folder = layers.personal_dir(7, 42)
_post(client) # 층 뼈대
line = {"차례": 0, "이름": "남", "규격": "", "수량": 1, "단위": "식", "갈래": "호표"}
money = {"계": 1, "노무비": 1, "재료비": None, "경비": None}
hopyos = [
{"번": 1, "이름": "가", "규격": "", "단위": "식", "금액": money, "줄": [
{**line, "참조": {"종류": "일위", "번": 2}, "금액": money}]},
{"번": 2, "이름": "나", "규격": "", "단위": "식", "금액": money, "줄": [
{**line, "참조": {"종류": "일위", "번": 1}, "금액": money}]},
] # fmt: skip
key = hs.save(folder, "loop.xls", {"시트": "일위대가", "호표": hopyos, "검산": {}}, None)["키"]
got = _build(client, 1, 키=key)
assert got["빠진줄"] == []
inner = _doc(folder, _doc(folder, got["참조"]["키"])["담은로직"][0]["참조"]["키"])
assert inner["담은로직"] == [] # 제2호표 안 제1호표 = 순환 → 빠짐
def test_길_권한과_위_층_파일(client):
client.who.update(role="SYSTEM_ADMIN")
assert _post(client, "system").status_code == 200
client.who.update(role="USER")
_build(client, 3, layer="system", 층="system", want=403) # 마스터 쓰기 = 시스템 관리자
got = _build(client, 3, 층="system") # 마스터 파일 → 내 층 사전조합
assert got["참조"]["층"] == "personal" and got["참조"]["주인"] == "7/42"
assert _build(client, 99, 층="system", want=404)
assert client.get(URL.format("personal")).json()["줄"] == [] # 파일은 마스터에만