Files
Aislo/resources/tester/test_715_sangeun_store.py
T
eomsangdonandClaude Opus 5.5 f4be6782a5 feat(751-3): M01 · M02 셈 끝수 = 품셈 1-2-2 버림 — 줄 0.1 원 · 비목 소계 1 원 · 계 = 셋 합 · B09 와 같은 값
- 끝수 한 곳 `master_cut.py`(B09 `rounddown` 재사용) — 엔진 답에 입힘: 줄 비목마다 0.1 원 버림 · 하위 로직 줄 = 하위 끝수 계 × 수량 · 덧줄 = 버린 줄 합으로 다시 셈 · 비목 합 1 원 버림 · 중간 칸도 버린 값
- 쓰는 곳: M01 시험 계산(`check_master.calc`) · 읽는 식(`master_text.lines`) · 옛 조합 미리보기(`master_combo`) · M02 `run_line`(단가산출 · 일위대가 · 펼침) · 별도계상 뺀 기준 · 켜쌓기율 곱 뒤 · 일위대가 반영 · 반영합 · 비율 줄 · 사전조합 안 줄
- 일위대가 반영 TS 짝(`Labor_Calc.ts`) 같은 끝수 · 거울 헬퍼가 상대 경로 .ts 가져옴
- B09 = `run_line(raw=True)` 엔진 날 값(제 단가로 다시 버림 · % 덧줄 율 비 그대로) — 검증_sub3 B09 247 칸 전후 같음
- `master_text` 꼴 셋 = `common_util_money` 로 갈음(줄 금액 `fmt_line_money` · 식 안 금액 = 버린 0.1 원)
- 화면: 상세 줄 금액 · 시험 계산 줄 · 중간 돈 = `formatLineMoney` · 일위대가 반영 칸 0.1 원 · 상세 바닥 · 박스 · 인력재료경비 비목 소계 = 줄 합 1 원 버림
- 테스트1 네 보기 · 서고 목록 = 124,419(옛 124,420 / 124,419.85) · 재료 줄 11,694.6 + 23,425.6 + 1,404.8 = 소계 36,525
- 새 지킴 시험 test_751_3_money_cut
- 옛 날 금액(끝수 없음)을 대조하던 시험 24 파일 고침 — 까닭 = 금액 규칙이 줄 0.1 원 · 소계 1 원 버림으로 바뀜(고정 값 → 새 값 · 줄 합 = 계 → 줄 합 아래 비목마다 1 원 안 · 곱 비 = 끝수 폭 안) · 실무 xlsx 영월 엑셀 같음 22 → 23 · 이어 붙이는 TS 시험(704_3 · 717_2b)에 `rounddown` 같은 식

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 06:16:01 +09:00

197 lines
8.4 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""M02 단가산출(SK) 서고 · 셈(PLAN 715-1b · 715-2) — 임시 storage.
고친 줄 값 = 손셈 · 덧줄 % 엔진이 다시 셈 · 뺀줄 뒤 고름 제자리 · 짝 잃음 남김 · 원값 서버 ·
바탕 · 사전조합에 SK 넣기 = 펼쳐 담음(740-1 · 원본 지워도 그대로) · 층 복사 왕복 같은 값.
"""
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)
cut = calc.labor.mcut.down # 751-3 — 줄 0.1 원 · 비목 합 1 원 버림
labor_sum = cut(Decimal("0.5") * Decimal(str(first["단가"])), 1) + Decimal(str(second["금액"]))
assert got["기준"]["노무비"] == pytest.approx(float(cut(labor_sum, 0)))
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_base_flattened_delete_free(world):
"""바탕 SK 넣기(740-1) = 펼쳐 담음 — 바탕 = M01 기초 · 위 덮개 겹침 · 값 같음 ·
원본 지워도 그대로."""
a = _sk(world["personal"], 바탕={"로직": PCT})
b = _sk(
world["personal"], 바탕={"참조": {**MINE, "키": a}}, 덮개={"labor:LB000002#0": {"qty": 0}}
)
assert _doc(world["personal"], b)["바탕"] == {"로직": PCT}
got = _calc(world["personal"], b)
bare = _bare(PCT)
assert got["기준"]["노무비"] == pytest.approx(
float(calc.labor.mcut.down(bare["줄"][0]["금액"], 0))
)
with store.at(world["personal"]):
store.delete("sangeun", a) # 쓰는 곳 없음(서로 독립)
assert _calc(world["personal"], b)["기준"] == got["기준"]
def test_precombo_holds_sangeun(world):
"""사전조합에 SK 넣기(740-1) = 로직 줄 + 패치(SK 몸) · 1 단위 값 × 수량 · SK 지워도 그대로."""
from M02_MasterTemplete import M02_Labor_Doc as labor
sk = _sk(world["personal"], 바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"qty": 0.5}})
one = _calc(world["personal"], sk)["1단위"]["계"]
with store.at(world["personal"]):
sc = refs.create(
"precombo",
{
"이름": "조합",
"단위": "㎡",
"담은로직": [{"참조": {**MINE, "키": sk}, "수량": {"손": 2}}],
},
)["이름"]
line = store.read("precombo", sc)["문서"]["담은로직"][0]
assert line["로직"] == PCT and line["패치"]["덮개"] and line["본뜬"]["키"] == sk
store.delete("sangeun", sk)
pill = {f"{refs.ref_id({**MINE, '키': sc})}#0": 2} # 723-1 안 줄 수량 = 쓰는 쪽 알약
got = labor.calc({"담은로직": [{"참조": {**MINE, "키": sc}, "수량": {"손": 1}}]}, pill)
assert got["반영합"]["계"] == pytest.approx(one * 2, abs=1)
def test_layer_copy_round_trip(world):
"""개인 B(바탕 = 개인 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"))
)
assert not list((world["company"] / "sangeun").glob("*.json"))[1:] # A 따로 복사 안 함
copy = _doc(world["company"], made["이름"])
assert copy["바탕"] == {"로직": PCT} 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 _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