- STmate wM_Mk_Cont(27번 §2) 본뜸: 단가 적용율 【노】【재】【경】 · 적용 옵션 여섯(화면 표기 그대로) · 적용제외 공정 - 설계 내역(/estimation/bill) 줄을 복사해 성분마다 적용률 — 절사는 설계 내역 규칙(bill_line) 그대로 · 설계 불변 - W 코드: 동일코드 한 벌(기본) / 개별생성 켜면 줄마다 · 적용 제외 줄은 설계 단가·설계 코드 - 0%: 「공내역 생성」 켜면 0 원 공내역 줄 · 끄면 적용 안 함(판정 대기) - 뜻 미확인 옵션(노무비율)·아직 안 선 옵션(기초단가 적용·일위대가 생성·비과세자재)은 칸만 받고 까닭을 화면에 - GET/PUT /estimation/contract · 탭 파일 B09_Estimation_UI_Tab_Contract.ts(등록은 서브) · 사전 b4 - ⚠ 계약 표본 0건 — 구조가 서는지까지만 시험 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
"""계약 단계 — 설계 → 계약내역 **구조**가 서는가 (PLAN 12장 · 2026-09-14 브레인 배정).
|
||
|
||
⚠ 값이 맞는지는 못 잰다 — 계약 표본 0건(STmate 27번 §9). 여기서 재는 것:
|
||
① 적용률 100% 면 설계와 같음 · 설계 줄(입력)은 안 바뀜
|
||
② 노·재·경 따로 곱해짐 · 절사는 설계 내역 규칙(성분 단가 원 미만 · 줄 성분마다)
|
||
③ 적용 제외 줄은 설계 단가 · W 코드 안 붙음
|
||
④ 0% + 「공내역 생성」 = 0 원 공내역 줄 · 옵션 끄면 0% = 적용 안 함
|
||
⑤ 동일코드 개별생성 켜면 줄마다 다른 W 코드 · 끄면 같은 코드 한 벌
|
||
⑥ 묶음 줄 합 · 틀린 적용률 거름
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
import sys
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from B09_Estimation.B09_Estimation_Contract import clean_settings, contract_bill # noqa: E402
|
||
|
||
ROWS = [
|
||
{"item_no": "1", "is_group": True, "in_bill": True},
|
||
{
|
||
"item_no": "1.1",
|
||
"is_group": False,
|
||
"in_bill": True,
|
||
"quantity": "12.5",
|
||
"price_code": "B-FP-09-03-02",
|
||
"unit_material_krw": "1001",
|
||
"unit_labor_krw": "2003",
|
||
"unit_expense_krw": "3005",
|
||
},
|
||
{
|
||
"item_no": "1.2",
|
||
"is_group": False,
|
||
"in_bill": True,
|
||
"quantity": "3",
|
||
"price_code": "B-FP-09-03-02",
|
||
"unit_material_krw": "1001",
|
||
"unit_labor_krw": "2003",
|
||
"unit_expense_krw": "3005",
|
||
},
|
||
{
|
||
"item_no": "1.3",
|
||
"is_group": False,
|
||
"in_bill": True,
|
||
"quantity": "2",
|
||
"price_code": "M-관급자재",
|
||
"unit_material_krw": "50000",
|
||
"unit_labor_krw": "0",
|
||
"unit_expense_krw": "0",
|
||
},
|
||
]
|
||
|
||
|
||
def _rows(settings: dict) -> dict[str, dict]:
|
||
return {row["item_no"]: row for row in contract_bill(ROWS, settings)["rows"]}
|
||
|
||
|
||
def test_100퍼센트면_설계와_같고_입력은_안_바뀐다() -> None:
|
||
before = copy.deepcopy(ROWS)
|
||
result = contract_bill(ROWS, clean_settings({})[0])
|
||
assert ROWS == before
|
||
assert result["totals"]["design"] == result["totals"]["contract"]
|
||
|
||
|
||
def test_노재경을_따로_곱하고_성분_단가를_원_미만_절사() -> None:
|
||
row = _rows({"labor_pct": "80", "material_pct": "90", "expense_pct": "70"})["1.1"]
|
||
assert (
|
||
row["contract_unit_material_krw"],
|
||
row["contract_unit_labor_krw"],
|
||
row["contract_unit_expense_krw"],
|
||
) == ("900", "1602", "2103") # 900.9→900 · 1602.4→1602 · 2103.5→2103
|
||
assert row["contract_material_krw"] == "11250" # 12.5 × 900
|
||
assert row["contract_code"] == "W-B-FP-09-03-02"
|
||
|
||
|
||
def test_적용_제외_줄은_설계_단가_W코드_없음() -> None:
|
||
row = _rows({"material_pct": "50", "excluded": ["1.3"]})["1.3"]
|
||
assert row["contract_unit_material_krw"] == "50000" and row["contract_excluded"] is True
|
||
assert not row["contract_code"].startswith("W-")
|
||
|
||
|
||
def test_0퍼센트_공내역_옵션() -> None:
|
||
zero = {"labor_pct": "0", "material_pct": "0", "expense_pct": "0"}
|
||
empty = _rows({**zero, "zero_makes_empty": True})["1.1"]
|
||
assert empty["contract_amount_krw"] == "0" and "공내역" in empty["contract_note"]
|
||
kept = _rows(zero)["1.1"]
|
||
assert kept["contract_unit_price_krw"] == "6009" # 옵션 끄면 0% = 적용 안 함
|
||
|
||
|
||
def test_동일코드_개별생성() -> None:
|
||
shared = _rows({"material_pct": "90"})
|
||
assert shared["1.1"]["contract_code"] == shared["1.2"]["contract_code"]
|
||
separate = _rows({"material_pct": "90", "separate_same_code": True})
|
||
assert separate["1.1"]["contract_code"] != separate["1.2"]["contract_code"]
|
||
|
||
|
||
def test_묶음_줄_합과_합계_비율() -> None:
|
||
result = contract_bill(ROWS, {"material_pct": "90", "labor_pct": "90", "expense_pct": "90"})
|
||
group = result["rows"][0]
|
||
children = [r for r in result["rows"] if not r.get("is_group")]
|
||
assert Decimal(group["contract_amount_krw"]) == sum(
|
||
Decimal(r["contract_amount_krw"]) for r in children
|
||
)
|
||
assert result["totals"]["ratio_pct"]["labor"] is not None
|
||
|
||
|
||
def test_틀린_적용률은_거른다() -> None:
|
||
cleaned, errors = clean_settings({"labor_pct": "-3", "material_pct": "abc", "expense_pct": ""})
|
||
assert len(errors) == 2 and cleaned["expense_pct"] == "100"
|