- 10 일반관리비: (주)공사/전문공사 = 요율 구간표만 갈림 · 밑수는 형식 칸(일반 = 순공사원가 + 관리품목자재대) - 11 절사: 총공사비/공급가액 N원 미만 — 고른 때만 이윤 자동보정(총공사비·공급가액 비례 부가세면 ÷1.1 반올림, 실무 봉화 373→339·영월 695→632, 산림조합은 차액 그대로) · 잔차 맞춤 - 12 부가세: 공급가액·재료비·없음·산림조합 형식·산림조합-면세품 (칸 이름은 기성 선택지를 받을 수 있게 문자열) - 기본값은 종전 계산과 같음 · 자동보정 줄은 「… 절사 자동보정」으로 적음 - 원가계산서 탭: 10·11·12 고르개 · 관리품목 자재대 · 면세품 금액 칸 - 시험: 기본값 불변 · 밑수·표 갈림 · 실무 절사 식 · 끝자리 · 골든셋 초록 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
95 lines
4.8 KiB
Python
95 lines
4.8 KiB
Python
"""원가계산서 기준 입력 10·11·12 — 일반관리비 주/전문 · 절사 · 부가세 방식 (PLAN 6장 새 절).
|
||
|
||
`Engine_Cost` 가 700줄에 가까워 **고르는 규칙만** 여기 둔다. 계산 사슬은 엔진 그대로.
|
||
|
||
근거(2026-09-14 조사 — STmate `TWM_KANJUB` 칸 힌트 · 실무 엑셀 수식)
|
||
10 일반관리비 — 식은 그대로, **요율 구간표만** 주공사/전문공사로 갈림
|
||
(KJ 「전문공사: 1/10 규모」). 밑수는 **형식마다** 다름(35번 §1) — 일반 T 「순공사원가 +
|
||
관리품목자재대」.
|
||
11 절사 — 「절사된 금액은 이윤에서 자동보정」(KJ 힌트). 실무 봉화·영월:
|
||
차액 = 총공사비 − 천원 미만 버림 · 이윤 − ROUND(차액 ÷ 1.1) − 잔차
|
||
(부가세가 공급가액 비례라 ÷1.1).
|
||
영덕(산림조합-면세품): 차액 그대로 뺌(부가세가 이윤과 무관).
|
||
12 부가세 — 「공급가액의 10% / 재료비의 10% / 없음 / 산림조합 형식 (재료비 + 산출경비 의 10%) /
|
||
산림조합-면세품 (재료비-면세품 + 산출경비의 10%)」(KJ 힌트). ⚠ 기성 단계는 선택지가 달라
|
||
(직접입력 · 공급가액 · 재료비 · 재료비+산출경비) 칸 이름을 문자열로 두어 늘릴 수 있게 함.
|
||
⚠ 기본값은 **전부 종전과 같게** — 공급가액 10% · 주공사 · 절사 없음(수동 이윤보정만).
|
||
자동 이윤보정은 설계자가 절사 기준을 **고른 때만** 돈다(2026-09-08 「조용히 깎지 않음」).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import ROUND_FLOOR, ROUND_HALF_UP, Decimal
|
||
|
||
_ZERO = Decimal(0)
|
||
|
||
# ── 10 일반관리비 ─────────────────────────────────────────────────────────
|
||
#: 요율 데이터 `rate_overhead` 의 표 이름 — (주)공사 / 전문공사.
|
||
OVERHEAD_CLASSES = {
|
||
"civil_landscape_industrial": "(주)공사",
|
||
"specialty_electric_communication_fire_other": "전문공사",
|
||
}
|
||
DEFAULT_OVERHEAD_CLASS = "civil_landscape_industrial"
|
||
|
||
#: 형식별 일반관리비 밑수 — 지금은 일반(T)만. 나머지 셋은 형식이 설 때 한 줄씩.
|
||
OVERHEAD_BASE_LABELS = {"general": "순공사원가+관리품목자재대"}
|
||
|
||
|
||
def overhead_base(form: str, net_construction_cost: Decimal, managed_material: Decimal) -> Decimal:
|
||
"""형식별 일반관리비 밑수. 모르는 형식은 멈춤 — 다른 형식 밑수로 조용히 세지 않음."""
|
||
if form == "general":
|
||
return net_construction_cost + managed_material
|
||
raise ValueError(f"일반관리비 밑수가 아직 없는 형식입니다: {form}")
|
||
|
||
|
||
# ── 12 부가가치세 ─────────────────────────────────────────────────────────
|
||
VAT_MODES = {
|
||
"supply": "공급가액",
|
||
"material": "재료비",
|
||
"none": "없음",
|
||
"forest_coop": "재료비+산출경비",
|
||
"forest_coop_exempt": "재료비−면세품+산출경비",
|
||
}
|
||
DEFAULT_VAT_MODE = "supply"
|
||
|
||
|
||
def vat_base(
|
||
mode: str,
|
||
total_cost: Decimal,
|
||
material_cost: Decimal,
|
||
direct_expense: Decimal,
|
||
tax_exempt_material: Decimal,
|
||
) -> Decimal:
|
||
"""부가세 밑수. 「없음」은 0. 모르는 방식은 멈춤."""
|
||
if mode == "supply":
|
||
return total_cost
|
||
if mode == "material":
|
||
return material_cost
|
||
if mode == "none":
|
||
return _ZERO
|
||
if mode == "forest_coop":
|
||
return material_cost + direct_expense
|
||
if mode == "forest_coop_exempt":
|
||
return material_cost - tax_exempt_material + direct_expense
|
||
raise ValueError(f"모르는 부가세 방식입니다: {mode}")
|
||
|
||
|
||
# ── 11 절사 ───────────────────────────────────────────────────────────────
|
||
#: 절사 기준 — `grand_total` 총공사비에서 조정 · `supply` 공급가액 자체를 자름. 빈 값 = 안 자름.
|
||
CUT_BASES = {"grand_total": "총공사비", "supply": "공급가액"}
|
||
CUT_UNITS_KRW = (10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000)
|
||
|
||
|
||
def cut_gap(amount: Decimal, unit: int) -> Decimal:
|
||
"""`unit` 원 미만 버림으로 떨어지는 몫."""
|
||
unit_decimal = Decimal(unit)
|
||
kept = (amount / unit_decimal).quantize(Decimal(1), rounding=ROUND_FLOOR) * unit_decimal
|
||
return amount - kept
|
||
|
||
|
||
def profit_cut(gap: Decimal, basis: str, vat_mode: str) -> Decimal:
|
||
"""이윤에서 뺄 몫. 총공사비 절사 + 부가세가 공급가액 비례면 ÷1.1 반올림(실무 봉화 373→339)."""
|
||
if basis == "grand_total" and vat_mode == "supply":
|
||
return (gap / Decimal("1.1")).quantize(Decimal(1), rounding=ROUND_HALF_UP)
|
||
return gap
|