Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -587,7 +587,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
continue
|
||||
if item["종류"] == "로직":
|
||||
try:
|
||||
bundle = evaluate(parse(item["요소"]), env, master, depth)
|
||||
bundle = mp.bundled(item, lambda: evaluate(parse(item["요소"]), env, master, depth))
|
||||
except EmptyInput as e:
|
||||
lines.append(_blank_line(item, str(e)))
|
||||
continue
|
||||
|
||||
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar
|
||||
from decimal import Decimal
|
||||
|
||||
import master_material as mm
|
||||
from master_unit import UNITS, convert, kind_of, same_kind # noqa: F401 — 단위 한 벌(712-2)
|
||||
@@ -121,6 +122,8 @@ def priced(master, item: dict, ref, env: dict, index: int | None):
|
||||
|
||||
|
||||
def _priced(master, item: dict, ref, env: dict, index: int | None):
|
||||
if item.get("단가") is not None: # 손 단가(715-1b · M02 단가산출 덮개 · 더한줄) — 찾기 없음
|
||||
return Decimal(str(item["단가"])), HAND, None, None
|
||||
unit = item.get("단위")
|
||||
picked = (CHOSEN.get() or {}).get(str(index)) if index is not None else None
|
||||
if not picked and empty_term(ref): # 자재표에 없는 품목 — 구분 안 첫 품목으로 잇지 않음
|
||||
@@ -143,6 +146,21 @@ def _priced(master, item: dict, ref, env: dict, index: int | None):
|
||||
return price, source, why, warn
|
||||
|
||||
|
||||
HAND = "손 단가"
|
||||
|
||||
|
||||
def bundled(item: dict, base):
|
||||
"""로직 줄 노 · 재 · 경 묶음 — 덧칸 `값묶음`(715-1b · M02 단가산출)이 셋 다면 그것 ·
|
||||
일부면 `base()`(엔진 값) 위에 · 계 = 셋 합 · 덧칸 없음 = `base()` 그대로."""
|
||||
given = item.get("값묶음")
|
||||
if not given:
|
||||
return base()
|
||||
costs = mf.COST_ITEMS
|
||||
have = {k: Decimal(str(v)) for k, v in given.items() if k in costs}
|
||||
got = have if len(have) == len(costs) else {**base(), **have}
|
||||
return {**got, "계": sum(got[k] for k in costs)}
|
||||
|
||||
|
||||
NO_ITEM = "자재표에 없는 품목 — 검색어 비움(결정 목록) · 품목을 골라 넣음"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||||
"""M02 단가산출(SK) 엔진 입력 덧칸(PLAN 715-1b · `master_pick`) — 고친 줄 값 = 손셈.
|
||||
|
||||
줄 단가 덮개 = 손 단가 · 로직 줄 노 · 재 · 경 덮개 = 값묶음(일부면 엔진 값 위) ·
|
||||
덧줄 % 가 덮인 값에서 다시 셈 · 더한줄 참조(SK · SC) · 고정 = 1 단위 묶음 · 덧칸 없는 줄 = 엔진 값.
|
||||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Labor_Doc as labor
|
||||
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 MINE, world # noqa: F401 — 같은 임시 층
|
||||
|
||||
PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」
|
||||
SUB = "GC000999" # 원/주 · 인력 둘 + 로직 줄(GC000275)
|
||||
|
||||
|
||||
def _whole():
|
||||
return m01.loaded()[1]
|
||||
|
||||
|
||||
def _run(**doc):
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
|
||||
whole = _whole()
|
||||
clean = sd.clean({"키": "SK-000001", "이름": "시험", "단위": "㎡", **doc}, None, whole)
|
||||
return labor._json(calc.run(clean, whole))
|
||||
|
||||
|
||||
def test_price_cover_is_hand_price_and_pct_follows():
|
||||
bare = labor.run_line(_whole(), PCT, {})
|
||||
first, second = bare["줄"][0], bare["줄"][1]
|
||||
got = _run(바탕={"로직": PCT}, 덮개={"labor:LB000036#0": {"labor": 200000}})
|
||||
line = got["줄"][0]
|
||||
assert line["단가"] == 200000 and line["출처"] == "손 단가"
|
||||
want = Decimal(str(first["수량"])) * 200000 + Decimal(str(second["금액"]))
|
||||
assert got["기준"]["노무비"] == pytest.approx(float(want))
|
||||
assert got["기준"]["경비"] == pytest.approx(float(want * 3 / 100), abs=1)
|
||||
|
||||
|
||||
def test_bundle_cover_on_logic_line_partial():
|
||||
bare = labor.run_line(_whole(), SUB, {})
|
||||
sub = bare["줄"][2]
|
||||
got = _run(바탕={"로직": SUB}, 덮개={"logic:GC000275#0": {"expense": 1000}})
|
||||
line = got["줄"][2]
|
||||
qty = Decimal(str(sub["수량"]))
|
||||
assert line["비목"]["경비"] == pytest.approx(float(qty * 1000))
|
||||
assert line["비목"]["노무비"] == pytest.approx(
|
||||
float(sub["비목"]["노무비"])
|
||||
) # 안 덮은 칸 = 엔진 값
|
||||
|
||||
|
||||
def test_untouched_lines_same_as_engine():
|
||||
bare = labor.run_line(_whole(), SUB, {})
|
||||
got = _run(바탕={"로직": SUB})
|
||||
assert got["기준"]["계"] == pytest.approx(float(bare["기준"]["계"]))
|
||||
|
||||
|
||||
def test_added_ref_and_fixed_lines(world):
|
||||
"""더한줄 = SK 참조 2 · 고정 줄 1 — 값 = 2 × 그 SK 1 단위 + 고정 단가."""
|
||||
with store.at(world["personal"]):
|
||||
sk = refs.create(
|
||||
"sangeun", {"이름": "밑", "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": []}
|
||||
)["이름"]
|
||||
unit = calc.calc_doc(store.read("sangeun", sk)["문서"])["1단위"]["계"]
|
||||
top = refs.create(
|
||||
"sangeun",
|
||||
{
|
||||
"이름": "위",
|
||||
"단위": "식",
|
||||
"바탕": None,
|
||||
"더한줄": [
|
||||
{"참조": {**MINE, "키": sk}, "수량": 2},
|
||||
{"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": 1},
|
||||
],
|
||||
},
|
||||
)["이름"]
|
||||
got = calc.calc_doc(store.read("sangeun", top)["문서"])
|
||||
assert got["기준"]["계"] == pytest.approx(unit * 2 + 5000, abs=1)
|
||||
expense = labor.run_line(_whole(), PCT, {})["기준"]["경비"]
|
||||
assert got["기준"]["경비"] == pytest.approx(5000 + 2 * float(expense), abs=1)
|
||||
Reference in New Issue
Block a user