- 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
153 lines
6.8 KiB
Python
153 lines
6.8 KiB
Python
"""일위대가 실무자료 → 사전조합 만들기(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()["줄"] == [] # 파일은 마스터에만
|