Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"""프로젝트 요율 덮어쓰기 — 프로젝트 단위 · 사유 저장 · 기본값↔고친 값 (PLAN 6장 새 절).
|
||||
|
||||
⚠ 겨누는 것
|
||||
① 마스터 요율판은 **안 바뀜**(덮어쓰기는 복사본에만)
|
||||
② 사유 없는 줄·틀린 주소·같은 칸 두 번은 거름
|
||||
③ 덮어쓴 요율이 계산에 실제로 걸림 · 안 쓴 프로젝트는 값이 그대로
|
||||
④ 요율표 칸이 기본값·지금 값·주소를 함께 싣고, 그 주소를 그대로 돌려주면 저장됨
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B09_Estimation.B09_Estimation_Engine_Cost import CostInput, calculate_cost # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_RateOverride import ( # noqa: E402
|
||||
apply_overrides,
|
||||
clean_overrides,
|
||||
)
|
||||
from B09_Estimation.B09_Estimation_Rates import RateLookupError, load_rate_dataset # noqa: E402
|
||||
from B09_Estimation.B09_Estimation_RateTable import rate_sections # noqa: E402
|
||||
|
||||
BASE = CostInput(
|
||||
direct_material_krw=Decimal(123_456_789),
|
||||
direct_labor_krw=Decimal(234_567_891),
|
||||
direct_expense_krw=Decimal(45_678_912),
|
||||
estimated_price_krw=Decimal(300_000_000), # 50억 미만 구간
|
||||
)
|
||||
OVERHEAD = {
|
||||
"variable": "rate_overhead",
|
||||
"table": "civil_landscape_industrial",
|
||||
"match": {"estimated_price_bracket": "lt_5_billion"},
|
||||
"rate_percent": "7.5",
|
||||
"reason": "발주처 지침(시험)",
|
||||
}
|
||||
|
||||
|
||||
def test_마스터는_안_바뀐다() -> None:
|
||||
master = load_rate_dataset()
|
||||
before = master.variables["rate_overhead"]["civil_landscape_industrial"][0]["rate_percent"]
|
||||
changed = apply_overrides(master, [OVERHEAD])
|
||||
assert (
|
||||
changed.variables["rate_overhead"]["civil_landscape_industrial"][0]["rate_percent"] == 7.5
|
||||
)
|
||||
assert (
|
||||
master.variables["rate_overhead"]["civil_landscape_industrial"][0]["rate_percent"] == before
|
||||
)
|
||||
assert (
|
||||
load_rate_dataset().variables["rate_overhead"]["civil_landscape_industrial"][0][
|
||||
"rate_percent"
|
||||
]
|
||||
== before
|
||||
)
|
||||
|
||||
|
||||
def test_덮어쓴_요율이_계산에_걸리고_안_쓰면_그대로() -> None:
|
||||
plain = calculate_cost(BASE)
|
||||
again = calculate_cost(replace(BASE, rate_overrides=()))
|
||||
assert plain.totals == again.totals
|
||||
changed = calculate_cost(replace(BASE, rate_overrides=(OVERHEAD,)))
|
||||
assert changed.line("general_overhead").rate_percent == Decimal("7.5")
|
||||
assert changed.amount("general_overhead") < plain.amount("general_overhead")
|
||||
assert any("요율 덮어쓰기 1건" in note and "발주처 지침" in note for note in changed.notes)
|
||||
|
||||
|
||||
def test_한_값짜리_요율도_덮어쓴다() -> None:
|
||||
sanjae = {
|
||||
"variable": "rate_sanjae",
|
||||
"table": None,
|
||||
"match": {},
|
||||
"rate_percent": "3.7",
|
||||
"reason": "고시 개정",
|
||||
}
|
||||
result = calculate_cost(replace(BASE, rate_overrides=(sanjae,)))
|
||||
assert result.line("industrial_accident_insurance").rate_percent == Decimal("3.7")
|
||||
|
||||
|
||||
def test_사유_주소_중복을_거른다() -> None:
|
||||
master = load_rate_dataset()
|
||||
kept, errors = clean_overrides(
|
||||
[
|
||||
{**OVERHEAD, "reason": " "},
|
||||
{**OVERHEAD, "match": {"estimated_price_bracket": "no_such"}},
|
||||
{**OVERHEAD, "rate_percent": "-1"},
|
||||
OVERHEAD,
|
||||
OVERHEAD,
|
||||
],
|
||||
master,
|
||||
)
|
||||
assert len(kept) == 1 and kept[0]["rate_percent"] == "7.5"
|
||||
assert len(errors) == 4
|
||||
assert any("사유" in e for e in errors) and any("두 번" in e for e in errors)
|
||||
|
||||
|
||||
def test_주소가_안_맞으면_계산이_멈춘다() -> None:
|
||||
stale = {**OVERHEAD, "match": {"estimated_price_bracket": "gone"}}
|
||||
with pytest.raises(RateLookupError):
|
||||
calculate_cost(replace(BASE, rate_overrides=(stale,)))
|
||||
|
||||
|
||||
def test_요율표_칸이_기본값과_주소를_싣고_그대로_돌려주면_저장된다() -> None:
|
||||
master = load_rate_dataset()
|
||||
current = apply_overrides(master, [OVERHEAD])
|
||||
sections = rate_sections(current, master)
|
||||
cells = [cell for s in sections for row in s["rows"] for cell in row if isinstance(cell, dict)]
|
||||
assert cells and all({"value", "default", "overridden", "address"} <= set(c) for c in cells)
|
||||
changed = [c for c in cells if c["overridden"]]
|
||||
assert len(changed) == 1 and (changed[0]["value"], changed[0]["default"]) == (7.5, 8.0)
|
||||
# 화면이 받은 주소 그대로 새 값·사유를 붙여 돌려주면 모두 저장 가능해야 함.
|
||||
raw = [{**c["address"], "rate_percent": "1", "reason": "왕복"} for c in cells]
|
||||
kept, errors = clean_overrides(raw, master)
|
||||
assert not errors and len(kept) == len(cells)
|
||||
Reference in New Issue
Block a user