- M02_Hopyo_Build — 몸 줄만(없으면 400) · 대상 여럿 같은 별칭 · 배 · 몫 · 통째 · 하위 호표 확인 전 409 · 본뜬 원본 · 확인 · 같은 본뜬 덮음 · confirmed(folder, source) - M02_Labor_Router — hopyo-build 엄격 몸(extra forbid) · hopyo-ledger GET · PUT · hopyo-memory 없앰 · picks 확인 · 올린 뒤 Fit.ensure · 시동 Ours.warm(있을 때만) · ?to= 만들 층 - M02_Hopyo_Ledger — times_of 나눔 - 시험 test_m02_hopyo_build.py 새로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
238 lines
12 KiB
Python
238 lines
12 KiB
Python
"""일위대가 실무자료 → 사전조합 만들기(PLAN 324-3 · 338-3 · `M02_Hopyo_Build`) — 확인한 줄만 ·
|
||
몸 없음 400 · 빠진 줄 까닭 · 로직 여럿 같은 별칭 · 같은 로직 감쌈 · 배 · 몫 · 자재 · 통째 ·
|
||
재검사 400(없는 로직 · 모르는 입력 · 배 ≤ 0 · 금액 칸) · 하위 호표 409 → 확인 뒤 참조 · 장부 씀 ·
|
||
`본뜬.확인` · 덮음 · 옛 즉시 확정 길 404 · 장부 길(보기 · 고치기 · 권한) · picks 확인 ·
|
||
master_data 원본 그대로. 층은 임시 폴더.
|
||
"""
|
||
|
||
import json
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Build as hb
|
||
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"
|
||
BUILD = "/api/m02/layers/{}/hopyo-build"
|
||
LEDGER = "/api/m02/layers/{}/hopyo-ledger"
|
||
|
||
|
||
def _build(client, no, lines=None, layer="personal", want=200, **extra):
|
||
body = {"층": "personal", "키": "HP-000001", "번": no, **extra}
|
||
if lines is not None:
|
||
body["줄"] = lines
|
||
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 _book(layer_dir):
|
||
return json.loads((layer_dir / hs.DIRNAME / f"{hs.MEMORY}.json").read_bytes())["줄"]
|
||
|
||
|
||
def test_확인한_줄만_별칭_수량_빠진_줄_장부(client):
|
||
before = master_hash()
|
||
assert _post(client).status_code == 200
|
||
lines = [
|
||
{"차례": 1, "대상": [{"로직": "GF000101", "입력값": {"지역": "서울"}, "배": 1}]},
|
||
{"차례": 2, "대상": [{"로직": "GF000113", "배": 2}]},
|
||
{"차례": 3, "뺌": "우리에 없음"},
|
||
]
|
||
got = _build(client, 3, lines)
|
||
mine = layers.personal_dir(7, 42)
|
||
doc = _doc(mine, got["참조"]["키"])
|
||
assert (doc["이름"], doc["단위"], doc["구분"]) == ("임도표지석 910×600×150", "개소", "실무자료")
|
||
assert doc["상세구분"].endswith("(공통단가).xls")
|
||
origin = doc["본뜬"]
|
||
assert (origin["층"], origin["키"], origin["원본"]) == (
|
||
"실무자료",
|
||
"HP-000001 제3호표",
|
||
["personal", "7/42"],
|
||
)
|
||
assert origin["확인"]["누가"] == {"id": 42, "이름": "갑"} and origin["확인"]["때"]
|
||
assert doc["담은로직"] == [
|
||
{
|
||
"로직": "GF000101",
|
||
"입력값": {"지역": "서울"},
|
||
"별칭": "구조물터파기(인력100%)",
|
||
"수량": {"손": 0.14},
|
||
"메모": "실무 0.14㎥ × 1 · 토사",
|
||
},
|
||
{
|
||
"로직": "GF000113",
|
||
"입력값": {},
|
||
"별칭": "구조물되메우기(인력100%)",
|
||
"수량": {"손": 0.1}, # 엑셀 0.05 × 배 2
|
||
"메모": "실무 0.05㎥ × 2",
|
||
},
|
||
]
|
||
assert doc["자재"] == [] # 표지석 = 몸에 없음 → 안 들어감
|
||
assert [(x["이름"], x["금액"], x["까닭"]) for x in got["빠진줄"]] == [
|
||
("임도표지석(받침,글새김포함)", 1400000, "확인 안 함"),
|
||
("잔토처리(인력100%)", 3097.1, "뺌 — 우리에 없음"),
|
||
]
|
||
assert got["비교"]["엑셀"] == 1415169 and got["비교"]["우리"] > 0
|
||
assert got["다시씀"] is False and got["장부"] == {"씀": 3}
|
||
book = _book(mine)
|
||
assert book[DIG]["갈래"] == "짝" and book[DIG]["짝"] == [
|
||
{"로직": "GF000101", "입력값": {"지역": "서울"}, "배": 1, "몫": None}
|
||
]
|
||
assert book[DIG]["처음파일"].endswith("(공통단가).xls")
|
||
assert book[DIG]["원래"] == {"품명": "구조물터파기(인력100%)", "규격": "토사", "단위": "㎥"}
|
||
assert book[SOIL]["갈래"] == "우리에 없음" and book[FILL]["짝"][0]["배"] == 2
|
||
assert hb.confirmed(mine) == {"HP-000001 제3호표": got["참조"]["키"]}
|
||
assert hb.confirmed(mine, ("system", None)) == {} # 다른 파일 층 = 따로
|
||
assert master_hash() == before
|
||
|
||
|
||
def test_로직_여럿_같은_별칭_감쌈_자재_몫_덮음(client):
|
||
assert _post(client).status_code == 200
|
||
lines = [
|
||
{"차례": 0, "대상": [{"자재": "MT004258", "배": 1}]},
|
||
{"차례": 1, "대상": [{"로직": "GF000101"}, {"로직": "GF000113", "배": 0.1, "몫": 0.1}]},
|
||
{"차례": 3, "대상": [{"로직": "GF000101"}]},
|
||
]
|
||
got = _build(client, 3, lines)
|
||
mine = layers.personal_dir(7, 42)
|
||
doc = _doc(mine, got["참조"]["키"])
|
||
first, second, wrapped = doc["담은로직"]
|
||
assert first["별칭"] == second["별칭"] == "구조물터파기(인력100%)" # 한 줄 로직 둘 = 같은 별칭
|
||
assert second["수량"] == {"손": 0.014} and second["메모"] == "실무 0.14㎥ × 0.1(몫 0.1) · 토사"
|
||
assert wrapped["별칭"] == "잔토처리(인력100%)" and wrapped["수량"] == {"손": 0.09}
|
||
inner = _doc(mine, wrapped["참조"]["키"])
|
||
assert inner["본뜬"]["키"] == "HP-000001 제3호표 4째 줄 GF000101"
|
||
assert inner["담은로직"][0]["로직"] == "GF000101" and inner["담은로직"][0]["수량"] == {"손": 1}
|
||
assert [(m["코드"], m["수량"], m["별도계상"]) for m in doc["자재"]] == [
|
||
("MT004258", {"손": 1}, "직접계산")
|
||
]
|
||
stone = next(v for k, v in _book(mine).items() if k.startswith("임도표지석(받침"))
|
||
assert stone["짝"][0]["자재"] == "MT004258"
|
||
files = sorted(p.name for p in (mine / "precombo").glob("*.json"))
|
||
again = _build(client, 3, lines[:1]) # 다시 확인 = 같은 본뜬 덮음
|
||
assert again["다시씀"] is True and again["참조"] == got["참조"]
|
||
assert _doc(mine, got["참조"]["키"])["담은로직"] == []
|
||
assert sorted(p.name for p in (mine / "precombo").glob("*.json")) == files
|
||
|
||
|
||
def test_몸_없음_재검사_400(client):
|
||
assert _post(client).status_code == 200
|
||
one = {"로직": "GF000101", "배": 1}
|
||
_build(client, 3, want=400) # 옛 몸(줄 없음) = 확인한 줄이 없음
|
||
_build(client, 3, [], want=400)
|
||
bad = [
|
||
[{"차례": 9, "대상": [one]}], # 호표 밖 줄
|
||
[{"차례": 1, "대상": [one]}, {"차례": 1, "대상": [one]}], # 겹친 줄
|
||
[{"차례": 1, "대상": []}], # 대상 · 뺌 없음
|
||
[{"차례": 1, "대상": [one], "뺌": "안 씀"}],
|
||
[{"차례": 1, "대상": [{"로직": "XX"}]}],
|
||
[{"차례": 1, "대상": [{"로직": "GC000422"}]}], # 돈 아닌 로직
|
||
[{"차례": 1, "대상": [{**one, "입력값": {"없는칸": 1}}]}],
|
||
[{"차례": 1, "대상": [{**one, "배": 0}]}],
|
||
[{"차례": 1, "대상": [{**one, "배": -1}]}],
|
||
[{"차례": 1, "대상": [{**one, "몫": 1.5}]}],
|
||
[{"차례": 1, "대상": [{**one, "자재": "MT004258"}]}],
|
||
[{"차례": 1, "대상": [{"자재": "MT000000"}]}],
|
||
[{"차례": 1, "대상": [{"호표": 3}]}], # 자기를 담음
|
||
]
|
||
for lines in bad:
|
||
_build(client, 3, lines, want=400)
|
||
for extra in ({"금액": 100}, {"품": {"보통인부": 1}}): # 금액 · 품 칸 = 받지 않음
|
||
assert (
|
||
client.post(
|
||
BUILD.format("personal"),
|
||
json={
|
||
"층": "personal",
|
||
"키": "HP-000001",
|
||
"번": 3,
|
||
"줄": [{"차례": 1, "대상": [{**one, **extra}]}],
|
||
},
|
||
).status_code
|
||
== 422
|
||
)
|
||
assert client.post(BUILD.format("personal"), json={
|
||
"층": "personal", "키": "HP-000001", "번": 3, "금액": 1,
|
||
"줄": [{"차례": 1, "대상": [one]}]}).status_code == 422 # fmt: skip
|
||
assert not (layers.personal_dir(7, 42) / "precombo").exists() or not list(
|
||
(layers.personal_dir(7, 42) / "precombo").glob("SC-*.json")
|
||
) # 틀린 몸은 아무것도 안 씀
|
||
assert client.post("/api/m02/layers/personal/hopyo-memory", json={"줄": {}}).status_code in (
|
||
404,
|
||
405,
|
||
) # 옛 즉시 확정 길 없음
|
||
|
||
|
||
def test_하위_호표_먼저_확인_퍼센트_빠짐_통째(client):
|
||
assert _post(client).status_code == 200
|
||
ref = [{"차례": 0, "대상": [{"호표": 46, "배": 1}]}]
|
||
assert "제46호표 먼저 확인" in _build(client, 45, ref, want=409)["detail"]
|
||
child = _build(client, 46, [{"차례": 0, "뺌": "안 씀"}])
|
||
assert any(x["까닭"].startswith("% 줄") for x in child["빠진줄"])
|
||
got = _build(client, 45, ref)
|
||
mine = layers.personal_dir(7, 42)
|
||
line = _doc(mine, got["참조"]["키"])["담은로직"][0]
|
||
assert line["참조"] == child["참조"] and line["별칭"] == "고화매트재료비"
|
||
assert line["수량"] == {"손": 2.3}
|
||
assert "고화매트재료비||m2" not in _book(mine) # 호표 참조 줄 = 장부 안 씀
|
||
whole = _build(client, 3, 통째={"대상": [{"로직": "GF000101", "배": 1}]})
|
||
doc = _doc(mine, whole["참조"]["키"])
|
||
assert [x["로직"] for x in doc["담은로직"]] == ["GF000101"]
|
||
assert doc["담은로직"][0]["수량"] == {"손": 1}
|
||
assert {x["까닭"] for x in whole["빠진줄"]} == {"호표 통째로 대신"}
|
||
head = next(v for k, v in _book(mine).items() if k.startswith("호표|임도표지석|"))
|
||
assert head["짝"][0]["로직"] == "GF000101" and head["원래"]["품명"] == "임도표지석"
|
||
_build(client, 3, [ref[0] | {"차례": 1}], 통째={"대상": [{"로직": "GF000101"}]}, want=400)
|
||
|
||
|
||
def test_길_권한과_위_층_파일_picks_확인(client):
|
||
client.who.update(role="SYSTEM_ADMIN")
|
||
assert _post(client, "system").status_code == 200
|
||
client.who.update(role="USER")
|
||
lines = [{"차례": 1, "대상": [{"로직": "GF000101"}]}]
|
||
_build(client, 3, lines, layer="system", 층="system", want=403) # 마스터 쓰기 = 시스템 관리자
|
||
got = _build(client, 3, lines, 층="system") # 마스터 파일 → 내 층 사전조합
|
||
assert got["참조"]["층"] == "personal" and got["참조"]["주인"] == "7/42"
|
||
_build(client, 99, lines, 층="system", want=404)
|
||
assert client.get(URL.format("personal")).json()["줄"] == [] # 파일은 마스터에만
|
||
rows = client.get("/api/m02/layers/personal/hopyo-picks").json()["줄"]
|
||
three = next(r for r in rows if r["번"] == 3)
|
||
assert three["확인"] == {"층": "personal", "주인": "7/42", "키": got["참조"]["키"]}
|
||
assert next(r for r in rows if r["번"] == 4)["확인"] is None
|
||
|
||
|
||
def test_장부_길_보기_고치기_권한(client):
|
||
before = master_hash()
|
||
assert _post(client).status_code == 200
|
||
_build(client, 3, [{"차례": 1, "대상": [{"로직": "GF000101"}]}])
|
||
rows = client.get(LEDGER.format("personal")).json()["줄"]
|
||
assert [(r["열쇠"], r["층"], r["고침"]) for r in rows] == [(DIG, "personal", True)]
|
||
put = {
|
||
"줄": {DIG: {"갈래": "짝", "짝": [{"로직": "GF000103", "배": 2}]}, SOIL: {"갈래": "안 씀"}}
|
||
}
|
||
res = client.put(LEDGER.format("personal"), json=put)
|
||
assert res.status_code == 200, res.text
|
||
assert res.json()["씀"] == 2
|
||
book = _book(layers.personal_dir(7, 42))
|
||
assert book[DIG]["짝"][0] == {"로직": "GF000103", "입력값": {}, "배": 2, "몫": None}
|
||
assert book[DIG]["처음파일"].endswith("(공통단가).xls") # 처음파일 둠
|
||
bad = {"줄": {DIG: {"갈래": "짝", "짝": [{"로직": "XX"}]}}}
|
||
assert client.put(LEDGER.format("personal"), json=bad).status_code == 400
|
||
assert client.put(LEDGER.format("personal"), json={"줄": {DIG: None}}).status_code == 200
|
||
assert DIG not in _book(layers.personal_dir(7, 42))
|
||
assert client.put(LEDGER.format("system"), json=put).status_code == 403 # 위 층 = 읽기만
|
||
client.who.update(role="SYSTEM_ADMIN")
|
||
assert client.put(LEDGER.format("system"), json=put).status_code == 200
|
||
client.who.update(role="USER")
|
||
rows = client.get(LEDGER.format("personal")).json()["줄"]
|
||
assert {(r["층"], r["고침"]) for r in rows} == {("personal", True), ("system", False)}
|
||
assert client.get(LEDGER.format("system")).json()["줄"][0]["고침"] is False
|
||
assert master_hash() == before
|