Files
Aislo/resources/tester/test_715_sangeun_store.py
T
eomsangdonandClaude Opus 5.5 d33706ceea fix(725-1·725-2): 단가산출 [항목 추가] 못 셈 줄 = 그 줄만 금액 없음 · 나머지 셈 계속
- 더한줄 로직 입력 = 줄 값 › 공통 옵션(바탕 입력값 · 화면 시험 입력) › 로직 기본값(M01 견본) 차례로 채움 — 셈 때 채워 문서엔 안 적음(공통 옵션 바꾸면 따라감)
- 그래도 빈 입력 · 수량 없는 일위대가(723-1) · 못 셈 참조 = 0 값묶음 줄(차례 그대로) + 줄 까닭 · 답 `빠진줄` · 계 = 나머지 합 · 줄 남아 빼기 됨
- 쓰는 자리(`unit` · 층 목록 기준금액 · 일위대가 펼침) = 빠진 줄 있으면 통째 기준 없음(부분 값 안 넘김)
- 화면 = 띠 「못 셈 줄 — 이름」 · 계산서 금액 — · M01 strict 셈 · Pick.ts 그대로
- 낡은 시험 고침: 715 store · 717-4 = 723-1 뒤 안 줄 수량을 알약(given)으로 · 719-2 = 수량 없음이 통째 까닭 아닌 빠진줄로
- 새 지킴 시험 test_725_1_sangeun_add_inputs(임시 storage)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 00:04:10 +09:00

198 lines
8.4 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}}],
},
)["이름"]
pill = {f"{refs.ref_id({**MINE, '키': sc})}#0": 2} # 723-1 안 줄 수량 = 쓰는 쪽 알약
got = labor.calc({"담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}, pill)
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