Files
Aislo/resources/tester/test_b09_contract.py
T
eomsangdonandClaude Opus 5 26097d6922 feat(b09): 계약 단계 1벌 — 노·재·경 단가 적용률 · 행별 적용 제외 · 0% 공내역 · W 코드
- 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
2026-09-14 04:41:10 +09:00

115 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""계약 단계 — 설계 → 계약내역 **구조**가 서는가 (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"