"""원가계산서 기준 입력 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