Merge remote-tracking branch 'origin/dev' into sub_laptop_1
This commit is contained in:
@@ -29,7 +29,9 @@ from B09_Estimation.B09_Estimation_Guards import (
|
||||
check_drain_pipe_not_double_counted,
|
||||
check_included_materials_not_listed,
|
||||
check_free_haul_not_priced,
|
||||
check_handoff_boundaries,
|
||||
check_haul_volume_within_cut,
|
||||
check_material_surcharge_once,
|
||||
)
|
||||
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
|
||||
from B09_Estimation.B09_Estimation_QuantityDigits import round_quantity
|
||||
@@ -503,6 +505,10 @@ def build_bill(
|
||||
# ── 4) 검사 — `in_bill=false` 줄에 금액이 붙지 않았는가 ──────────────────────
|
||||
check_excluded_rows_not_priced(rows=[r.as_dict() for r in result.excluded])
|
||||
|
||||
# 이중계상 경계 ①②③(명세 6장) — B08 이 갈 곳 칸으로 판정한 어긴 자리 · 자재 할증 한 번.
|
||||
check_handoff_boundaries(payload.get("double_count_violations"))
|
||||
check_material_surcharge_once(materials)
|
||||
|
||||
# ㉦ 큰돌쌓기 품에 포함된 자재(고임돌·채움콘크리트)를 따로 세지 않았는가.
|
||||
check_included_materials_not_listed(
|
||||
work_item_codes=[row.code or "" for row in result.rows],
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
_TOLERANCE = Decimal("0.5")
|
||||
|
||||
@@ -219,6 +220,67 @@ def check_excluded_rows_not_priced(
|
||||
)
|
||||
|
||||
|
||||
def check_handoff_boundaries(violations: list[str] | None) -> None:
|
||||
"""이중계상 경계 ①②③(명세 6장) — B08 인계가 갈 곳 칸으로 판정해 보낸 어긴 자리.
|
||||
|
||||
판정은 성분 칸을 가진 B08 한 곳에서 한다(`B08_Quantity_Engine_Handoff_Boundaries`).
|
||||
여기서는 **멈추기만** 한다 — 목록이 차 있는데 내역서를 세우면 같은 물량이 두 번 금액이 된다.
|
||||
"""
|
||||
if violations:
|
||||
raise DoubleCountError("이중계상 경계를 어겼습니다 — " + " / ".join(violations))
|
||||
|
||||
|
||||
#: 품셈 [주]가 「재료량에 할증 포함」이라 적은 공종 — 그 재료가 일위대가 재료비로 붙으면
|
||||
#: **할증 뒤 값**이 들어가 자재총괄에서 한 번 더 붙는다(㉠). 원문 넷, 코드는 마스터가 붙인 자리.
|
||||
SURCHARGE_INCLUDED_ITEMS: dict[str, str] = {
|
||||
"FP-12-02": "용적 배합 콘크리트 참고표 「재료량에는 할증률이 포함」(마스터가 12-2 에 붙임)",
|
||||
"FP-12-38-02": "유로폼 사용수량 [주]① 「재료량에는 재료의 할증 및 손율이 포함」",
|
||||
"FP-13-11-04": "돌망태 사각형 [주]① 「자재비에는 재료의 할증을 포함」",
|
||||
"AX-WK-c0842a0d": "모르타르 배합 참고자료 ※ 「위 재료량은 할증이 포함된 것이다」",
|
||||
}
|
||||
|
||||
|
||||
def check_materials_before_surcharge(book: Any) -> None:
|
||||
"""③ 일위대가 재료비는 할증 전 — 할증 포함 재료량을 준 공종에 자재 줄이 붙으면 멈춘다.
|
||||
|
||||
⚠ 지금은 그 재료들이 자재 단가 층이 없어 **안 붙은 줄**로만 보인다 — 층이 서는 날 여기서 걸린다.
|
||||
"""
|
||||
from B09_Estimation.B09_Estimation_PriceBook import PriceKind
|
||||
|
||||
for title_code, details in book.details.items():
|
||||
code = title_code[2:].split("#", 1)[0] if title_code.startswith("B-") else ""
|
||||
if code not in SURCHARGE_INCLUDED_ITEMS:
|
||||
continue
|
||||
for detail in details:
|
||||
ref = book.titles.get(detail.ref_code)
|
||||
if ref is not None and ref.kind is PriceKind.MATERIAL:
|
||||
raise DoubleCountError(
|
||||
f"{title_code}: 자재 「{ref.name}」이 일위대가 재료비로 붙었습니다 — "
|
||||
f"{SURCHARGE_INCLUDED_ITEMS[code]}. 일위대가 재료비는 할증 전 값이고 "
|
||||
"할증은 자재총괄 한 곳뿐입니다 (PLAN 8-7 ㉠)."
|
||||
)
|
||||
|
||||
|
||||
def check_material_surcharge_once(materials: list[Any]) -> None:
|
||||
"""③ 자재 줄 합계 = 순수량 × (1 + 할증률) **한 번** — 율이 없거나 품셈 포함이면 순수량 그대로.
|
||||
|
||||
내역서 자재대는 `total_amount` 를 수량으로 쓴다. 인계 도중 어디서든 할증이 한 번 더 붙으면
|
||||
그 값이 그대로 금액이 되므로, **금액을 만드는 자리**에서 순수량과 율로 되짚는다.
|
||||
"""
|
||||
for material in materials:
|
||||
net = Decimal(str(material.net_amount))
|
||||
included = "할증 포함" in str(material.surcharge_note or "")
|
||||
rate = None if included else material.surcharge_pct
|
||||
expected = net if rate is None else net * (Decimal(1) + Decimal(str(rate)) / Decimal(100))
|
||||
total = Decimal(str(material.total_amount))
|
||||
if abs(total - expected) > max(Decimal("1e-6"), abs(expected) * Decimal("1e-9")):
|
||||
raise DoubleCountError(
|
||||
f"자재 「{material.material_name}」: 합계 {total} 가 순수량 {net} × "
|
||||
f"(1+{rate or 0}%) = {expected} 와 다릅니다 — 할증이 두 번 붙었거나 빠졌습니다 "
|
||||
"(PLAN 8-7 ㉠ 할증은 자재총괄 한 번)."
|
||||
)
|
||||
|
||||
|
||||
#: 제잡비 「윗단」 값을 쓴다는 뜻 — 물빼기 파이프를 **설치하는** 경우다.
|
||||
#: 품셈 13-6-2 [주]③ 「… 상단에는 물빼기 파이프 설치에 관계되는 노무비, 재료비를
|
||||
#: 포함한다」. 그러므로 윗단을 쓰면 파이프를 **따로 세면 안 된다**.
|
||||
|
||||
@@ -950,6 +950,10 @@ def build_unit_prices(
|
||||
build.book, [code for code in build.book.titles if code.startswith("B-")]
|
||||
)
|
||||
build.labor_reliability = _labor_reliability_in_use(build.book)
|
||||
# ③ 할증 포함 재료량을 준 공종에 자재가 재료비로 붙지 않았는가(명세 6장 · ㉠).
|
||||
from B09_Estimation.B09_Estimation_Guards import check_materials_before_surcharge
|
||||
|
||||
check_materials_before_surcharge(build.book)
|
||||
return build
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user