197 lines
8.3 KiB
Python
197 lines
8.3 KiB
Python
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
|
"""M02 단가산출(SK) 서고 · 셈(PLAN 715-1b · 715-2) — 임시 storage.
|
|
|
|
고친 줄 값 = 손셈 · 덧줄 % 엔진이 다시 셈 · 뺀줄 뒤 고름 제자리 · 짝 잃음 남김 · 원값 서버 ·
|
|
순환 422 · 쓰는 곳 지우기 409 · 사전조합이 SK 를 담음 · 층 복사 왕복 같은 값.
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
|
|
import pytest
|
|
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Labor_Ref as refs
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
|
from resources.tester.test_m02_labor_ref import COMPANY, MINE, world # noqa: F401 — 같은 임시 층
|
|
|
|
PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」
|
|
TWO_MATS = "GC000565" # 원/m · 재료 줄 둘(검색어)
|
|
|
|
|
|
def _sk(folder, **doc):
|
|
with store.at(folder):
|
|
return refs.create("sangeun", {"이름": "내 단가산출", "단위": "㎡", "더한줄": [], **doc})[
|
|
"이름"
|
|
]
|
|
|
|
|
|
def _doc(folder, key):
|
|
with store.at(folder):
|
|
return store.read("sangeun", key)["문서"]
|
|
|
|
|
|
def _calc(folder, key, given=None):
|
|
with store.at(folder):
|
|
return calc.calc_doc(_doc(folder, key), given)
|
|
|
|
|
|
def _whole():
|
|
return m01.loaded()[1]
|
|
|
|
|
|
def _bare(code):
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
return labor.run_line(_whole(), code, {})
|
|
|
|
|
|
def test_qty_cover_hand_and_pct_recomputed(world):
|
|
"""품 덮개 = 손셈 · 「노무비 * 3 / 100」 덧줄 다시 셈 · 원값 = 서버가 셈."""
|
|
bare = _bare(PCT)
|
|
first, second = bare["줄"][0], bare["줄"][1]
|
|
key = _sk(
|
|
world["personal"], 바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"qty": 0.5, "원값": 9}}
|
|
)
|
|
doc = _doc(world["personal"], key)
|
|
assert doc["덮개"]["labor:LB000036#0"]["원값"]["qty"] == pytest.approx(float(first["수량"]))
|
|
assert len(doc["바탕판"]) == 16
|
|
got = _calc(world["personal"], key)
|
|
labor_sum = Decimal("0.5") * Decimal(str(first["단가"])) + Decimal(str(second["금액"]))
|
|
assert got["기준"]["노무비"] == pytest.approx(float(labor_sum))
|
|
assert got["기준"]["경비"] == pytest.approx(float(labor_sum * 3 / 100), abs=1)
|
|
assert got["줄"][0]["덮개"] == ["qty"] and got["줄"][0]["cid"] == "labor:LB000036#0"
|
|
assert got["짝잃음"] == []
|
|
# 덧줄 율 덮개 = 5 %
|
|
key = _sk(
|
|
world["personal"],
|
|
바탕={"로직": PCT},
|
|
덮개={"pct:" + bare["줄"][2]["이름"] + "#0": {"qty": 5}},
|
|
)
|
|
got = _calc(world["personal"], key)
|
|
assert got["기준"]["경비"] == pytest.approx(float(bare["기준"]["노무비"]) * 0.05, abs=1)
|
|
|
|
|
|
def test_drop_then_pick_stays_on_its_line(world):
|
|
"""뺀줄 뒤 고름 제자리 — 둘째 재료 줄을 고른 자재가 첫 재료 줄을 뺀 뒤에도 그 줄 품목."""
|
|
import master_pick as mp
|
|
|
|
whole = _whole()
|
|
row = whole.logic(TWO_MATS)
|
|
item = row["호표"][2]
|
|
picks = mp.search(whole, item["요소"], {}, item.get("단위"))
|
|
want = picks[1] if len(picks) > 1 else picks[0]
|
|
first_cid = "material:" + row["호표"][1]["이름"] + "#0"
|
|
second_cid = "material:" + item["이름"] + "#0"
|
|
key = _sk(
|
|
world["personal"],
|
|
단위="m",
|
|
바탕={"로직": TWO_MATS},
|
|
뺀줄=[first_cid],
|
|
고름={second_cid: want},
|
|
자재표시={second_cid: {"별도계상": "직접계산"}}, # 기본 = 별도계상(222-1) 줄 빠짐
|
|
)
|
|
got = _calc(world["personal"], key)
|
|
names = [x["이름"] for x in got["줄"]]
|
|
assert row["호표"][1]["이름"] not in names
|
|
line = next(x for x in got["줄"] if x["cid"] == second_cid)
|
|
assert line["품목"]["키"] == want
|
|
|
|
|
|
def test_lost_cover_kept_and_listed(world):
|
|
key = _sk(world["personal"], 바탕={"로직": PCT}, 덮개={"labor:LB999999#0": {"qty": 1}})
|
|
got = _calc(world["personal"], key)
|
|
assert got["짝잃음"][0]["cid"] == "labor:LB999999#0"
|
|
assert "labor:LB999999#0" in _doc(world["personal"], key)["덮개"]
|
|
assert got["기준"]["계"] == pytest.approx(float(_bare(PCT)["기준"]["계"]))
|
|
|
|
|
|
def test_chain_loop_422_and_delete_409(world):
|
|
"""바탕 사슬 A ← B · B 를 A 의 바탕으로 = 422 · 쓰는 곳 있는 A 지우기 = 409(이름)."""
|
|
a = _sk(world["personal"], 바탕={"로직": PCT})
|
|
b = _sk(
|
|
world["personal"], 바탕={"참조": {**MINE, "키": a}}, 덮개={"labor:LB000002#0": {"qty": 0}}
|
|
)
|
|
got = _calc(world["personal"], b)
|
|
bare = _bare(PCT)
|
|
assert got["기준"]["노무비"] == pytest.approx(float(bare["줄"][0]["금액"]))
|
|
with store.at(world["personal"]):
|
|
doc = store.read("sangeun", a)
|
|
with pytest.raises(store.StoreError) as e:
|
|
store.write(
|
|
"sangeun", a, doc["판"], {**doc["문서"], "바탕": {"참조": {**MINE, "키": b}}}
|
|
)
|
|
assert e.value.status == 422
|
|
with pytest.raises(store.StoreError) as e:
|
|
store.delete("sangeun", a)
|
|
assert e.value.status == 409 and "단가산출 개인 7/42" in str(e.value.detail)
|
|
|
|
|
|
def test_precombo_holds_sangeun(world):
|
|
"""사전조합 참조 줄이 SK 를 담음 — 1 단위 값 × 수량 · SK 지우기 막음."""
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
|
|
sk = _sk(world["personal"], 바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"qty": 0.5}})
|
|
with store.at(world["personal"]):
|
|
sc = refs.create(
|
|
"precombo",
|
|
{
|
|
"이름": "조합",
|
|
"단위": "㎡",
|
|
"담은로직": [{"참조": {**MINE, "키": sk}, "수량": {"손": 2}}],
|
|
},
|
|
)["이름"]
|
|
got = labor.calc({"담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}, {})
|
|
with pytest.raises(store.StoreError) as e:
|
|
store.delete("sangeun", sk)
|
|
one = _calc(world["personal"], sk)["1단위"]["계"]
|
|
assert got["반영합"]["계"] == pytest.approx(one * 2, abs=1)
|
|
assert e.value.status == 409 and "일위대가 개인 7/42" in str(e.value.detail)
|
|
|
|
|
|
def test_layer_copy_round_trip(world):
|
|
"""개인 B(바탕 = 개인 A) → 회사로 복사 = A 도 같이(새 키 · 본뜬) → 개인으로 다시 = 같은 값."""
|
|
a = _sk(world["personal"], 바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"qty": 0.5}})
|
|
b = _sk(world["personal"], 바탕={"참조": {**MINE, "키": a}}, 뺀줄=["labor:LB000002#0"])
|
|
want = _calc(world["personal"], b)["기준"]
|
|
src = _doc(world["personal"], b)
|
|
with store.at(world["company"]):
|
|
made = refs.create(
|
|
"sangeun", refs.carry(refs.origin_mark(src, {**MINE, "키": b}), ("personal", "7/42"))
|
|
)
|
|
copy = _doc(world["company"], made["이름"])
|
|
assert copy["바탕"]["참조"]["층"] == "company" and copy["본뜬"]["키"] == b
|
|
assert _calc(world["company"], made["이름"])["기준"] == want
|
|
with store.at(world["personal"]):
|
|
back = refs.create(
|
|
"sangeun",
|
|
refs.carry(refs.origin_mark(copy, {**COMPANY, "키": made["이름"]}), ("company", "7")),
|
|
)
|
|
assert _doc(world["personal"], back["이름"])["바탕"]["참조"]["층"] == "company"
|
|
assert _calc(world["personal"], back["이름"])["기준"] == want
|
|
|
|
|
|
def test_refusals(world):
|
|
with store.at(world["personal"]):
|
|
for bad, status in (
|
|
({"이름": "x", "더한줄": []}, 400), # 단위 빔
|
|
({"이름": "x", "단위": "㎡", "더한줄": [], "바탕": {"로직": "GC999999"}}, 400),
|
|
(
|
|
{
|
|
"이름": "x",
|
|
"단위": "㎡",
|
|
"더한줄": [],
|
|
"바탕": {"참조": {**MINE, "키": "SK-000999"}},
|
|
},
|
|
400,
|
|
),
|
|
({"이름": "x", "단위": "㎡"}, 400), # 뼈대(더한줄) 없음
|
|
):
|
|
with pytest.raises(store.StoreError) as e:
|
|
refs.create("sangeun", bad)
|
|
assert e.value.status == status, bad
|
|
key = _sk(world["personal"], 바탕={"로직": PCT})
|
|
with store.at(world["personal"]), pytest.raises(store.StoreError) as e:
|
|
calc.calc_doc({**_doc(world["personal"], key), "바탕": {"참조": {**MINE, "키": key}}})
|
|
assert e.value.status == 422
|