feat(B08): 화면에 안 보이던 확정 대기 셋을 드러냄
목록에만 있고 화면에 없으면 사용자는 그것이 잠정인 줄도 모름. 셋 다 「지금 어떤 값으로 돌고 있는지 + 왜 잠정인지」를 함께 보임. - 콘크리트 타설 방식을 좌측 패널 칸으로 냄. ⚠ 금액에 바로 걸리는 값이라 기본값으로 돌고 있으면 「기본값 「레디믹스트」로 계산 중 — 아직 안 정한 값」 안내를 띄움. 설정 기본을 None 으로 바꿔 「안 정함」과 「일부러 레디믹스트를 고른 것」을 가름 — 값을 미리 넣으면 그 구별이 사라짐. 되돌리기도 됨. - 물구멍 근거에 잠정값을 적음 — 「관 Ø 미정(법 3~6㎝ / 실무 Ø50) · 간격 2.0㎡당 1개소(법 2~3㎡당 1개소 이상)」. 「미확정」만으로는 무엇을 정해야 하는지 모름. - 준비공의 벌목 줄에 공종 미확정 사유와 후보를 함께 적음(수확베기·단목베기· 위험목 베기 중 어느 것인지 원본이 말하지 않음). 검증 — 전체 580 passed, tsc 오류 0. 화면에서 셋 다 뜨는 것과 타설 방식 저장·되돌리기까지 확인 후 검증으로 바꾼 값은 원래대로 복원. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,7 +55,11 @@ def preparation_rows(slope_totals: dict[str, float] | None = None) -> list[dict[
|
||||
"status": STATUS_COUNTED_ELSEWHERE,
|
||||
# ⚠ 값을 여기서 또 내면 같은 나무를 두 번 벤다. 참고로 면적만 보인다.
|
||||
"reference_amount": tree_area,
|
||||
"reason": "토공집계의 「지장목제거」로 이미 섬 — 여기서 또 세우면 이중계상",
|
||||
"reason": (
|
||||
"토공집계의 「지장목제거」로 이미 섬 — 여기서 또 세우면 이중계상. "
|
||||
"⚠ 다만 **공종 미확정** — 품셈 4장이 벌목을 목적별로 갈라(수확베기·단목베기·"
|
||||
"위험목 베기) 임도 지장목이 어디에 붙는지 원본이 말하지 않음. 지금은 공종코드 없이 감."
|
||||
),
|
||||
"work_item_code": None,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -238,7 +238,10 @@ def stone_masonry(
|
||||
"m",
|
||||
masonry_area / constants["weep_hole_area_m2"] * constants["weep_hole_length_m"],
|
||||
DESTINATION["물구멍"],
|
||||
"돌쌓기 ÷ 2㎡/개소 × 0.5 m/개소 (관 규격 미확정)",
|
||||
# ⚠ 「미확정」만 적으면 사용자가 무엇을 정해야 하는지 모른다 —
|
||||
# **지금 무슨 값으로 돌고 있는지**를 함께 적는다(원단위 미확보와 같은 방식).
|
||||
"돌쌓기 ÷ 2㎡/개소 × 0.5 m/개소 · ⚠ 잠정: 관 Ø 미정(법 3~6㎝ / 실무 Ø50) ·"
|
||||
" 간격 2.0㎡당 1개소(법 2~3㎡당 1개소 이상)",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -36,8 +36,10 @@ from B08_Quantity.B08_Quantity_Engine_HaulSummary import summary_input_rows
|
||||
from B08_Quantity.B08_Quantity_Engine_SlopeArea import build_table as build_slope_table
|
||||
from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slopes
|
||||
from common_util.common_util_project_settings import (
|
||||
CONCRETE_PLACING_METHODS,
|
||||
ROCK_METHODS,
|
||||
application_ratio,
|
||||
concrete_placing_method,
|
||||
quantity_settings,
|
||||
rock_classes,
|
||||
save_section,
|
||||
@@ -99,6 +101,9 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
||||
table["preparation"] = build_preparation_table(
|
||||
slope.get("totals") or {}, await _route_structures(project_id)
|
||||
)
|
||||
method, method_is_default = concrete_placing_method(settings)
|
||||
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
||||
table["concrete_placing"] = {"method": method, "is_default": method_is_default}
|
||||
table["settings"] = settings
|
||||
table["project_root_known"] = project_root is not None
|
||||
table["route_id"] = route_id
|
||||
@@ -164,6 +169,8 @@ class QuantitySettingsBody(BaseModel):
|
||||
# 자재별 관급/사급 — `{자재명: {"supply": …, "install_by": …}}`.
|
||||
# 표 안에서 줄마다 고른 값이 여기로 온다(2026-09-07 확정).
|
||||
material_supply: dict[str, Any] | None = None
|
||||
# 콘크리트 타설 방식. `""` 는 「안 정함」으로 되돌리는 뜻이라 서버가 None 으로 만든다.
|
||||
concrete_placing_method: str | None = None
|
||||
|
||||
|
||||
@router.put("/{project_id}/quantity/settings")
|
||||
@@ -183,6 +190,10 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
content={"status": "error", "message": "프로젝트 저장 폴더를 찾지 못했습니다."},
|
||||
)
|
||||
values = {key: value for key, value in body.model_dump().items() if value is not None}
|
||||
if "concrete_placing_method" in values:
|
||||
method = values["concrete_placing_method"]
|
||||
# 「안 정함」으로 되돌릴 수 있어야 한다 — 빈 값이면 지운다(8-22 ② 와 같은 자리).
|
||||
values["concrete_placing_method"] = method if method in CONCRETE_PLACING_METHODS else None
|
||||
if "rock_methods" in values:
|
||||
# 「안 정함」(빈 값)은 저장하지 않는다 — 정한 것과 구별이 안 된다. 통째로 갈아 끼우므로
|
||||
# 여기서 버리면 그 갈래는 미지정으로 돌아간다.
|
||||
@@ -195,7 +206,10 @@ async def save_quantity_settings(project_id: UUID, body: QuantitySettingsBody) -
|
||||
# ⚠ 고른 값을 **되돌릴 수 있어야** 하는 칸은 통째로 갈아 끼운다 — 병합이면
|
||||
# 「안 정함」으로 되돌아가지 않는다(2026-09-07 화면에서 걸린 자리).
|
||||
saved = await asyncio.to_thread(
|
||||
_save_quantity, root, values, ("rock_methods", "material_supply")
|
||||
_save_quantity,
|
||||
root,
|
||||
values,
|
||||
("rock_methods", "material_supply", "concrete_placing_method"),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("B08 설정 저장 실패(쓰기): project_id=%s", project_id)
|
||||
|
||||
@@ -63,6 +63,8 @@ export interface QuantitySettings {
|
||||
rock_methods?: Record<string, string>;
|
||||
/** 자재별 관급/사급 — 표 안에서 줄마다 고른 값. */
|
||||
material_supply?: Record<string, { supply: string; install_by: string | null }>;
|
||||
/** 콘크리트 타설 방식. `null`·없음이면 **아직 안 정한 것**이고 화면이 기본값 안내를 띄운다. */
|
||||
concrete_placing_method?: string | null;
|
||||
}
|
||||
|
||||
export interface EarthworkTable {
|
||||
|
||||
@@ -32,6 +32,18 @@ const CSS = `
|
||||
box-shadow: inset 2px 0 0 var(--color-accent, #6c8ebf);
|
||||
}
|
||||
|
||||
/* 잠정값 안내 — 금액에 걸리는 값이 조용히 기본으로 돌지 않게. */
|
||||
.b08-quantity__notice {
|
||||
margin: 0.35rem 0 0;
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.4;
|
||||
color: var(--color-text);
|
||||
background: var(--color-surface-raised);
|
||||
border-left: 3px solid var(--color-warning, #c08a3e);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.b08-grid__caption {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -83,6 +83,7 @@ async function saveQuantitySettings(projectId: string, draft: DraftSettings): Pr
|
||||
// 병합해 옛 값이 남아 되돌릴 길이 없다(화면에서 걸린 자리). 빈 값은 서버가 버린다.
|
||||
rock_methods: draft.rock_methods,
|
||||
material_supply: draft.material_supply,
|
||||
concrete_placing_method: draft.concrete_placing_method,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -172,6 +173,8 @@ interface DraftSettings {
|
||||
application_ratios_pct: Record<string, number>;
|
||||
// 갈래별 시공법 — `""` 는 「안 정함」이고 저장에서 빠진다.
|
||||
rock_methods: Record<string, string>;
|
||||
// 콘크리트 타설 방식 — `""` 는 「안 정함」이고 저장에서 지워진다.
|
||||
concrete_placing_method: string;
|
||||
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
||||
material_supply: Record<string, SupplyChoice>;
|
||||
dirty: boolean;
|
||||
@@ -252,6 +255,35 @@ function buildQuantitySidePanel(
|
||||
}
|
||||
}
|
||||
|
||||
// ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ──
|
||||
panel.append(field(L("B08_Quantity_Side_Placing"), ""));
|
||||
panel.append(
|
||||
selectField(
|
||||
L("B08_Quantity_Side_Placing_Label"),
|
||||
draft.concrete_placing_method,
|
||||
[
|
||||
{ value: "", label: L("B08_Quantity_Placing_Unset") },
|
||||
{ value: "ready_mixed", label: L("B08_Quantity_Placing_Ready") },
|
||||
{ value: "machine_mixed", label: L("B08_Quantity_Placing_Machine") },
|
||||
{ value: "hand_mixed", label: L("B08_Quantity_Placing_Hand") },
|
||||
],
|
||||
(value) => {
|
||||
draft.concrete_placing_method = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
const placing = (
|
||||
table as unknown as { concrete_placing?: { method: string; is_default: boolean } }
|
||||
).concrete_placing;
|
||||
if (placing?.is_default) {
|
||||
// 「확인 필요」만 있으면 무엇을 정해야 하는지 모른다 — **지금 무엇으로 돌고 있는지**를 함께 적는다.
|
||||
const notice = document.createElement("p");
|
||||
notice.className = "b08-quantity__notice";
|
||||
notice.textContent = L("B08_Quantity_Placing_Default_Notice");
|
||||
panel.append(notice);
|
||||
}
|
||||
|
||||
const saveButton = createButton({
|
||||
label: L("B08_Quantity_Btn_Save"),
|
||||
variant: "ghost",
|
||||
@@ -431,6 +463,7 @@ export async function renderB08Quantity(root: HTMLElement): Promise<void> {
|
||||
rock_ratios_pct: { ...(stored.rock_ratios_pct ?? {}) },
|
||||
application_ratios_pct: { ...(stored.application_ratios_pct ?? {}) },
|
||||
rock_methods: { ...((stored.rock_methods ?? {}) as Record<string, string>) },
|
||||
concrete_placing_method: (stored.concrete_placing_method as string) ?? "",
|
||||
material_supply: { ...((stored.material_supply ?? {}) as Record<string, SupplyChoice>) },
|
||||
dirty: false,
|
||||
};
|
||||
|
||||
@@ -94,9 +94,10 @@ def default_settings() -> dict[str, Any]:
|
||||
# 화면에 드러난다 — 사급으로 조용히 넘기면 관급자재대가 새 나간다.
|
||||
"material_supply": {},
|
||||
# 콘크리트 타설 방식 — `ready_mixed`(FP-12-01-01) / `machine_mixed`(-02) /
|
||||
# `hand_mixed`(-03). **설계 판단**이라 사용자가 고르는 값이고, 기본은 실무가
|
||||
# 쓰는 레디믹스트로 둔다. ⚠ 잠정이며 사용자 확정 대기 항목이다.
|
||||
"concrete_placing_method": "ready_mixed",
|
||||
# `hand_mixed`(-03). **설계 판단**이라 사용자가 고른다.
|
||||
# ⚠ 기본은 `None` — 「안 정함」과 「일부러 레디믹스트를 고른 것」을 갈라야
|
||||
# 화면이 「기본값 적용 중」을 정직하게 띄운다. 값을 미리 넣으면 그 구별이 사라진다.
|
||||
"concrete_placing_method": None,
|
||||
"dataset_versions": {},
|
||||
},
|
||||
"estimation": {
|
||||
@@ -189,6 +190,23 @@ def rock_classes(settings: dict[str, Any]) -> list[str]:
|
||||
return list(ROCK_CLASS_SETS.get(name, ROCK_CLASS_SETS[DEFAULT_ROCK_CLASS_SET]))
|
||||
|
||||
|
||||
#: 타설 방식 — 안 정했을 때 쓰는 값. **금액에 바로 걸리는 자리**라 화면이 잠정임을 띄운다.
|
||||
CONCRETE_PLACING_METHODS = ("ready_mixed", "machine_mixed", "hand_mixed")
|
||||
DEFAULT_CONCRETE_PLACING_METHOD = "ready_mixed"
|
||||
|
||||
|
||||
def concrete_placing_method(settings: dict[str, Any]) -> tuple[str, bool]:
|
||||
"""(타설 방식, 기본값을 쓰고 있는가).
|
||||
|
||||
⚠ 둘째 값이 참이면 **사용자가 아직 안 정한 것**이다 — 화면이 「기본값 적용 중」을 띄워야
|
||||
한다. 조용히 기본으로 돌면 사용자는 그것이 잠정인 줄도 모른다.
|
||||
"""
|
||||
stored = settings.get("concrete_placing_method")
|
||||
if stored in CONCRETE_PLACING_METHODS:
|
||||
return str(stored), False
|
||||
return DEFAULT_CONCRETE_PLACING_METHOD, True
|
||||
|
||||
|
||||
def application_ratio(settings: dict[str, Any], key: str) -> float:
|
||||
"""반영률을 0~1 로. 없으면 100 %(=1.0) — 실무 관측치를 기본값으로 쓰지 않는다."""
|
||||
raw = (settings.get("application_ratios_pct") or {}).get(key, 100)
|
||||
|
||||
@@ -628,6 +628,16 @@ export const ui_locales_b2 = {
|
||||
B08_Quantity_Method_Unset: ["안 정함", "Not set"],
|
||||
B08_Quantity_Method_Ripping: ["긁어내기(암절취)", "Ripping"],
|
||||
B08_Quantity_Method_Blasting: ["터뜨리기(발파암)", "Blasting"],
|
||||
B08_Quantity_Side_Placing: ["콘크리트 타설", "Concrete Placing"],
|
||||
B08_Quantity_Side_Placing_Label: ["타설 방식", "Method"],
|
||||
B08_Quantity_Placing_Unset: ["안 정함(기본값 사용)", "Not set (default)"],
|
||||
B08_Quantity_Placing_Ready: ["레디믹스트", "Ready-mixed"],
|
||||
B08_Quantity_Placing_Machine: ["기계비빔", "Machine-mixed"],
|
||||
B08_Quantity_Placing_Hand: ["인력비빔", "Hand-mixed"],
|
||||
B08_Quantity_Placing_Default_Notice: [
|
||||
"⚠ 기본값 「레디믹스트」로 계산 중 — 아직 안 정한 값입니다. 타설 방식에 따라 단가가 갈립니다.",
|
||||
"⚠ Using the default (ready-mixed) — not yet decided. Unit prices differ by method.",
|
||||
],
|
||||
B08_Quantity_Material_Failed: [
|
||||
"자재총괄을 불러오지 못했습니다.",
|
||||
"Failed to load the material summary.",
|
||||
|
||||
Reference in New Issue
Block a user