feat(B09): 공구손료·잡재료 칸 — 기본은 빔, 넣으면 주재료비의 %로 붙음
사용자 확정 5차 작은 것 1 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」. 근거는 산림품셈 1-2-6 — 주재료비(할증수량 제외)의 2~5%까지, 산정 근거 명시. - 기초자료 탭 「산출 조건」에 칸 하나 + [적용]. 비면 줄 자체가 안 섬(지금 상태 그대로). - 밑수는 **자재 줄만** — 노무·경비, 하위 일위대가 재료비는 안 듦(층마다 거듭 세지 않음). - 상한 5% 초과는 거절(400) — 조용히 깎아 넣지 않음. - ⚠ 지금은 일위대가에 주재료비가 선 공종이 0개라 붙을 밑수가 없음 — 그 사실을 칸 밑에 띄움. - 곁다리: 비율 줄(제잡비·공구손료)이 저장했다 읽으면 사라지던 것을 고침. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -247,7 +247,8 @@ async def _build_for(project_id: UUID):
|
||||
machines = tuple(
|
||||
sorted((str(k), str(v)) for k, v in (settings.get("machine_choices") or {}).items())
|
||||
)
|
||||
return cached_build(ranges, machines)
|
||||
# 공구손료·잡재료 — **비어 있는 것이 기본**이라 안 넣으면 줄이 안 선다(확정 5차 작은 것 1).
|
||||
return cached_build(ranges, machines, str(settings.get("misc_material_percent") or ""))
|
||||
|
||||
|
||||
@router.get("/{project_id}/estimation/base-data")
|
||||
@@ -370,11 +371,46 @@ async def get_factor_choices(project_id: UUID) -> JSONResponse:
|
||||
}
|
||||
)
|
||||
|
||||
from B09_Estimation.B09_Estimation_PriceBook import PriceKind
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import (
|
||||
MISC_MATERIAL_MAX_PERCENT,
|
||||
MISC_MATERIAL_MIN_PERCENT,
|
||||
)
|
||||
|
||||
# 「넣을 데가 있는가」 — 주재료비가 선 일위대가가 몇인지 세어 그대로 알린다.
|
||||
# 지금은 사급 자재 단가가 미결(확정 5차 큰 것 8)이라 0 이 정상이다.
|
||||
book = (await _build_for(project_id)).book
|
||||
with_material = sum(
|
||||
1
|
||||
for unit_code, unit_title in book.titles.items()
|
||||
if unit_title.kind is PriceKind.UNIT_PRICE and book.material_base(unit_code) > 0
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"status": "success",
|
||||
"ranges": ranges,
|
||||
"machines": machines,
|
||||
"misc_material": {
|
||||
"percent": str(settings.get("misc_material_percent") or ""),
|
||||
"min": str(MISC_MATERIAL_MIN_PERCENT),
|
||||
"max": str(MISC_MATERIAL_MAX_PERCENT),
|
||||
"basis": [
|
||||
"산림품셈 1-2-6 — 「각 항목에 명시되어 있지 않는 잡재료 및 소모재료 등을"
|
||||
" 계상하고자 할 때에는 주재료비(재료비의 할증수량 제외)의 2~5%까지"
|
||||
" 별도 계상하되 산정 근거를 명시하여야 한다」",
|
||||
"⚠ 비워 두면 안 붙습니다 — 지금은 안 붙고 있는 상태입니다"
|
||||
" (사용자 확정 2026-09-09 「지금은 안 넣되 숫자 넣으면 되게 열어 둘 것」).",
|
||||
],
|
||||
"base_items": with_material,
|
||||
"base_note": (
|
||||
""
|
||||
if with_material
|
||||
else "⚠ 지금은 일위대가에 주재료비가 선 공종이 하나도 없습니다"
|
||||
" — 자재는 자재대 표에서 따로 금액이 섭니다. 값을 넣어도 붙을 밑수가"
|
||||
" 없으므로, 사급 자재 단가가 서는 날 이 칸이 함께 살아납니다."
|
||||
),
|
||||
},
|
||||
"notes": [
|
||||
"고를 수 있는 것은 원문에 적힌 값뿐입니다 — 그 밖의 수는 만들지 않습니다.",
|
||||
"바꾸면 그 공종 단가가 바로 달라집니다. [저장]한 값은 이 프로젝트에만 걸립니다.",
|
||||
@@ -394,6 +430,8 @@ class FactorChoiceBody(BaseModel):
|
||||
|
||||
range_factor_choices: dict[str, str] | None = None
|
||||
machine_choices: dict[str, str] | None = None
|
||||
#: 공구손료·잡재료 비율 — **빈 문자열이면 안 붙는다**(칸을 도로 비우는 길).
|
||||
misc_material_percent: str | None = None
|
||||
|
||||
|
||||
@router.put("/{project_id}/estimation/factors")
|
||||
@@ -422,6 +460,16 @@ async def put_factor_choices(project_id: UUID, body: FactorChoiceBody) -> JSONRe
|
||||
for key, value in body.machine_choices.items()
|
||||
if str(value) in MACHINE_OPTION_CODES
|
||||
}
|
||||
if body.misc_material_percent is not None:
|
||||
from B09_Estimation.B09_Estimation_UnitPrice import parse_misc_material_percent
|
||||
|
||||
try:
|
||||
percent = parse_misc_material_percent(body.misc_material_percent)
|
||||
except ValueError as exc:
|
||||
# ⚠ 조용히 깎아 넣지 않는다 — 범위 밖 값을 상한으로 접으면 사용자가 넣은 값과
|
||||
# 금액이 어긋난 채로 선다.
|
||||
return JSONResponse(status_code=400, content={"status": "error", "message": str(exc)})
|
||||
values["misc_material_percent"] = "" if percent is None else str(percent)
|
||||
try:
|
||||
save_section(root, "estimation", values, replace_keys=tuple(values))
|
||||
return JSONResponse(content={"status": "success", **values})
|
||||
|
||||
Reference in New Issue
Block a user